diff --git a/CHANGES.md b/CHANGES.md index 336f0d783..b4fac21c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,11 @@ ones in. --> [#1269](https://github.com/cylc/cylc-ui/pull/1269) - Upgraded Vue and Vuetify frameworks to v3. +### Fixes + +[#1312](https://github.com/cylc/cylc-ui/pull/1312) - +Fix incorrect latest job info in table view. + ------------------------------------------------------------------------------- ## __cylc-ui-1.6.0 (Released 2023-04-27)__ diff --git a/src/components/cylc/table/Table.vue b/src/components/cylc/table/Table.vue index 893244fc6..21ca5d26b 100644 --- a/src/components/cylc/table/Table.vue +++ b/src/components/cylc/table/Table.vue @@ -60,14 +60,14 @@ along with this program. If not, see .
{{ item.value.task.name }}
diff --git a/src/store/workflows.module.js b/src/store/workflows.module.js index 1bd61b2ab..a3472c9ce 100644 --- a/src/store/workflows.module.js +++ b/src/store/workflows.module.js @@ -80,7 +80,7 @@ const getters = { while (stack.length) { item = stack.pop() if (parentNodeTypes.includes(item.type)) { - // this is above "nodeTyoe" in the tree, look through its child nodes + // this is above "nodeType" in the tree, look through its child nodes stack.push(...item.children) } else if ( item.type === nodeType && @@ -99,12 +99,11 @@ function createTree (state) { if (state.cylcTree) { return } - const tree = { + state.cylcTree = { $index: {}, - id: '$root' + id: '$root', + children: [], } - tree.children = [] - state.cylcTree = tree // console.log('@@') } diff --git a/src/utils/tasks.js b/src/utils/tasks.js index fec678be0..6e4485919 100644 --- a/src/utils/tasks.js +++ b/src/utils/tasks.js @@ -20,7 +20,7 @@ import { TASK_OUTPUT_NAMES } from '@/model/TaskOutput.model' /** * States used when the parent is stopped. - * @type {Array} + * @type {TaskState[]} */ const isStoppedOrderedStates = [ TaskState.SUBMIT_FAILED, @@ -35,9 +35,9 @@ const isStoppedOrderedStates = [ /** * Gives a single state, based on a list of states of children nodes. - * @param childStates {Array} children nodes - * @param isStopped {boolean} whether the parent node is stopped or not - * @returns {string} a valid Task State name, or null if not found + * @param {TaskState[]} childStates children nodes + * @param {boolean} isStopped whether the parent node is stopped or not + * @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) { @@ -51,10 +51,7 @@ function extractGroupState (childStates, isStopped = false) { } function latestJob (taskProxy) { - if (taskProxy && taskProxy.children && taskProxy.children.length > 0) { - return taskProxy.children[0].node - } - return null + return taskProxy?.children?.[0]?.node } /** Returns an array of task messages and custom outputs for a job node. diff --git a/src/views/Table.vue b/src/views/Table.vue index 4fa52d93e..df74a2e7a 100644 --- a/src/views/Table.vue +++ b/src/views/Table.vue @@ -65,23 +65,13 @@ export default { }, tasks () { const ret = [] - let latestJob - let previousJob for (const workflow of this.workflows) { for (const cycle of workflow.children) { for (const task of cycle.children) { - latestJob = null - previousJob = null - if (task.children.length) { - latestJob = task.children.slice(-1)[0] - if (task.children.length > 1) { - previousJob = task.children.slice(-2)[0] - } - } ret.push({ task, - latestJob, - previousJob + latestJob: task.children[0], + previousJob: task.children[1], }) } } diff --git a/tests/unit/components/cylc/table/table.vue.spec.js b/tests/unit/components/cylc/table/table.vue.spec.js index a404efdd2..a24a70239 100644 --- a/tests/unit/components/cylc/table/table.vue.spec.js +++ b/tests/unit/components/cylc/table/table.vue.spec.js @@ -17,26 +17,18 @@ import { mount } from '@vue/test-utils' import { createVuetify } from 'vuetify' +import sinon from 'sinon' import { simpleTableTasks } from './table.data' import TaskState from '@/model/TaskState.model' import CylcObjectPlugin from '@/components/cylc/cylcObject/plugin' import Table from '@/components/cylc/table/Table.vue' import { VDataTable, VDataTableFooter } from 'vuetify/labs/VDataTable' +import WorkflowService from '@/services/workflow.service' const $eventBus = { emit () {} } -const $workflowService = { - register () {}, - unregister () {}, - subscribe () {}, - introspection: Promise.resolve({ - mutations: [ - { args: [] } - ], - types: [] - }) -} +const $workflowService = sinon.createStubInstance(WorkflowService) const vuetify = createVuetify({ components: { VDataTable, VDataTableFooter } diff --git a/tests/unit/components/cylc/tree/tree.vue.spec.js b/tests/unit/components/cylc/tree/tree.vue.spec.js index 4e1a26df9..7ed3f3da9 100644 --- a/tests/unit/components/cylc/tree/tree.vue.spec.js +++ b/tests/unit/components/cylc/tree/tree.vue.spec.js @@ -19,25 +19,17 @@ import { nextTick } from 'vue' import { mount } from '@vue/test-utils' import { createVuetify } from 'vuetify' +import sinon from 'sinon' import Tree from '@/components/cylc/tree/Tree.vue' import { simpleWorkflowTree4Nodes } from './tree.data' import CylcObjectPlugin from '@/components/cylc/cylcObject/plugin' import cloneDeep from 'lodash/cloneDeep' +import WorkflowService from '@/services/workflow.service' const $eventBus = { emit () {} } -const $workflowService = { - register () {}, - unregister () {}, - subscribe () {}, - introspection: Promise.resolve({ - mutations: [ - { args: [] } - ], - types: [] - }) -} +const $workflowService = sinon.createStubInstance(WorkflowService) const vuetify = createVuetify() describe('Tree component', () => { diff --git a/tests/unit/components/cylc/tree/treeitem.vue.spec.js b/tests/unit/components/cylc/tree/treeitem.vue.spec.js index e3c5a7e74..3dfb55b02 100644 --- a/tests/unit/components/cylc/tree/treeitem.vue.spec.js +++ b/tests/unit/components/cylc/tree/treeitem.vue.spec.js @@ -19,6 +19,7 @@ import { mount } from '@vue/test-utils' import { Assertion } from 'chai' import { createVuetify } from 'vuetify' +import sinon from 'sinon' import TreeItem from '@/components/cylc/tree/TreeItem.vue' import { simpleWorkflowNode, @@ -26,6 +27,7 @@ import { simpleTaskNode } from './tree.data' import CylcObjectPlugin from '@/components/cylc/cylcObject/plugin' +import WorkflowService from '@/services/workflow.service' /** * Helper function for expecting TreeItem to be expanded. @@ -50,12 +52,7 @@ Assertion.addMethod('expanded', function () { ) }) -const $workflowService = { - introspection: Promise.resolve({ - mutations: [], - types: [] - }) -} +const $workflowService = sinon.createStubInstance(WorkflowService) const $eventBus = { emit: () => {} } diff --git a/tests/unit/utils/tasks.spec.js b/tests/unit/utils/tasks.spec.js index a7d88b1ff..706010d7d 100644 --- a/tests/unit/utils/tasks.spec.js +++ b/tests/unit/utils/tasks.spec.js @@ -56,37 +56,33 @@ describe('tasks', () => { expect(extractGroupState([])).to.equal('') }) }) - describe('latestJob', () => { - it('should return the correct value for latestJob', () => { - const tests = [ - { - taskProxy: null, - expected: null - }, - { - taskProxy: {}, - expected: null - }, - { - taskProxy: { - children: [] - }, - expected: null - }, - { - taskProxy: { - children: [ - { - node: 1 - } - ] - }, - expected: 1 - } - ] - tests.forEach(test => { - expect(latestJob(test.taskProxy)).to.equal(test.expected) - }) + describe.each([ + { + taskProxy: null, + expected: undefined + }, + { + taskProxy: {}, + expected: undefined + }, + { + taskProxy: { + children: [] + }, + expected: undefined + }, + { + taskProxy: { + children: [ + { node: 'foo' }, + { node: 'bar' }, + ] + }, + expected: 'foo' + } + ])('latestJob($taskProxy)', ({ taskProxy, expected }) => { + it(`returns ${expected}`, () => { + expect(latestJob(taskProxy)).to.equal(expected) }) }) describe('dtMean', () => { diff --git a/tests/unit/views/table.vue.spec.js b/tests/unit/views/table.vue.spec.js new file mode 100644 index 000000000..77bee88a5 --- /dev/null +++ b/tests/unit/views/table.vue.spec.js @@ -0,0 +1,104 @@ +/** + * 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 { mount } from '@vue/test-utils' +import { createStore } from 'vuex' +import sinon from 'sinon' +import storeOptions from '@/store/options' +import Table from '@/views/Table.vue' +import WorkflowService from '@/services/workflow.service' +import User from '@/model/User.model' + +chai.config.truncateThreshold = 0 + +const workflows = [ + { + id: '~user/one', + children: [ + { + id: '~user/one//1', + children: [ + { + id: '~user/one//1/eventually_succeeded', + children: [ + { + id: '~user/one//1/eventually_succeeded/3', + children: [], + }, + { + id: '~user/one//1/eventually_succeeded/2', + children: [], + }, + { + id: '~user/one//1/eventually_succeeded/1', + children: [], + }, + ], + }, + { + id: '~user/one//1/failed', + children: [ + { + id: '~user/one//1/failed/1', + children: [], + }, + ], + }, + ] + } + ] + }, +] + +describe('Table view', () => { + let store, $workflowService + beforeEach(() => { + store = createStore(storeOptions) + const user = new User('cylc', [], new Date(), true, 'localhost', 'owner') + store.commit('user/SET_USER', user) + $workflowService = sinon.createStubInstance(WorkflowService) + }) + + it('computes tasks', async () => { + const wrapper = mount(Table, { + shallow: true, + global: { + plugins: [store], + mocks: { $workflowService } + }, + props: { + workflowName: 'one', + }, + data: () => ({ + // Override computed property + workflows + }) + }) + + expect(wrapper.vm.tasks).toMatchObject([ + { + task: { id: '~user/one//1/eventually_succeeded' }, + latestJob: { id: '~user/one//1/eventually_succeeded/3' }, + previousJob: { id: '~user/one//1/eventually_succeeded/2' } + }, + { + task: { id: '~user/one//1/failed' }, + latestJob: { id: '~user/one//1/failed/1' }, + } + ]) + }) +})