From 73972be9bab0049e17287c4b6416226c5bf30c69 Mon Sep 17 00:00:00 2001 From: "christopher.bennett" Date: Thu, 30 Apr 2026 15:50:17 +0100 Subject: [PATCH 01/17] Combined the two toolbars --- src/components/cylc/Toolbar.vue | 513 +++++++++++++++- src/components/cylc/workspace/Toolbar.vue | 560 ------------------ src/views/Workspace.vue | 2 +- .../cylc/workspace/toolbar.vue.spec.js | 2 +- 4 files changed, 501 insertions(+), 576 deletions(-) delete mode 100644 src/components/cylc/workspace/Toolbar.vue diff --git a/src/components/cylc/Toolbar.vue b/src/components/cylc/Toolbar.vue index 5a13ffad7..e38477e68 100644 --- a/src/components/cylc/Toolbar.vue +++ b/src/components/cylc/Toolbar.vue @@ -15,10 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> - + diff --git a/src/components/cylc/workspace/Toolbar.vue b/src/components/cylc/workspace/Toolbar.vue deleted file mode 100644 index 91ce36700..000000000 --- a/src/components/cylc/workspace/Toolbar.vue +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - diff --git a/src/views/Workspace.vue b/src/views/Workspace.vue index afd22e6ee..69025cb8a 100644 --- a/src/views/Workspace.vue +++ b/src/views/Workspace.vue @@ -41,7 +41,7 @@ import graphqlMixin from '@/mixins/graphql' import subscriptionMixin from '@/mixins/subscription' import ViewState from '@/model/ViewState.model' import Lumino from '@/components/cylc/workspace/Lumino.vue' -import Toolbar from '@/components/cylc/workspace/Toolbar.vue' +import Toolbar from '@/components/cylc/Toolbar.vue' import { toolbarHeight } from '@/utils/toolbar' export default { diff --git a/tests/unit/components/cylc/workspace/toolbar.vue.spec.js b/tests/unit/components/cylc/workspace/toolbar.vue.spec.js index e2ee4cb2b..e672d9f9a 100644 --- a/tests/unit/components/cylc/workspace/toolbar.vue.spec.js +++ b/tests/unit/components/cylc/workspace/toolbar.vue.spec.js @@ -19,7 +19,7 @@ import { createVuetify } from 'vuetify' import { createStore } from 'vuex' import { mount } from '@vue/test-utils' import storeOptions from '@/store/options' -import Toolbar from '@/components/cylc/workspace/Toolbar.vue' +import Toolbar from '@/components/cylc/Toolbar.vue' import CommandMenuPlugin from '@/components/cylc/commandMenu/plugin' import sinon from 'sinon' import WorkflowService from '@/services/workflow.service' From 7dbfe38c645cf86ec3fe20176ff03f3b63098bb0 Mon Sep 17 00:00:00 2001 From: "christopher.bennett" Date: Thu, 30 Apr 2026 16:41:02 +0100 Subject: [PATCH 02/17] Use correct icon --- src/components/cylc/Toolbar.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/cylc/Toolbar.vue b/src/components/cylc/Toolbar.vue index e38477e68..7a4d3c9d8 100644 --- a/src/components/cylc/Toolbar.vue +++ b/src/components/cylc/Toolbar.vue @@ -32,7 +32,7 @@ along with this program. If not, see . @click.stop="toggleDrawer" id="toggle-drawer" > - {{ drawer ? icons.arrowLeft : icons.list }} + {{ drawer ? icons.backBurger : icons.list }} Date: Fri, 1 May 2026 09:11:52 +0100 Subject: [PATCH 03/17] Added test coverage --- .../cylc/workspace/toolbar.vue.spec.js | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/tests/unit/components/cylc/workspace/toolbar.vue.spec.js b/tests/unit/components/cylc/workspace/toolbar.vue.spec.js index e672d9f9a..7970417a9 100644 --- a/tests/unit/components/cylc/workspace/toolbar.vue.spec.js +++ b/tests/unit/components/cylc/workspace/toolbar.vue.spec.js @@ -25,17 +25,18 @@ import sinon from 'sinon' import WorkflowService from '@/services/workflow.service' import { useDrawer } from '@/utils/toolbar' import { vuetifyOptions } from '@/plugins/vuetify' +import { mdiViewList, mdiBackburger } from '@mdi/js' const $workflowService = sinon.createStubInstance(WorkflowService) const vuetify = createVuetify(vuetifyOptions) -describe('Workspace toolbar component', () => { +describe('Toolbar component', () => { let store const { drawer: drawerState } = useDrawer() beforeEach(() => { store = createStore(storeOptions) - store.commit('user/SET_USER', { owner: 'rincewind' }) + store.commit('user/SET_USER', { owner: 'rincewind', permissions: ['play', 'pause', 'resume', 'stop'] }) drawerState.value = false }) @@ -66,4 +67,84 @@ describe('Workspace toolbar component', () => { await wrapper.vm.$nextTick() expect(wrapper.find('#toggle-drawer').exists()).to.equal(true) }) + + it('shows backburger icon when drawer is open and list icon when closed', async () => { + const wrapper = mount(Toolbar, { + global: { + plugins: [store, vuetify, CommandMenuPlugin], + mocks: { $workflowService }, + provide: { versionInfo: null }, + }, + props: { + views: new Map(), + workflowName: 'strewth', + }, + }) + // Drawer closed -> list icon + drawerState.value = false + await wrapper.vm.$nextTick() + const btn = wrapper.find('#toggle-drawer') + expect(btn.find('svg path').attributes('d')).to.equal(mdiViewList) + // Drawer open -> backburger icon + drawerState.value = true + await wrapper.vm.$nextTick() + expect(btn.find('svg path').attributes('d')).to.equal(mdiBackburger) + }) + + it('toggles drawer on button click', async () => { + const wrapper = mount(Toolbar, { + global: { + plugins: [store, vuetify, CommandMenuPlugin], + mocks: { $workflowService }, + provide: { versionInfo: null }, + }, + props: { + views: new Map(), + workflowName: 'strewth', + }, + }) + expect(drawerState.value).to.equal(false) + await wrapper.find('#toggle-drawer').trigger('click') + expect(drawerState.value).to.equal(true) + await wrapper.find('#toggle-drawer').trigger('click') + expect(drawerState.value).to.equal(false) + }) + + it('does not show workflow controls without workflowName', async () => { + const wrapper = mount(Toolbar, { + global: { + plugins: [store, vuetify, CommandMenuPlugin], + mocks: { $workflowService }, + provide: { versionInfo: null }, + }, + props: { + views: new Map(), + }, + }) + await wrapper.vm.$nextTick() + // Burger button and title should still be present + expect(wrapper.find('#toggle-drawer').exists()).to.equal(true) + expect(wrapper.find('.c-toolbar-title').exists()).to.equal(true) + // Workflow-specific controls should not be present + expect(wrapper.find('#workflow-play-button').exists()).to.equal(false) + expect(wrapper.find('#workflow-stop-button').exists()).to.equal(false) + expect(wrapper.find('#workflow-mutate-button').exists()).to.equal(false) + expect(wrapper.find('.c-workflow-controls').exists()).to.equal(false) + }) + + it('displays the title from the store', async () => { + store.commit('app/setTitle', "I'm your pain when you can't feel") + const wrapper = mount(Toolbar, { + global: { + plugins: [store, vuetify, CommandMenuPlugin], + mocks: { $workflowService }, + provide: { versionInfo: null }, + }, + props: { + views: new Map(), + }, + }) + await wrapper.vm.$nextTick() + expect(wrapper.find('.c-toolbar-title').text()).to.include("I'm your pain when you can't feel") + }) }) From 5c58d2c376769ca17822a765c2324d751d8024fb Mon Sep 17 00:00:00 2001 From: "christopher.bennett" Date: Tue, 5 May 2026 15:06:08 +0100 Subject: [PATCH 04/17] Removed "Alert" that just duplicated the toolbar --- src/views/WorkflowsTable.vue | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/views/WorkflowsTable.vue b/src/views/WorkflowsTable.vue index d1d95979f..ee910f1c6 100644 --- a/src/views/WorkflowsTable.vue +++ b/src/views/WorkflowsTable.vue @@ -17,20 +17,11 @@ along with this program. If not, see . @@ -251,302 +43,23 @@ along with this program. If not, see . import { inject } from 'vue' import { mapState } from 'vuex' import { - mdiCog, - mdiMicrosoftXboxControllerMenu, - mdiPause, - mdiPlay, - mdiPlusBoxMultiple, - mdiStop, mdiViewList, mdiBackburger, - mdiAccount, - mdiChevronDown, - mdiArrowULeftTop, - mdiInformationOutline, } from '@mdi/js' -import { startCase } from 'lodash' -import { until } from '@/utils/reactivity' -import { useDrawer, toolbarHeight } from '@/utils/toolbar' -import WorkflowState from '@/model/WorkflowState.model' -import graphql from '@/mixins/graphql' -import { - mutationStatus -} from '@/utils/aotf' -import subscriptionComponentMixin from '@/mixins/subscriptionComponent' -import SubscriptionQuery from '@/model/SubscriptionQuery.model' -import gql from 'graphql-tag' -import { eventBus } from '@/services/eventBus' -import { upperFirst } from 'lodash-es' -import WarningIcon from '@/components/cylc/WarningIcon.vue' - -const QUERY = gql(` -subscription Workflow ($workflowId: ID) { - deltas(workflows: [$workflowId]) { - added { - ...AddedDelta - } - updated (stripNull: true) { - ...UpdatedDelta - } - pruned { - ...PrunedDelta - } - } -} - -fragment WorkflowData on Workflow { - id - host - owner - status - statusMsg - nEdgeDistance - cylcVersion - runMode -} - -fragment AddedDelta on Added { - workflow { - ...WorkflowData - } -} - -fragment UpdatedDelta on Updated { - workflow { - ...WorkflowData - } -} - -fragment PrunedDelta on Pruned { - workflow -} -`) export default { - name: 'Toolbar', - setup () { const { drawer, toggleDrawer } = useDrawer() - - const uisVersionInfo = inject('versionInfo') - const uisFlowVersion = uisVersionInfo?.value?.['cylc-flow'] ?? '' - - return { - eventBus, - drawer, - toggleDrawer, - toolbarHeight, - uisFlowVersion, - icons: { - add: mdiPlusBoxMultiple, - hold: mdiPause, - info: mdiInformationOutline, - list: mdiViewList, - backBurger: mdiBackburger, - menu: mdiMicrosoftXboxControllerMenu, - run: mdiPlay, - stop: mdiStop, - mdiCog, - mdiAccount, - mdiChevronDown, - mdiArrowULeftTop, - }, - } + return { drawer, toggleDrawer, toolbarHeight } }, - components: { - WarningIcon, - }, - - mixins: [ - graphql, - subscriptionComponentMixin - ], - - props: { - /** - * All possible view component classes that can be rendered - * - * @type {Map} - */ - views: { - type: Map, - default: () => new Map() - }, - workflowName: { - type: String, - default: null - } - }, - - data: () => ({ - expecting: { - play: null, - paused: null, - stop: null - }, - changingNWindow: false, - }), - computed: { - ...mapState('app', ['title']), - ...mapState('user', ['user']), - ...mapState('workflows', ['cylcTree']), - query () { - if (!this.workflowName) return null - return new SubscriptionQuery( - QUERY, - this.variables, - 'workflow', - [], - /* isDelta */ true, - /* isGlobalCallback */ true - ) - }, - currentWorkflow () { - if (!this.workflowName) return null - return this.cylcTree.$index[this.workflowId] - }, - isRunning () { - return ( - this.currentWorkflow && - ( - this.currentWorkflow.node.status === WorkflowState.RUNNING.name || - this.currentWorkflow.node.status === WorkflowState.PAUSED.name || - this.currentWorkflow.node.status === WorkflowState.STOPPING.name - ) - ) - }, - isPaused () { - return ( - this.currentWorkflow && - this.currentWorkflow.node.status === WorkflowState.PAUSED.name - ) - }, - isStopped () { - return ( - !this.currentWorkflow || - this.currentWorkflow.node.status === WorkflowState.STOPPED.name - ) - }, - statusMessage () { - return upperFirst(this.currentWorkflow?.node?.statusMsg || '') - }, - versionPopup () { - let ret = '' - if (this.currentWorkflow?.node?.cylcVersion) { - ret += ` • Cylc ${this.currentWorkflow.node.cylcVersion}` - } - return ret - }, - enabled () { - return { - playToggle: ( - this.user.permissions.includes('play') && - this.isStopped && - ( - this.expecting.play === null || - this.expecting.play === this.isRunning - ) - ), - pauseToggle: ( - ( - (this.isPaused && this.user.permissions.includes('resume')) || - (!this.isPaused && this.user.permissions.includes('pause')) - ) && - !this.isStopped && - !this.expecting.stop && - this.currentWorkflow.node.status !== WorkflowState.STOPPING.name && - ( - this.expecting.paused === null || - this.expecting.paused === this.isPaused - ) - ), - stopToggle: ( - this.user.permissions.includes('stop') && - !this.isStopped && - ( - this.expecting.stop === null || - this.expecting.stop === this.isStopped - ) - ) - } - }, - nWindow: { - get () { - return this.currentWorkflow?.node?.nEdgeDistance ?? 1 - }, - async set (val) { - if (val == null || this.isStopped) return - - this.changingNWindow = true - if (await this.setGraphWindow(val)) { - await until(() => this.currentWorkflow?.node?.nEdgeDistance === val) - } - this.changingNWindow = false - }, - } - }, - - watch: { - isRunning () { - this.expecting.play = null - }, - isPaused () { - this.expecting.paused = null - }, - isStopped () { - this.expecting.stop = null - }, - currentWorkflow () { - this.expecting = { - play: null, - paused: null, - stop: null, - } - }, + ...mapState('app', ['title']) }, - methods: { - onClickPlay () { - this.$workflowService.mutate( - 'play', - this.currentWorkflow.id - ).then(ret => { - if (ret[0] === mutationStatus.SUCCEEDED) { - this.expecting.play = !this.isRunning - } - }) - }, - onClickReleaseHold () { - this.$workflowService.mutate( - this.isPaused ? 'resume' : 'pause', - this.currentWorkflow.id - ).then(response => { - if (response.status === mutationStatus.SUCCEEDED) { - this.expecting.paused = !this.isPaused - } - }) - }, - onClickStop () { - this.$workflowService.mutate( - 'stop', - this.currentWorkflow.id - ).then(response => { - if (response.status === mutationStatus.SUCCEEDED) { - this.expecting.stop = WorkflowState.STOPPING - } - }) - }, - async setGraphWindow (nWindow) { - const { status } = await this.$workflowService.mutate( - 'setGraphWindowExtent', - this.currentWorkflow.id, - { nEdgeDistance: nWindow } - ) - return status === mutationStatus.SUCCEEDED - }, - startCase, + icons: { + mdiViewList, + mdiBackburger, }, } diff --git a/src/layouts/Default.vue b/src/layouts/Default.vue index 533be965e..80c68d661 100644 --- a/src/layouts/Default.vue +++ b/src/layouts/Default.vue @@ -18,11 +18,11 @@ along with this program. If not, see . + Settings + + + + + + + From a767fe77877844c72c90227f7aa992443f0ddcd8 Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Wed, 13 May 2026 17:52:51 +0100 Subject: [PATCH 13/17] Remove title banner for user profile page Duplicates toolbar title --- src/views/UserProfile.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/views/UserProfile.vue b/src/views/UserProfile.vue index bfa308cfc..7cde1d4be 100644 --- a/src/views/UserProfile.vue +++ b/src/views/UserProfile.vue @@ -19,13 +19,6 @@ along with this program. If not, see .