Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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.
29 changes: 26 additions & 3 deletions src/components/cylc/TaskStateBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@

<template>
<div
class="task-state-badge d-flex justify-center align-center px-1 font-weight-medium"
class="task-state-badge d-flex justify-center align-center font-weight-medium"
:class="state"
>
{{ value }}
<v-icon v-if="isModifier" >{{ icon }}</v-icon>
{{ isModifier ? '' : value }}
<v-tooltip
location="top"
:open-delay="400"
>
{{ value }} {{ displayName }} task{{ value > 1 ? 's': '' }}.
{{ displayText }}
<template v-if="latestTasks?.length">
Latest:
<span
Expand All @@ -42,6 +43,7 @@

<script setup>
import { computed } from 'vue'
import { taskHeld, taskRetry } from '@/utils/icons'

const props = defineProps({
state: {
Expand All @@ -58,7 +60,28 @@ const props = defineProps({
},
})

const icons = {
held: taskHeld,
retry: taskRetry,
}

const isModifier = computed(
() => ['held', 'retry'].includes(props.state)
)

const displayName = computed(
() => props.state === 'submitted' ? 'preparing/submitted' : props.state
)

const displayText = computed(
() => isModifier.value
? `One or more ${props.state} task(s).`
: `${props.value} ${displayName.value} task${props.value > 1 ? 's' : ''}`
)

const icon = computed(
() => isModifier.value
? icons[props.state]
: null
)
</script>
7 changes: 7 additions & 0 deletions src/components/cylc/tree/GScanTreeItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ function getStatesInfo (node, stateTotals = {}) {
} else if (node.type === 'workflow' && node.node.stateTotals) {
// if we hit a workflow node, stop and merge state

if (node.node.containsHeld) {
stateTotals.held = true
}
if (node.node.containsRetry) {
stateTotals.retry = true
}

// 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 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 @@ -21,6 +21,8 @@
"failed": 2,
"succeeded": 2
},
"containsHeld": true,
"containsRetry": true,
"latestStateTasks": {
"submitted": [],
"running": [
Expand Down
9 changes: 9 additions & 0 deletions src/styles/cylc/_job.scss
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,12 @@ $themes: (
}
}
}

.task-state-badge.retry, .task-state-badge.held {
font-size: 0.8em;
min-width: 0;
background: none;
border: none;
padding: 0 0 0.2em 0;
margin: none;
}
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
12 changes: 12 additions & 0 deletions tests/e2e/specs/gscan.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ 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 .task-state-badge:first')
.should('have.class', 'held')
// child run2 contributes the running tasks
cy.get('@parent').find('.node:first .task-state-badge:nth-child(2)')
.should('have.class', 'retry')
})
})

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