1515 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616 */
1717
18- import { sortedIndexBy } from '@/components/cylc/common/sort'
19- import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/sort'
18+ // TODO: move to the `options` parameter that is passed to deltas; ideally it would be stored in DB or localstorage.
19+ const DEFAULT_PARTS_SEPARATOR = '|'
20+ const DEFAULT_NAMES_SEPARATOR = '/'
2021
2122/**
2223 * @typedef {Object } TreeNode
2324 * @property {String } id
24- * @property {String } name
25+ * @property {String|null } name
2526 * @property {String } type
2627 * @property {WorkflowGraphQLData } node
2728 */
29+
2830/**
2931 * @typedef {TreeNode } WorkflowGScanNode
3032 */
@@ -34,12 +36,60 @@ import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/
3436 * @property {Array<WorkflowNamePartGScanNode> } children
3537 */
3638
39+ /**
40+ * Create a workflow node for GScan component.
41+ *
42+ * If the `hierarchy` parameter is `true`, then the workflow name will be split by
43+ * `/`'s. For each part, a new `WorkflowNamePart` will be added in the hierarchy.
44+ * With the final node being the last part of the name.
45+ *
46+ * The last part of a workflow name may be the workflow name (e.g. `five`), or its
47+ * run ID (e.g. `run1`, if workflow name is `five/run1`).
48+ *
49+ * @param {WorkflowGraphQLData } workflow
50+ * @param {boolean } hierarchy - whether to parse the Workflow name and create a hierarchy or not
51+ * @param {String } partsSeparator - separator for workflow name parts (e.g. '|' as in 'part1|part2|...')
52+ * @param {String } namesSeparator - separator used for workflow and run names (e.g. '/' as in 'workflow/run1')
53+ * @returns {TreeNode|null }
54+ */
55+ function createWorkflowNode ( workflow , hierarchy , partsSeparator = DEFAULT_PARTS_SEPARATOR , namesSeparator = DEFAULT_NAMES_SEPARATOR ) {
56+ if ( ! hierarchy ) {
57+ return newWorkflowNode ( workflow , null )
58+ }
59+ const workflowNameParts = parseWorkflowNameParts ( workflow . id , partsSeparator , namesSeparator )
60+ let prefix = workflowNameParts . user
61+ // The root node, returned in this function.
62+ let rootNode = null
63+ // And a helper used when iterating the array...
64+ let currentNode = null
65+ for ( const part of workflowNameParts . parts ) {
66+ prefix = prefix === null ? part : `${ prefix } ${ partsSeparator } ${ part } `
67+ const partNode = newWorkflowPartNode ( prefix , part )
68+ if ( rootNode === null ) {
69+ rootNode = currentNode = partNode
70+ } else {
71+ currentNode . children . push ( partNode )
72+ currentNode = partNode
73+ }
74+ }
75+ const workflowNode = newWorkflowNode ( workflow , workflowNameParts . name )
76+
77+ if ( currentNode === null ) {
78+ // We will return the workflow node only. It will be appended directly to the tree as a new leaf.
79+ rootNode = workflowNode
80+ } else {
81+ // 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.
82+ currentNode . children . push ( workflowNode )
83+ }
84+ return rootNode
85+ }
86+
3787/**
3888 * Create a new Workflow Node.
3989 *
4090 * @private
4191 * @param {WorkflowGraphQLData } workflow
42- * @param {string |null } part
92+ * @param {String |null } part
4393 * @returns {WorkflowGScanNode }
4494 */
4595function newWorkflowNode ( workflow , part ) {
@@ -60,11 +110,11 @@ function newWorkflowNode (workflow, part) {
60110 */
61111function newWorkflowPartNode ( id , part ) {
62112 return {
63- id : `workflow-name-part- ${ id } ` ,
113+ id,
64114 name : part ,
65115 type : 'workflow-name-part' ,
66116 node : {
67- id : id ,
117+ id,
68118 name : part ,
69119 status : ''
70120 } ,
@@ -73,90 +123,74 @@ function newWorkflowPartNode (id, part) {
73123}
74124
75125/**
76- * Create a workflow node for GScan component.
77- *
78- * If the `hierarchy` parameter is `true`, then the workflow name will be split by
79- * `/`'s. For each part, a new `WorkflowNamePart` will be added in the hierarchy.
80- * With the final node being the last part of the name.
81- *
82- * The last part of a workflow name may be the workflow name (e.g. `five`), or its
83- * run ID (e.g. `run1`, if workflow name is `five/run1`).
84- *
85- * @param {WorkflowGraphQLData } workflow
86- * @param {boolean } hierarchy - whether to parse the Workflow name and create a hierarchy or not
87- * @returns {TreeNode|null }
126+ * @typedef {Object } ParsedWorkflowNameParts
127+ * @property {String } workflowId - workflow ID
128+ * @property {String } partsSeparator - parts separator parameter used to parse the name
129+ * @property {String } namesSeparator - names separator parameter used to parse the name
130+ * @property {String } user - parsed workflow user/owner
131+ * @property {String } workflowName - original workflow name
132+ * @property {Array<String> } parts - workflow name parts
133+ * @property {String } name - workflow name (last part, used to display nodes in the GScan tree)
88134 */
89- function createWorkflowNode ( workflow , hierarchy ) {
90- if ( ! hierarchy ) {
91- return newWorkflowNode ( workflow , null )
92- }
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...
100- let rootNode = null
101- // And a helper used when iterating the array...
102- 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 )
111135
112- if ( rootNode === null ) {
113- rootNode = currentNode = partNode
114- } else {
115- currentNode . children . push ( partNode )
116- currentNode = partNode
117- }
118- }
119- return rootNode
136+ /**
137+ * Return the workflow name parts as an array of node IDs. The first node in the array is the top of the
138+ * branch, with the workflow node ID at its other end, as a leaf-node.
139+ *
140+ * @param {ParsedWorkflowNameParts } workflowNameParts
141+ * @return {Array<String> }
142+ */
143+ function getWorkflowNamePartsNodesIds ( workflowNameParts ) {
144+ let prefix = workflowNameParts . user
145+ const nodesIds = workflowNameParts . parts
146+ . map ( part => {
147+ prefix = `${ prefix } ${ workflowNameParts . partsSeparator } ${ part } `
148+ return prefix
149+ } )
150+ nodesIds . push ( workflowNameParts . workflowId )
120151}
121152
122153/**
123- * Add the new hierarchical node to the list of existing nodes.
154+ * Parses the workflow name parts. A simple name such as `user|workflow-name` will return a structure
155+ * with each part of the name, including the given parameters of this function (to simplify sending
156+ * the data to other methods).
124157 *
125- * New nodes are added in order.
158+ * More complicated names such as `user|top/level/other/leaf` return the structure with an array of
159+ * each name part too. This is useful for functions that need to manipulate the tree of GScan nodes,
160+ * and necessary as we don't have this information from the server (only the name which doesn't
161+ * split the name parts).
126162 *
127- * @param {WorkflowGScanNode|WorkflowNamePartGScanNode } node
128- * @param {Array<WorkflowGScanNode|WorkflowNamePartGScanNode> } nodes
129- * @return {Array<WorkflowGScanNode|WorkflowNamePartGScanNode> }
163+ * @param {String } workflowId - Workflow ID
164+ * @param {String } partsSeparator - separator for workflow name parts (e.g. '|' as in 'user|research/workflow/run1')
165+ * @param {String } namesSeparator - separator used for workflow and run names (e.g. '/' as in 'research/workflow/run1')
166+ * @return {ParsedWorkflowNameParts }
130167 */
131- function addNodeToTree ( node , nodes ) {
132- // N.B.: We must compare nodes by ID, not only by part-name,
133- // since we can have research/nwp/run1 workflow, and research workflow;
134- // in this case we do not want to confuse the research part-name with
135- // the research workflow.
136- const existingNode = nodes . find ( ( existingNode ) => existingNode . id === node . id )
137- if ( ! existingNode ) {
138- // Here we calculate what is the index for this element. If we decide to have ASC and DESC,
139- // then we just need to invert the location of the element, something like
140- // `sortedIndex = (array.length - sortedIndex)`.
141- const sortedIndex = sortedIndexBy (
142- nodes ,
143- node ,
144- ( n ) => n . name ,
145- sortWorkflowNamePartNodeOrWorkflowNode
146- )
147- nodes . splice ( sortedIndex , 0 , node )
148- } else {
149- if ( node . children ) {
150- for ( const child of node . children ) {
151- // Recursion. Note that we are changing the `nodes` to the children of the existing node.
152- addNodeToTree ( child , existingNode . children )
153- }
154- }
168+ function parseWorkflowNameParts ( workflowId , partsSeparator = DEFAULT_PARTS_SEPARATOR , namesSeparator = DEFAULT_NAMES_SEPARATOR ) {
169+ if ( ! workflowId || workflowId . trim ( ) === '' ) {
170+ throw new Error ( 'Missing ID for workflow name parts' )
171+ }
172+ const idParts = workflowId . split ( partsSeparator )
173+ if ( idParts . length !== 2 ) {
174+ throw new Error ( `Invalid parts found, expected at least 2 parts in ${ workflowId } ` )
175+ }
176+ const user = idParts [ 0 ]
177+ const workflowName = idParts [ 1 ]
178+ const parts = workflowName . split ( namesSeparator )
179+ // The name, used for display in the tree. Can be a workflow name like 'd', or a runN like 'run1'.
180+ const name = parts . pop ( )
181+ return {
182+ workflowId,
183+ partsSeparator,
184+ namesSeparator,
185+ user, // user
186+ workflowName, // a/b/c/d/run1
187+ parts, // [a, b, c, d]
188+ name // run1
155189 }
156- return nodes
157190}
158191
159192export {
160- addNodeToTree ,
161- createWorkflowNode
193+ createWorkflowNode ,
194+ getWorkflowNamePartsNodesIds ,
195+ parseWorkflowNameParts
162196}
0 commit comments