164 lines
4.5 KiB
TypeScript
164 lines
4.5 KiB
TypeScript
import type { Edge, Node } from '@xyflow/svelte';
|
|
|
|
export const JOIN_MODE_ANY = 'any';
|
|
export const JOIN_MODE_ALL = 'all';
|
|
|
|
export type JoinMode = typeof JOIN_MODE_ANY | typeof JOIN_MODE_ALL;
|
|
|
|
export type JoinModeAnalysis = {
|
|
incomingCount: number;
|
|
mode: JoinMode | null;
|
|
invalidMode: boolean;
|
|
allAllowed: boolean;
|
|
allDisabledReason: string;
|
|
};
|
|
|
|
const text = (value: unknown) => (value == null ? '' : String(value).trim());
|
|
|
|
export function parseJoinMode(value: unknown): JoinMode | null {
|
|
if (value === undefined) {
|
|
return JOIN_MODE_ANY;
|
|
}
|
|
const normalized = text(value).toLowerCase();
|
|
if (normalized === JOIN_MODE_ANY) {
|
|
return JOIN_MODE_ANY;
|
|
}
|
|
if (normalized === JOIN_MODE_ALL) {
|
|
return JOIN_MODE_ALL;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function getJoinModeBadge(value: unknown) {
|
|
return parseJoinMode(value) === JOIN_MODE_ALL ? '等待全部' : '';
|
|
}
|
|
|
|
export function analyzeJoinMode(
|
|
nodes: Node[],
|
|
edges: Edge[],
|
|
nodeId: string,
|
|
): JoinModeAnalysis {
|
|
const targetNode = nodes.find((node) => node.id === nodeId);
|
|
const directInward = edges.filter((edge) => edge.target === nodeId);
|
|
const hasJoinMode = targetNode?.data
|
|
? Object.prototype.hasOwnProperty.call(targetNode.data, 'joinMode')
|
|
: false;
|
|
const mode = parseJoinMode(targetNode?.data?.joinMode);
|
|
const invalidMode = hasJoinMode && mode === null;
|
|
|
|
if (!targetNode) {
|
|
return {
|
|
incomingCount: directInward.length,
|
|
mode,
|
|
invalidMode,
|
|
allAllowed: false,
|
|
allDisabledReason: '节点不存在,请刷新画布后重试。',
|
|
};
|
|
}
|
|
|
|
if (text(targetNode.parentId)) {
|
|
return {
|
|
incomingCount: directInward.length,
|
|
mode,
|
|
invalidMode,
|
|
allAllowed: false,
|
|
allDisabledReason: '显式循环子图暂不支持等待全部上游,请使用“任一上游完成”。',
|
|
};
|
|
}
|
|
|
|
if (directInward.length <= 1) {
|
|
return {
|
|
incomingCount: directInward.length,
|
|
mode,
|
|
invalidMode,
|
|
allAllowed: true,
|
|
allDisabledReason: '',
|
|
};
|
|
}
|
|
|
|
const guaranteedNodes = findGuaranteedNodes(nodes, edges);
|
|
const allAllowed = directInward.every(
|
|
(edge) => !hasEdgeCondition(edge) && guaranteedNodes.has(edge.source),
|
|
);
|
|
if (allAllowed) {
|
|
return {
|
|
incomingCount: directInward.length,
|
|
mode,
|
|
invalidMode,
|
|
allAllowed: true,
|
|
allDisabledReason: '',
|
|
};
|
|
}
|
|
|
|
let reason = '存在条件、互斥或无法证明必达的上游路径,可能永久等待。请调整连线或使用“任一上游完成”。';
|
|
if (directInward.some(hasEdgeCondition)) {
|
|
reason = '存在带条件的直接入边,部分入边可能不会到达。请调整连线或使用“任一上游完成”。';
|
|
} else if (directInward.some((edge) => {
|
|
const source = nodes.find((node) => node.id === edge.source);
|
|
return hasAdvancedCondition(source);
|
|
})) {
|
|
reason = '上游节点包含高级执行条件,无法保证每条入边都会到达。请调整条件或使用“任一上游完成”。';
|
|
}
|
|
|
|
return {
|
|
incomingCount: directInward.length,
|
|
mode,
|
|
invalidMode,
|
|
allAllowed: false,
|
|
allDisabledReason: reason,
|
|
};
|
|
}
|
|
|
|
function findGuaranteedNodes(nodes: Node[], edges: Edge[]) {
|
|
const guaranteed = new Set(
|
|
nodes
|
|
.filter((node) => !text(node.parentId) && node.type === 'startNode')
|
|
.map((node) => node.id),
|
|
);
|
|
const inwardByTarget = new Map<string, Edge[]>();
|
|
edges.forEach((edge) => {
|
|
const inward = inwardByTarget.get(edge.target) || [];
|
|
inward.push(edge);
|
|
inwardByTarget.set(edge.target, inward);
|
|
});
|
|
|
|
let changed = true;
|
|
while (changed) {
|
|
changed = false;
|
|
nodes.forEach((node) => {
|
|
if (
|
|
text(node.parentId)
|
|
|| guaranteed.has(node.id)
|
|
|| hasAdvancedCondition(node)
|
|
) {
|
|
return;
|
|
}
|
|
const mode = parseJoinMode(node.data?.joinMode);
|
|
if (!mode) {
|
|
return;
|
|
}
|
|
const inward = inwardByTarget.get(node.id) || [];
|
|
const isGuaranteed = mode === JOIN_MODE_ALL
|
|
? inward.length > 0 && inward.every(
|
|
(edge) => !hasEdgeCondition(edge) && guaranteed.has(edge.source),
|
|
)
|
|
: inward.some(
|
|
(edge) => !hasEdgeCondition(edge) && guaranteed.has(edge.source),
|
|
);
|
|
if (isGuaranteed) {
|
|
guaranteed.add(node.id);
|
|
changed = true;
|
|
}
|
|
});
|
|
}
|
|
return guaranteed;
|
|
}
|
|
|
|
function hasAdvancedCondition(node: Node | undefined) {
|
|
return Boolean(text(node?.data?.condition));
|
|
}
|
|
|
|
function hasEdgeCondition(edge: Edge) {
|
|
return Boolean(text(edge.data?.condition));
|
|
}
|