From 0dfac1a00c876e0b1518e26359da0b3590776f50 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 11 Oct 2024 11:35:13 +0100 Subject: [PATCH] info: add completion expression --- cypress/component/info.cy.js | 38 +++++++++++++++++-- src/components/cylc/Info.vue | 54 +++++++++++++++++++++++++-- src/utils/outputs.js | 71 ++++++++++++++++++++++++++++++++++++ src/views/Info.vue | 4 ++ 4 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 src/utils/outputs.js diff --git a/cypress/component/info.cy.js b/cypress/component/info.cy.js index f9cfad5f7..132cecb6d 100644 --- a/cypress/component/info.cy.js +++ b/cypress/component/info.cy.js @@ -98,7 +98,15 @@ const TASK = { message: 'failed', satisfied: false, }, - ] + { + label: 'x', + message: 'xxx', + satisfied: true, + } + ], + runtime: { + completion: '(succeeded and x) or failed' + } }, children: [ { @@ -127,7 +135,7 @@ describe('Info component', () => { task: TASK, class: 'job_theme--default', // NOTE: expand all sections by default - panelExpansion: [0, 1, 2], + panelExpansion: [0, 1, 2, 3], } }) @@ -175,7 +183,7 @@ describe('Info component', () => { // the outputs panel cy.get('.outputs-panel.v-expansion-panel--active').should('be.visible') .find('.condition') - .should('have.length', 3) + .should('have.length', 4) .then((selector) => { expect(selector[0]).to.contain('started') expect(selector[0].classList.toString()).to.equal('condition satisfied') @@ -185,6 +193,30 @@ describe('Info component', () => { expect(selector[2]).to.contain('failed') expect(selector[2].classList.toString()).to.equal('condition') + + expect(selector[3]).to.contain('x') + expect(selector[3].classList.toString()).to.equal('condition satisfied') + }) + + // the completion panel + cy.get('.completion-panel.v-expansion-panel--active').should('be.visible') + .find('.condition') + .should('have.length', 5) + .then((selector) => { + expect(selector[0]).to.contain('(') + expect(selector[0].classList.toString()).to.equal('condition blank') + + expect(selector[1]).to.contain('succeeded') + expect(selector[1].classList.toString()).to.equal('condition') + + expect(selector[2]).to.contain('and x') + expect(selector[2].classList.toString()).to.equal('condition satisfied') + + expect(selector[3]).to.contain(')') + expect(selector[3].classList.toString()).to.equal('condition blank') + + expect(selector[4]).to.contain('or failed') + expect(selector[4].classList.toString()).to.equal('condition') }) }) diff --git a/src/components/cylc/Info.vue b/src/components/cylc/Info.vue index 9d22716cd..04581ec43 100644 --- a/src/components/cylc/Info.vue +++ b/src/components/cylc/Info.vue @@ -135,6 +135,32 @@ along with this program. If not, see . + + + + Completion + + + +
    +
  • + + + + {{ line }} + +
  • +
+
+
+ @@ -142,6 +168,7 @@ along with this program. If not, see . @@ -214,18 +250,22 @@ export default { .condition { opacity: 0.6; } - .condition.satisfied { + .condition.satisfied, .condition.blank { opacity: 1; } // prefixes a tick or cross before the entry .condition:before { - content: '\25CB'; - padding-right: 0.5em; + display: inline-block; + content: '\25CB'; /* empty circle */ + width: 1.5em; color: rgb(0, 0, 0); } .condition.satisfied:before { - content: '\25CF'; + content: '\25CF'; /* filled circle */ + } + .condition.blank:before { + content: ''; /* blank */ } // for prerequsite task "aliases" (used in conditional expressions) @@ -265,5 +305,11 @@ export default { list-style: none; } } + + .completion-panel { + li { + list-style: none; + } + } } diff --git a/src/utils/outputs.js b/src/utils/outputs.js new file mode 100644 index 000000000..deb0b1173 --- /dev/null +++ b/src/utils/outputs.js @@ -0,0 +1,71 @@ +/** + * 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 . + */ + +/** Functionality relating to task output formatting. **/ + +/** Format a completion expression for display. + * + * @param {str} completion - The task's completion expression. + * @param {Array} outputs - The task's outputs as obtained from GraphQL as an + * array of objects with "label" and "satisfied" attributes. + * + * @returns {Array} - [isSatisfied, indentLevel, text] + **/ + +export function formatCompletion (completion, outputs) { + // the array to return + const lines = [] + // indent level of the expression + let indent = 0 + // text yet to be added to the return result + let buffer = '' + + // break the completion expression down into parts and iterate over them + for (let part of completion.split(/(and|or|\(|\))/)) { + part = part.trim() + + if (!part) { + continue + } + + if (part === '(') { + // open bracket + lines.push([null, indent, `${buffer}(`]) + buffer = '' + indent = indent + 1 + } else if (part === ')') { + // close bracket + indent = indent - 1 + lines.push([null, indent, `${buffer})`]) + buffer = '' + } else if (part === 'and' || part === 'or') { + // local operator + buffer = `${part} ` + } else { + // Cylc output -> look it up in the outputs Array + for (const output of outputs) { + if (output.label === part) { + lines.push([output.satisfied, indent, `${buffer}${part}`]) + break + } + } + buffer = '' + } + } + + return lines +} diff --git a/src/views/Info.vue b/src/views/Info.vue index d3bcd1568..3b60bfa5e 100644 --- a/src/views/Info.vue +++ b/src/views/Info.vue @@ -94,6 +94,10 @@ fragment TaskProxyData on TaskProxy { label satisfied } + + runtime { + completion + } } fragment TaskDefinitionData on Task {