Skip to content

Commit 84e7a91

Browse files
committed
Further tidy graph collapsing logic
1 parent 5dcb5b8 commit 84e7a91

File tree

2 files changed

+85
-190
lines changed

2 files changed

+85
-190
lines changed

src/views/Graph.vue

Lines changed: 83 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -710,45 +710,26 @@ export default {
710710
return convertTree(tree)
711711
},
712712
713-
/**
714-
* Removes a node from an array of nodes
715-
* @property {String} nodeName - name of node to be removed
716-
* @property {String} cyclePoint - isodatTime of node to be removed
717-
* @property {Node[]} nodes - array of nodes included in the graph
718-
* @returns {Node[]} updated array of nodes included in the graph (with node removed)
719-
*/
720-
removeNode (nodeName, cyclePoint, nodes) {
721-
const nodeId = this.workflows[0].tokens.clone({ cycle: cyclePoint, task: nodeName }).id
722-
const nodesFiltered = nodes.filter((node) => {
723-
return node.id !== nodeId
724-
})
725-
return nodesFiltered
726-
},
727-
728713
/**
729714
* Creates an edge from the name and cycle of source and target nodes
730-
* @param {('noCollapsed'|'collapsedTarget'|'collapsedSource'|'collapsedSourceAndTarget')} edgeType
731-
* @param {string} sourceName - name of source node
732-
* @param {string} targetName - name of target node
733-
* @param {string} sourceCyclePoint - isodatTime of source node
734-
* @param {string} targetCyclePoint - isodatTime of target node
715+
* @param {Object} param0 - the source and target node names and cycles
735716
* @returns {[string, Edge]} the created edge
736717
*/
737-
createEdge (edgeType, sourceName, targetName, sourceCyclePoint, targetCyclePoint) {
738-
// adds a new edge object to 'edges' array
718+
createEdge ({ sourceName, targetName, sourceCycle, targetCycle }) {
739719
let src, tgt
740-
if (edgeType === 'noCollapsed') {
741-
src = `${sourceCyclePoint}/${sourceName}`
742-
tgt = `${targetCyclePoint}/${targetName}`
743-
} else if (edgeType === 'collapsedTarget') {
744-
src = `${sourceCyclePoint}/${sourceName}|${targetName}`
745-
tgt = `${sourceCyclePoint}/${sourceName}|${targetName}`
746-
} else if (edgeType === 'collapsedSource') {
747-
src = `${sourceCyclePoint}|${targetCyclePoint}/${targetName}`
748-
tgt = `${sourceCyclePoint}|${targetCyclePoint}/${targetName}`
749-
} else if (edgeType === 'collapsedSourceAndTarget') {
750-
src = `${sourceName}|${targetName}`
751-
tgt = `${sourceName}|${targetName}`
720+
if (sourceName === sourceCycle && targetName === targetCycle) {
721+
// both source & target collapsed by cycle point
722+
src = tgt = `${sourceCycle}|${targetCycle}`
723+
} else if (sourceName === sourceCycle) {
724+
// source collapsed by cycle point
725+
src = tgt = `${sourceCycle}|${targetCycle}/${targetName}`
726+
} else if (targetName === targetCycle) {
727+
// target collapsed by cycle point
728+
src = tgt = `${sourceCycle}/${sourceName}|${targetCycle}`
729+
} else {
730+
// not collapsed by cycle point
731+
src = `${sourceCycle}/${sourceName}`
732+
tgt = `${targetCycle}/${targetName}`
752733
}
753734
const tokens = this.workflows[0].tokens.clone({
754735
cycle: `$edge|${src}|${tgt}`
@@ -758,16 +739,14 @@ export default {
758739
759740
/**
760741
* Removes source and target edges from the map of edges.
761-
* @param {string} task - task name of source node of edge to be removed
762-
* @param {string} cycle - cycle point of source node of edge to be removed
763-
* @param {Map<string, Edge>} edges - array of edges included in the graph
742+
* @param {string} id - ID of source node of edge to be removed
743+
* @param {Map<string, Edge>} edges - edges included in the graph
764744
* @returns {Edge[]} removed edge store arrays
765745
*/
766-
removeEdges (task, cycle, edges) {
767-
const nodeID = this.workflows[0].tokens.clone({ cycle, task }).id
746+
removeEdges (id, edges) {
768747
const removed = []
769748
for (const [edgeID, edge] of edges) {
770-
if ([edge.source.id, edge.target.id].includes(nodeID)) {
749+
if ([edge.source.id, edge.target.id].includes(id)) {
771750
edges.delete(edgeID) && removed.push(edge)
772751
}
773752
}
@@ -1166,30 +1145,6 @@ export default {
11661145
this.panZoomWidget.zoom(relativeZoom * width / desiredWidth)
11671146
},
11681147
1169-
edgeHasCollapsedTargetAncestorOnly (edge) {
1170-
const target = this.getCollapsedAncestor(edge.target.id)
1171-
if (target && !this.getCollapsedAncestor(edge.source.id)) {
1172-
return target
1173-
}
1174-
},
1175-
1176-
edgeHasCollapsedSourceAncestorOnly (edge) {
1177-
const source = this.getCollapsedAncestor(edge.source.id)
1178-
if (source && !this.getCollapsedAncestor(edge.target.id)) {
1179-
return source
1180-
}
1181-
},
1182-
1183-
edgeHasCollapsedTargetandSourceAncestor (edge) {
1184-
const target = this.getCollapsedAncestor(edge.target.id)
1185-
if (target) {
1186-
const source = this.getCollapsedAncestor(edge.source.id)
1187-
if (source) {
1188-
return { target, source }
1189-
}
1190-
}
1191-
},
1192-
11931148
async refresh () {
11941149
// refresh the graph layout if required
11951150
if (this.updating) {
@@ -1206,153 +1161,93 @@ export default {
12061161
const edges = this.getGraphEdges()
12071162
12081163
// ----------------------------------------
1209-
const removedEdges = []
1210-
1211-
// For all cycles...
1212-
for (const cycle of this.cycleArrayStore) {
1213-
// ...loop through collapsed families...
1214-
for (const family of this.collapseFamily) {
1164+
for (const family of this.collapseFamily) {
1165+
for (const cycle of this.cycleArrayStore) {
12151166
// ...get the node from the index...
1216-
const indexSearch = Object.values(this.cylcTree.$index).find(
1217-
(node) => node.name === family && node.tokens.cycle === cycle
1218-
)
1219-
if (!indexSearch) continue
1167+
const famNode = this.cylcTree.$index[
1168+
this.workflows[0].tokens.clone({ cycle, task: family }).id
1169+
]
1170+
if (!famNode) continue
12201171
// ...now we have a node - we have to understand all its relations in the graph...
12211172
// ---------------REMOVE NODES BASED ON FAMILY------------
12221173
// must do this before removing nodes and edges based on cycle as
12231174
// cycle collapsing takes priority of families
12241175
// ...this node is collapsed so need to remove any of its children
12251176
// (nodes and edges) from the graph if it has any...
1226-
for (const { name } of this.allChildrenLookUp[indexSearch.id]) {
1227-
if (name !== indexSearch.name) {
1177+
for (const { id } of this.allChildrenLookUp[famNode.id]) {
1178+
if (id !== famNode.id) {
12281179
// REMOVE NODES
1229-
nodes = this.removeNode(name, cycle, nodes)
1180+
nodes = nodes.filter((node) => node.id !== id)
12301181
// REMOVE EDGES
12311182
// if there is an edge with a source or target it needs removing
1232-
removedEdges.push(
1233-
...this.removeEdges(name, cycle, edges),
1234-
)
1183+
for (const edge of this.removeEdges(id, edges)) {
1184+
// ---------------ADD EDGES BASED ON FAMILY------------
1185+
// skip if one end of edge is in a collapsed cycle - deal with this later:
1186+
if (this.collapseCycle.some(
1187+
(x) => x === edge.source.cycle || x === edge.target.cycle
1188+
)) continue
1189+
1190+
// ...this node is collapsed so need to remove any of its children (nodes and edges) from the graph if it has any...
1191+
const sourceName = this.getCollapsedAncestor(edge.source.id) ?? edge.source.task
1192+
const targetName = this.getCollapsedAncestor(edge.target.id) ?? edge.target.task
1193+
// prevent self-loop edges:
1194+
if (sourceName === targetName && edge.source.cycle === edge.target.cycle) continue
1195+
1196+
edges.set(
1197+
...this.createEdge({
1198+
sourceName,
1199+
sourceCycle: edge.source.cycle,
1200+
targetName,
1201+
targetCycle: edge.target.cycle,
1202+
})
1203+
)
1204+
}
12351205
}
12361206
}
12371207
// ...now we have removed any parts of child nodes that shouldnt be there we can add nodes and edges that should be...
12381208
// ---------------ADD NODES BASED ON FAMILY------------
1239-
if (!this.collapseCycle.includes(cycle) && !this.getCollapsedAncestor(indexSearch.id)) {
1240-
nodes.push(indexSearch)
1241-
}
1242-
1243-
// ...this node is collapsed so need to remove any of its children (nodes and edges) from the graph if it has any...
1244-
for (const { id: childID } of this.allChildrenLookUp[indexSearch.id]) {
1245-
for (const edge of removedEdges.filter(({ source }) => source.id === childID)) {
1246-
if (!this.collapseCycle.includes(edge.source.cycle)) {
1247-
const collapsedSourceAncestor = this.edgeHasCollapsedSourceAncestorOnly(edge)
1248-
if (collapsedSourceAncestor) {
1249-
edges.set(
1250-
...this.createEdge('noCollapsed', collapsedSourceAncestor, edge.target.task, edge.source.cycle, edge.target.cycle)
1251-
)
1252-
}
1253-
1254-
if (!this.collapseCycle.includes(edge.target.cycle)) {
1255-
const collapsedAncestors = this.edgeHasCollapsedTargetandSourceAncestor(edge)
1256-
if (collapsedAncestors && (collapsedAncestors.source !== collapsedAncestors.target || edge.source.cycle !== edge.target.cycle)) {
1257-
edges.set(
1258-
...this.createEdge('noCollapsed', collapsedAncestors.source, collapsedAncestors.target, edge.source.cycle, edge.target.cycle)
1259-
)
1260-
}
1261-
}
1262-
}
1263-
}
1264-
for (const edge of removedEdges.filter(({ target }) => target.id === childID)) {
1265-
if (!this.collapseCycle.includes(edge.target.cycle)) {
1266-
const collapsedTargetAncestor = this.edgeHasCollapsedTargetAncestorOnly(edge)
1267-
if (collapsedTargetAncestor) {
1268-
edges.set(
1269-
...this.createEdge('noCollapsed', edge.source.task, collapsedTargetAncestor, edge.source.cycle, edge.target.cycle)
1270-
)
1271-
}
1272-
1273-
if (!this.collapseCycle.includes(edge.source.cycle)) {
1274-
const collapsedAncestors = this.edgeHasCollapsedTargetandSourceAncestor(edge)
1275-
if (collapsedAncestors && (collapsedAncestors.source !== collapsedAncestors.target || edge.source.cycle !== edge.target.cycle)) {
1276-
edges.set(
1277-
...this.createEdge('noCollapsed', collapsedAncestors.source, collapsedAncestors.target, edge.source.cycle, edge.target.cycle)
1278-
)
1279-
}
1280-
}
1281-
}
1282-
}
1209+
if (!this.collapseCycle.includes(cycle) && !this.getCollapsedAncestor(famNode.id)) {
1210+
nodes.push(famNode)
12831211
}
12841212
}
12851213
}
12861214
for (const cycle of this.collapseCycle) {
1287-
const indexSearch = Object.values(this.cylcTree.$index).find((node) => {
1288-
return node.name === cycle
1289-
})
1290-
if (indexSearch) {
1291-
// ---------------REMOVE NODES BASED ON CYCLE POINT------------
1292-
for (const { name } of this.allChildrenLookUp[indexSearch.id]) {
1293-
if (name !== indexSearch.name) {
1294-
// REMOVE NODES
1295-
nodes = this.removeNode(name, cycle, nodes)
1296-
// REMOVE EDGES
1297-
// if there is an edge with a source or target it needs removing
1298-
removedEdges.push(
1299-
...this.removeEdges(name, cycle, edges)
1300-
)
1301-
}
1302-
}
1303-
// ---------------ADD NODES BASED ON CYCLE POINT------------
1304-
nodes.push(indexSearch)
1305-
// next bit starts here
1306-
// ---------------ADD EDGES BASED ON CYCLE POINT------------
1307-
for (const { id: childID } of this.allChildrenLookUp[indexSearch.id]) {
1308-
for (const edge of removedEdges.filter(({ source }) => source.id === childID)) {
1215+
const cycleNode = this.cylcTree.$index[
1216+
this.workflows[0].tokens.clone({ cycle }).id
1217+
]
1218+
if (!cycleNode) continue
1219+
// ---------------REMOVE NODES BASED ON CYCLE POINT------------
1220+
for (const { id } of this.allChildrenLookUp[cycleNode.id]) {
1221+
if (id !== cycleNode.id) {
1222+
// REMOVE NODES
1223+
nodes = nodes.filter((node) => node.id !== id)
1224+
// REMOVE EDGES
1225+
// if there is an edge with a source or target it needs removing
1226+
for (const edge of this.removeEdges(id, edges)) {
1227+
// ---------------ADD EDGES BASED ON CYCLE POINT------------
1228+
// prevent self-loop edges:
13091229
if (edge.source.cycle === edge.target.cycle) continue
1310-
// edge has collapsed source cycle only
1311-
if (!this.collapseCycle.includes(edge.target.cycle) && this.collapseCycle.includes(edge.source.cycle)) {
1312-
const collapsedTargetAncestor = this.getCollapsedAncestor(edge.target.id)
1313-
if (collapsedTargetAncestor) {
1314-
edges.set(
1315-
...this.createEdge('collapsedSource', edge.source.cycle, collapsedTargetAncestor, edge.source.cycle, edge.target.cycle)
1316-
)
1317-
} else {
1318-
edges.set(
1319-
...this.createEdge('collapsedSource', edge.source.cycle, edge.target.task, edge.source.cycle, edge.target.cycle)
1320-
)
1321-
}
1322-
}
1323-
1324-
// edge has collapsed target and source cycle
1325-
if (this.collapseCycle.includes(edge.target.cycle) && this.collapseCycle.includes(edge.source.cycle)) {
1326-
edges.set(
1327-
...this.createEdge('collapsedSourceAndTarget', edge.source.cycle, edge.target.cycle, edge.source.cycle, edge.target.cycle)
1328-
)
1329-
}
1330-
}
1331-
for (const edge of removedEdges.filter(({ target }) => target.id === childID)) {
1332-
if (edge.source.cycle === edge.target.cycle) continue
1333-
// edge has collapsed target cycle only
1334-
if (this.collapseCycle.includes(edge.target.cycle) && !this.collapseCycle.includes(edge.source.cycle)) {
1335-
const collapsedSourceAncestor = this.getCollapsedAncestor(edge.source.id)
1336-
if (collapsedSourceAncestor) {
1337-
edges.set(
1338-
...this.createEdge('collapsedTarget', collapsedSourceAncestor, edge.target.cycle, edge.source.cycle, edge.target.cycle)
1339-
)
1340-
} else {
1341-
edges.set(
1342-
...this.createEdge('collapsedTarget', edge.source.task, edge.target.cycle, edge.source.cycle, edge.target.cycle)
1343-
)
1344-
}
1345-
}
13461230
1347-
// edge has collapsed target and source cycle
1348-
if (this.collapseCycle.includes(edge.target.cycle) && this.collapseCycle.includes(edge.source.cycle)) {
1349-
edges.set(
1350-
...this.createEdge('collapsedSourceAndTarget', edge.source.cycle, edge.target.cycle, edge.source.cycle, edge.target.cycle)
1351-
)
1352-
}
1231+
const sourceName = this.collapseCycle.includes(edge.source.cycle)
1232+
? edge.source.cycle
1233+
: this.getCollapsedAncestor(edge.source.id) ?? edge.source.task
1234+
const targetName = this.collapseCycle.includes(edge.target.cycle)
1235+
? edge.target.cycle
1236+
: this.getCollapsedAncestor(edge.target.id) ?? edge.target.task
1237+
1238+
edges.set(
1239+
...this.createEdge({
1240+
sourceName,
1241+
sourceCycle: edge.source.cycle,
1242+
targetName,
1243+
targetCycle: edge.target.cycle,
1244+
})
1245+
)
13531246
}
13541247
}
13551248
}
1249+
// ---------------ADD NODES BASED ON CYCLE POINT------------
1250+
nodes.push(cycleNode)
13561251
}
13571252
13581253
// ----------------------------------------

tests/unit/views/graph.vue.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ describe('Graph view', () => {
168168
it('it removes edge node by source', () => {
169169
const wrapper = mountFunction()
170170
// in this test we remove the first edge with a source of 'user/one/run1//1/sleepy'
171-
expect(wrapper.vm.removeEdges('sleepy', '2', edges)).toStrictEqual([
171+
expect(wrapper.vm.removeEdges('user/one/run1//2/sleepy', edges)).toStrictEqual([
172172
// the returned removed edges array
173173
{
174174
tokens: new Tokens('user/one/run1//$edge|2/sleepy|1/failed'),
@@ -204,7 +204,7 @@ describe('Graph view', () => {
204204
// await wrapper.vm.$nextTick()
205205
// in this test we remove the first edge with a target of 'user/one/run1//1/failed'
206206

207-
expect(wrapper.vm.removeEdges('failed', '1', edges)).toStrictEqual([
207+
expect(wrapper.vm.removeEdges('user/one/run1//1/failed', edges)).toStrictEqual([
208208
// the returned edges array with the edge removed
209209
{
210210
tokens: new Tokens('user/one/run1//$edge|1/succeeded|1/failed'),

0 commit comments

Comments
 (0)