diff --git a/src/components/cylc/commandMenu/Menu.vue b/src/components/cylc/commandMenu/Menu.vue index a75a8e065..a11bdf2e7 100644 --- a/src/components/cylc/commandMenu/Menu.vue +++ b/src/components/cylc/commandMenu/Menu.vue @@ -126,6 +126,7 @@ import { mapGetters, mapState } from 'vuex' import WorkflowState from '@/model/WorkflowState.model' import { eventBus } from '@/services/eventBus' import CopyBtn from '@/components/core/CopyBtn.vue' +import { upperFirst } from 'lodash-es' export default { name: 'CommandMenu', @@ -199,14 +200,14 @@ export default { // can happen briefly when switching workflows return } - let ret = this.node.type + let ret = upperFirst(this.node.type) if (this.node.type !== 'cycle') { // NOTE: cycle point nodes don't have associated node data at present - ret += ' - ' + ret += ' • ' if (this.node.type === 'workflow') { - ret += this.node.node.statusMsg || this.node.node.status || 'state unknown' + ret += upperFirst(this.node.node.statusMsg || this.node.node.status || 'state unknown') } else { - ret += this.node.node.state || 'state unknown' + ret += upperFirst(this.node.node.state || 'state unknown') if (this.node.node.isHeld) { ret += ' (held)' } @@ -216,6 +217,9 @@ export default { if (this.node.node.isRunahead) { ret += ' (runahead)' } + if (this.node.node.flowNums) { + ret += ` • Flows: ${this.node.node.flowNums.replace(/\[|\]/g, '')}` + } } } return ret diff --git a/src/components/cylc/tree/TreeItem.vue b/src/components/cylc/tree/TreeItem.vue index 8a7ecad70..dc210d593 100644 --- a/src/components/cylc/tree/TreeItem.vue +++ b/src/components/cylc/tree/TreeItem.vue @@ -85,6 +85,19 @@ along with this program. If not, see . /> {{ node.name }} + + {{ formatFlowNums(node.node.flowNums) }} + + Flows: {{ formatFlowNums(node.node.flowNums) }} + + diff --git a/src/services/mock/json/workflows/one.json b/src/services/mock/json/workflows/one.json index 45ea00f2b..7ba953204 100644 --- a/src/services/mock/json/workflows/one.json +++ b/src/services/mock/json/workflows/one.json @@ -115,6 +115,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/root", "name": "root", @@ -133,6 +134,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/SUCCEEDED", "name": "SUCCEEDED", @@ -152,6 +154,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/BAD", "name": "BAD", @@ -170,6 +173,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/BAD", "name": "BAD", @@ -188,6 +192,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/root", "name": "root", @@ -206,6 +211,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/SUCCEEDED", "name": "SUCCEEDED", @@ -225,6 +231,7 @@ "isQueued": false, "isRunahead": false, "cyclePoint": "20000102T0000Z", + "flowNums": "[1]", "firstParent": { "id": "~user/one//20000102T0000Z/root", "name": "root", diff --git a/src/styles/cylc/_tree.scss b/src/styles/cylc/_tree.scss index d80b9d64c..573e7a622 100644 --- a/src/styles/cylc/_tree.scss +++ b/src/styles/cylc/_tree.scss @@ -97,6 +97,7 @@ $icon-width: 1.5rem; .node-data { display: flex; flex-wrap: nowrap; + align-items: center; .node-summary { display: flex; flex-wrap: nowrap; diff --git a/src/utils/tasks.js b/src/utils/tasks.js index 370edf39c..53ac914a2 100644 --- a/src/utils/tasks.js +++ b/src/utils/tasks.js @@ -40,7 +40,7 @@ const isStoppedOrderedStates = [ * @returns {string} a valid Task State name, or empty string if not found * @link @see https://github.com/cylc/cylc-flow/blob/d66ae5c3ce8c749c8178d1cd53cb8c81d1560346/lib/cylc/task_state_prop.py */ -function extractGroupState (childStates, isStopped = false) { +export function extractGroupState (childStates, isStopped = false) { const states = isStopped ? isStoppedOrderedStates : TaskState.enumValues for (const state of states) { if (childStates.includes(state.name)) { @@ -50,7 +50,7 @@ function extractGroupState (childStates, isStopped = false) { return '' } -function latestJob (taskProxy) { +export function latestJob (taskProxy) { return taskProxy?.children?.[0]?.node } @@ -67,7 +67,7 @@ function latestJob (taskProxy) { * } * } */ -function jobMessageOutputs (jobNode) { +export function jobMessageOutputs (jobNode) { const ret = [] for (const message of jobNode.node.messages || []) { @@ -96,7 +96,7 @@ function jobMessageOutputs (jobNode) { * 00:00:00, rather than undefined * @return {string=} Formatted duration */ -function formatDuration (dur, allowZeros = false) { +export function formatDuration (dur, allowZeros = false) { if (dur || (dur === 0 && allowZeros === true)) { const seconds = dur % 60 const minutes = ((dur - seconds) / 60) % 60 @@ -118,16 +118,16 @@ function formatDuration (dur, allowZeros = false) { return undefined } -function dtMean (taskNode) { +export function dtMean (taskNode) { // Convert to an easily read duration format: const dur = taskNode.node?.task?.meanElapsedTime return formatDuration(dur) } -export { - extractGroupState, - latestJob, - jobMessageOutputs, - formatDuration, - dtMean +/** + * @param {string} flowNums - Flow numbers in DB format + * @returns {string} - Flow numbers in pretty format + */ +export function formatFlowNums (flowNums) { + return JSON.parse(flowNums).join(', ') || 'None' } diff --git a/src/views/Tree.vue b/src/views/Tree.vue index d6a029c45..83dbb964d 100644 --- a/src/views/Tree.vue +++ b/src/views/Tree.vue @@ -193,6 +193,7 @@ fragment TaskProxyData on TaskProxy { firstParent { id } + flowNums } fragment JobData on Job { diff --git a/tests/unit/components/cylc/tree/tree.vue.spec.js b/tests/unit/components/cylc/tree/tree.vue.spec.js index 94abbe904..060fff6c7 100644 --- a/tests/unit/components/cylc/tree/tree.vue.spec.js +++ b/tests/unit/components/cylc/tree/tree.vue.spec.js @@ -15,28 +15,21 @@ * along with this program. If not, see . */ -// we mount the tree to include the TreeItem component and other vuetify children components import { mount } from '@vue/test-utils' import { vi } from 'vitest' -import { createVuetify } from 'vuetify' import { cloneDeep } from 'lodash' import Tree from '@/components/cylc/tree/Tree.vue' import { simpleWorkflowTree4Nodes } from './tree.data' -import CommandMenuPlugin from '@/components/cylc/commandMenu/plugin' - -const vuetify = createVuetify() describe('Tree component', () => { const mountFunction = (props) => mount(Tree, { - global: { - plugins: [vuetify, CommandMenuPlugin], - }, props: { workflows: cloneDeep(simpleWorkflowTree4Nodes), autoStripTypes: ['workflow'], filterState: null, ...props, - } + }, + shallow: true, }) it.each([ diff --git a/tests/unit/utils/tasks.spec.js b/tests/unit/utils/tasks.spec.js index 0511e3f36..7a7f66c36 100644 --- a/tests/unit/utils/tasks.spec.js +++ b/tests/unit/utils/tasks.spec.js @@ -16,7 +16,14 @@ */ import TaskState from '@/model/TaskState.model' -import { dtMean, extractGroupState, latestJob, formatDuration, jobMessageOutputs } from '@/utils/tasks' +import { + dtMean, + extractGroupState, + latestJob, + formatDuration, + jobMessageOutputs, + formatFlowNums, +} from '@/utils/tasks' describe('tasks', () => { describe('extractGroupState', () => { @@ -207,4 +214,14 @@ describe('tasks', () => { ]) }) }) + + describe('formatFlowNums', () => { + it.each([ + ['[1]', '1'], + ['[1, 4, 8]', '1, 4, 8'], + ['[]', 'None'], + ])('formatFlowNums(%s) -> %s', (input, expected) => { + expect(formatFlowNums(input)).toEqual(expected) + }) + }) })