Skip to content

Commit

Permalink
Merge pull request #2016 from cylc/flow-nums
Browse files Browse the repository at this point in the history
Show flow numbers
  • Loading branch information
hjoliver authored Jan 8, 2025
2 parents b6c2d08 + b5d310f commit e263a14
Show file tree
Hide file tree
Showing 26 changed files with 476 additions and 134 deletions.
1 change: 1 addition & 0 deletions changes.d/2016.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show [flow numbers](https://cylc.github.io/cylc-doc/stable/html/glossary.html#term-flow) when applicable. Removed tasks (flow=None) are now dimmed.
79 changes: 79 additions & 0 deletions cypress/component/treeItem.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

import TreeItem from '@/components/cylc/tree/TreeItem.vue'
import {
simpleCyclepointNode,
} from '$tests/unit/components/cylc/tree/tree.data'

// Get lists of: a) all tree item nodes; b) only visible ones.
Cypress.Commands.add('getNodeTypes', () => {
cy.get('.c-treeitem')
.then(($els) => {
const all = Array.from($els, ({ dataset }) => dataset.nodeType)
const visible = all.filter((val, i) => $els[i].checkVisibility())
return { all, visible }
})
})

// Expand/collapse the first node of this type.
Cypress.Commands.add('toggleNode', (nodeType) => {
cy.get(`[data-node-type=${nodeType}] .node-expand-collapse-button:first`).click()
})

describe('TreeItem component', () => {
it('expands/collapses children', () => {
cy.vmount(TreeItem, {
props: {
node: simpleCyclepointNode,
filteredOutNodesCache: new WeakMap(),
},
})
cy.addVuetifyStyles(cy)

cy.getNodeTypes()
.should('deep.equal', {
// Auto expand everything down to task nodes by default
all: ['cycle', 'task'],
visible: ['cycle', 'task']
})

cy.toggleNode('task')
cy.getNodeTypes()
.should('deep.equal', {
all: ['cycle', 'task', 'job'],
visible: ['cycle', 'task', 'job']
})

cy.toggleNode('cycle')
cy.getNodeTypes()
.should('deep.equal', {
// All previously expanded nodes under cycle should be hidden but remain rendered
all: ['cycle', 'task', 'job'],
visible: ['cycle']
})

cy.toggleNode('cycle')
cy.toggleNode('job')
cy.getNodeTypes()
.should('deep.equal', {
// Job node does not use a child TreeItem
all: ['cycle', 'task', 'job'],
visible: ['cycle', 'task', 'job']
})
})
})
13 changes: 9 additions & 4 deletions src/components/cylc/commandMenu/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ 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'
import { formatFlowNums } from '@/utils/tasks'

export default {
name: 'CommandMenu',
Expand Down Expand Up @@ -199,14 +201,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)'
}
Expand All @@ -216,6 +218,9 @@ export default {
if (this.node.node.isRunahead) {
ret += ' (runahead)'
}
if (this.node.node.flowNums) {
ret += ` • Flows: ${formatFlowNums(this.node.node.flowNums)}`
}
}
}
return ret
Expand Down
56 changes: 56 additions & 0 deletions src/components/cylc/common/FlowNumsChip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!--
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 <http://www.gnu.org/licenses/>.
-->

<template>
<v-chip
v-if="showFlowNums"
label
density="compact"
size="small"
class="ml-1 cursor-default"
:prepend-icon="mdiLabelOutline"
data-cy="flow-num-chip"
>
{{ flowNumsStr }}
<v-tooltip location="right">
Flows: {{ flowNumsStr }}
</v-tooltip>
</v-chip>
</template>

<script setup>
import { formatFlowNums } from '@/utils/tasks'
import { mdiLabelOutline } from '@mdi/js'
import { computed } from 'vue'

const props = defineProps({
flowNums: {
type: String,
required: false,
},
})

const flowNumsStr = computed(
() => props.flowNums && formatFlowNums(props.flowNums)
)

/** Hide flow=1 and flow=None by default */
const showFlowNums = computed(
() => flowNumsStr.value && !['1', 'None'].includes(flowNumsStr.value)
)

</script>
20 changes: 17 additions & 3 deletions src/components/cylc/table/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
v-model:items-per-page="itemsPerPage"
>
<template v-slot:item.task.name="{ item }">
<div class="d-flex align-content-center flex-nowrap">
<div
class="d-flex align-center flex-nowrap"
:class="{ 'flow-none': isFlowNone(item.task.node.flowNums) }"
:data-cy-task-name="item.task.name"
>
<div style="width: 2em;">
<Task
v-command-menu="item.task"
Expand All @@ -44,7 +48,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:previous-state="item.previousJob?.node?.state"
/>
</div>
<div>{{ item.task.name }}</div>
{{ item.task.name }}
<FlowNumsChip
:flowNums="item.task.node.flowNums"
class="ml-2"
/>
</div>
</template>
<template v-slot:item.task.node.task.meanElapsedTime="{ item }">
Expand Down Expand Up @@ -110,13 +118,17 @@ import {
datetimeComparator,
numberComparator,
} from '@/components/cylc/table/sort'
import { dtMean } from '@/utils/tasks'
import {
dtMean,
isFlowNone,
} from '@/utils/tasks'
import { useCyclePointsOrderDesc } from '@/composables/localStorage'
import {
initialOptions,
updateInitialOptionsEvent,
useInitialOptions
} from '@/utils/initialOptions'
import FlowNumsChip from '@/components/cylc/common/FlowNumsChip.vue'

export default {
name: 'TableComponent',
Expand All @@ -132,6 +144,7 @@ export default {
},

components: {
FlowNumsChip,
Task,
Job,
},
Expand Down Expand Up @@ -243,6 +256,7 @@ export default {
icons: {
mdiChevronDown
},
isFlowNone,
itemsPerPageOptions: [
{ value: 10, title: '10' },
{ value: 20, title: '20' },
Expand Down
61 changes: 32 additions & 29 deletions src/components/cylc/tree/TreeItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<div
v-show="!filteredOutNodesCache.get(node)"
class="c-treeitem"
:data-node-type="node.type"
:data-node-name="node.name"
>
<div
class="node d-flex align-center"
Expand All @@ -30,7 +32,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
v-if="renderExpandCollapseBtn"
aria-label="Expand/collapse"
class="node-expand-collapse-button flex-shrink-0"
@click="toggleExpandCollapse"
@click="toggleExpandCollapse()"
:style="expandCollapseBtnStyle"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
Expand Down Expand Up @@ -63,7 +65,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
<span class="mx-1">{{ node.name }}</span>
</template>
<template v-else-if="node.type === 'task'">
<div
v-else-if="node.type === 'task'"
class="d-flex align-center"
:class="{ 'flow-none': isFlowNone(node.node.flowNums) }"
>
<!-- Task summary -->
<Task
v-command-menu="node"
Expand All @@ -85,7 +91,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</div>
<span class="mx-1">{{ node.name }}</span>
</template>
<FlowNumsChip :flowNums="node.node.flowNums"/>
</div>
<template v-else-if="node.type === 'job'">
<Job
v-command-menu="node"
Expand Down Expand Up @@ -134,7 +141,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="ml-2 bg-grey text-white"
size="small"
link
@click="toggleExpandCollapse"
@click="toggleExpandCollapse()"
>
+{{ jobMessageOutputs.length - 5 }}
</v-chip>
Expand Down Expand Up @@ -175,20 +182,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>

<script>
import { mdiChevronRight } from '@mdi/js'
import {
mdiChevronRight,
} from '@mdi/js'
import Task from '@/components/cylc/Task.vue'
import Job from '@/components/cylc/Job.vue'
import JobDetails from '@/components/cylc/tree/JobDetails.vue'
import {
jobMessageOutputs,
latestJob,
jobMessageOutputs
isFlowNone,
} from '@/utils/tasks'
import { getIndent, getNodeChildren } from '@/components/cylc/tree/util'
import { once } from '@/utils'
import { useToggle } from '@vueuse/core'
import FlowNumsChip from '@/components/cylc/common/FlowNumsChip.vue'

export default {
name: 'TreeItem',

components: {
FlowNumsChip,
Task,
Job,
JobDetails,
Expand Down Expand Up @@ -240,27 +254,23 @@ export default {
},
},

data () {
setup (props) {
const [isExpanded, toggleExpandCollapse] = useToggle(
props.autoExpandTypes.includes(props.node.type)
)
// Toggles to true when this.isExpanded first becomes true and doesn't get recomputed afterwards
const renderChildren = once(isExpanded)

return {
manuallyExpanded: null,
isExpanded,
isFlowNone,
latestJob,
renderChildren,
toggleExpandCollapse,
}
},

computed: {
isExpanded: {
get () {
return this.manuallyExpanded ?? this.autoExpandTypes.includes(this.node.type)
},
set (value) {
this.manuallyExpanded = value
}
},

renderChildren () {
// Toggles to true when this.isExpanded first becomes true and doesn't get recomputed afterwards
return this.renderChildren || this.isExpanded
},

hasChildren () {
return (
// "job" nodes have auto-generated "job-detail" nodes
Expand Down Expand Up @@ -310,13 +320,6 @@ export default {
}
},

methods: {
toggleExpandCollapse () {
this.isExpanded = !this.isExpanded
},
latestJob
},

icons: {
mdiChevronRight,
},
Expand Down
1 change: 1 addition & 0 deletions src/services/mock/checkpoint/get_checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
meanElapsedTime
name
}
flowNums
}
fragment JobData on Job {
Expand Down
2 changes: 2 additions & 0 deletions src/services/mock/json/workflows/multi.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"isQueued": true,
"isRunahead": false,
"name": "foo",
"flowNums": "[1]",
"task": {
"meanElapsedTime": 0,
"__typename": "Task"
Expand All @@ -162,6 +163,7 @@
"isQueued": false,
"isRunahead": false,
"name": "foo",
"flowNums": "[1]",
"task": {
"meanElapsedTime": 0,
"__typename": "Task"
Expand Down
Loading

0 comments on commit e263a14

Please sign in to comment.