From 3eae5f4c75886357fb39873b299e3da631fce315 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Mon, 23 Aug 2021 09:26:05 +1200 Subject: [PATCH 01/16] Fix jsdoc --- src/mixins/graphql.js | 2 +- src/store/workflows.module.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mixins/graphql.js b/src/mixins/graphql.js index 6b50f567a..38e6ae601 100644 --- a/src/mixins/graphql.js +++ b/src/mixins/graphql.js @@ -56,7 +56,7 @@ export default { /** * GraphQL query variables. * - * @returns {{workflowId: string}} + * @returns {Object.} */ variables () { return { diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index d2799252a..05b065ee3 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -48,12 +48,12 @@ const state = { lookup: {} }, /** - * This contains a list of workflows returned from GraphQL and is used by components + * This contains workflows returned from GraphQL indexed by their ID's. And is used by components * such as GScan, Dashboard, and WorkflowsTable. * - * @type {Array} + * @type {Object.} */ - workflows: [], + workflows: {}, /** * This holds the name of the current workflow. This is set by VueRouter * and is used to decide what's the current workflow. It is used in conjunction From 6e2211b95ae9792cebe277d5d511c74ecd3e5a32 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Mon, 23 Aug 2021 10:19:21 +1200 Subject: [PATCH 02/16] Move tree workflow data into its own Vuex module to avoid confusion --- src/mixins/index.js | 1 - src/store/options.js | 4 +- src/store/tree.module.js | 118 ++++++++++++++++++++++++++++++++++ src/store/workflows.module.js | 86 ------------------------- src/views/Tree.vue | 10 +-- 5 files changed, 126 insertions(+), 93 deletions(-) create mode 100644 src/store/tree.module.js diff --git a/src/mixins/index.js b/src/mixins/index.js index 85add48d2..7048be7b8 100644 --- a/src/mixins/index.js +++ b/src/mixins/index.js @@ -19,7 +19,6 @@ import i18n from '@/i18n' /** * Here we can define the operations that are common to components/views. - * @type {{methods: {setPageTitle(*=, *=): string}}} */ export default { /** diff --git a/src/store/options.js b/src/store/options.js index b8346b556..c5ae3985f 100644 --- a/src/store/options.js +++ b/src/store/options.js @@ -19,6 +19,7 @@ import { app } from './app.module' import { workflows } from './workflows.module' import { user } from './user.module' +import { tree } from './tree.module' // State const state = { @@ -80,7 +81,8 @@ export default { modules: { app, workflows, - user + user, + tree }, actions, mutations, diff --git a/src/store/tree.module.js b/src/store/tree.module.js new file mode 100644 index 000000000..277160e80 --- /dev/null +++ b/src/store/tree.module.js @@ -0,0 +1,118 @@ +/** + * Copyright (C) NIWA & British Crown (Met Office) & Contributors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import { clear } from '@/components/cylc/tree' +import applyDeltasLookup from '@/components/cylc/workflow/deltas' +import Alert from '@/model/Alert.model' +import applyDeltasTree from '@/components/cylc/tree/deltas' + +const state = { + /** + * This stores workflow data as a hashmap/dictionary. The keys + * are the ID's of the entities returned from GraphQL. + * + * The values of the dictionary hold the GraphQL data returned as-is. + * + * The intention is for workflow views to look up data in this structure + * and re-use, instead of duplicating it. + * + * @type {Object.} + */ + lookup: {}, + /** + * This is the CylcTree, which contains the hierarchical tree data structure. + * It is created from the GraphQL data, with the only difference that this one + * contains hierarchy, while the lookup (not workflow.lookup) is flat-ish. + * + * The nodes in the .tree property have a reference or pointer (.node) to the + * data in the lookup map above, to avoid data duplication. + * + * @type {Workflow} + */ + workflow: { + tree: {}, + lookup: {} + } +} + +const mutations = { + SET_WORKFLOW (state, data) { + state.workflow = data + }, + SET_LOOKUP (state, data) { + state.lookup = data + }, + CLEAR_WORKFLOW (state) { + clear(state.workflow) + state.workflow = { + tree: { + id: '', + type: 'workflow', + children: [] + }, + lookup: {} + } + } +} + +const actions = { + applyWorkflowDeltas ({ commit, state }, data) { + // modifying state directly in an action results in warnings... + const lookup = Object.assign({}, state.lookup) + const result = applyDeltasLookup(data, lookup) + if (result.errors.length === 0) { + commit('SET_LOOKUP', lookup) + } + result.errors.forEach(error => { + commit('SET_ALERT', new Alert(error[0], null, 'error'), { root: true }) + // eslint-disable-next-line no-console + console.warn(...error) + }) + }, + clearWorkflow ({ commit }) { + commit('SET_LOOKUP', {}) + }, + applyTreeDeltas ({ commit, state }, data) { + // modifying state directly in an action results in warnings... + const workflow = state.workflow + const lookup = state.lookup + // TODO: this could be an options object stored in the Vuex store, in some module... + const options = { + cyclePointsOrderDesc: localStorage.cyclePointsOrderDesc + ? JSON.parse(localStorage.cyclePointsOrderDesc) + : true + } + const result = applyDeltasTree(data, workflow, lookup, options) + if (result.errors.length === 0) { + commit('SET_WORKFLOW', workflow) + } + result.errors.forEach(error => { + commit('SET_ALERT', new Alert(error[0], null, 'error'), { root: true }) + // eslint-disable-next-line no-console + console.warn(...error) + }) + }, + clearTree ({ commit }) { + commit('CLEAR_WORKFLOW') + } +} + +export const tree = { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index 05b065ee3..bbdb89b1d 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -15,38 +15,8 @@ * along with this program. If not, see . */ import applyDeltasWorkflows from '@/components/cylc/gscan/deltas' -import applyDeltasLookup from '@/components/cylc/workflow/deltas' -import applyDeltasTree from '@/components/cylc/tree/deltas' -import Alert from '@/model/Alert.model' -import { clear } from '@/components/cylc/tree/index' const state = { - /** - * This stores workflow data as a hashmap/dictionary. The keys - * are the ID's of the entities returned from GraphQL. - * - * The values of the dictionary hold the GraphQL data returned as-is. - * - * The intention is for workflow views to look up data in this structure - * and re-use, instead of duplicating it. - * - * @type {Object.} - */ - lookup: {}, - /** - * This is the CylcTree, which contains the hierarchical tree data structure. - * It is created from the GraphQL data, with the only difference that this one - * contains hierarchy, while the lookup (not workflow.lookup) is flat-ish. - * - * The nodes in the .tree property have a reference or pointer (.node) to the - * data in the lookup map above, to avoid data duplication. - * - * @type {Workflow} - */ - workflow: { - tree: {}, - lookup: {} - }, /** * This contains workflows returned from GraphQL indexed by their ID's. And is used by components * such as GScan, Dashboard, and WorkflowsTable. @@ -82,23 +52,6 @@ const mutations = { }, SET_WORKFLOW_NAME (state, data) { state.workflowName = data - }, - SET_WORKFLOW (state, data) { - state.workflow = data - }, - SET_LOOKUP (state, data) { - state.lookup = data - }, - CLEAR_WORKFLOW (state) { - clear(state.workflow) - state.workflow = { - tree: { - id: '', - type: 'workflow', - children: [] - }, - lookup: {} - } } } @@ -114,45 +67,6 @@ const actions = { }, clearWorkflows ({ commit }) { commit('SET_WORKFLOWS', []) - }, - applyWorkflowDeltas ({ commit, state }, data) { - // modifying state directly in an action results in warnings... - const lookup = Object.assign({}, state.lookup) - const result = applyDeltasLookup(data, lookup) - if (result.errors.length === 0) { - commit('SET_LOOKUP', lookup) - } - result.errors.forEach(error => { - commit('SET_ALERT', new Alert(error[0], null, 'error'), { root: true }) - // eslint-disable-next-line no-console - console.warn(...error) - }) - }, - clearWorkflow ({ commit }) { - commit('SET_LOOKUP', {}) - }, - applyTreeDeltas ({ commit, state }, data) { - // modifying state directly in an action results in warnings... - const workflow = state.workflow - const lookup = state.lookup - // TODO: this could be an options object stored in the Vuex store, in some module... - const options = { - cyclePointsOrderDesc: localStorage.cyclePointsOrderDesc - ? JSON.parse(localStorage.cyclePointsOrderDesc) - : true - } - const result = applyDeltasTree(data, workflow, lookup, options) - if (result.errors.length === 0) { - commit('SET_WORKFLOW', workflow) - } - result.errors.forEach(error => { - commit('SET_ALERT', new Alert(error[0], null, 'error'), { root: true }) - // eslint-disable-next-line no-console - console.warn(...error) - }) - }, - clearTree ({ commit }) { - commit('CLEAR_WORKFLOW') } } diff --git a/src/views/Tree.vue b/src/views/Tree.vue index fb06f45ef..b115dab6b 100644 --- a/src/views/Tree.vue +++ b/src/views/Tree.vue @@ -70,7 +70,7 @@ export default { } }, computed: { - ...mapState('workflows', ['workflow']), + ...mapState('tree', ['workflow']), workflows () { return this.workflow && this.workflow.tree && @@ -84,12 +84,12 @@ export default { this.variables, 'workflow', [ - 'workflows/applyWorkflowDeltas', - 'workflows/applyTreeDeltas' + 'tree/applyWorkflowDeltas', + 'tree/applyTreeDeltas' ], [ - 'workflows/clearWorkflow', - 'workflows/clearTree' + 'tree/clearWorkflow', + 'tree/clearTree' ] ) } From 744fb9a303887c947e4248c74fb45a9a01029799 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Mon, 23 Aug 2021 13:29:36 +1200 Subject: [PATCH 03/16] Move the function that applies deltas to a lookup-like structure to a common module (so GScan can re-use it too) --- src/components/cylc/common/deltas.js | 135 +++++++++++++++++++++++ src/components/cylc/workflow/deltas.js | 142 ------------------------- src/store/tree.module.js | 2 +- src/store/workflows.module.js | 35 ++++++ 4 files changed, 171 insertions(+), 143 deletions(-) delete mode 100644 src/components/cylc/workflow/deltas.js diff --git a/src/components/cylc/common/deltas.js b/src/components/cylc/common/deltas.js index ef5a2f9ef..acc3c0641 100644 --- a/src/components/cylc/common/deltas.js +++ b/src/components/cylc/common/deltas.js @@ -15,6 +15,11 @@ * along with this program. If not, see . */ +import Vue from 'vue' +import mergeWith from 'lodash/mergeWith' +import isArray from 'lodash/isArray' +import { mergeWithCustomizer } from '@/components/cylc/common/merge' + /** * @typedef {Object} GraphQLResponseData * @property {Deltas} deltas @@ -79,3 +84,133 @@ * @property {Array} familyProxies - IDs of family proxies removed * @property {Array} jobs - IDs of jobs removed */ + +/** + * @typedef {Object} Result + * @property {Array} errors + */ + +/** + * @param {DeltasAdded} added + * @param {Object.} lookup + * @return {Result} + */ +function applyDeltasAdded (added, lookup) { + const result = { + errors: [] + } + Object.values(added).forEach(addedValue => { + const items = isArray(addedValue) ? addedValue : [addedValue] + items.forEach(addedData => { + // An example for a data without .id, is the empty delta with __typename: "Added". It does occur, and can cause runtime errors. + if (addedData.id) { + try { + Vue.set(lookup, addedData.id, addedData) + } catch (error) { + result.errors.push([ + 'Error applying added-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', + error, + addedData, + lookup + ]) + } + } + }) + }) + return result +} + +/** + * Deltas updated. + * + * @param updated {DeltasUpdated} updated + * @param {Object.} lookup + * @return {Result} + */ +function applyDeltasUpdated (updated, lookup) { + const result = { + errors: [] + } + Object.values(updated).forEach(updatedValue => { + const items = isArray(updatedValue) ? updatedValue : [updatedValue] + items.forEach(updatedData => { + // An example for a data without .id, is the empty delta with __typename: "Updated". It does occur, and can cause runtime errors. + if (updatedData.id) { + try { + const existingNode = lookup[updatedData.id] + if (existingNode) { + mergeWith(existingNode, updatedData, mergeWithCustomizer) + } else { + // TODO: we are adding in the updated. Is that OK? Should we revisit it later perhaps? + Vue.set(lookup, updatedData.id, updatedData) + } + } catch (error) { + result.errors.push([ + 'Error applying updated-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', + error, + updatedData, + lookup + ]) + } + } + }) + }) + return result +} + +/** + * Deltas pruned. + * + * @param {DeltasPruned} pruned - deltas pruned + * @param {Object.} lookup + * @return {Result} + */ +function applyDeltasPruned (pruned, lookup) { + Object.values(pruned).forEach(prunedData => { + // It can be a String, when you get a delta with only '{ __typename: "Pruned" }' + const items = isArray(prunedData) ? prunedData : [prunedData] + for (const id of items) { + if (lookup[id]) { + delete lookup[id] + } + } + }) + return { + errors: [] + } +} + +/** + * A function that simply applies the deltas to a lookup object. + * + * The entries in deltas will be the value of the lookup, and their ID's + * will be the keys. + * + * This function can be used with any lookup-like structure. When + * entries are updated it will merge with a customizer maintaining + * the Vue reactivity. + * + * @param {GraphQLResponseData} data + * @param {Object.} lookup + */ +export default function (data, lookup) { + const added = data.deltas.added + const updated = data.deltas.updated + const pruned = data.deltas.pruned + const errors = [] + if (added) { + const result = applyDeltasAdded(added, lookup) + errors.push(...result.errors) + } + if (updated) { + const result = applyDeltasUpdated(updated, lookup) + errors.push(...result.errors) + } + if (pruned) { + const result = applyDeltasPruned(pruned, lookup) + errors.push(...result.errors) + } + return { + errors + } +} diff --git a/src/components/cylc/workflow/deltas.js b/src/components/cylc/workflow/deltas.js deleted file mode 100644 index be970debe..000000000 --- a/src/components/cylc/workflow/deltas.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright (C) NIWA & British Crown (Met Office) & Contributors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import Vue from 'vue' -import mergeWith from 'lodash/mergeWith' -import isArray from 'lodash/isArray' -import { mergeWithCustomizer } from '@/components/cylc/common/merge' - -/** - * @typedef {Object} Result - * @property {Array} errors - */ - -/** - * @param {DeltasAdded} added - * @param {Object.} lookup - * @return {Result} - */ -function applyDeltasAdded (added, lookup) { - const result = { - errors: [] - } - Object.values(added).forEach(addedValue => { - const items = isArray(addedValue) ? addedValue : [addedValue] - items.forEach(addedData => { - // An example for a data without .id, is the empty delta with __typename: "Added". It does occur, and can cause runtime errors. - if (addedData.id) { - try { - Vue.set(lookup, addedData.id, addedData) - } catch (error) { - result.errors.push([ - 'Error applying added-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', - error, - addedData, - lookup - ]) - } - } - }) - }) - return result -} - -/** - * Deltas updated. - * - * @param updated {DeltasUpdated} updated - * @param {Object.} lookup - * @return {Result} - */ -function applyDeltasUpdated (updated, lookup) { - const result = { - errors: [] - } - Object.values(updated).forEach(updatedValue => { - const items = isArray(updatedValue) ? updatedValue : [updatedValue] - items.forEach(updatedData => { - // An example for a data without .id, is the empty delta with __typename: "Updated". It does occur, and can cause runtime errors. - if (updatedData.id) { - try { - const existingNode = lookup[updatedData.id] - if (existingNode) { - mergeWith(existingNode, updatedData, mergeWithCustomizer) - } else { - // TODO: we are adding in the updated. Is that OK? Should we revisit it later perhaps? - Vue.set(lookup, updatedData.id, updatedData) - } - } catch (error) { - result.errors.push([ - 'Error applying updated-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', - error, - updatedData, - lookup - ]) - } - } - }) - }) - return result -} - -/** - * Deltas pruned. - * - * @param {DeltasPruned} pruned - deltas pruned - * @param {Object.} lookup - * @return {Result} - */ -function applyDeltasPruned (pruned, lookup) { - Object.values(pruned).forEach(prunedData => { - // It can be a String, when you get a delta with only '{ __typename: "Pruned" }' - const items = isArray(prunedData) ? prunedData : [prunedData] - for (const id of items) { - if (lookup[id]) { - delete lookup[id] - } - } - }) - return { - errors: [] - } -} - -/** - * @param {*} data - * @param {Object.} lookup - */ -export default function (data, lookup) { - const added = data.deltas.added - const updated = data.deltas.updated - const pruned = data.deltas.pruned - const errors = [] - if (added) { - const result = applyDeltasAdded(added, lookup) - errors.push(...result.errors) - } - if (updated) { - const result = applyDeltasUpdated(updated, lookup) - errors.push(...result.errors) - } - if (pruned) { - const result = applyDeltasPruned(pruned, lookup) - errors.push(...result.errors) - } - return { - errors - } -} diff --git a/src/store/tree.module.js b/src/store/tree.module.js index 277160e80..9ae9bd8d6 100644 --- a/src/store/tree.module.js +++ b/src/store/tree.module.js @@ -15,7 +15,7 @@ * along with this program. If not, see . */ import { clear } from '@/components/cylc/tree' -import applyDeltasLookup from '@/components/cylc/workflow/deltas' +import applyDeltasLookup from '@/components/cylc/common/deltas' import Alert from '@/model/Alert.model' import applyDeltasTree from '@/components/cylc/tree/deltas' diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index bbdb89b1d..2f192e2d3 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -14,6 +14,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +import Vue from 'vue' +import Alert from '@/model/Alert.model' import applyDeltasWorkflows from '@/components/cylc/gscan/deltas' const state = { @@ -24,6 +26,9 @@ const state = { * @type {Object.} */ workflows: {}, + gscan: { + tree: {} + }, /** * This holds the name of the current workflow. This is set by VueRouter * and is used to decide what's the current workflow. It is used in conjunction @@ -50,6 +55,17 @@ const mutations = { SET_WORKFLOWS (state, data) { state.workflows = data }, + SET_GSCAN (state, data) { + state.gscan = data + }, + CLEAR_GSCAN (state) { + Object.keys(state.gscan.tree).forEach(key => { + Vue.delete(state.gscan.tree, key) + }) + state.gscan = { + tree: {} + } + }, SET_WORKFLOW_NAME (state, data) { state.workflowName = data } @@ -67,6 +83,25 @@ const actions = { }, clearWorkflows ({ commit }) { commit('SET_WORKFLOWS', []) + }, + applyGScanDeltas ({ commit, state }, data) { + // TODO + const gscan = state.gscan + const options = { + hierarchical: true + } + const result = applyDeltasGscan(data, gscan, options) + if (result.errors.length === 0) { + commit('SET_GSCAN', gscan) + } + result.errors.forEach(error => { + commit('SET_ALERT', new Alert(error[0], null, 'error'), { root: true }) + // eslint-disable-next-line no-console + console.warn(...error) + }) + }, + clearGScan ({ commit }) { + commit('CLEAR_GSCAN') } } From 5c4bf2c243e2e47dba0ec5ad016e23d4404aaf0b Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Mon, 23 Aug 2021 14:46:04 +1200 Subject: [PATCH 04/16] Begin working on the functions to apply deltas (with propagated states!) to GScan --- src/components/cylc/gscan/GScan.vue | 34 +++++----- src/components/cylc/gscan/deltas.js | 97 +++++++++++++++++++++++++---- src/components/cylc/gscan/index.js | 24 +++++++ src/components/cylc/tree/deltas.js | 6 +- src/store/workflows.module.js | 6 +- 5 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 src/components/cylc/gscan/index.js diff --git a/src/components/cylc/gscan/GScan.vue b/src/components/cylc/gscan/GScan.vue index a1c2147b4..d28486555 100644 --- a/src/components/cylc/gscan/GScan.vue +++ b/src/components/cylc/gscan/GScan.vue @@ -206,7 +206,7 @@ import { WorkflowState } from '@/model/WorkflowState.model' import Job from '@/components/cylc/Job' import Tree from '@/components/cylc/tree/Tree' import WorkflowIcon from '@/components/cylc/gscan/WorkflowIcon' -import { addNodeToTree, createWorkflowNode } from '@/components/cylc/gscan/nodes' +// import { addNodeToTree, createWorkflowNode } from '@/components/cylc/gscan/nodes' import { filterHierarchically } from '@/components/cylc/gscan/filters' import { GSCAN_DELTAS_SUBSCRIPTION } from '@/graphql/queries' @@ -226,7 +226,9 @@ export default { GSCAN_DELTAS_SUBSCRIPTION, {}, 'root', - ['workflows/applyWorkflowsDeltas'], + [ + 'workflows/applyGScanDeltas' + ], ['workflows/clearWorkflows'] ), maximumTasksDisplayed: 5, @@ -310,16 +312,16 @@ export default { } }, computed: { - ...mapState('workflows', ['workflows']), - workflowNodes () { - // NOTE: In case we decide to allow the user to switch between hierarchical and flat - // gscan view, then all we need to do is just pass a boolean data-property to - // the `createWorkflowNode` function below. Then reactivity will take care of - // the rest. - const reducer = (acc, workflow) => addNodeToTree(createWorkflowNode(workflow, /* hierarchy */true), acc) - return Object.values(this.workflows) - .reduce(reducer, []) - }, + ...mapState('workflows', ['workflows', 'gscan']), + // workflowNodes () { + // // NOTE: In case we decide to allow the user to switch between hierarchical and flat + // // gscan view, then all we need to do is just pass a boolean data-property to + // // the `createWorkflowNode` function below. Then reactivity will take care of + // // the rest. + // const reducer = (acc, workflow) => addNodeToTree(createWorkflowNode(workflow, /* hierarchy */true), acc) + // return Object.values(this.workflows) + // .reduce(reducer, []) + // }, /** * @return {Array} */ @@ -348,7 +350,7 @@ export default { deep: true, immediate: false, handler: function (newVal) { - this.filteredWorkflows = this.filterHierarchically(this.workflowNodes, this.searchWorkflows, this.workflowStates, this.taskStates) + this.filteredWorkflows = this.filterHierarchically(this.gscan.tree.children || [], this.searchWorkflows, this.workflowStates, this.taskStates) } }, /** @@ -358,13 +360,13 @@ export default { searchWorkflows: { immediate: false, handler: function (newVal) { - this.filteredWorkflows = this.filterHierarchically(this.workflowNodes, newVal, this.workflowStates, this.taskStates) + this.filteredWorkflows = this.filterHierarchically(this.gscan.tree.children || [], newVal, this.workflowStates, this.taskStates) } }, - workflowNodes: { + gscan: { immediate: true, handler: function () { - this.filteredWorkflows = this.filterHierarchically(this.workflowNodes, this.searchWorkflows, this.workflowStates, this.taskStates) + this.filteredWorkflows = this.filterHierarchically(this.gscan.tree.children || [], this.searchWorkflows, this.workflowStates, this.taskStates) } } }, diff --git a/src/components/cylc/gscan/deltas.js b/src/components/cylc/gscan/deltas.js index 671234a0f..109e498a6 100644 --- a/src/components/cylc/gscan/deltas.js +++ b/src/components/cylc/gscan/deltas.js @@ -14,21 +14,92 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import Vue from 'vue' -import { mergeWith } from 'lodash' -import { mergeWithCustomizer } from '@/components/cylc/common/merge' +import { createWorkflowNode } from '@/components/cylc/gscan/nodes' +import { addWorkflow } from '@/components/cylc/gscan/index' -export default function (data, workflows) { - const added = data.deltas.added - const updated = data.deltas.updated - const pruned = data.deltas.pruned - if (added && added.workflow && added.workflow.status) { - Vue.set(workflows, added.workflow.id, added.workflow) +function applyDeltasAdded (added, gscan, options) { + const result = { + errors: [] } - if (updated && updated.workflow && workflows[updated.workflow.id]) { - mergeWith(workflows[updated.workflow.id], updated.workflow, mergeWithCustomizer) + if (added.workflow) { + const hierarchical = options.hierarchical || true + const workflowNode = createWorkflowNode(added.workflow, hierarchical) + try { + addWorkflow(workflowNode, gscan, options) + } catch (error) { + result.errors.push([ + 'Error applying added-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', + error, + added.workflow, + gscan, + options + ]) + } } - if (pruned && pruned.workflow) { - Vue.delete(workflows, pruned.workflow) + return result +} + +function applyDeltasUpdated () { + const result = { + errors: [] + } + return result +} + +function applyDeltasPruned () { + const result = { + errors: [] + } + return result +} + +const DELTAS = { + added: applyDeltasAdded, + updated: applyDeltasUpdated, + pruned: applyDeltasPruned +} + +/** + * @param {Deltas} deltas + * @param {*} gscan + * @param {*} options + * @returns {Result} + */ +function handleDeltas (deltas, gscan, options) { + const errors = [] + Object.keys(DELTAS).forEach(key => { + if (deltas[key]) { + const handlingFunction = DELTAS[key] + const result = handlingFunction(deltas[key], gscan, options) + errors.push(...result.errors) + } + }) + return { + errors + } +} + +/** + * @param {GraphQLResponseData} data + * @param {*} gscan + * @param {*}options + * @returns {Result} + */ +export default function (data, gscan, options) { + const deltas = data.deltas + try { + return handleDeltas(deltas, gscan, options) + } catch (error) { + return { + errors: [ + [ + 'Unexpected error applying gscan deltas', + error, + deltas, + gscan, + options + ] + ] + } } } diff --git a/src/components/cylc/gscan/index.js b/src/components/cylc/gscan/index.js new file mode 100644 index 000000000..83060d8c4 --- /dev/null +++ b/src/components/cylc/gscan/index.js @@ -0,0 +1,24 @@ +/** + * Copyright (C) NIWA & British Crown (Met Office) & Contributors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +function addWorkflow (workflow, gscan, options) { + +} + +export { + addWorkflow +} diff --git a/src/components/cylc/tree/deltas.js b/src/components/cylc/tree/deltas.js index 0724aa748..54be0a750 100644 --- a/src/components/cylc/tree/deltas.js +++ b/src/components/cylc/tree/deltas.js @@ -210,6 +210,7 @@ function handleDeltas (deltas, workflow, lookup, options) { * @param {Workflow} workflow * @param {Lookup} lookup * @param {*} options + * @return {Result} */ export default function (data, workflow, lookup, options) { const deltas = data.deltas @@ -251,11 +252,12 @@ export default function (data, workflow, lookup, options) { return { errors: [ [ - 'Unexpected error applying deltas', + 'Unexpected error applying tree deltas', error, deltas, workflow, - lookup + lookup, + options ] ] } diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index 2f192e2d3..d52b58dfb 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -16,7 +16,8 @@ */ import Vue from 'vue' import Alert from '@/model/Alert.model' -import applyDeltasWorkflows from '@/components/cylc/gscan/deltas' +import applyDeltasLookup from '@/components/cylc/common/deltas' +import applyDeltasGscan from '@/components/cylc/gscan/deltas' const state = { /** @@ -78,14 +79,13 @@ const actions = { applyWorkflowsDeltas ({ commit, state }, data) { // modifying state directly in an action results in warnings... const workflows = Object.assign({}, state.workflows) - applyDeltasWorkflows(data, workflows) + applyDeltasLookup(data, workflows) commit('SET_WORKFLOWS', workflows) }, clearWorkflows ({ commit }) { commit('SET_WORKFLOWS', []) }, applyGScanDeltas ({ commit, state }, data) { - // TODO const gscan = state.gscan const options = { hierarchical: true From bb99812b23277ffeccb92590b7ca3bbb8754ecd3 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Tue, 24 Aug 2021 10:23:00 +1200 Subject: [PATCH 05/16] Fix message (was added, is actually for updated) --- src/components/cylc/tree/deltas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/cylc/tree/deltas.js b/src/components/cylc/tree/deltas.js index 54be0a750..f8787bb04 100644 --- a/src/components/cylc/tree/deltas.js +++ b/src/components/cylc/tree/deltas.js @@ -114,7 +114,7 @@ function applyDeltasUpdated (updated, workflow, lookup, options) { } } catch (error) { result.errors.push([ - 'Error applying added-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', + 'Error applying updated-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', error, updatedData, workflow, From 1e2354a04000acf7b9ccde28634d15d6172f71a8 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Tue, 24 Aug 2021 10:23:31 +1200 Subject: [PATCH 06/16] Add a lookup to gscan structure (to access workflow when update/pruning it) --- src/store/workflows.module.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index d52b58dfb..b03a26dc8 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -27,8 +27,13 @@ const state = { * @type {Object.} */ workflows: {}, + /** + * This is the data structure used by GScan component. The tree holds the hierarchical GScan, + * and the lookup is a helper structure for quick access to nodes in the tree. + */ gscan: { - tree: {} + tree: {}, + lookup: {} }, /** * This holds the name of the current workflow. This is set by VueRouter @@ -60,11 +65,14 @@ const mutations = { state.gscan = data }, CLEAR_GSCAN (state) { - Object.keys(state.gscan.tree).forEach(key => { - Vue.delete(state.gscan.tree, key) - }) + for (const property of ['tree', 'lookup']) { + Object.keys(state.gscan[property]).forEach(key => { + Vue.delete(state.gscan[property], key) + }) + } state.gscan = { - tree: {} + tree: {}, + lookup: {} } }, SET_WORKFLOW_NAME (state, data) { From 68f48f14187f7269353c1e054b03688d4d92d923 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Tue, 24 Aug 2021 10:34:01 +1200 Subject: [PATCH 07/16] Base work for handling deltas in GScan --- src/components/cylc/gscan/deltas.js | 80 +++++++++++++++++++++++++++-- src/components/cylc/gscan/index.js | 39 +++++++++++++- src/components/cylc/gscan/nodes.js | 12 ++--- 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/components/cylc/gscan/deltas.js b/src/components/cylc/gscan/deltas.js index 109e498a6..0c0cd5d9f 100644 --- a/src/components/cylc/gscan/deltas.js +++ b/src/components/cylc/gscan/deltas.js @@ -14,9 +14,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +import { addWorkflow, updateWorkflow, removeWorkflow } from '@/components/cylc/gscan/index' import { createWorkflowNode } from '@/components/cylc/gscan/nodes' -import { addWorkflow } from '@/components/cylc/gscan/index' +/** + * Deltas added. + * + * @param {DeltasAdded} added + * @param {GScan} gscan + * @param {*} options + * @returns {Result} + */ function applyDeltasAdded (added, gscan, options) { const result = { errors: [] @@ -39,17 +47,73 @@ function applyDeltasAdded (added, gscan, options) { return result } -function applyDeltasUpdated () { +/** + * Deltas updated. + * + * @param {DeltasUpdated} updated + * @param {GScan} gscan + * @param {*} options + * @returns {Result} + */ +function applyDeltasUpdated (updated, gscan, options) { const result = { errors: [] } + if (updated.workflow) { + const updatedData = updated.workflow + const hierarchical = options.hierarchical || true + try { + const existingData = gscan.lookup[updatedData.id] + if (!existingData) { + result.errors.push([ + `Updated node [${updatedData.id}] not found in workflow lookup`, + updatedData, + gscan, + options + ]) + } else { + const workflowNode = createWorkflowNode(updatedData, hierarchical) + updateWorkflow(workflowNode, gscan, options) + } + } catch (error) { + result.errors.push([ + 'Error applying updated-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', + error, + updatedData, + gscan, + options + ]) + } + } return result } -function applyDeltasPruned () { +/** + * Deltas pruned. + * + * @param {DeltasPruned} pruned + * @param {GScan} gscan + * @param {*} options + * @returns {Result} + */ +function applyDeltasPruned (pruned, gscan, options) { const result = { errors: [] } + if (pruned.workflow) { + const workflowId = pruned.workflow + try { + removeWorkflow(workflowId, gscan, options) + } catch (error) { + result.errors.push([ + 'Error applying pruned-delta, see browser console logs for more. Please reload your browser tab to retrieve the full flow state', + error, + workflowId, + gscan, + options + ]) + } + } return result } @@ -60,8 +124,14 @@ const DELTAS = { } /** + * Handle the deltas. This function receives the new set of deltas, and the GScan object. + * + * The GScan object contains a tree property that holds the hierarchical (or not) GScan, + * and a lookup helper dictionary used for ease of access to leaf or intermediary tree + * nodes. + * * @param {Deltas} deltas - * @param {*} gscan + * @param {GScan} gscan * @param {*} options * @returns {Result} */ @@ -82,7 +152,7 @@ function handleDeltas (deltas, gscan, options) { /** * @param {GraphQLResponseData} data * @param {*} gscan - * @param {*}options + * @param {*} options * @returns {Result} */ export default function (data, gscan, options) { diff --git a/src/components/cylc/gscan/index.js b/src/components/cylc/gscan/index.js index 83060d8c4..8f6c56ef9 100644 --- a/src/components/cylc/gscan/index.js +++ b/src/components/cylc/gscan/index.js @@ -15,10 +15,47 @@ * along with this program. If not, see . */ +/** + * @typedef {Object} GScan + */ + +/** + * @typedef {Object} Lookup + */ + +/** + * @typedef {Object} Tree + */ + +/** + * @param {WorkflowGraphQLData} workflow + * @param {GScan} gscan + * @param {*} options + */ function addWorkflow (workflow, gscan, options) { } +/** + * @param {WorkflowGraphQLData} workflow + * @param {GScan} gscan + * @param {*} options + */ +function updateWorkflow (workflow, gscan, options) { + +} + +/** + * @param {WorkflowGraphQLData} workflow + * @param {GScan} gscan + * @param {*} options + */ +function removeWorkflow (workflow, gscan, options) { + +} + export { - addWorkflow + addWorkflow, + updateWorkflow, + removeWorkflow } diff --git a/src/components/cylc/gscan/nodes.js b/src/components/cylc/gscan/nodes.js index 524257d42..5af91990a 100644 --- a/src/components/cylc/gscan/nodes.js +++ b/src/components/cylc/gscan/nodes.js @@ -19,7 +19,7 @@ import { sortedIndexBy } from '@/components/cylc/common/sort' import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/sort' /** - * @typedef {Object} WorkflowGScanNode + * @typedef {Object} TreeNode * @property {String} id * @property {String} name * @property {String} type @@ -27,11 +27,11 @@ import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/ */ /** - * @typedef {Object} WorkflowNamePartGScanNode - * @property {String} id - * @property {String} name - * @property {String} type - * @property {WorkflowGraphQLData} node + * @typedef {TreeNode} WorkflowGScanNode + */ + +/** + * @typedef {TreeNode} WorkflowNamePartGScanNode * @property {Array} children */ From 8fef2c212e4813463bce4c99c3d1c960cd0d0d83 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Tue, 24 Aug 2021 15:40:52 +1200 Subject: [PATCH 08/16] Handle adding nodes --- src/components/cylc/gscan/GScan.vue | 9 ++-- src/components/cylc/gscan/deltas.js | 11 ++--- src/components/cylc/gscan/index.js | 65 ++++++++++++++++++++++++--- src/components/cylc/gscan/nodes.js | 2 +- src/components/cylc/tree/TreeItem.vue | 4 ++ src/components/cylc/tree/nodes.js | 4 +- src/store/workflows.module.js | 4 +- 7 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/components/cylc/gscan/GScan.vue b/src/components/cylc/gscan/GScan.vue index d28486555..118d0abab 100644 --- a/src/components/cylc/gscan/GScan.vue +++ b/src/components/cylc/gscan/GScan.vue @@ -312,7 +312,7 @@ export default { } }, computed: { - ...mapState('workflows', ['workflows', 'gscan']), + ...mapState('workflows', ['gscan']), // workflowNodes () { // // NOTE: In case we decide to allow the user to switch between hierarchical and flat // // gscan view, then all we need to do is just pass a boolean data-property to @@ -350,7 +350,7 @@ export default { deep: true, immediate: false, handler: function (newVal) { - this.filteredWorkflows = this.filterHierarchically(this.gscan.tree.children || [], this.searchWorkflows, this.workflowStates, this.taskStates) + this.filteredWorkflows = this.filterHierarchically(this.gscan.tree, this.searchWorkflows, this.workflowStates, this.taskStates) } }, /** @@ -360,13 +360,14 @@ export default { searchWorkflows: { immediate: false, handler: function (newVal) { - this.filteredWorkflows = this.filterHierarchically(this.gscan.tree.children || [], newVal, this.workflowStates, this.taskStates) + this.filteredWorkflows = this.filterHierarchically(this.gscan.tree, newVal, this.workflowStates, this.taskStates) } }, gscan: { immediate: true, + deep: true, handler: function () { - this.filteredWorkflows = this.filterHierarchically(this.gscan.tree.children || [], this.searchWorkflows, this.workflowStates, this.taskStates) + this.filteredWorkflows = this.filterHierarchically(this.gscan.tree, this.searchWorkflows, this.workflowStates, this.taskStates) } } }, diff --git a/src/components/cylc/gscan/deltas.js b/src/components/cylc/gscan/deltas.js index 0c0cd5d9f..37a15eebf 100644 --- a/src/components/cylc/gscan/deltas.js +++ b/src/components/cylc/gscan/deltas.js @@ -21,7 +21,7 @@ import { createWorkflowNode } from '@/components/cylc/gscan/nodes' * Deltas added. * * @param {DeltasAdded} added - * @param {GScan} gscan + * @param {import('./index').GScan} gscan * @param {*} options * @returns {Result} */ @@ -51,7 +51,7 @@ function applyDeltasAdded (added, gscan, options) { * Deltas updated. * * @param {DeltasUpdated} updated - * @param {GScan} gscan + * @param {import('./index').GScan} gscan * @param {*} options * @returns {Result} */ @@ -61,7 +61,6 @@ function applyDeltasUpdated (updated, gscan, options) { } if (updated.workflow) { const updatedData = updated.workflow - const hierarchical = options.hierarchical || true try { const existingData = gscan.lookup[updatedData.id] if (!existingData) { @@ -72,7 +71,8 @@ function applyDeltasUpdated (updated, gscan, options) { options ]) } else { - const workflowNode = createWorkflowNode(updatedData, hierarchical) + // TODO: hierarchy is always false here? + const workflowNode = createWorkflowNode(updatedData, false) updateWorkflow(workflowNode, gscan, options) } } catch (error) { @@ -92,7 +92,7 @@ function applyDeltasUpdated (updated, gscan, options) { * Deltas pruned. * * @param {DeltasPruned} pruned - * @param {GScan} gscan + * @param {import('./index').GScan} gscan * @param {*} options * @returns {Result} */ @@ -100,6 +100,7 @@ function applyDeltasPruned (pruned, gscan, options) { const result = { errors: [] } + // TODO: why not pruned.workflows??? if (pruned.workflow) { const workflowId = pruned.workflow try { diff --git a/src/components/cylc/gscan/index.js b/src/components/cylc/gscan/index.js index 8f6c56ef9..a01be6f09 100644 --- a/src/components/cylc/gscan/index.js +++ b/src/components/cylc/gscan/index.js @@ -17,27 +17,80 @@ /** * @typedef {Object} GScan + * @property {Array} tree + * @property {Lookup} lookup */ /** * @typedef {Object} Lookup */ -/** - * @typedef {Object} Tree - */ +import { sortedIndexBy } from '@/components/cylc/common/sort' +import { sortWorkflowNamePartNodeOrWorkflowNode } from '@/components/cylc/gscan/sort' /** - * @param {WorkflowGraphQLData} workflow + * @param {TreeNode} workflow * @param {GScan} gscan * @param {*} options */ function addWorkflow (workflow, gscan, options) { + const hierarchical = options.hierarchical || true + if (hierarchical) { + addHierarchicalWorkflow(workflow, gscan.lookup, gscan.tree, options) + } else { + gscan.lookup[workflow.id] = workflow + gscan.tree.push(workflow) + } +} +/** + * @param workflow + * @param {Lookup} lookup + * @param {Array} tree + * @param {*} options + */ +function addHierarchicalWorkflow (workflow, lookup, tree, options) { + if (!lookup[workflow.id]) { + // a new node, let's add this node and its descendants to the lookup + lookup[workflow.id] = workflow + if (workflow.children) { + const stack = [...workflow.children] + while (stack.length) { + const currentNode = stack.shift() + lookup[currentNode.id] = currentNode + if (currentNode.children) { + stack.push(...currentNode.children) + } + } + } + // and now add the top-level node to the tree + // Here we calculate what is the index for this element. If we decide to have ASC and DESC, + // then we just need to invert the location of the element, something like + // `sortedIndex = (array.length - sortedIndex)`. + const sortedIndex = sortedIndexBy( + tree, + workflow, + (n) => n.name, + sortWorkflowNamePartNodeOrWorkflowNode + ) + tree.splice(sortedIndex, 0, workflow) + } else { + // we will have to merge the hierarchies + const existingNode = lookup[workflow.id] + // TODO: combine states summaries? + // Copy array since we will iterate it, and modify existingNode.children + if (existingNode.children) { + const children = [...workflow.children] + for (const child of children) { + // Recursion + addHierarchicalWorkflow(child, lookup, existingNode.children, options) + } + } + } } /** - * @param {WorkflowGraphQLData} workflow + * @param {TreeNode} workflow * @param {GScan} gscan * @param {*} options */ @@ -46,7 +99,7 @@ function updateWorkflow (workflow, gscan, options) { } /** - * @param {WorkflowGraphQLData} workflow + * @param {TreeNode} workflow * @param {GScan} gscan * @param {*} options */ diff --git a/src/components/cylc/gscan/nodes.js b/src/components/cylc/gscan/nodes.js index 5af91990a..d0373ee9f 100644 --- a/src/components/cylc/gscan/nodes.js +++ b/src/components/cylc/gscan/nodes.js @@ -85,7 +85,7 @@ function newWorkflowPartNode (id, part) { * * @param {WorkflowGraphQLData} workflow * @param {boolean} hierarchy - whether to parse the Workflow name and create a hierarchy or not - * @returns {WorkflowGScanNode|WorkflowNamePartGScanNode|null} + * @returns {TreeNode} */ function createWorkflowNode (workflow, hierarchy) { if (!hierarchy) { diff --git a/src/components/cylc/tree/TreeItem.vue b/src/components/cylc/tree/TreeItem.vue index df49fd1c3..645fd67c3 100644 --- a/src/components/cylc/tree/TreeItem.vue +++ b/src/components/cylc/tree/TreeItem.vue @@ -284,8 +284,12 @@ export default { } }, created () { + // console.log(`TreeItem ${this.node.id} created!`) this.$emit('tree-item-created', this) }, + updated () { + // console.log(`TreeItem ${this.node.id} updated!`) + }, beforeDestroy () { this.$emit('tree-item-destroyed', this) }, diff --git a/src/components/cylc/tree/nodes.js b/src/components/cylc/tree/nodes.js index e52ea692e..063b30e0c 100644 --- a/src/components/cylc/tree/nodes.js +++ b/src/components/cylc/tree/nodes.js @@ -38,7 +38,7 @@ import Vue from 'vue' * Create a workflow node. Uses the same properties (by reference) as the given workflow, * only adding new properties such as type, children, etc. * - * @param workflow {WorkflowGraphQLData} workflow + * @param {WorkflowGraphQLData} workflow workflow * @return {WorkflowNode} */ function createWorkflowNode (workflow) { @@ -70,7 +70,7 @@ function createWorkflowNode (workflow) { * - 'a|b' results in a cycle point node ID 'a|b' * - '' results in a cycle point node ID '' * - * @param node {GraphQLData} a tree node + * @param {GraphQLData} node a tree node * @throws {Error} - if there was an error extracting the cycle point ID * @return {string} - the cycle point ID */ diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index b03a26dc8..837be3af7 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -32,7 +32,7 @@ const state = { * and the lookup is a helper structure for quick access to nodes in the tree. */ gscan: { - tree: {}, + tree: [], lookup: {} }, /** @@ -71,7 +71,7 @@ const mutations = { }) } state.gscan = { - tree: {}, + tree: [], lookup: {} } }, From bcf743bb104ed3e590b38568a32ae3fd92615494 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Tue, 24 Aug 2021 21:37:46 +1200 Subject: [PATCH 09/16] A few performance improvements for TreeItem --- src/components/cylc/tree/TreeItem.vue | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/cylc/tree/TreeItem.vue b/src/components/cylc/tree/TreeItem.vue index 645fd67c3..60a0c423b 100644 --- a/src/components/cylc/tree/TreeItem.vue +++ b/src/components/cylc/tree/TreeItem.vue @@ -194,7 +194,6 @@ along with this program. If not, see . . v-on:tree-item-expanded="$listeners['tree-item-expanded']" v-on:tree-item-collapsed="$listeners['tree-item-collapsed']" v-on:tree-item-clicked="$listeners['tree-item-clicked']" + > @@ -249,7 +249,7 @@ export default { active: false, selected: false, isExpanded: this.initialExpanded, - leafProperties: [ + leafProperties: Object.freeze([ { title: 'platform', property: 'platform' @@ -274,7 +274,7 @@ export default { title: 'finish time', property: 'finishedTime' } - ], + ]), filtered: true } }, @@ -284,12 +284,8 @@ export default { } }, created () { - // console.log(`TreeItem ${this.node.id} created!`) this.$emit('tree-item-created', this) }, - updated () { - // console.log(`TreeItem ${this.node.id} updated!`) - }, beforeDestroy () { this.$emit('tree-item-destroyed', this) }, From 2470e7709e3c495387c16fac307f17a749a0905b Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Tue, 24 Aug 2021 22:08:53 +1200 Subject: [PATCH 10/16] Avoid undefined error in Tree component --- src/components/cylc/tree/Tree.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/cylc/tree/Tree.vue b/src/components/cylc/tree/Tree.vue index dedc28157..ceb3250bc 100644 --- a/src/components/cylc/tree/Tree.vue +++ b/src/components/cylc/tree/Tree.vue @@ -218,6 +218,9 @@ export default { }) }, tasksFilterStates: function () { + if (!this.activeFilters) { + return [] + } return this.activeFilters.states.map(selectedTaskState => { return selectedTaskState }) From 263ff2e129b742715fa72926c05c7ef133c25aa4 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Wed, 8 Sep 2021 17:02:01 +1200 Subject: [PATCH 11/16] Create WorkflowStateSummary component --- src/components/cylc/gscan/GScan.vue | 80 ++----------- .../cylc/gscan/WorkflowStateSummary.vue | 110 ++++++++++++++++++ 2 files changed, 117 insertions(+), 73 deletions(-) create mode 100644 src/components/cylc/gscan/WorkflowStateSummary.vue diff --git a/src/components/cylc/gscan/GScan.vue b/src/components/cylc/gscan/GScan.vue index 118d0abab..aa71b3c5a 100644 --- a/src/components/cylc/gscan/GScan.vue +++ b/src/components/cylc/gscan/GScan.vue @@ -142,42 +142,12 @@ along with this program. If not, see . - - - - - - - {{ countTasksInState(scope.node.node, state) }} {{ state }}. Recent {{ state }} tasks: -
- - {{ task }}
-
-
-
-
+
@@ -203,9 +173,9 @@ import subscriptionComponentMixin from '@/mixins/subscriptionComponent' import TaskState from '@/model/TaskState.model' import SubscriptionQuery from '@/model/SubscriptionQuery.model' import { WorkflowState } from '@/model/WorkflowState.model' -import Job from '@/components/cylc/Job' import Tree from '@/components/cylc/tree/Tree' import WorkflowIcon from '@/components/cylc/gscan/WorkflowIcon' +import WorkflowStateSummary from '@/components/cylc/gscan/WorkflowStateSummary' // import { addNodeToTree, createWorkflowNode } from '@/components/cylc/gscan/nodes' import { filterHierarchically } from '@/components/cylc/gscan/filters' import { GSCAN_DELTAS_SUBSCRIPTION } from '@/graphql/queries' @@ -213,9 +183,9 @@ import { GSCAN_DELTAS_SUBSCRIPTION } from '@/graphql/queries' export default { name: 'GScan', components: { - Job, Tree, - WorkflowIcon + WorkflowIcon, + WorkflowStateSummary }, mixins: [ subscriptionComponentMixin @@ -415,42 +385,6 @@ export default { return `/workflows/${ node.node.name }` } return '' - }, - - /** - * Get number of tasks we have in a given state. The states are retrieved - * from `latestStateTasks`, and the number of tasks in each state is from - * the `stateTotals`. (`latestStateTasks` includes old tasks). - * - * @param {WorkflowGraphQLData} workflow - the workflow object retrieved from GraphQL - * @param {string} state - a workflow state - * @returns {number|*} - the number of tasks in the given state - */ - countTasksInState (workflow, state) { - if (Object.hasOwnProperty.call(workflow.stateTotals, state)) { - return workflow.stateTotals[state] - } - return 0 - }, - - getTaskStateClasses (workflow, state) { - const tasksInState = this.countTasksInState(workflow, state) - return tasksInState === 0 ? ['empty-state'] : [] - }, - - // TODO: temporary filter, remove after b0 - https://github.com/cylc/cylc-ui/pull/617#issuecomment-805343847 - getLatestStateTasks (latestStateTasks) { - // Values found in: https://github.com/cylc/cylc-flow/blob/9c542f9f3082d3c3d9839cf4330c41cfb2738ba1/cylc/flow/data_store_mgr.py#L143-L149 - const validValues = [ - TaskState.SUBMITTED.name, - TaskState.SUBMIT_FAILED.name, - TaskState.RUNNING.name, - TaskState.SUCCEEDED.name, - TaskState.FAILED.name - ] - return latestStateTasks.filter(entry => { - return validValues.includes(entry[0]) - }) } } } diff --git a/src/components/cylc/gscan/WorkflowStateSummary.vue b/src/components/cylc/gscan/WorkflowStateSummary.vue new file mode 100644 index 000000000..3416004c7 --- /dev/null +++ b/src/components/cylc/gscan/WorkflowStateSummary.vue @@ -0,0 +1,110 @@ + + + + + From 9d931a9948a4ecd297c12b6b49010277cad76d41 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Thu, 9 Sep 2021 09:39:12 +1200 Subject: [PATCH 12/16] Simplify the WorkflowStateSummary component, replace methods by computed, use more props --- src/components/cylc/gscan/GScan.vue | 4 +- .../cylc/gscan/WorkflowStateSummary.vue | 83 ++++++++++++------- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/components/cylc/gscan/GScan.vue b/src/components/cylc/gscan/GScan.vue index aa71b3c5a..463e06b2b 100644 --- a/src/components/cylc/gscan/GScan.vue +++ b/src/components/cylc/gscan/GScan.vue @@ -146,7 +146,9 @@ along with this program. If not, see . class="text-right c-gscan-workflow-states" > diff --git a/src/components/cylc/gscan/WorkflowStateSummary.vue b/src/components/cylc/gscan/WorkflowStateSummary.vue index 3416004c7..4d641d674 100644 --- a/src/components/cylc/gscan/WorkflowStateSummary.vue +++ b/src/components/cylc/gscan/WorkflowStateSummary.vue @@ -19,9 +19,9 @@ along with this program. If not, see . - {{ countTasksInState(node.node, state) }} {{ state }}. Recent {{ state }} tasks: + {{ countTasksInState(state) }} {{ state }}. Recent {{ state }} tasks:
{{ task }}
@@ -56,10 +56,33 @@ along with this program. If not, see .