Skip to content

Commit d010331

Browse files
authored
Merge pull request #1224 from datamel/disable-buttons-for-invalid-states
Disable Buttons in Menu, based on workflow state
2 parents 62c91e7 + 805dab2 commit d010331

File tree

7 files changed

+78
-14
lines changed

7 files changed

+78
-14
lines changed

src/components/cylc/cylcObject/Menu.vue

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
5959
<v-list-item
6060
v-for="{ mutation, requiresInfo, authorised } in displayMutations"
6161
:key="mutation.name"
62-
:disabled="!authorised"
62+
:disabled="isDisabled(mutation, authorised)"
6363
@click.stop="enact(mutation, requiresInfo)"
6464
class="c-mutation"
6565
>
6666
<v-list-item-avatar>
67-
<v-icon :disabled="!authorised" large>
67+
<v-icon :disabled="isDisabled(mutation, authorised)" large>
6868
{{ mutation._icon }}
6969
</v-icon>
7070
</v-list-item-avatar>
@@ -136,7 +136,8 @@ import Mutation from '@/components/cylc/Mutation'
136136
import {
137137
mdiPencil
138138
} from '@mdi/js'
139-
import { mapState } from 'vuex'
139+
import { mapGetters, mapState } from 'vuex'
140+
import WorkflowState from '@/model/WorkflowState.model'
140141
141142
export default {
142143
name: 'CylcObjectMenu',
@@ -160,6 +161,7 @@ export default {
160161
dialogKey: false,
161162
expanded: false,
162163
node: null,
164+
workflowStatus: null,
163165
mutations: [],
164166
isLoadingMutations: true,
165167
showMenu: false,
@@ -181,6 +183,7 @@ export default {
181183
},
182184
183185
computed: {
186+
...mapGetters('workflows', ['getNodes']),
184187
primaryMutations () {
185188
return this.$workflowService.primaryMutations[this.node.type] || []
186189
},
@@ -194,11 +197,15 @@ export default {
194197
}
195198
const shortList = this.primaryMutations
196199
if (!this.expanded && shortList.length) {
197-
return this.mutations.filter(
198-
x => shortList.includes(x.mutation.name)
199-
).sort(
200-
(x, y) => shortList.indexOf(x.mutation.name) - shortList.indexOf(y.mutation.name)
201-
)
200+
return this.mutations
201+
// filter for shortlisted mutations
202+
.filter(x => shortList.includes(x.mutation.name))
203+
// filter out mutations which aren't relevant to the workflow state
204+
.filter(x => !this.isDisabled(x.mutation, true))
205+
// sort by definition order
206+
.sort(
207+
(x, y) => shortList.indexOf(x.mutation.name) - shortList.indexOf(y.mutation.name)
208+
)
202209
}
203210
return this.mutations
204211
},
@@ -232,12 +239,29 @@ export default {
232239
233240
methods: {
234241
isEditable (authorised, mutation) {
235-
if (!authorised || mutation.name === 'log') {
242+
if (mutation.name === 'log' || this.isDisabled(mutation, authorised)) {
236243
return true
237244
} else {
238245
return false
239246
}
240247
},
248+
isDisabled (mutation, authorised) {
249+
if (this.node.type !== 'workflow') {
250+
const nodeReturned = this.getNodes(
251+
'workflow', [this.node.tokens.workflow_id])
252+
if (nodeReturned.length) {
253+
this.workflowStatus = nodeReturned[0].node.status
254+
} else { this.workflowStatus = WorkflowState.RUNNING.name }
255+
} else {
256+
this.workflowStatus = this.node.node.status
257+
}
258+
if (
259+
(!mutation._validStates.includes(this.workflowStatus)) ||
260+
!authorised) {
261+
return true
262+
}
263+
return false
264+
},
241265
openDialog (mutation) {
242266
if (mutation.name === 'log') {
243267
this.showMenu = false

src/services/mock/json/IntrospectionQuery.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1427,7 +1427,7 @@
14271427
"fields": [
14281428
{
14291429
"name": "broadcast",
1430-
"description": "Override `[runtime]` configurations in a running workflow.\n\nUses for broadcast include making temporary changes to task\nbehaviour, and task-to-downstream-task communication via\nenvironment variables.\n\nA broadcast can set/override any `[runtime]` configuration for all\ncycles or for a specific cycle. If a task is affected by\nspecific-cycle and all-cycle broadcasts at the same time, the\nspecific takes precedence.\n\nBroadcasts can also target all tasks, specific tasks or families of\ntasks. If a task is affected by broadcasts to multiple ancestor\nnamespaces (tasks it inherits from), the result is determined by\nnormal `[runtime]` inheritance.\n\nBroadcasts are applied at the time of job submission.\n\nBroadcasts persist, even across restarts. Broadcasts made to\nspecific cycle points will expire when the cycle point is older\nthan the oldest active cycle point in the workflow.\n\nActive broadcasts can be revoked using the \"clear\" mode.\nAny broadcasts matching the specified cycle points and\nnamespaces will be revoked.\n\nNote: a \"clear\" broadcast for a specific cycle or namespace does\n*not* clear all-cycle or all-namespace broadcasts.",
1430+
"description": "Override `[runtime]` configurations in a running workflow.\n\nUses for broadcast include making temporary changes to task\nbehaviour, and task-to-downstream-task communication via\nenvironment variables.\n\nA broadcast can set/override any `[runtime]` configuration for all\ncycles or for a specific cycle. If a task is affected by\nspecific-cycle and all-cycle broadcasts at the same time, the\nspecific takes precedence.\n\nBroadcasts can also target all tasks, specific tasks or families of\ntasks. If a task is affected by broadcasts to multiple ancestor\nnamespaces (tasks it inherits from), the result is determined by\nnormal `[runtime]` inheritance.\n\nBroadcasts are applied at the time of job submission.\n\nBroadcasts persist, even across restarts. Broadcasts made to\nspecific cycle points will expire when the cycle point is older\nthan the oldest active cycle point in the workflow.\n\nActive broadcasts can be revoked using the \"clear\" mode.\nAny broadcasts matching the specified cycle points and\nnamespaces will be revoked.\n\nNote: a \"clear\" broadcast for a specific cycle or namespace does\n*not* clear all-cycle or all-namespace broadcasts.\n Valid for: paused, running workflows.",
14311431
"args": [
14321432
{
14331433
"name": "cutoff",

src/utils/aotf.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
import Alert from '@/model/Alert.model'
5151
import store from '@/store/index'
5252
import { Tokens } from '@/utils/uid'
53+
import { WorkflowState } from '@/model/WorkflowState.model'
5354

5455
// Typedef imports
5556
/* eslint-disable no-unused-vars, no-duplicate-imports */
@@ -167,6 +168,7 @@ export const cylcObjects = Object.freeze({
167168
export const primaryMutations = {
168169
[cylcObjects.Workflow]: [
169170
'play',
171+
'resume',
170172
'pause',
171173
'stop',
172174
'reload',
@@ -432,9 +434,35 @@ export function processMutations (mutations, types) {
432434
mutation._icon = mutationIcons[mutation.name] || mutationIcons['']
433435
mutation._shortDescription = getMutationShortDesc(mutation.description)
434436
mutation._help = getMutationExtendedDesc(mutation.description)
437+
mutation._validStates = getStates(mutation.description)
435438
processArguments(mutation, types)
436439
}
437440
}
441+
/**
442+
* Get the workflow states that the mutation is valid for.
443+
*
444+
* @export
445+
* @param {string=} text - Full mutation description.
446+
* @return {Array<String>}
447+
*/
448+
export function getStates (text) {
449+
const defaultStates = [
450+
WorkflowState.RUNNING.name,
451+
WorkflowState.PAUSED.name,
452+
WorkflowState.STOPPING.name,
453+
WorkflowState.STOPPED.name
454+
]
455+
if (!text) {
456+
return defaultStates
457+
}
458+
const re = /Valid\sfor:\s(.*)\sworkflows./
459+
// default to all workflow states
460+
const validStates = text.match(re)
461+
if (validStates) {
462+
return validStates[1].replace(/\s/g, '').split(',')
463+
}
464+
return defaultStates
465+
}
438466

439467
/**
440468
* Get the first part of a mutation description (up to the first double newline).

tests/e2e/specs/menu.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
describe('CylcObject Menu component', () => {
19-
const collapsedWorkflowMenuLength = 6 // (5 mutations + "show more" btn)
19+
const collapsedWorkflowMenuLength = 7 // (6 mutations + "show more" btn)
2020
const expandedWorkflowMenuLength = 21
2121

2222
beforeEach(() => {

tests/e2e/support/graphql.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const MUTATIONS = [
2727
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
2828
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
2929
officia deserunt mollit anim id est laborum.
30+
Valid for: running workflows.
3031
`,
3132
args: [
3233
{
@@ -43,6 +44,7 @@ const MUTATIONS = [
4344
_title: 'Unauthorised Mutation',
4445
description: `
4546
A mutation user will not be authorised for.
47+
Valid for: running workflows.
4648
`,
4749
args: [
4850
{

tests/unit/components/cylc/tree/tree.data.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ const simpleWorkflowTree4Nodes = [
2626
type: 'workflow',
2727
node: {
2828
__typename: 'Workflow',
29-
state: 'running'
29+
state: 'running',
30+
node: {
31+
status: 'running'
32+
}
3033
},
3134
children: [
3235
{

tests/unit/utils/aotf.spec.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,26 @@ describe('aotf (Api On The Fly)', () => {
5959
})
6060
})
6161

62+
describe('getStates', () => {
63+
it('gets valid states', () => {
64+
expect(aotf.getStates('Valid for: running, stopped workflows.')).to.deep.equal(['running', 'stopped'])
65+
})
66+
})
67+
6268
describe('processMutations', () => {
6369
it('should add computed fields', () => {
6470
const input = {
6571
name: 'fooBar',
66-
description: 'Short description.\n\nLong\ndescription.',
72+
description: 'Short description.\n\nLong\ndescription.\nValid for: stopped, paused workflows.',
6773
args: []
6874
}
6975
const output = {
7076
...input,
7177
_title: 'Foo Bar',
7278
_icon: aotf.mutationIcons[''],
7379
_shortDescription: 'Short description.',
74-
_help: 'Long\ndescription.'
80+
_help: 'Long\ndescription.\nValid for: stopped, paused workflows.',
81+
_validStates: ['stopped', 'paused']
7582
}
7683
aotf.processMutations([input], null)
7784
expect(input).to.deep.equal(output)

0 commit comments

Comments
 (0)