Skip to content

Commit 97e71a9

Browse files
committed
Displayed applied filters in workflow execution modal
1 parent 089a765 commit 97e71a9

File tree

6 files changed

+201
-18
lines changed

6 files changed

+201
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* used switches to represent boolean flags (\#457);
2121
* implemented continue/restart workflow (\#465);
2222
* set default first task when continuing a workflow (\#466);
23+
* displayed applied filters in workflow execution modal (\#466);
2324

2425
# 0.10.2
2526

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/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/workflow/RunWorkflowModal.svelte

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
import { page } from '$app/stores';
33
import { replaceEmptyStrings } from '$lib/common/component_utilities';
44
import { AlertError } from '$lib/common/errors';
5-
import { generateNewUniqueDatasetName, getFirstTaskIndexForContinuingWorkflow } from '$lib/common/job_utilities';
5+
import {
6+
generateNewUniqueDatasetName,
7+
getFirstTaskIndexForContinuingWorkflow
8+
} from '$lib/common/job_utilities';
9+
import BooleanIcon from '$lib/components/common/BooleanIcon.svelte';
610
import Modal from '$lib/components/common/Modal.svelte';
711
812
/** @type {import('$lib/types-v2').DatasetV2[]} */
@@ -50,8 +54,10 @@
5054
export function open(action) {
5155
mode = action;
5256
replaceExistingDataset = true;
57+
applyingWorkflow = false;
58+
checkingConfiguration = false;
5359
workerInitControl = '';
54-
if ((mode === 'run' || mode === 'restart') && workflow.task_list.length > 0) {
60+
if (mode === 'run' || mode === 'restart') {
5561
firstTaskIndex = 0;
5662
} else {
5763
firstTaskIndex = getFirstTaskIndexForContinuingWorkflow(workflow.task_list, statuses);
@@ -121,7 +127,9 @@
121127
}
122128
123129
async function replaceDataset() {
124-
const { id, name, zarr_dir } = getSelectedDataset();
130+
const { id, name, zarr_dir } = /** @type {import('$lib/types-v2').DatasetV2} */ (
131+
selectedDataset
132+
);
125133
await handleDatasetDelete(id);
126134
const newDatasets = datasets.filter((d) => d.id !== id);
127135
const newDataset = await handleDatasetCreate(name, zarr_dir);
@@ -130,7 +138,7 @@
130138
}
131139
132140
async function createNewDataset() {
133-
const { zarr_dir } = getSelectedDataset();
141+
const { zarr_dir } = /** @type {import('$lib/types-v2').DatasetV2} */ (selectedDataset);
134142
const newDataset = await handleDatasetCreate(newDatasetName, zarr_dir);
135143
onDatasetsUpdated([...datasets, newDataset], newDataset.id);
136144
}
@@ -185,14 +193,27 @@
185193
}
186194
}
187195
188-
function computeNewDatasetName() {
189-
newDatasetName = generateNewUniqueDatasetName(datasets, getSelectedDataset().name);
196+
/** @type {{ [key: string]: string|number|boolean }} */
197+
let appliedAttributeFilters = {};
198+
/** @type {{ [key: string]: boolean }} */
199+
let appliedTypeFilters = {};
200+
201+
function showConfirmRun() {
202+
checkingConfiguration = true;
203+
const wft = workflow.task_list[firstTaskIndex || 0];
204+
if (mode === 'restart') {
205+
appliedAttributeFilters = { ...wft.input_filters.attributes };
206+
appliedTypeFilters = { ...wft.input_filters.types };
207+
} else {
208+
const dataset = /** @type {import('$lib/types-v2').DatasetV2} */ (selectedDataset);
209+
appliedAttributeFilters = { ...dataset.filters.attributes, ...wft.input_filters.attributes };
210+
appliedTypeFilters = { ...dataset.filters.types, ...wft.input_filters.types };
211+
}
190212
}
191213
192-
function getSelectedDataset() {
193-
return /** @type {import('$lib/types-v2').DatasetV2}*/ (
194-
datasets.find((d) => d.id === selectedDatasetId)
195-
);
214+
function computeNewDatasetName() {
215+
const dataset = /** @type {import('$lib/types-v2').DatasetV2} */ (selectedDataset);
216+
newDatasetName = generateNewUniqueDatasetName(datasets, dataset.name);
196217
}
197218
</script>
198219
@@ -384,6 +405,28 @@
384405
</div>
385406
</div>
386407
</div>
408+
{#if checkingConfiguration}
409+
<hr />
410+
<h6 class="mt-3">Applied filters</h6>
411+
{#if Object.entries(appliedAttributeFilters).length > 0 || Object.entries(appliedTypeFilters).length > 0}
412+
<p>
413+
Currently, the following filters are applied to the image list before it is passed to
414+
the first task:
415+
</p>
416+
<ul class="mb-0">
417+
{#each Object.entries(appliedAttributeFilters) as [key, value]}
418+
<li>{key}: <code>{value}</code></li>
419+
{/each}
420+
</ul>
421+
<ul class="mt-0 mb-1">
422+
{#each Object.entries(appliedTypeFilters) as [key, value]}
423+
<li>{key}: <BooleanIcon {value} /></li>
424+
{/each}
425+
</ul>
426+
{:else}
427+
<p class="mb-0">No filters</p>
428+
{/if}
429+
{/if}
387430
</form>
388431
</svelte:fragment>
389432
<svelte:fragment slot="footer">
@@ -402,13 +445,15 @@
402445
Confirm
403446
</button>
404447
{:else}
405-
<button
406-
class="btn btn-primary"
407-
on:click={() => (checkingConfiguration = true)}
408-
disabled={runBtnDisabled}
409-
>
448+
<button class="btn btn-primary" on:click={showConfirmRun} disabled={runBtnDisabled}>
410449
Run
411450
</button>
412451
{/if}
413452
</svelte:fragment>
414453
</Modal>
454+
455+
<style>
456+
:global(.boolean-icon) {
457+
vertical-align: top;
458+
}
459+
</style>

src/routes/v2/projects/[projectId]/workflows/[workflowId]/+page.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,20 +717,22 @@
717717
<button
718718
class="btn btn-success"
719719
on:click|preventDefault={() => openRunWorkflowModal('run')}
720-
disabled={selectedDatasetId === undefined}
720+
disabled={selectedDatasetId === undefined || workflow.task_list.length === 0}
721721
>
722722
<i class="bi-play-fill" /> Run workflow
723723
</button>
724724
{:else}
725725
<button
726726
class="btn btn-success"
727727
on:click|preventDefault={() => openRunWorkflowModal('continue')}
728+
disabled={workflow.task_list.length === 0}
728729
>
729730
<i class="bi-play-fill" /> Continue workflow
730731
</button>
731732
<button
732733
class="btn btn-primary"
733734
on:click|preventDefault={() => openRunWorkflowModal('restart')}
735+
disabled={workflow.task_list.length === 0}
734736
>
735737
<i class="bi bi-arrow-clockwise" /> Restart workflow
736738
</button>

tests/v2/run_mock_tasks.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ test('Collect and run mock tasks [v2]', async ({ page, workflow, request }) => {
230230
await modal.waitFor();
231231
await expect(
232232
modal
233-
.getByRole('combobox', { name: 'First task (Optional)' })
233+
.getByRole('combobox', { name: 'First task (Required)' })
234234
.getByRole('option', { selected: true })
235235
).toHaveText('generic_task');
236236
await modal.getByRole('button', { name: 'Run', exact: true }).click();

0 commit comments

Comments
 (0)