Skip to content

Commit e12ff6f

Browse files
Merge pull request #2169 from oliver-sanders/log-stream
stream workflow events and (re-)implement warning triangles
2 parents ea2e606 + afb1500 commit e12ff6f

File tree

15 files changed

+460
-53
lines changed

15 files changed

+460
-53
lines changed

src/components/cylc/EventChip.vue

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!--
2+
Copyright (C) NIWA & British Crown (Met Office) & Contributors.
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
-->
17+
18+
<template>
19+
<v-chip size="x-small" v-bind="props">{{ level }}</v-chip>
20+
</template>
21+
22+
<script>
23+
export default {
24+
name: 'LogLevelChip',
25+
26+
props: {
27+
level: {
28+
type: String,
29+
required: true,
30+
},
31+
},
32+
33+
computed: {
34+
props () {
35+
if (this.level === 'CRITICAL') {
36+
return { color: 'red', variant: 'flat' }
37+
}
38+
if (this.level === 'ERROR') {
39+
return { color: 'red-darken-4', variant: 'flat' }
40+
}
41+
if (this.level === 'WARNING') {
42+
return { color: 'amber-darken-4', variant: 'flat' }
43+
}
44+
if (this.level === 'INFO') {
45+
return { color: 'grey', variant: 'flat' }
46+
}
47+
return { color: 'grey', variant: 'outlined' }
48+
}
49+
}
50+
}
51+
</script>

src/components/cylc/WarningIcon.vue

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,67 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

1818
<!--
1919
WarningIcon - A dismiss-able warning icon
20+
21+
states:
22+
* no warnings: grey + translucent
23+
* new warnings: yellow
24+
* warnings dismissed: grey
2025
-->
2126

2227
<template>
2328
<span
2429
class="c-warn"
25-
style="display:inline-block; vertical-align:middle"
30+
:class="{'active': workflow.node.warningActive}"
31+
style="display: inline-block;"
2632
>
2733
<v-tooltip
2834
:activator="null"
2935
location="bottom"
30-
:disabled="!message"
36+
:disabled="!workflow.node.logRecords?.length"
3137
>
3238
<template
3339
v-slot:activator="{ props }"
3440
>
41+
<!-- NOTE: the click.prevent suppresses router navigation -->
3542
<svg
3643
viewBox="0 0 100 100"
3744
v-bind="props"
3845
@click="deactivate"
46+
@click.prevent
47+
style="
48+
vertical-align: middle;
49+
cursor: pointer;
50+
"
51+
:style="[workflow.node.logRecords?.length ? {opacity: 1} : {opacity: 0.3}]"
3952
>
4053
<path
41-
:d="path()"
42-
:stroke-width="strokeWidth()"
43-
v-bind:class="{'active': active}"
54+
:d="$options.path"
55+
:stroke-width="$options.strokeWidth"
56+
v-bind:class="{'active': workflow.node.warningActive}"
4457
/>
4558
</svg>
4659
</template>
47-
<span>{{ message }}</span>
60+
Recent warnings (click to dismiss):
61+
<table>
62+
<tr
63+
v-for="(event, index) in (workflow.node.logRecords || []).slice().reverse()"
64+
:key="index"
65+
>
66+
<td style="padding-right: 0.5em; vertical-align: top;">
67+
<EventChip :level="event.level" />
68+
</td><td>
69+
<span>{{ event.message }}</span>
70+
</td>
71+
</tr>
72+
</table>
4873
</v-tooltip>
4974
</span>
5075
</template>
5176

5277
<script>
78+
import EventChip from '@/components/cylc/EventChip.vue'
79+
import { store } from '@/store/index'
80+
5381
/* stuff we want to do once, when the component is loaded, rather than
5482
* every time it is used. */
5583
// stroke
@@ -85,34 +113,25 @@ function pathJoin (list) {
85113
}
86114
87115
export default {
88-
name: 'Warning',
89-
data: function () {
90-
return {
91-
active: this.startActive
92-
}
116+
name: 'WarningIcon',
117+
118+
components: {
119+
EventChip,
93120
},
121+
94122
props: {
95-
message: {
96-
type: String,
97-
required: false
98-
},
99-
startActive: {
100-
type: Boolean,
101-
required: false
123+
workflow: {
124+
required: true,
102125
}
103126
},
104-
methods: {
105-
path () {
106-
return PATH
107-
},
108127
109-
strokeWidth () {
110-
return sw
128+
methods: {
129+
deactivate () {
130+
store.commit('workflows/UPDATE', { id: this.workflow.id, warningActive: false })
111131
},
132+
},
112133
113-
deactivate () {
114-
this.active = false
115-
}
116-
}
134+
strokeWidth: sw,
135+
path: PATH,
117136
}
118137
</script>

src/components/cylc/tree/GScanTreeItem.vue

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
4949
<div class="d-flex text-right c-gscan-workflow-states flex-grow-0">
5050
<!-- task summary tooltips -->
5151
<!-- a v-tooltip does not work directly set on Cylc job component, so we use a div to wrap it -->
52+
<div
53+
class="ma-0 pa-0"
54+
min-width="0"
55+
min-height="0"
56+
style="font-size: 120%; width: auto;"
57+
>
58+
<WarningIcon v-if="workflowWarnings" :workflow="node" />
59+
</div>
5260
<div
5361
v-for="[state, tasks] in Object.entries(descendantTaskInfo.latestTasks)"
5462
:key="`${node.id}-${state}`"
@@ -90,8 +98,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
9098
import Job from '@/components/cylc/Job.vue'
9199
import WorkflowIcon from '@/components/cylc/gscan/WorkflowIcon.vue'
92100
import TreeItem from '@/components/cylc/tree/TreeItem.vue'
101+
import WarningIcon from '@/components/cylc/WarningIcon.vue'
93102
import { JobStateNames } from '@/model/JobState.model'
94103
import { WorkflowState } from '@/model/WorkflowState.model'
104+
import { useWorkflowWarnings } from '@/composables/localStorage'
95105
96106
/**
97107
* Get aggregated task state totals and latest task states for all descendents of a node.
@@ -108,7 +118,9 @@ function traverseChildren (node, stateTotals = {}, latestTasks = {}) {
108118
traverseChildren(child, stateTotals, latestTasks)
109119
}
110120
} else if (node.type === 'workflow' && node.node.stateTotals) {
111-
// if we are at the end of a node (or at least, hit a workflow node), stop and merge the latest state tasks from this node with all the others from the tree
121+
// if we are at the end of a node (or at least, hit a workflow node), stop and merge state
122+
123+
// the latest state tasks from this node with all the others from the tree
112124
for (const [state, totals] of Object.entries(node.node.stateTotals)) {
113125
if (JobStateNames.includes(state)) { // filter only valid states
114126
// (cast as numbers so they dont get concatenated as strings)
@@ -134,9 +146,14 @@ export default {
134146
components: {
135147
Job,
136148
TreeItem,
137-
WorkflowIcon
149+
WarningIcon,
150+
WorkflowIcon,
138151
},
139152
153+
data: () => ({
154+
workflowWarnings: useWorkflowWarnings()
155+
}),
156+
140157
props: {
141158
node: {
142159
type: Object,
@@ -190,5 +207,6 @@ export default {
190207
191208
nodeTypes: ['workflow-part', 'workflow'],
192209
maxTasksDisplayed: 5,
210+
WorkflowState,
193211
}
194212
</script>

src/components/cylc/workspace/Toolbar.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
4646
<!-- control bar elements displayed only when there is a current workflow in the store -->
4747
<template v-if="currentWorkflow">
4848
<div class="c-workflow-controls flex-shrink-0">
49+
<WarningIcon
50+
:workflow="currentWorkflow"
51+
style="font-size: 120%; padding-right: 0.3em;"
52+
/>
53+
4954
<v-btn
5055
id="workflow-mutate-button"
5156
v-command-menu="currentWorkflow"
@@ -242,6 +247,7 @@ import SubscriptionQuery from '@/model/SubscriptionQuery.model'
242247
import gql from 'graphql-tag'
243248
import { eventBus } from '@/services/eventBus'
244249
import { upperFirst } from 'lodash-es'
250+
import WarningIcon from '@/components/cylc/WarningIcon.vue'
245251
246252
const QUERY = gql(`
247253
subscription Workflow ($workflowId: ID) {
@@ -297,6 +303,10 @@ export default {
297303
}
298304
},
299305
306+
components: {
307+
WarningIcon,
308+
},
309+
300310
mixins: [
301311
graphql,
302312
subscriptionComponentMixin

src/composables/localStorage.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ export const useCyclePointsOrderDesc = () => useLocalStorage('cyclePointsOrderDe
3030
export const useJobTheme = () => useLocalStorage('jobTheme', 'default')
3131

3232
export const useReducedAnimation = () => useLocalStorage('reducedAnimation', false)
33+
34+
export const useWorkflowWarnings = () => useLocalStorage('useWorkflowWarnings', true)

src/services/mock/json/workflows/multi.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@
8484
"submitted": [],
8585
"running": []
8686
},
87+
"logRecords": [
88+
{
89+
"level": "CRITICAL",
90+
"message": "Workflow underwent a sudden and gratuitous total existence failure"
91+
},
92+
{
93+
"level": "ERROR",
94+
"message": "SOS"
95+
},
96+
{
97+
"level": "WARNING",
98+
"message": "Reality may go on the blink at infinite improbability."
99+
}
100+
],
87101
"__typename": "Workflow"
88102
},
89103
"cyclePoints": [

0 commit comments

Comments
 (0)