Skip to content

Commit 4c4e6ca

Browse files
authored
Merge pull request #466 from fractal-analytics-platform/default-task-continuing-workflow
Workflow execution modal improvements
2 parents 59958ec + e9ff01c commit 4c4e6ca

File tree

9 files changed

+285
-31
lines changed

9 files changed

+285
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* supported editing of single dataset images (\#457);
2020
* used switches to represent boolean flags (\#457);
2121
* implemented continue/restart workflow (\#465);
22+
* set default first task when continuing a workflow (\#466);
23+
* displayed applied filters in workflow execution modal (\#466);
2224

2325
# 0.10.2
2426

__tests__/job_utilities.test.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { it, expect } from 'vitest';
22
import {
33
extractJobErrorParts,
44
extractRelevantJobError,
5-
generateNewUniqueDatasetName
5+
generateNewUniqueDatasetName,
6+
getFirstTaskIndexForContinuingWorkflow
67
} from '$lib/common/job_utilities.js';
78

89
const completeTracebackError = `TASK ERROR:Task id: 15 (Create OME-Zarr structure), e.workflow_task_order=0
@@ -237,6 +238,19 @@ it('generates new unique dataset name', () => {
237238
);
238239
});
239240

241+
it('get first task index for continuing workflow', () => {
242+
expect(testGetFirstTaskIndexForContinuingWorkflow([null, null, null, null])).toEqual(0);
243+
expect(testGetFirstTaskIndexForContinuingWorkflow(['done', 'done', null, null])).toEqual(2);
244+
expect(testGetFirstTaskIndexForContinuingWorkflow(['done', 'failed', 'done', null])).toEqual(1);
245+
expect(testGetFirstTaskIndexForContinuingWorkflow(['done', null, 'done', null])).toEqual(1);
246+
expect(testGetFirstTaskIndexForContinuingWorkflow(['done', 'done', 'done', 'done'])).toEqual(
247+
undefined
248+
);
249+
expect(testGetFirstTaskIndexForContinuingWorkflow(['submitted', 'failed', null, null])).toEqual(
250+
undefined
251+
);
252+
});
253+
240254
/**
241255
* @param {string[]} names
242256
*/
@@ -245,3 +259,20 @@ function getMockedDatasets(names) {
245259
return { id: index + 1, name };
246260
});
247261
}
262+
263+
/**
264+
* @param {Array<string|null>} values
265+
* @returns {number|undefined}
266+
*/
267+
function testGetFirstTaskIndexForContinuingWorkflow(values) {
268+
const workflowTasks = values.map((_, i) => {
269+
return {
270+
id: i + 1,
271+
order: i
272+
};
273+
});
274+
const statuses = Object.fromEntries(
275+
values.map((v, i) => [i + 1, v]).filter((e) => e[1] !== null)
276+
);
277+
return getFirstTaskIndexForContinuingWorkflow(workflowTasks, statuses);
278+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { fireEvent, render } from '@testing-library/svelte';
3+
import { readable } from 'svelte/store';
4+
5+
// Mocking fetch
6+
global.fetch = vi.fn();
7+
8+
// Mocking the page store
9+
vi.mock('$app/stores', () => {
10+
return {
11+
page: readable({
12+
data: {
13+
userInfo: { slurm_accounts: [] }
14+
}
15+
})
16+
};
17+
});
18+
19+
// Mocking bootstrap.Modal
20+
class MockModal {
21+
show = vi.fn();
22+
hide = vi.fn();
23+
}
24+
MockModal.getInstance = vi.fn();
25+
26+
global.window.bootstrap = {
27+
Modal: MockModal
28+
};
29+
30+
import RunWorkflowModal from '../../src/lib/components/v2/workflow/RunWorkflowModal.svelte';
31+
32+
const dataset1 = {
33+
id: 1,
34+
filters: {
35+
attributes: {
36+
k1: 'dataset-value1',
37+
k3: 'dataset-value3'
38+
},
39+
types: {
40+
t1: false,
41+
t3: true
42+
}
43+
}
44+
};
45+
46+
const workflow1 = {
47+
id: 1,
48+
task_list: [
49+
{
50+
id: 1,
51+
task: { name: 'task1' },
52+
input_filters: {
53+
attributes: { k1: 'wft-value1', k2: 'wft-value2' },
54+
types: { t1: true, t2: false }
55+
}
56+
}
57+
]
58+
};
59+
60+
describe('RunWorkflowModal', () => {
61+
it('When continuing a workflow, show applied filters prioritizing the first workflow task filters', async () => {
62+
const result = render(RunWorkflowModal, {
63+
datasets: [dataset1],
64+
selectedDatasetId: 1,
65+
workflow: workflow1,
66+
onJobSubmitted: vi.fn(),
67+
onDatasetsUpdated: vi.fn(),
68+
statuses: { 1: 'failed' }
69+
});
70+
71+
result.component.open('continue');
72+
await fireEvent.click(result.getByRole('button', { name: 'Run' }));
73+
74+
const items = await result.findAllByRole('listitem');
75+
76+
expect(items.length).toEqual(6);
77+
expect(result.getByText('k1:').innerHTML).toContain('wft-value1');
78+
expect(result.getByText('k2:').innerHTML).toContain('wft-value2');
79+
expect(result.getByText('k3:').innerHTML).toContain('dataset-value3');
80+
expect(result.getByText('t1:').innerHTML).toContain('text-success');
81+
expect(result.getByText('t2:').innerHTML).toContain('text-danger');
82+
expect(result.getByText('t3:').innerHTML).toContain('text-success');
83+
});
84+
85+
it('When restarting a workflow, show applied filters from workflow task only', async () => {
86+
const result = render(RunWorkflowModal, {
87+
datasets: [dataset1],
88+
selectedDatasetId: 1,
89+
workflow: workflow1,
90+
onJobSubmitted: vi.fn(),
91+
onDatasetsUpdated: vi.fn(),
92+
statuses: { 1: 'done' }
93+
});
94+
95+
result.component.open('restart');
96+
await fireEvent.click(result.getByRole('button', { name: 'Run' }));
97+
98+
const items = await result.findAllByRole('listitem');
99+
100+
expect(items.length).toEqual(4);
101+
expect(result.getByText('k1:').innerHTML).toContain('wft-value1');
102+
expect(result.getByText('k2:').innerHTML).toContain('wft-value2');
103+
expect(result.getByText('t1:').innerHTML).toContain('text-success');
104+
expect(result.getByText('t2:').innerHTML).toContain('text-danger');
105+
});
106+
107+
it('Handles empty filters list', async () => {
108+
const result = render(RunWorkflowModal, {
109+
datasets: [
110+
{
111+
id: 1,
112+
filters: { attributes: {}, types: {} }
113+
}
114+
],
115+
selectedDatasetId: 1,
116+
workflow: {
117+
id: 1,
118+
task_list: [
119+
{
120+
id: 1,
121+
task: { name: 'task1' },
122+
input_filters: { attributes: {}, types: {} }
123+
}
124+
]
125+
},
126+
onJobSubmitted: vi.fn(),
127+
onDatasetsUpdated: vi.fn(),
128+
statuses: { 1: 'failed' }
129+
});
130+
131+
result.component.open('continue');
132+
await fireEvent.click(result.getByRole('button', { name: 'Run' }));
133+
expect(result.getByText('No filters')).toBeDefined();
134+
});
135+
});

src/lib/common/job_utilities.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,16 @@ export function generateNewUniqueDatasetName(datasets, selectedDatasetName) {
133133
} while (datasets.filter((d) => d.name === newDatasetName).length > 0);
134134
return newDatasetName;
135135
}
136+
137+
/**
138+
* @param {Array<import("$lib/types-v2").WorkflowTaskV2>} workflowTasks
139+
* @param {{[key: number]: import('$lib/types').JobStatus}} statuses
140+
* @returns {number|undefined}
141+
*/
142+
export function getFirstTaskIndexForContinuingWorkflow(workflowTasks, statuses) {
143+
if (workflowTasks.find((wft) => statuses[wft.id] === 'submitted')) {
144+
// we can't re-submit while something is running
145+
return undefined;
146+
}
147+
return workflowTasks.find((wft) => !(wft.id in statuses) || statuses[wft.id] === 'failed')?.order;
148+
}

src/lib/components/common/BooleanIcon.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919
font-weight: bold;
2020
margin: 0 -5px -5px -5px;
2121
line-height: 0;
22-
display: block;
22+
display: inline-block;
2323
}
2424
</style>

src/lib/components/v2/projects/datasets/DatasetFiltersModal.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
const result = await response.json();
5050
if (!response.ok) {
5151
console.log('Dataset update failed', result);
52-
throw new AlertError(await response.json());
52+
throw new AlertError(result);
5353
}
5454
updateDatasetCallback(result);
5555
},

0 commit comments

Comments
 (0)