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)
+ })
+ })
})