capdag 0.186.476 → 0.188.482
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cap-fab-renderer.js +436 -35
- package/package.json +1 -1
package/cap-fab-renderer.js
CHANGED
|
@@ -379,8 +379,12 @@ function layoutForMode(mode) {
|
|
|
379
379
|
};
|
|
380
380
|
if (mode === 'browse') {
|
|
381
381
|
return Object.assign({}, base, {
|
|
382
|
-
'elk.layered.spacing.nodeNodeBetweenLayers':
|
|
383
|
-
'elk.spacing.
|
|
382
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': 220,
|
|
383
|
+
'elk.layered.spacing.edgeEdgeBetweenLayers': 44,
|
|
384
|
+
'elk.layered.spacing.edgeNodeBetweenLayers': 52,
|
|
385
|
+
'elk.spacing.edgeEdge': 34,
|
|
386
|
+
'elk.spacing.edgeNode': 42,
|
|
387
|
+
'elk.spacing.nodeNode': 118,
|
|
384
388
|
});
|
|
385
389
|
}
|
|
386
390
|
if (mode === 'strand') {
|
|
@@ -536,8 +540,10 @@ function buildStylesheet() {
|
|
|
536
540
|
// text no longer reads as floating metadata.
|
|
537
541
|
'text-rotation': 'autorotate',
|
|
538
542
|
'text-margin-y': -6,
|
|
539
|
-
'curve-style': '
|
|
540
|
-
'control-point-step-size':
|
|
543
|
+
'curve-style': 'data(curveStyle)',
|
|
544
|
+
'control-point-step-size': 'data(controlPointStepSize)',
|
|
545
|
+
'control-point-distances': 'data(controlPointDistances)',
|
|
546
|
+
'control-point-weights': 'data(controlPointWeights)',
|
|
541
547
|
'width': 1.5,
|
|
542
548
|
'line-color': 'data(color)',
|
|
543
549
|
'target-arrow-color': 'data(color)',
|
|
@@ -690,6 +696,12 @@ function assertArray(value, path) {
|
|
|
690
696
|
}
|
|
691
697
|
}
|
|
692
698
|
|
|
699
|
+
function assertObject(value, path) {
|
|
700
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
701
|
+
throw new Error(`CapFabRenderer: ${path} must be an object`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
693
705
|
function validateBrowseData(data) {
|
|
694
706
|
assertArray(data, 'browse mode data');
|
|
695
707
|
data.forEach((cap, idx) => {
|
|
@@ -789,6 +801,18 @@ function validateBodyOutcome(outcome, path) {
|
|
|
789
801
|
}
|
|
790
802
|
}
|
|
791
803
|
|
|
804
|
+
function validateRunIOItem(item, path) {
|
|
805
|
+
assertObject(item, path);
|
|
806
|
+
assertString(item.label, `${path}.label`);
|
|
807
|
+
assertString(item.path, `${path}.path`);
|
|
808
|
+
if (typeof item.is_directory !== 'boolean') {
|
|
809
|
+
throw new Error(`CapFabRenderer run mode: ${path}.is_directory must be a boolean`);
|
|
810
|
+
}
|
|
811
|
+
if (typeof item.file_count !== 'number' || item.file_count < 0) {
|
|
812
|
+
throw new Error(`CapFabRenderer run mode: ${path}.file_count must be a non-negative number`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
792
816
|
function validateRunPayload(data) {
|
|
793
817
|
if (!data || typeof data !== 'object') {
|
|
794
818
|
throw new Error('CapFabRenderer run mode: data must be an object');
|
|
@@ -812,6 +836,23 @@ function validateRunPayload(data) {
|
|
|
812
836
|
if (typeof data.total_body_count !== 'number' || data.total_body_count < 0) {
|
|
813
837
|
throw new Error('CapFabRenderer run mode: data.total_body_count must be a non-negative number');
|
|
814
838
|
}
|
|
839
|
+
if (data.input_items !== undefined) {
|
|
840
|
+
assertArray(data.input_items, 'run mode data.input_items');
|
|
841
|
+
data.input_items.forEach((item, idx) => {
|
|
842
|
+
validateRunIOItem(item, `run mode data.input_items[${idx}]`);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
if (data.input_runs !== undefined) {
|
|
846
|
+
assertArray(data.input_runs, 'run mode data.input_runs');
|
|
847
|
+
data.input_runs.forEach((run, idx) => {
|
|
848
|
+
assertObject(run, `run mode data.input_runs[${idx}]`);
|
|
849
|
+
validateRunIOItem(run.input, `run mode data.input_runs[${idx}].input`);
|
|
850
|
+
assertArray(run.outputs, `run mode data.input_runs[${idx}].outputs`);
|
|
851
|
+
run.outputs.forEach((output, outIdx) => {
|
|
852
|
+
validateRunIOItem(output, `run mode data.input_runs[${idx}].outputs[${outIdx}]`);
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
}
|
|
815
856
|
}
|
|
816
857
|
|
|
817
858
|
function validateEditorGraphPayload(data) {
|
|
@@ -932,6 +973,81 @@ function edgeHueColor(edgeIdx) {
|
|
|
932
973
|
return `hsl(${hue}, 60%, 55%)`;
|
|
933
974
|
}
|
|
934
975
|
|
|
976
|
+
function centeredOrdinal(index, total) {
|
|
977
|
+
if (!Number.isInteger(index) || !Number.isInteger(total) || total <= 0) {
|
|
978
|
+
throw new Error('CapFabRenderer: centeredOrdinal requires integer index/total');
|
|
979
|
+
}
|
|
980
|
+
return index - ((total - 1) / 2);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function crowdOffsets(index, total, step, maxAbs) {
|
|
984
|
+
if (total <= 1) return 0;
|
|
985
|
+
const raw = centeredOrdinal(index, total) * step;
|
|
986
|
+
if (maxAbs === undefined) return raw;
|
|
987
|
+
return Math.max(-maxAbs, Math.min(maxAbs, raw));
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function annotateCrowdedBrowseEdges(edges) {
|
|
991
|
+
const bySource = new Map();
|
|
992
|
+
const byTarget = new Map();
|
|
993
|
+
|
|
994
|
+
for (const edge of edges) {
|
|
995
|
+
if (!bySource.has(edge.source)) bySource.set(edge.source, []);
|
|
996
|
+
bySource.get(edge.source).push(edge);
|
|
997
|
+
if (!byTarget.has(edge.target)) byTarget.set(edge.target, []);
|
|
998
|
+
byTarget.get(edge.target).push(edge);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const stableSort = (a, b) => {
|
|
1002
|
+
const targetCmp = a.target.localeCompare(b.target);
|
|
1003
|
+
if (targetCmp !== 0) return targetCmp;
|
|
1004
|
+
const titleCmp = a.title.localeCompare(b.title);
|
|
1005
|
+
if (titleCmp !== 0) return titleCmp;
|
|
1006
|
+
return a.id.localeCompare(b.id);
|
|
1007
|
+
};
|
|
1008
|
+
const reverseStableSort = (a, b) => {
|
|
1009
|
+
const sourceCmp = a.source.localeCompare(b.source);
|
|
1010
|
+
if (sourceCmp !== 0) return sourceCmp;
|
|
1011
|
+
const titleCmp = a.title.localeCompare(b.title);
|
|
1012
|
+
if (titleCmp !== 0) return titleCmp;
|
|
1013
|
+
return a.id.localeCompare(b.id);
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
for (const group of bySource.values()) group.sort(stableSort);
|
|
1017
|
+
for (const group of byTarget.values()) group.sort(reverseStableSort);
|
|
1018
|
+
|
|
1019
|
+
const sourceIndex = new Map();
|
|
1020
|
+
const targetIndex = new Map();
|
|
1021
|
+
for (const group of bySource.values()) {
|
|
1022
|
+
group.forEach((edge, idx) => sourceIndex.set(edge.id, idx));
|
|
1023
|
+
}
|
|
1024
|
+
for (const group of byTarget.values()) {
|
|
1025
|
+
group.forEach((edge, idx) => targetIndex.set(edge.id, idx));
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
for (const edge of edges) {
|
|
1029
|
+
const sourceGroup = bySource.get(edge.source) || [edge];
|
|
1030
|
+
const targetGroup = byTarget.get(edge.target) || [edge];
|
|
1031
|
+
const sourceCount = sourceGroup.length;
|
|
1032
|
+
const targetCount = targetGroup.length;
|
|
1033
|
+
const crowdCount = Math.max(sourceCount, targetCount);
|
|
1034
|
+
|
|
1035
|
+
if (crowdCount <= 2) {
|
|
1036
|
+
edge.curveStyle = 'bezier';
|
|
1037
|
+
edge.controlPointStepSize = 40;
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const sourceOffset = crowdOffsets(sourceIndex.get(edge.id), sourceCount, 18, 64);
|
|
1042
|
+
const targetOffset = crowdOffsets(targetIndex.get(edge.id), targetCount, 18, 64);
|
|
1043
|
+
|
|
1044
|
+
edge.curveStyle = 'unbundled-bezier';
|
|
1045
|
+
edge.controlPointDistances = `${sourceOffset} ${targetOffset}`;
|
|
1046
|
+
edge.controlPointWeights = '0.22 0.78';
|
|
1047
|
+
edge.controlPointStepSize = 56;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
935
1051
|
// --------- Browse mode builder ----------------------------------------------
|
|
936
1052
|
|
|
937
1053
|
function buildBrowseGraphData(capabilities) {
|
|
@@ -985,6 +1101,7 @@ function buildBrowseGraphData(capabilities) {
|
|
|
985
1101
|
edges.forEach((edge, i) => {
|
|
986
1102
|
edge.color = edgeHueColor(i);
|
|
987
1103
|
});
|
|
1104
|
+
annotateCrowdedBrowseEdges(edges);
|
|
988
1105
|
|
|
989
1106
|
const nodes = Array.from(nodesMap.values());
|
|
990
1107
|
for (const node of nodes) {
|
|
@@ -1032,6 +1149,10 @@ function browseCytoscapeElements(built) {
|
|
|
1032
1149
|
fullUrn: edge.capability.urn,
|
|
1033
1150
|
capFabEdgeIndex: edge.capFabEdgeIndex,
|
|
1034
1151
|
color: edge.color,
|
|
1152
|
+
curveStyle: edge.curveStyle || 'bezier',
|
|
1153
|
+
controlPointStepSize: edge.controlPointStepSize || 40,
|
|
1154
|
+
controlPointDistances: edge.controlPointDistances || '',
|
|
1155
|
+
controlPointWeights: edge.controlPointWeights || '',
|
|
1035
1156
|
},
|
|
1036
1157
|
};
|
|
1037
1158
|
});
|
|
@@ -1553,6 +1674,253 @@ function stripRunBackboneReplicaNodes(built, dropStepIds) {
|
|
|
1553
1674
|
};
|
|
1554
1675
|
}
|
|
1555
1676
|
|
|
1677
|
+
function emptyRunBackbone() {
|
|
1678
|
+
return {
|
|
1679
|
+
nodes: [],
|
|
1680
|
+
edges: [],
|
|
1681
|
+
sourceMediaUrn: '',
|
|
1682
|
+
targetMediaUrn: '',
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function buildExternalInputRunGraphData(
|
|
1687
|
+
data,
|
|
1688
|
+
inputRuns,
|
|
1689
|
+
allOutcomes,
|
|
1690
|
+
visibleSuccess,
|
|
1691
|
+
visibleFailure,
|
|
1692
|
+
hiddenSuccessCount,
|
|
1693
|
+
hiddenFailureCount,
|
|
1694
|
+
displayNameFor
|
|
1695
|
+
) {
|
|
1696
|
+
if (inputRuns.length <= 1 || allOutcomes.length === 0) return null;
|
|
1697
|
+
|
|
1698
|
+
const capSteps = data.resolved_strand.steps
|
|
1699
|
+
.filter(step => Object.keys(step.step_type)[0] === 'Cap')
|
|
1700
|
+
.map(step => step.step_type.Cap);
|
|
1701
|
+
if (capSteps.length === 0) {
|
|
1702
|
+
throw new Error('CapFabRenderer run mode: external multi-input runs require at least one Cap step in resolved_strand.');
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
const visibleOutcomes = visibleSuccess.concat(visibleFailure);
|
|
1706
|
+
const CapUrn = requireHostDependency('CapUrn');
|
|
1707
|
+
const sourceCanonical = canonicalMediaUrn(data.resolved_strand.source_media_urn);
|
|
1708
|
+
const targetCanonical = canonicalMediaUrn(data.resolved_strand.target_media_urn);
|
|
1709
|
+
const anchorNodeId = 'external-input-anchor';
|
|
1710
|
+
const replicaNodes = [{
|
|
1711
|
+
group: 'nodes',
|
|
1712
|
+
data: {
|
|
1713
|
+
id: anchorNodeId,
|
|
1714
|
+
label: displayNameFor(sourceCanonical),
|
|
1715
|
+
fullUrn: sourceCanonical,
|
|
1716
|
+
},
|
|
1717
|
+
classes: 'strand-source',
|
|
1718
|
+
}];
|
|
1719
|
+
const replicaEdges = [];
|
|
1720
|
+
const showMoreNodes = [];
|
|
1721
|
+
|
|
1722
|
+
for (const outcome of allOutcomes) {
|
|
1723
|
+
if (outcome.body_index >= inputRuns.length) {
|
|
1724
|
+
throw new Error(
|
|
1725
|
+
`CapFabRenderer run mode: body_outcomes[body_index=${outcome.body_index}] exceeds input_runs length ${inputRuns.length}`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
function buildReplica(outcome) {
|
|
1731
|
+
const runDef = inputRuns[outcome.body_index];
|
|
1732
|
+
const success = outcome.success;
|
|
1733
|
+
const nodeClass = success ? 'body-success' : 'body-failure';
|
|
1734
|
+
const edgeClass = success ? 'body-success' : 'body-failure';
|
|
1735
|
+
const edgeColor = success ? 'var(--graph-body-edge-success)' : 'var(--graph-body-edge-failure)';
|
|
1736
|
+
const bodyKey = `external-body-${outcome.body_index}`;
|
|
1737
|
+
const sourceNodeId = `${bodyKey}-input`;
|
|
1738
|
+
const sourceLabel = typeof outcome.title === 'string' && outcome.title.length > 0
|
|
1739
|
+
? outcome.title
|
|
1740
|
+
: runDef.input.label;
|
|
1741
|
+
|
|
1742
|
+
replicaNodes.push({
|
|
1743
|
+
group: 'nodes',
|
|
1744
|
+
data: {
|
|
1745
|
+
id: sourceNodeId,
|
|
1746
|
+
label: sourceLabel,
|
|
1747
|
+
fullUrn: runDef.input.path,
|
|
1748
|
+
bodyIndex: outcome.body_index,
|
|
1749
|
+
bodyTitle: sourceLabel,
|
|
1750
|
+
},
|
|
1751
|
+
classes: `${nodeClass} run-input-item`,
|
|
1752
|
+
});
|
|
1753
|
+
replicaEdges.push({
|
|
1754
|
+
group: 'edges',
|
|
1755
|
+
data: {
|
|
1756
|
+
id: `${bodyKey}-entry`,
|
|
1757
|
+
source: anchorNodeId,
|
|
1758
|
+
target: sourceNodeId,
|
|
1759
|
+
label: '',
|
|
1760
|
+
title: runDef.input.path,
|
|
1761
|
+
fullUrn: '',
|
|
1762
|
+
color: getCssVar('--graph-edge-color'),
|
|
1763
|
+
bodyIndex: outcome.body_index,
|
|
1764
|
+
},
|
|
1765
|
+
classes: edgeClass,
|
|
1766
|
+
});
|
|
1767
|
+
|
|
1768
|
+
let traceEnd = capSteps.length;
|
|
1769
|
+
if (!success) {
|
|
1770
|
+
if (typeof outcome.failed_cap === 'string' && outcome.failed_cap.length > 0) {
|
|
1771
|
+
const failedCap = CapUrn.fromString(outcome.failed_cap);
|
|
1772
|
+
traceEnd = 0;
|
|
1773
|
+
for (let i = 0; i < capSteps.length; i++) {
|
|
1774
|
+
const candidate = CapUrn.fromString(capSteps[i].cap_urn);
|
|
1775
|
+
if (candidate.isEquivalent(failedCap)) {
|
|
1776
|
+
traceEnd = i + 1;
|
|
1777
|
+
break;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
} else {
|
|
1781
|
+
traceEnd = 0;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
let prevNodeId = sourceNodeId;
|
|
1786
|
+
for (let i = 0; i < traceEnd; i++) {
|
|
1787
|
+
const cap = capSteps[i];
|
|
1788
|
+
const targetMedia = canonicalMediaUrn(i === capSteps.length - 1 ? data.resolved_strand.target_media_urn : data.resolved_strand.steps.filter(step => Object.keys(step.step_type)[0] === 'Cap')[i].to_spec);
|
|
1789
|
+
const isLastExecutedStep = i === traceEnd - 1;
|
|
1790
|
+
const outputs = success ? runDef.outputs : [];
|
|
1791
|
+
|
|
1792
|
+
if (success && isLastExecutedStep && outputs.length > 0) {
|
|
1793
|
+
outputs.forEach((output, outputIdx) => {
|
|
1794
|
+
const outputNodeId = `${bodyKey}-output-${outputIdx}`;
|
|
1795
|
+
replicaNodes.push({
|
|
1796
|
+
group: 'nodes',
|
|
1797
|
+
data: {
|
|
1798
|
+
id: outputNodeId,
|
|
1799
|
+
label: output.label,
|
|
1800
|
+
fullUrn: output.path,
|
|
1801
|
+
bodyIndex: outcome.body_index,
|
|
1802
|
+
bodyTitle: sourceLabel,
|
|
1803
|
+
},
|
|
1804
|
+
classes: nodeClass,
|
|
1805
|
+
});
|
|
1806
|
+
replicaEdges.push({
|
|
1807
|
+
group: 'edges',
|
|
1808
|
+
data: {
|
|
1809
|
+
id: `${bodyKey}-output-edge-${i}-${outputIdx}`,
|
|
1810
|
+
source: prevNodeId,
|
|
1811
|
+
target: outputNodeId,
|
|
1812
|
+
label: outputIdx === 0 ? cap.title : '',
|
|
1813
|
+
title: cap.title,
|
|
1814
|
+
fullUrn: cap.cap_urn,
|
|
1815
|
+
color: edgeColor,
|
|
1816
|
+
bodyIndex: outcome.body_index,
|
|
1817
|
+
},
|
|
1818
|
+
classes: edgeClass,
|
|
1819
|
+
});
|
|
1820
|
+
});
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
const stepNodeId = `${bodyKey}-step-${i}`;
|
|
1825
|
+
replicaNodes.push({
|
|
1826
|
+
group: 'nodes',
|
|
1827
|
+
data: {
|
|
1828
|
+
id: stepNodeId,
|
|
1829
|
+
label: displayNameFor(isLastExecutedStep ? targetCanonical : targetMedia),
|
|
1830
|
+
fullUrn: isLastExecutedStep ? targetCanonical : targetMedia,
|
|
1831
|
+
bodyIndex: outcome.body_index,
|
|
1832
|
+
bodyTitle: sourceLabel,
|
|
1833
|
+
},
|
|
1834
|
+
classes: nodeClass,
|
|
1835
|
+
});
|
|
1836
|
+
replicaEdges.push({
|
|
1837
|
+
group: 'edges',
|
|
1838
|
+
data: {
|
|
1839
|
+
id: `${bodyKey}-step-edge-${i}`,
|
|
1840
|
+
source: prevNodeId,
|
|
1841
|
+
target: stepNodeId,
|
|
1842
|
+
label: cap.title,
|
|
1843
|
+
title: cap.title,
|
|
1844
|
+
fullUrn: cap.cap_urn,
|
|
1845
|
+
color: edgeColor,
|
|
1846
|
+
bodyIndex: outcome.body_index,
|
|
1847
|
+
},
|
|
1848
|
+
classes: edgeClass,
|
|
1849
|
+
});
|
|
1850
|
+
prevNodeId = stepNodeId;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
visibleOutcomes.forEach(buildReplica);
|
|
1855
|
+
|
|
1856
|
+
if (hiddenSuccessCount > 0) {
|
|
1857
|
+
showMoreNodes.push({
|
|
1858
|
+
group: 'nodes',
|
|
1859
|
+
data: {
|
|
1860
|
+
id: 'show-more-success',
|
|
1861
|
+
label: `+${hiddenSuccessCount} more succeeded`,
|
|
1862
|
+
fullUrn: '',
|
|
1863
|
+
showMoreGroup: 'success',
|
|
1864
|
+
hiddenCount: hiddenSuccessCount,
|
|
1865
|
+
},
|
|
1866
|
+
classes: 'show-more body-success',
|
|
1867
|
+
});
|
|
1868
|
+
replicaEdges.push({
|
|
1869
|
+
group: 'edges',
|
|
1870
|
+
data: {
|
|
1871
|
+
id: 'show-more-success-edge',
|
|
1872
|
+
source: anchorNodeId,
|
|
1873
|
+
target: 'show-more-success',
|
|
1874
|
+
label: '',
|
|
1875
|
+
title: '',
|
|
1876
|
+
fullUrn: '',
|
|
1877
|
+
color: 'var(--graph-body-edge-success)',
|
|
1878
|
+
},
|
|
1879
|
+
classes: 'body-success',
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
if (hiddenFailureCount > 0) {
|
|
1883
|
+
showMoreNodes.push({
|
|
1884
|
+
group: 'nodes',
|
|
1885
|
+
data: {
|
|
1886
|
+
id: 'show-more-failure',
|
|
1887
|
+
label: `+${hiddenFailureCount} failed`,
|
|
1888
|
+
fullUrn: '',
|
|
1889
|
+
showMoreGroup: 'failure',
|
|
1890
|
+
hiddenCount: hiddenFailureCount,
|
|
1891
|
+
},
|
|
1892
|
+
classes: 'show-more body-failure',
|
|
1893
|
+
});
|
|
1894
|
+
replicaEdges.push({
|
|
1895
|
+
group: 'edges',
|
|
1896
|
+
data: {
|
|
1897
|
+
id: 'show-more-failure-edge',
|
|
1898
|
+
source: anchorNodeId,
|
|
1899
|
+
target: 'show-more-failure',
|
|
1900
|
+
label: '',
|
|
1901
|
+
title: '',
|
|
1902
|
+
fullUrn: '',
|
|
1903
|
+
color: 'var(--graph-body-edge-failure)',
|
|
1904
|
+
},
|
|
1905
|
+
classes: 'body-failure',
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
return {
|
|
1910
|
+
strandBuilt: emptyRunBackbone(),
|
|
1911
|
+
replicaNodes,
|
|
1912
|
+
replicaEdges,
|
|
1913
|
+
showMoreNodes,
|
|
1914
|
+
totals: {
|
|
1915
|
+
hiddenSuccessCount,
|
|
1916
|
+
hiddenFailureCount,
|
|
1917
|
+
totalBodyCount: data.total_body_count,
|
|
1918
|
+
visibleSuccessCount: visibleSuccess.length,
|
|
1919
|
+
visibleFailureCount: visibleFailure.length,
|
|
1920
|
+
},
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1556
1924
|
function buildRunGraphData(data) {
|
|
1557
1925
|
validateRunPayload(data);
|
|
1558
1926
|
|
|
@@ -1566,19 +1934,36 @@ function buildRunGraphData(data) {
|
|
|
1566
1934
|
const strandBuiltRaw = buildStrandGraphData(strandInput);
|
|
1567
1935
|
let strandBuiltCollapsed = collapseStrandShapeTransitions(strandBuiltRaw);
|
|
1568
1936
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1937
|
+
const inputItems = Array.isArray(data.input_items) ? data.input_items : [];
|
|
1938
|
+
const inputRuns = Array.isArray(data.input_runs) ? data.input_runs : [];
|
|
1939
|
+
const inputReplicaNodes = [];
|
|
1940
|
+
const inputReplicaEdges = [];
|
|
1941
|
+
inputItems.forEach((item, idx) => {
|
|
1942
|
+
const nodeId = `input-item-${idx}`;
|
|
1943
|
+
inputReplicaNodes.push({
|
|
1944
|
+
group: 'nodes',
|
|
1945
|
+
data: {
|
|
1946
|
+
id: nodeId,
|
|
1947
|
+
label: item.label,
|
|
1948
|
+
fullUrn: item.path,
|
|
1949
|
+
inputIndex: idx,
|
|
1950
|
+
inputPath: item.path,
|
|
1951
|
+
},
|
|
1952
|
+
classes: 'run-input-item',
|
|
1953
|
+
});
|
|
1954
|
+
inputReplicaEdges.push({
|
|
1955
|
+
group: 'edges',
|
|
1956
|
+
data: {
|
|
1957
|
+
id: `input-item-edge-${idx}`,
|
|
1958
|
+
source: nodeId,
|
|
1959
|
+
target: 'input_slot',
|
|
1960
|
+
label: '',
|
|
1961
|
+
title: item.path,
|
|
1962
|
+
fullUrn: '',
|
|
1963
|
+
color: getCssVar('--graph-edge-color'),
|
|
1964
|
+
},
|
|
1965
|
+
});
|
|
1966
|
+
});
|
|
1582
1967
|
|
|
1583
1968
|
// Locate the ForEach/Collect span in the raw steps. Positional
|
|
1584
1969
|
// IDs survive the collapse (node IDs are `step_${i}` from the
|
|
@@ -1605,6 +1990,36 @@ function buildRunGraphData(data) {
|
|
|
1605
1990
|
const hiddenFailureCount = failures.length - visibleFailure.length;
|
|
1606
1991
|
const visibleOutcomes = visibleSuccess.concat(visibleFailure);
|
|
1607
1992
|
|
|
1993
|
+
// Look up a display name for a media URN via the host-supplied
|
|
1994
|
+
// `media_display_names` map. Uses `MediaUrn.isEquivalent` for
|
|
1995
|
+
// semantic URN equality.
|
|
1996
|
+
const MediaUrn = requireHostDependency('MediaUrn');
|
|
1997
|
+
const mediaDisplayNames = data.media_display_names || {};
|
|
1998
|
+
const displayEntries = [];
|
|
1999
|
+
for (const [urn, display] of Object.entries(mediaDisplayNames)) {
|
|
2000
|
+
if (typeof display !== 'string' || display.length === 0) continue;
|
|
2001
|
+
try {
|
|
2002
|
+
displayEntries.push({ media: MediaUrn.fromString(urn), display });
|
|
2003
|
+
} catch (_) { /* ignore malformed keys */ }
|
|
2004
|
+
}
|
|
2005
|
+
function displayNameFor(canonicalUrn) {
|
|
2006
|
+
return requireExplicitDisplayName(canonicalUrn, displayEntries, 'run node');
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
const externalInputRunBuilt = buildExternalInputRunGraphData(
|
|
2010
|
+
data,
|
|
2011
|
+
inputRuns,
|
|
2012
|
+
allOutcomes,
|
|
2013
|
+
visibleSuccess,
|
|
2014
|
+
visibleFailure,
|
|
2015
|
+
hiddenSuccessCount,
|
|
2016
|
+
hiddenFailureCount,
|
|
2017
|
+
displayNameFor
|
|
2018
|
+
);
|
|
2019
|
+
if (externalInputRunBuilt !== null) {
|
|
2020
|
+
return externalInputRunBuilt;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1608
2023
|
// Per-body replicas only fire when there's a ForEach AND at
|
|
1609
2024
|
// least one visible outcome. Without outcomes, the strand
|
|
1610
2025
|
// backbone renders the "plan preview" unchanged.
|
|
@@ -1628,8 +2043,8 @@ function buildRunGraphData(data) {
|
|
|
1628
2043
|
if (!shouldExpand) {
|
|
1629
2044
|
return {
|
|
1630
2045
|
strandBuilt: strandBuiltCollapsed,
|
|
1631
|
-
replicaNodes:
|
|
1632
|
-
replicaEdges:
|
|
2046
|
+
replicaNodes: inputReplicaNodes,
|
|
2047
|
+
replicaEdges: inputReplicaEdges,
|
|
1633
2048
|
showMoreNodes: [],
|
|
1634
2049
|
totals: {
|
|
1635
2050
|
hiddenSuccessCount,
|
|
@@ -1729,24 +2144,10 @@ function buildRunGraphData(data) {
|
|
|
1729
2144
|
|
|
1730
2145
|
const replicaNodes = [];
|
|
1731
2146
|
const replicaEdges = [];
|
|
2147
|
+
replicaNodes.push(...inputReplicaNodes);
|
|
2148
|
+
replicaEdges.push(...inputReplicaEdges);
|
|
1732
2149
|
let replicasBuiltCount = 0;
|
|
1733
2150
|
|
|
1734
|
-
// Look up a display name for a media URN via the host-supplied
|
|
1735
|
-
// `media_display_names` map. Uses `MediaUrn.isEquivalent` for
|
|
1736
|
-
// semantic URN equality.
|
|
1737
|
-
const MediaUrn = requireHostDependency('MediaUrn');
|
|
1738
|
-
const mediaDisplayNames = data.media_display_names || {};
|
|
1739
|
-
const displayEntries = [];
|
|
1740
|
-
for (const [urn, display] of Object.entries(mediaDisplayNames)) {
|
|
1741
|
-
if (typeof display !== 'string' || display.length === 0) continue;
|
|
1742
|
-
try {
|
|
1743
|
-
displayEntries.push({ media: MediaUrn.fromString(urn), display });
|
|
1744
|
-
} catch (_) { /* ignore malformed keys */ }
|
|
1745
|
-
}
|
|
1746
|
-
function displayNameFor(canonicalUrn) {
|
|
1747
|
-
return requireExplicitDisplayName(canonicalUrn, displayEntries, 'run node');
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
2151
|
// The per-body "entry" node represents one item of the
|
|
1751
2152
|
// sequence being iterated. Its URN is:
|
|
1752
2153
|
// * the sequence producer cap's `to_spec` (if such a cap
|
package/package.json
CHANGED