Skip to content

Commit

Permalink
Merge pull request #1224 from datamel/disable-buttons-for-invalid-states
Browse files Browse the repository at this point in the history
Disable Buttons in Menu, based on workflow state
  • Loading branch information
wxtim authored Mar 2, 2023
2 parents 62c91e7 + 805dab2 commit d010331
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 14 deletions.
42 changes: 33 additions & 9 deletions src/components/cylc/cylcObject/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<v-list-item
v-for="{ mutation, requiresInfo, authorised } in displayMutations"
:key="mutation.name"
:disabled="!authorised"
:disabled="isDisabled(mutation, authorised)"
@click.stop="enact(mutation, requiresInfo)"
class="c-mutation"
>
<v-list-item-avatar>
<v-icon :disabled="!authorised" large>
<v-icon :disabled="isDisabled(mutation, authorised)" large>
{{ mutation._icon }}
</v-icon>
</v-list-item-avatar>
Expand Down Expand Up @@ -136,7 +136,8 @@ import Mutation from '@/components/cylc/Mutation'
import {
mdiPencil
} from '@mdi/js'
import { mapState } from 'vuex'
import { mapGetters, mapState } from 'vuex'
import WorkflowState from '@/model/WorkflowState.model'
export default {
name: 'CylcObjectMenu',
Expand All @@ -160,6 +161,7 @@ export default {
dialogKey: false,
expanded: false,
node: null,
workflowStatus: null,
mutations: [],
isLoadingMutations: true,
showMenu: false,
Expand All @@ -181,6 +183,7 @@ export default {
},
computed: {
...mapGetters('workflows', ['getNodes']),
primaryMutations () {
return this.$workflowService.primaryMutations[this.node.type] || []
},
Expand All @@ -194,11 +197,15 @@ export default {
}
const shortList = this.primaryMutations
if (!this.expanded && shortList.length) {
return this.mutations.filter(
x => shortList.includes(x.mutation.name)
).sort(
(x, y) => shortList.indexOf(x.mutation.name) - shortList.indexOf(y.mutation.name)
)
return this.mutations
// filter for shortlisted mutations
.filter(x => shortList.includes(x.mutation.name))
// filter out mutations which aren't relevant to the workflow state
.filter(x => !this.isDisabled(x.mutation, true))
// sort by definition order
.sort(
(x, y) => shortList.indexOf(x.mutation.name) - shortList.indexOf(y.mutation.name)
)
}
return this.mutations
},
Expand Down Expand Up @@ -232,12 +239,29 @@ export default {
methods: {
isEditable (authorised, mutation) {
if (!authorised || mutation.name === 'log') {
if (mutation.name === 'log' || this.isDisabled(mutation, authorised)) {
return true
} else {
return false
}
},
isDisabled (mutation, authorised) {
if (this.node.type !== 'workflow') {
const nodeReturned = this.getNodes(
'workflow', [this.node.tokens.workflow_id])
if (nodeReturned.length) {
this.workflowStatus = nodeReturned[0].node.status
} else { this.workflowStatus = WorkflowState.RUNNING.name }
} else {
this.workflowStatus = this.node.node.status
}
if (
(!mutation._validStates.includes(this.workflowStatus)) ||
!authorised) {
return true
}
return false
},
openDialog (mutation) {
if (mutation.name === 'log') {
this.showMenu = false
Expand Down
2 changes: 1 addition & 1 deletion src/services/mock/json/IntrospectionQuery.json
Original file line number Diff line number Diff line change
Expand Up @@ -1427,7 +1427,7 @@
"fields": [
{
"name": "broadcast",
"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.",
"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.",
"args": [
{
"name": "cutoff",
Expand Down
28 changes: 28 additions & 0 deletions src/utils/aotf.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
import Alert from '@/model/Alert.model'
import store from '@/store/index'
import { Tokens } from '@/utils/uid'
import { WorkflowState } from '@/model/WorkflowState.model'

// Typedef imports
/* eslint-disable no-unused-vars, no-duplicate-imports */
Expand Down Expand Up @@ -167,6 +168,7 @@ export const cylcObjects = Object.freeze({
export const primaryMutations = {
[cylcObjects.Workflow]: [
'play',
'resume',
'pause',
'stop',
'reload',
Expand Down Expand Up @@ -432,9 +434,35 @@ export function processMutations (mutations, types) {
mutation._icon = mutationIcons[mutation.name] || mutationIcons['']
mutation._shortDescription = getMutationShortDesc(mutation.description)
mutation._help = getMutationExtendedDesc(mutation.description)
mutation._validStates = getStates(mutation.description)
processArguments(mutation, types)
}
}
/**
* Get the workflow states that the mutation is valid for.
*
* @export
* @param {string=} text - Full mutation description.
* @return {Array<String>}
*/
export function getStates (text) {
const defaultStates = [
WorkflowState.RUNNING.name,
WorkflowState.PAUSED.name,
WorkflowState.STOPPING.name,
WorkflowState.STOPPED.name
]
if (!text) {
return defaultStates
}
const re = /Valid\sfor:\s(.*)\sworkflows./
// default to all workflow states
const validStates = text.match(re)
if (validStates) {
return validStates[1].replace(/\s/g, '').split(',')
}
return defaultStates
}

/**
* Get the first part of a mutation description (up to the first double newline).
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/specs/menu.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

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

beforeEach(() => {
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/support/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const MUTATIONS = [
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
Valid for: running workflows.
`,
args: [
{
Expand All @@ -43,6 +44,7 @@ const MUTATIONS = [
_title: 'Unauthorised Mutation',
description: `
A mutation user will not be authorised for.
Valid for: running workflows.
`,
args: [
{
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/components/cylc/tree/tree.data.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ const simpleWorkflowTree4Nodes = [
type: 'workflow',
node: {
__typename: 'Workflow',
state: 'running'
state: 'running',
node: {
status: 'running'
}
},
children: [
{
Expand Down
11 changes: 9 additions & 2 deletions tests/unit/utils/aotf.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,26 @@ describe('aotf (Api On The Fly)', () => {
})
})

describe('getStates', () => {
it('gets valid states', () => {
expect(aotf.getStates('Valid for: running, stopped workflows.')).to.deep.equal(['running', 'stopped'])
})
})

describe('processMutations', () => {
it('should add computed fields', () => {
const input = {
name: 'fooBar',
description: 'Short description.\n\nLong\ndescription.',
description: 'Short description.\n\nLong\ndescription.\nValid for: stopped, paused workflows.',
args: []
}
const output = {
...input,
_title: 'Foo Bar',
_icon: aotf.mutationIcons[''],
_shortDescription: 'Short description.',
_help: 'Long\ndescription.'
_help: 'Long\ndescription.\nValid for: stopped, paused workflows.',
_validStates: ['stopped', 'paused']
}
aotf.processMutations([input], null)
expect(input).to.deep.equal(output)
Expand Down

0 comments on commit d010331

Please sign in to comment.