Skip to content

Commit f3a81df

Browse files
committed
Update and delete GScan tree and lookup nodes
1 parent a4d1f75 commit f3a81df

File tree

2 files changed

+113
-24
lines changed

2 files changed

+113
-24
lines changed

src/components/cylc/gscan/index.js

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import { mergeWith } from 'lodash'
1919
import { sortedIndexBy } from '@/components/cylc/common/sort'
2020
import { mergeWithCustomizer } from '@/components/cylc/common/merge'
2121
import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/sort'
22-
import { createWorkflowNode } from '@/components/cylc/gscan/nodes'
22+
import {
23+
createWorkflowNode,
24+
parseWorkflowNameParts
25+
} from '@/components/cylc/gscan/nodes'
2326

2427
/**
2528
* @typedef {Object} GScan
@@ -89,6 +92,7 @@ function addHierarchicalWorkflow (workflow, lookup, tree, options) {
8992
// TODO: combine states summaries?
9093
if (existingNode.children) {
9194
// Copy array since we will iterate it, and modify existingNode.children
95+
// (see the tree.splice above.)
9296
const children = [...workflow.children]
9397
for (const child of children) {
9498
// Recursion
@@ -115,15 +119,66 @@ function updateWorkflow (workflow, gscan, options) {
115119
}
116120
mergeWith(existingData.node, workflow, mergeWithCustomizer)
117121
Vue.set(gscan.lookup, existingData.id, existingData)
122+
// FIXME: we need to sort its parent again!
123+
// TODO: create workflow hierarchy (from workflow object), then iterate
124+
// it and use lookup to fetch the existing node. Finally, combine
125+
// the gscan states (latestStateTasks & stateTotals).
118126
}
119127

120128
/**
121-
* @param {TreeNode} workflow
129+
* @private
130+
* @param {String} id - ID of the tree node
131+
* @param {Array<TreeNode>} tree
132+
* @param {Lookup} lookup
133+
*/
134+
function removeNode (id, lookup, tree) {
135+
Vue.delete(lookup, id)
136+
const treeNode = tree.find(node => node.id === id)
137+
if (treeNode) {
138+
Vue.delete(tree, tree.indexOf(treeNode))
139+
}
140+
}
141+
142+
/**
143+
* @param {String} workflowId
122144
* @param {GScan} gscan
123145
* @param {*} options
124146
*/
125-
function removeWorkflow (workflow, gscan, options) {
126-
147+
function removeWorkflow (workflowId, gscan, options) {
148+
const workflow = gscan.lookup[workflowId]
149+
if (!workflow) {
150+
throw new Error(`Pruned node [${workflow.id}] not found in workflow lookup`)
151+
}
152+
const hierarchical = options.hierarchical || true
153+
if (hierarchical) {
154+
const workflowNameParts = parseWorkflowNameParts(workflowId)
155+
const nodeIds = []
156+
let prefix = workflowNameParts.user
157+
for (const part of workflowNameParts.parts) {
158+
prefix = `${prefix}${workflowNameParts.partsSeparator}${part}`
159+
nodeIds.push(prefix)
160+
}
161+
nodeIds.push(workflowId)
162+
// We start from the leaf-node, going upward to make sure we don't leave nodes with no children.
163+
for (let i = nodeIds.length - 1; i >= 0; i--) {
164+
const nodeId = nodeIds[i]
165+
const node = gscan.lookup[nodeId]
166+
if (node.children && node.children.length > 0) {
167+
// We stop as soon as we find a node that still has children.
168+
break
169+
}
170+
// Now we can remove the node from the lookup, and from its parents children array.
171+
const previousIndex = i - 1
172+
const parentId = previousIndex >= 0 ? nodeIds[previousIndex] : null
173+
if (parentId && !gscan.lookup[parentId]) {
174+
throw new Error(`Failed to locate parent ${parentId} in GScan lookup`)
175+
}
176+
const parentChildren = parentId ? gscan.lookup[parentId].children : gscan.tree
177+
removeNode(nodeId, gscan.lookup, parentChildren)
178+
}
179+
} else {
180+
removeNode(workflowId, gscan.lookup, gscan.tree)
181+
}
127182
}
128183

129184
export {

src/components/cylc/gscan/nodes.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
import { sortedIndexBy } from '@/components/cylc/common/sort'
1919
import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/sort'
2020

21+
// TODO: move to the `options` parameter that is passed to deltas; ideally it would be stored in DB or localstorage.
22+
const DEFAULT_PARTS_SEPARATOR = '|'
23+
const DEFAULT_NAMES_SEPARATOR = '/'
24+
2125
/**
2226
* @typedef {Object} TreeNode
2327
* @property {String} id
@@ -60,18 +64,46 @@ function newWorkflowNode (workflow, part) {
6064
*/
6165
function newWorkflowPartNode (id, part) {
6266
return {
63-
id: `workflow-name-part-${id}`,
67+
id,
6468
name: part,
6569
type: 'workflow-name-part',
6670
node: {
67-
id: id,
71+
id,
6872
name: part,
6973
status: ''
7074
},
7175
children: []
7276
}
7377
}
7478

79+
/**
80+
* @param {String} workflowId - Workflow ID
81+
* @param {String} partsSeparator - separator for workflow name parts (e.g. '|' as in 'user|research/workflow/run1')
82+
* @param {String} namesSeparator - separator used for workflow and run names (e.g. '/' as in 'research/workflow/run1')
83+
*/
84+
function parseWorkflowNameParts (workflowId, partsSeparator = DEFAULT_PARTS_SEPARATOR, namesSeparator = DEFAULT_NAMES_SEPARATOR) {
85+
if (!workflowId || workflowId.trim() === '') {
86+
throw new Error('Missing ID for workflow name parts')
87+
}
88+
const idParts = workflowId.split(partsSeparator)
89+
if (idParts.length !== 2) {
90+
throw new Error(`Invalid parts found, expected at least 2 parts in ${workflowId}`)
91+
}
92+
const user = idParts[0]
93+
const workflowName = idParts[1]
94+
const parts = workflowName.split(namesSeparator)
95+
// The name, used for display in the tree. Can be a workflow name like 'd', or a runN like 'run1'.
96+
const name = parts.pop()
97+
return {
98+
partsSeparator,
99+
namesSeparator,
100+
user, // user
101+
workflowName, // a/b/c/d/run1
102+
parts, // [a, b, c, d]
103+
name // run1
104+
}
105+
}
106+
75107
/**
76108
* Create a workflow node for GScan component.
77109
*
@@ -84,38 +116,39 @@ function newWorkflowPartNode (id, part) {
84116
*
85117
* @param {WorkflowGraphQLData} workflow
86118
* @param {boolean} hierarchy - whether to parse the Workflow name and create a hierarchy or not
119+
* @param {String} partsSeparator - separator for workflow name parts (e.g. '|' as in 'part1|part2|...')
120+
* @param {String} namesSeparator - separator used for workflow and run names (e.g. '/' as in 'workflow/run1')
87121
* @returns {TreeNode|null}
88122
*/
89-
function createWorkflowNode (workflow, hierarchy) {
123+
function createWorkflowNode (workflow, hierarchy, partsSeparator = DEFAULT_PARTS_SEPARATOR, namesSeparator = DEFAULT_NAMES_SEPARATOR) {
90124
if (!hierarchy) {
91125
return newWorkflowNode(workflow, null)
92126
}
93-
const workflowIdParts = workflow.id.split('|')
94-
// The prefix contains all the ID parts, except for the workflow name.
95-
let prefix = workflowIdParts.slice(0, workflowIdParts.length - 1)
96-
// The name is here.
97-
const workflowName = workflow.name
98-
const parts = workflowName.split('/')
99-
// Returned node...
127+
const workflowNameParts = parseWorkflowNameParts(workflow.id, partsSeparator, namesSeparator)
128+
let prefix = workflowNameParts.user
129+
// The root node, returned in this function.
100130
let rootNode = null
101131
// And a helper used when iterating the array...
102132
let currentNode = null
103-
while (parts.length > 0) {
104-
const part = parts.shift()
105-
// For the first part, we need to add an ID separator `|`, but for the other parts
106-
// we actually want to use the name parts separator `/`.
107-
prefix = prefix.includes('/') ? `${prefix}/${part}` : `${prefix}|${part}`
108-
const partNode = parts.length !== 0
109-
? newWorkflowPartNode(prefix, part)
110-
: newWorkflowNode(workflow, part)
111-
133+
for (const part of workflowNameParts.parts) {
134+
prefix = prefix === null ? part : `${prefix}${partsSeparator}${part}`
135+
const partNode = newWorkflowPartNode(prefix, part)
112136
if (rootNode === null) {
113137
rootNode = currentNode = partNode
114138
} else {
115139
currentNode.children.push(partNode)
116140
currentNode = partNode
117141
}
118142
}
143+
const workflowNode = newWorkflowNode(workflow, workflowNameParts.name)
144+
145+
if (currentNode === null) {
146+
// We will return the workflow node only. It will be appended directly to the tree as a new leaf.
147+
rootNode = workflowNode
148+
} else {
149+
// Add the workflow node to the end of the branch as a leaf. Note that the top of the branch is returned in this case.
150+
currentNode.children.push(workflowNode)
151+
}
119152
return rootNode
120153
}
121154

@@ -158,5 +191,6 @@ function addNodeToTree (node, nodes) {
158191

159192
export {
160193
addNodeToTree,
161-
createWorkflowNode
194+
createWorkflowNode,
195+
parseWorkflowNameParts
162196
}

0 commit comments

Comments
 (0)