Skip to content

Commit 902a539

Browse files
authored
Merge pull request #1108 from oliver-sanders/etchasketch.dev
graph view and central data store
2 parents 4995dfc + 3d28f92 commit 902a539

File tree

102 files changed

+5574
-7213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+5574
-7213
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ jobs:
8383
env:
8484
CYPRESS_baseUrl: http://localhost:8080/
8585

86+
- uses: actions/upload-artifact@v2
87+
if: failure()
88+
with:
89+
name: cypress-screenshots
90+
path: tests/e2e/screenshots
91+
8692
- name: Upload coverage to Codecov
8793
uses: codecov/codecov-action@v3
8894
with:

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,22 @@ ones in. -->
1515

1616
### Enhancements
1717

18+
[#1108](https://github.com/cylc/cylc-ui/pull/1108) -
19+
Graph view: A new view which displays tasks and dependencies as a graph (like
20+
the Cylc 7 graph view).
21+
22+
[#1108](https://github.com/cylc/cylc-ui/pull/1108) -
23+
Tree view: Task messages are now shown along with outputs.
24+
1825
[#1124](https://github.com/cylc/cylc-ui/pull/1075) - Table view: More options
1926
for number of tasks per page.
2027

2128
### Fixes
2229

30+
[#1108](https://github.com/cylc/cylc-ui/pull/1108) -
31+
Tree view: Task outputs are now correctly associated with the jobs that created
32+
them.
33+
2334
[#1075](https://github.com/cylc/cylc-ui/pull/1075) - Reverse default sort order
2435
of the table view so it matches the tree view.
2536

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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+
import { TaskStateUserOrder, JobStates } from '@/model/TaskState.model'
19+
import GraphNode from '@/components/cylc/GraphNode'
20+
import { Tokens } from '@/utils/uid'
21+
import {
22+
MEAN_ELAPSED_TIME,
23+
getStartTime
24+
} from './utils/task'
25+
26+
function makeTaskNode (id, state, jobStates) {
27+
const tokens = new Tokens(id)
28+
const jobs = []
29+
let itt = 1
30+
let job
31+
for (const jobState of jobStates) {
32+
const jobTokens = tokens.clone({ job: `0${itt}` })
33+
job = {
34+
id: jobTokens.id,
35+
name: jobTokens.job,
36+
node: {
37+
state: jobState
38+
}
39+
}
40+
if (jobState === 'running') { // TODO constant
41+
job.node.startedTime = getStartTime(50)
42+
}
43+
jobs.push(job)
44+
itt++
45+
}
46+
47+
const task = {
48+
id: tokens.id,
49+
name: tokens.task,
50+
tokens,
51+
node: {
52+
cyclePoint: tokens.cycle,
53+
isHeld: false,
54+
state,
55+
task: {
56+
meanElapsedTime: MEAN_ELAPSED_TIME
57+
}
58+
}
59+
}
60+
61+
return [task, jobs]
62+
}
63+
64+
const GraphNodeSVG = {
65+
template: `
66+
<svg id="app" class="job_theme--default" width="100%" height="100%">
67+
<GraphNode :task="task" :jobs="jobs" :maxJobs="maxJobs" />
68+
</svg>
69+
`,
70+
props: {
71+
task: {
72+
required: true
73+
},
74+
jobs: {
75+
required: true
76+
},
77+
maxJobs: {
78+
default: 6,
79+
required: false
80+
}
81+
},
82+
components: { GraphNode }
83+
}
84+
85+
describe('graph node component', () => {
86+
it('Renders with multiple jobs', () => {
87+
const [task, jobs] = makeTaskNode(
88+
'~a/b//20000101T0000Z/task_name',
89+
'running',
90+
['running', 'failed', 'failed', 'failed']
91+
)
92+
cy.mount(
93+
GraphNodeSVG,
94+
{
95+
propsData: { task, jobs }
96+
}
97+
)
98+
// there should be 4 jobs
99+
cy.get('.c-graph-node:last .jobs')
100+
.children()
101+
.should('have.length', 4)
102+
// there shouldn't be a job overflow indicator
103+
cy.get('.c-graph-node:last .job-overflow').should('not.exist')
104+
105+
cy.get('.c-graph-node').last().parent().screenshot(
106+
`graph-node-multiple-jobs`,
107+
{ overwrite: true, disableTimersAndAnimations: false }
108+
)
109+
})
110+
111+
it('Hides excessive numbers of jobs', () => {
112+
const [task, jobs] = makeTaskNode(
113+
'~a/b//20000101T0000Z/task_name',
114+
'running',
115+
['running', 'failed', 'failed', 'failed', 'failed', 'failed']
116+
)
117+
cy.mount(
118+
GraphNodeSVG,
119+
{
120+
propsData: { task, jobs, maxJobs: 4 }
121+
}
122+
)
123+
// there should be <maxJobs> jobs
124+
cy.get('.c-graph-node:last .jobs')
125+
.children()
126+
.should('have.length', 4)
127+
// there should be a job overflow indicator with the number of overflow jobs
128+
cy.get('.c-graph-node:last .job-overflow')
129+
.should('exist')
130+
.get('text')
131+
.contains('+2')
132+
133+
cy.get('.c-graph-node').last().parent().screenshot(
134+
`graph-node-overflow-jobs`,
135+
{ overwrite: true, disableTimersAndAnimations: false }
136+
)
137+
})
138+
139+
it('Renders for each task state', () => {
140+
let task
141+
let jobs
142+
let jobStates
143+
for (const state of TaskStateUserOrder) {
144+
jobStates = []
145+
for (const jobState of JobStates) {
146+
if (state.name === jobState.name) {
147+
jobStates = [state.name]
148+
break
149+
}
150+
}
151+
[task, jobs] = makeTaskNode(
152+
`~a/b//20000101T0000Z/${state.name}`,
153+
state.name,
154+
jobStates
155+
)
156+
console.log(jobs)
157+
cy.mount(GraphNodeSVG, { propsData: { task, jobs } })
158+
cy.get('.c-graph-node').last().parent().screenshot(
159+
`graph-node-${state.name}`,
160+
{ overwrite: true, disableTimersAndAnimations: false }
161+
)
162+
}
163+
})
164+
165+
it('Renders for each task modifier', () => {
166+
let task
167+
let jobs
168+
for (const modifier of ['isHeld', 'isQueued', 'isRunahead']) {
169+
[task, jobs] = makeTaskNode(
170+
`~a/b//20000101T0000Z/${modifier}`,
171+
'waiting',
172+
[]
173+
)
174+
task.node[modifier] = true
175+
cy.mount(GraphNodeSVG, { propsData: { task, jobs } })
176+
cy.get('.c-graph-node').last().parent().screenshot(
177+
`graph-node-${modifier}`,
178+
{ overwrite: true, disableTimersAndAnimations: false }
179+
)
180+
}
181+
})
182+
183+
it('Animates task progress', () => {
184+
let task
185+
let jobs
186+
for (const percent of [0, 25, 50, 75, 100]) {
187+
[task, jobs] = makeTaskNode(
188+
`~a/b//${percent}/running`,
189+
'running',
190+
['running']
191+
)
192+
jobs[0].node.startedTime = getStartTime(percent)
193+
cy.mount(GraphNodeSVG, { propsData: { task, jobs } })
194+
cy.get('.c-graph-node').last().parent().screenshot(
195+
`graph-node-running-${percent}`,
196+
{ overwrite: true, disableTimersAndAnimations: false }
197+
)
198+
// check the progress animation
199+
.get('.c8-task:last .status > .progress')
200+
// the animation duration should be equal to the expected job duration
201+
.should('have.css', 'animation-duration', `${MEAN_ELAPSED_TIME}s`)
202+
// the offset should be set to the "percent" of the expected job duration
203+
.should('have.css', 'animation-delay')
204+
.and('match', /([\d\.]+)s/) // NOTE the delay should be negative
205+
.then((number) => {
206+
// convert the duration string into a number that we can test
207+
cy.wrap(Number(number.match(/([\d\.]+)s/)[1]))
208+
// ensure this number is ±5 from the expected value
209+
// (give it a little bit of margin to allow for timing error)
210+
.should('closeTo', MEAN_ELAPSED_TIME * (percent / 100), 5)
211+
})
212+
}
213+
})
214+
})

0 commit comments

Comments
 (0)