Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes.d/2378.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added held and retry icons to the workflows sidebar, these will indicate if any n=0 tasks are in these states.
5 changes: 1 addition & 4 deletions src/components/cylc/TaskStateBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
:class="state"
>
{{ value }}
<v-tooltip
location="top"
:open-delay="400"
>
<v-tooltip>
{{ value }} {{ displayName }} task{{ value > 1 ? 's': '' }}.
<template v-if="latestTasks?.length">
Latest:
Expand Down
1 change: 0 additions & 1 deletion src/components/cylc/WarningIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:activator="null"
location="bottom"
:disabled="!workflow.node.logRecords?.length"
:open-delay="400"
>
<template
v-slot:activator="{ props }"
Expand Down
24 changes: 16 additions & 8 deletions src/components/cylc/gscan/GScan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
v-else
class="c-gscan-workflows flex-grow-1 pl-2"
>
<Tree
:workflows="workflows"
:node-filter-func="filterNode"
tree-item-component="GScanTreeItem"
class="c-gscan-workflow pa-0"
ref="tree"
v-bind="{ filterState }"
/>
<v-defaults-provider :defaults="{
VTooltip: {
location: 'top',
openDelay: 400,
eager: false,
}
}">
<Tree
:workflows="workflows"
:node-filter-func="filterNode"
tree-item-component="GScanTreeItem"
class="c-gscan-workflow pa-0"
ref="tree"
v-bind="{ filterState }"
/>
</v-defaults-provider>
</div>
<!-- when no workflows are returned in the GraphQL query -->
<div v-if="!workflows.length">
Expand Down
36 changes: 28 additions & 8 deletions src/components/cylc/tree/GScanTreeItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<div class="c-gscan-workflow-name flex-grow-1">
<span>
{{ node.name || node.id }}
<v-tooltip
location="top"
style="overflow-wrap: anywhere;"
>
<v-tooltip style="overflow-wrap: anywhere;">
{{ node.id }}
</v-tooltip>
</span>
</div>
<div class="d-flex c-gscan-workflow-states flex-grow-0">
<div class="d-flex c-gscan-workflow-states flex-grow-0 align-center">
<v-icon
v-for="modifier in statesInfo.modifiers"
:key="modifier"
:icon="modifierIcons[modifier]"
v-tooltip="`Has ${modifier} tasks.`"
size="1em"
class="modifier-badge"
:class="modifier"
/>
<TaskStateBadge
v-for="(value, state) in statesInfo.stateTotals"
:key="state"
Expand Down Expand Up @@ -86,6 +92,7 @@ import TreeItem from '@/components/cylc/tree/TreeItem.vue'
import WarningIcon from '@/components/cylc/WarningIcon.vue'
import TaskState from '@/model/TaskState.model'
import { WorkflowState } from '@/model/WorkflowState.model'
import { taskHeld, taskRetry } from '@/utils/icons'
import { useCompactMode, useWorkflowWarnings } from '@/composables/localStorage'

const nodeTypes = ['workflow-part', 'workflow']
Expand All @@ -98,27 +105,40 @@ const taskStatesOrdered = [
TaskState.RUNNING.name,
]

const modifierIcons = {
held: taskHeld,
retrying: taskRetry,
}

/**
* Get aggregated task state totals for all descendents of a node.
*
* Also get latest state tasks for workflow nodes.
*
* @param {Object} node
* @param {Record<string, number>} stateTotals - Accumulator for state totals.
* @param {Set<string>} modifiers - Accumulator for modifier states.
*/
function getStatesInfo (node, stateTotals = {}) {
function getStatesInfo (node, stateTotals = {}, modifiers = new Set()) {
const latestTasks = {}
// if we aren't at the end of the node tree, continue recurse until we hit something other then a workflow part
if (node.type === 'workflow-part' && node.children) {
// at every branch, recurse all child nodes except stopped workflows
for (const child of node.children) {
if (child.node.status !== WorkflowState.STOPPED.name) {
getStatesInfo(child, stateTotals, latestTasks)
getStatesInfo(child, stateTotals, modifiers)
}
}
} else if (node.type === 'workflow' && node.node.stateTotals) {
// if we hit a workflow node, stop and merge state

if (node.node.containsHeld) {
modifiers.add('held')
}
if (node.node.containsRetry) {
modifiers.add('retrying')
}

// the non-zero state totals from this node with all the others from the tree
for (const state of taskStatesOrdered) {
let nodeTotal = node.node.stateTotals[state]
Expand All @@ -138,7 +158,7 @@ function getStatesInfo (node, stateTotals = {}) {
}
}
}
return { stateTotals, latestTasks }
return { stateTotals, latestTasks, modifiers }
}

const workflowWarnings = useWorkflowWarnings()
Expand Down
6 changes: 5 additions & 1 deletion src/components/cylc/workspace/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:icon="$options.icons.info"
id="info-icon"
/>
<v-tooltip v-if="isRunning" activator="#info-icon">
<v-tooltip
v-if="isRunning"
activator="#info-icon"
:eager="false"
>
<dl>
<dt><strong>Owner:</strong> {{ currentWorkflow.node.owner }}</dt>
<dt><strong>Host:</strong> {{ currentWorkflow.node.host }}</dt>
Expand Down
2 changes: 2 additions & 0 deletions src/services/mock/json/workflows/one.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"failed": 2,
"succeeded": 2
},
"containsHeld": true,
"containsRetry": true,
"latestStateTasks": {
"submitted": [],
"running": [
Expand Down
5 changes: 4 additions & 1 deletion src/styles/cylc/_job.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ $cjob: ".c-job .job rect";
min-width: $height;
border-radius: $height;
border: 0.2em solid;
margin: 0 0.1em;
line-height: normal;
margin: 0 1px;
}
.modifier-badge {
margin: 0 2px;
}

$teal: rgb(109,213,194);
Expand Down
2 changes: 2 additions & 0 deletions src/utils/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@
import { siJupyter } from 'simple-icons'

export const jupyterLogo = siJupyter.svg.replace(/.*d="(.*)".*/, '$1')
export const taskRetry = 'm14.7 2.5c-0.179-0.0044-0.358-0.0037-0.538 0.0021-0.958 0.031-1.92 0.208-2.86 0.539-3.3 1.17-5.65 4.05-6.2 7.44l-5.13-1.23 6.86 9.23 6.86-9.23-5.35 1.19c0.511-2.02 2-3.69 4.02-4.41 2.5-0.886 5.28-0.124 6.98 1.91 1.7 2.04 1.95 4.91 0.625 7.21-1.32 2.3-3.93 3.53-6.54 3.09a1.58 1.58 0 0 0-1.82 1.3 1.58 1.58 0 0 0 1.3 1.82c3.91 0.661 7.84-1.19 9.81-4.63 1.98-3.44 1.6-7.76-0.938-10.8-1.79-2.14-4.39-3.35-7.07-3.41z'
export const taskHeld = 'm12 0.5c-6.34 0-11.5 5.17-11.5 11.5-1.9e-7 6.33 5.16 11.5 11.5 11.5 6.34 0 11.5-5.17 11.5-11.5 0-6.33-5.16-11.5-11.5-11.5zm0 2.74c4.85 0 8.76 3.9 8.76 8.76 0 4.85-3.9 8.76-8.76 8.76-4.85 0-8.76-3.9-8.76-8.76 0-4.85 3.9-8.76 8.76-8.76zm-3.2 2.36a2.05 2.05 0 0 0-2.05 2.05v8.7a2.05 2.05 0 0 0 2.05 2.05 2.05 2.05 0 0 0 2.05-2.05v-8.7a2.05 2.05 0 0 0-2.05-2.05zm6.4 0a2.05 2.05 0 0 0-2.05 2.05v8.7a2.05 2.05 0 0 0 2.05 2.05 2.05 2.05 0 0 0 2.05-2.05v-8.7a2.05 2.05 0 0 0-2.05-2.05z'
Comment on lines +33 to +34
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are icons derived directly from the task modifiers in the SVGTask component which were transformed into the Vuetify icon format (single path, 24px square dimensions).

For info, quick runthrough of how to do this:

  • Extract SVG as start point, save as file.
  • Open in Inkscape.
  • Apply configured CSS to objects.
  • Convert objects to paths.
  • Unify all paths into a single path.
  • Rescale down to 24x24px.
  • Export as optimised CSS.
  • Reduce the sig fig precision until it breaks (3 in this case).
  • Copy path (the d="..." bit) out of exported SVG.

I did export the transformed retry modifier pre-scaling.

This diff applies it to the `SVGTask`:
diff --git a/src/components/cylc/SVGTask.vue b/src/components/cylc/SVGTask.vue
index 43b6f1db..e5eca12c 100644
--- a/src/components/cylc/SVGTask.vue
+++ b/src/components/cylc/SVGTask.vue
@@ -231,10 +231,18 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
         Circular arrow representing a retry.
       -->
        <g class="retry">
-        <!-- An arc describing the arrow -->
-        <path d="m25, 50 a30 30 1 1 1 25 30 "/>
-        <!-- The arrowhead -->
-        <polygon points="0,40 26,75 52,40, 25,46"/>
+         <path d="m55.8 14.4c-0.679-0.0166-1.36-0.0142-2.04 0.00781-3.63 0.117-7.28 0.787-10.8 2.04-12.5 4.43-21.4 15.4-23.5 28.2l-19.5-4.67 26 35 26-35-20.3 4.5c1.94-7.65 7.57-14 15.2-16.7 9.49-3.36 20-0.471 26.4 7.26 6.44 7.73 7.39 18.6 2.37 27.3-5.01 8.72-14.9 13.4-24.8 11.7a6 6 0 0 0-6.92 4.92 6 6 0 0 0 4.92 6.92c14.8 2.51 29.7-4.51 37.2-17.6 7.5-13 6.07-29.4-3.56-41-6.77-8.13-16.6-12.7-26.8-12.9z"/>
+         <!--
+           The path above is an optimised reuduction of:
+
+           An arc describing the arrow:
+           <path d="m25, 50 a30 30 1 1 1 25 30 " style="stroke-linecap="round"; stroke: 12px;" />
+           The arrowhead:
+           <polygon points="0,40 26,75 52,40, 25,46"/>
+
+           Created by converting both objects to paths, combining them, then
+           optmising.
+         -->
       </g>
 
       <!-- Wallclock
@@ -542,12 +550,6 @@ const modifierTransform = _getModifierTransform()
 
     &.retry .modifier {
       .retry path {
-        stroke: $foreground;
-        stroke-width: 12px;
-        stroke-linecap: round;
-      }
-      .retry polygon {
-        stroke: none;
         fill: $foreground;
       }
     }

However, I didn't include that in this PR because:

  • Unnecessary change right now!
  • Less maintainable (although it's pretty static, so maybe that doesn't matter).
  • Profiling required to determine whether this is more efficient (what takes longer, the DOM or bezier maths?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can check these on https://www.svgviewer.dev/ or similar:

image image

2 changes: 2 additions & 0 deletions src/views/Workflows.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ fragment WorkflowData on Workflow {
status
statusMsg
stateTotals
containsHeld
containsRetry
logRecords {
level
message
Expand Down
11 changes: 11 additions & 0 deletions tests/e2e/specs/gscan.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,17 @@ describe('GScan component', () => {
})
})

describe('Task waiting badges', () => {
it('displays retry and held icons', () => {
cy.get('.c-gscan')
.find('[data-node-name="one"]').as('parent')
.find('.node:first .modifier-badge.held')
.should('be.visible')
cy.get('@parent').find('.node:first .modifier-badge.retrying')
.should('be.visible')
})
})

describe('Warnings', () => {
it('collates warnings up the tree', () => {
// NOTE: Log events may be duplicated in offline-mode due to the way the
Expand Down