Skip to content

Commit 8a7b140

Browse files
authored
Merge pull request #452 from fractal-analytics-platform/input-filters-and-dataset-page
Added input filters tab on workflow page and improved dataset page
2 parents 44e13ea + cb23933 commit 8a7b140

30 files changed

+1463
-478
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
* added admin "Tasks V1/V2 compatibility" page (\#450);
1414
* supported adding V1 task in V2 workflow (\#450);
1515
* removed read only property from V2 datasets (\#450);
16+
* added input filters tab in workflow task page (\#452);
17+
* added searchable dropdowns for image filters (\#452);
18+
* moved editing of dataset inside dataset page (\#452);
1619

1720
# 0.10.2
1821

__tests__/Paginator.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,16 @@ describe('Paginator', () => {
8787
});
8888
it('previous is disabled on first page', async () => {
8989
const result = render(Paginator, {
90-
props: { pageSize: 10, totalCount: 3, currentPage: 1, onPageChange: () => {} }
90+
props: { pageSize: 10, totalCount: 13, currentPage: 1, onPageChange: () => {} }
9191
});
92-
expect(getPageItems(result)).toEqual(['«', '1', '»']);
92+
expect(getPageItems(result)).toEqual(['«', '1', '2', '»']);
9393
expect(result.queryByLabelText('Previous').disabled).true;
9494
});
9595
it('next is disabled on last page', async () => {
9696
const result = render(Paginator, {
97-
props: { pageSize: 10, totalCount: 3, currentPage: 1, onPageChange: () => {} }
97+
props: { pageSize: 10, totalCount: 13, currentPage: 2, onPageChange: () => {} }
9898
});
99-
expect(getPageItems(result)).toEqual(['«', '1', '»']);
99+
expect(getPageItems(result)).toEqual(['«', '1', '2', '»']);
100100
expect(result.queryByLabelText('Next').disabled).true;
101101
});
102102
});

__tests__/v2/CreateUpdateDatasetModal.test.js renamed to __tests__/v2/CreateDatasetModal.test.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ global.window.bootstrap = {
2727
Modal: MockModal
2828
};
2929

30-
import CreateUpdateDatasetModal from '../../src/lib/components/v2/projects/datasets/CreateUpdateDatasetModal.svelte';
30+
import CreateDatasetModal from '../../src/lib/components/v2/projects/datasets/CreateDatasetModal.svelte';
3131

3232
const defaultProps = {
33-
props: { createDatasetCallback: vi.fn(), updateDatasetCallback: vi.fn() }
33+
props: { createDatasetCallback: vi.fn() }
3434
};
3535

36-
describe('CreateUpdateDatasetModal', () => {
36+
describe('CreateDatasetModal', () => {
3737
fetch.mockResolvedValue({
3838
ok: true,
3939
status: 200,
@@ -48,15 +48,15 @@ describe('CreateUpdateDatasetModal', () => {
4848
});
4949

5050
it('validate missing name and zarr dir', async () => {
51-
const result = render(CreateUpdateDatasetModal, defaultProps);
51+
const result = render(CreateDatasetModal, defaultProps);
5252
await fireEvent.click(result.getByRole('button', { name: 'Save' }));
5353
expect(result.queryAllByText('Required field').length).eq(2);
5454
});
5555

5656
it('create dataset with string filter', async () => {
5757
const createDatasetCallback = vi.fn();
58-
const result = render(CreateUpdateDatasetModal, {
59-
props: { createDatasetCallback, updateDatasetCallback: vi.fn() }
58+
const result = render(CreateDatasetModal, {
59+
props: { createDatasetCallback }
6060
});
6161
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
6262
target: { value: 'my dataset' }
@@ -85,8 +85,8 @@ describe('CreateUpdateDatasetModal', () => {
8585

8686
it('create dataset with number filter', async () => {
8787
const createDatasetCallback = vi.fn();
88-
const result = render(CreateUpdateDatasetModal, {
89-
props: { createDatasetCallback, updateDatasetCallback: vi.fn() }
88+
const result = render(CreateDatasetModal, {
89+
props: { createDatasetCallback }
9090
});
9191
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
9292
target: { value: 'my dataset' }
@@ -116,8 +116,8 @@ describe('CreateUpdateDatasetModal', () => {
116116

117117
it('create dataset with type filter set to false', async () => {
118118
const createDatasetCallback = vi.fn();
119-
const result = render(CreateUpdateDatasetModal, {
120-
props: { createDatasetCallback, updateDatasetCallback: vi.fn() }
119+
const result = render(CreateDatasetModal, {
120+
props: { createDatasetCallback }
121121
});
122122
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
123123
target: { value: 'my dataset' }
@@ -145,8 +145,8 @@ describe('CreateUpdateDatasetModal', () => {
145145

146146
it('create dataset with type filter set to true', async () => {
147147
const createDatasetCallback = vi.fn();
148-
const result = render(CreateUpdateDatasetModal, {
149-
props: { createDatasetCallback, updateDatasetCallback: vi.fn() }
148+
const result = render(CreateDatasetModal, {
149+
props: { createDatasetCallback }
150150
});
151151
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
152152
target: { value: 'my dataset' }

__tests__/v2/FiltersCreationForm.test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,22 @@ describe('FiltersCreationForm', () => {
6969
const result = render(FiltersCreationForm);
7070
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
7171
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
72-
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'foo' } });
7372
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'number' } });
73+
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'foo' } });
7474
expect(result.component.validateFields()).false;
7575
await tick();
7676
expect(result.getByText('Invalid number')).toBeDefined();
7777
});
7878

79+
it('switch to boolean attribute', async () => {
80+
const result = render(FiltersCreationForm);
81+
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
82+
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
83+
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'boolean' } });
84+
expect(result.getByLabelText('Value').value).eq('true');
85+
expect(result.component.validateFields()).true;
86+
});
87+
7988
it('validate duplicated attribute key', async () => {
8089
const result = render(FiltersCreationForm);
8190
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));

playwright.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default defineConfig({
4747

4848
webServer: [
4949
{
50-
command: './tests/start-test-server.sh 2.0.0a6',
50+
command: './tests/start-test-server.sh 2.0.0a8',
5151
port: 8000,
5252
waitForPort: true,
5353
stdout: 'pipe',

src/lib/common/component_utilities.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,22 @@ export function downloadBlob(content, filename, contentType) {
250250
downloader.setAttribute('download', filename);
251251
downloader.click();
252252
}
253+
254+
/**
255+
* @param {{ [key: string]: null | string | number | boolean}} oldObject
256+
* @param {{ [key: string]: null | string | number | boolean}} newObject
257+
*/
258+
export function objectChanged(oldObject, newObject) {
259+
if (Object.keys(oldObject).length !== Object.keys(newObject).length) {
260+
return true;
261+
}
262+
for (const [oldKey, oldValue] of Object.entries(oldObject)) {
263+
if (!(oldKey in newObject)) {
264+
return true;
265+
}
266+
if (oldValue !== newObject[oldKey]) {
267+
return true;
268+
}
269+
}
270+
return false;
271+
}

src/lib/components/common/ConfirmActionButton.svelte

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
export let message = '';
1313
/** @type {Modal} */
1414
let modal;
15+
let loading = false;
1516
1617
const onOpen = () => {
1718
// Remove old errors
@@ -25,7 +26,10 @@
2526
* Executes the callback handling possible errors
2627
*/
2728
const handleCallbackAction = async () => {
28-
modal.confirmAndHide(callbackAction);
29+
loading = true;
30+
modal.confirmAndHide(callbackAction, function () {
31+
loading = false;
32+
});
2933
};
3034
</script>
3135

@@ -34,16 +38,25 @@
3438
<h1 class="modal-title fs-5">Confirm action</h1>
3539
</svelte:fragment>
3640
<svelte:fragment slot="body">
37-
<p>You're about to:</p>
38-
<p class="badge bg-{style} fs-6 wrap">{message}</p>
39-
<p>Do you confirm?</p>
41+
{#if !!$$slots.body}
42+
<slot name="body" />
43+
{:else}
44+
<p>You're about to:</p>
45+
<p class="badge bg-{style} fs-6 wrap">{message}</p>
46+
<p>Do you confirm?</p>
47+
{/if}
4048
<div class="container">
4149
<div id="errorAlert-{modalId}" />
4250
</div>
4351
</svelte:fragment>
4452
<svelte:fragment slot="footer">
4553
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
46-
<button class="btn btn-primary" on:click={handleCallbackAction}>Confirm</button>
54+
<button class="btn btn-primary" on:click={handleCallbackAction}>
55+
{#if loading}
56+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" />
57+
{/if}
58+
Confirm
59+
</button>
4760
</svelte:fragment>
4861
</Modal>
4962

src/lib/components/common/Paginator.svelte

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,26 @@
1717
/**
1818
* @param {number} newCurrentPage
1919
*/
20-
function setCurrentPage(newCurrentPage) {
21-
onPageChange(newCurrentPage, pageSize);
20+
async function setCurrentPage(newCurrentPage) {
21+
await onPageChange(newCurrentPage, pageSize);
22+
fixCurrentPageFocus();
2223
}
2324
24-
function setPageSize() {
25+
async function setPageSize() {
2526
currentPage = 1;
26-
onPageChange(currentPage, pageSize);
27+
await onPageChange(currentPage, pageSize);
28+
fixCurrentPageFocus();
29+
}
30+
31+
/**
32+
* The focus of the current page button may be set on the wrong item due to the redrawing
33+
* of the component. This function force setting the focus to the correct active button.
34+
*/
35+
function fixCurrentPageFocus() {
36+
const activeItem = document.querySelector('.pagination .page-item.active button');
37+
if (activeItem instanceof HTMLButtonElement) {
38+
activeItem.focus();
39+
}
2740
}
2841
2942
/**
@@ -64,9 +77,9 @@
6477
}
6578
</script>
6679

67-
<div class="row row-cols-lg-auto justify-content-center">
68-
{#if totalCount > 0}
69-
<div class="col-12 mt-5">
80+
<div class="row justify-content-center">
81+
{#if numberOfPages > 1}
82+
<div class="col">
7083
<nav aria-label="Page navigation">
7184
<ul class="pagination justify-content-center">
7285
<li class="page-item">
@@ -113,25 +126,25 @@
113126
</ul>
114127
</nav>
115128
</div>
116-
<div class="col-12 mt-5">
117-
<div class="input-group mb-3">
118-
<label class="input-group-text" for="page_size">Page size</label>
119-
<select
120-
class="form-control"
121-
id="page_size"
122-
bind:value={pageSize}
123-
on:change={() => setPageSize()}
124-
>
125-
{#each availablePageSizes as pageSize}
126-
<option value={pageSize}>{pageSize}</option>
127-
{/each}
128-
</select>
129-
</div>
130-
</div>
131129
{/if}
132130
</div>
133-
<div class="row">
134-
<div class="mt-3 col">
131+
<div class="row row-cols-lg-auto justify-content-center">
132+
<div class="col-6">
133+
<div class="input-group">
134+
<label class="input-group-text" for="page_size">Page size</label>
135+
<select
136+
class="form-control"
137+
id="page_size"
138+
bind:value={pageSize}
139+
on:change={() => setPageSize()}
140+
>
141+
{#each availablePageSizes as pageSize}
142+
<option value={pageSize}>{pageSize}</option>
143+
{/each}
144+
</select>
145+
</div>
146+
</div>
147+
<div class="col-6 mt-2">
135148
<p class="text-center">Total results: {totalCount}</p>
136149
</div>
137150
</div>

src/lib/components/v1/workflow/ArgumentsSchema.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
validSchema = false;
6060
}
6161
}
62+
63+
$: {
64+
if (!unsavedChanges) {
65+
schemaComponent?.discardChanges(args);
66+
}
67+
}
6268
</script>
6369
6470
<div id="workflow-arguments-schema-panel">

src/lib/components/v2/projects/ProjectDatasetsList.svelte

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script>
22
import ConfirmActionButton from '$lib/components/common/ConfirmActionButton.svelte';
33
import { AlertError } from '$lib/common/errors';
4-
import CreateUpdateDatasetModal from './datasets/CreateUpdateDatasetModal.svelte';
4+
import CreateDatasetModal from './datasets/CreateDatasetModal.svelte';
55
import { onMount } from 'svelte';
66
77
/** @type {import('$lib/types-v2').DatasetV2[]} */
@@ -13,15 +13,9 @@
1313
p.name.toLowerCase().includes(datasetSearch.toLowerCase())
1414
);
1515
16-
/** @type {CreateUpdateDatasetModal} */
17-
let createUpdateDatasetModal;
18-
1916
function createDatasetCallback(/** @type {import('$lib/types-v2').DatasetV2} */ newDataset) {
2017
datasets = [...datasets, newDataset];
2118
}
22-
function updateDatasetCallback(/** @type {import('$lib/types-v2').DatasetV2} */ updatedDataset) {
23-
datasets = datasets.map((d) => (d.id === updatedDataset.id ? updatedDataset : d));
24-
}
2519
2620
/**
2721
* Deletes a project's dataset from the server
@@ -74,7 +68,8 @@
7468
<button
7569
class="btn btn-primary float-end"
7670
type="button"
77-
on:click={() => createUpdateDatasetModal.openForCreate()}
71+
data-bs-target="#createDatasetModal"
72+
data-bs-toggle="modal"
7873
>
7974
Create new dataset
8075
</button>
@@ -96,16 +91,12 @@
9691
<tr>
9792
<td>{dataset.name}</td>
9893
<td>
99-
<a class="btn btn-light" href="/v2/projects/{dataset.project_id}/datasets/{dataset.id}">
94+
<a
95+
class="btn btn-light"
96+
href="/v2/projects/{dataset.project_id}/datasets/{dataset.id}"
97+
>
10098
<i class="bi bi-arrow-up-right-square" /> Open
10199
</a>
102-
<button
103-
class="btn btn-primary"
104-
type="button"
105-
on:click|preventDefault={() => createUpdateDatasetModal.openForEdit(dataset)}
106-
>
107-
<i class="bi bi-pencil" /> Edit
108-
</button>
109100
<ConfirmActionButton
110101
modalId="confirmDatasetDeleteModal{dataset.id}"
111102
style={'danger'}
@@ -123,8 +114,4 @@
123114
</table>
124115
</div>
125116

126-
<CreateUpdateDatasetModal
127-
bind:this={createUpdateDatasetModal}
128-
{createDatasetCallback}
129-
{updateDatasetCallback}
130-
/>
117+
<CreateDatasetModal {createDatasetCallback} />

0 commit comments

Comments
 (0)