Skip to content

Commit

Permalink
Merge pull request #707 from fractal-analytics-platform/fix-confirm-d…
Browse files Browse the repository at this point in the history
…ialog-filter

Merge dev-1.15 & minor fixes
  • Loading branch information
zonia3000 authored Jan 28, 2025
2 parents 05c4ab3 + f9e2991 commit a36c012
Show file tree
Hide file tree
Showing 53 changed files with 2,574 additions and 1,357 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ LOG_LEVEL_CONSOLE=warn

FRACTAL_RUNNER_BACKEND=local
#WARNING_BANNER_PATH=/path/to/banner.txt
ENABLE_INTERACTIVE_ATTRIBUTE_FILTERS=false
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ LOG_LEVEL_CONSOLE=info

FRACTAL_RUNNER_BACKEND=local
#WARNING_BANNER_PATH=/path/to/banner.txt
ENABLE_INTERACTIVE_ATTRIBUTE_FILTERS=false
3 changes: 2 additions & 1 deletion .github/workflows/end_to_end_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
OAUTH_DEXIDP_OIDC_CONFIGURATION_ENDPOINT: "http://127.0.0.1:5556/dex/.well-known/openid-configuration"
PUBLIC_OAUTH_CLIENT_NAME: dexidp
PUBLIC_FRACTAL_VIZARR_VIEWER_URL: http://localhost:3000/vizarr
ENABLE_INTERACTIVE_ATTRIBUTE_FILTERS: true

strategy:
matrix:
Expand Down Expand Up @@ -75,6 +76,6 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-folder
name: test-results-folder-node-${{ matrix.node-version }}
path: test-results
overwrite: true
4 changes: 1 addition & 3 deletions .github/workflows/github_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,5 @@ jobs:

- name: Release
uses: softprops/action-gh-release@v2
env:
GITHUB_REF_NAME: ${{ github.ref_name }}
with:
files: node-${{ matrix.node-version }}-fractal-web-${GITHUB_REF_NAME}.tar.gz
files: node-${{ matrix.node-version }}-fractal-web-*.tar.gz
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
*Note: Numbers like (\#123) point to closed Pull Requests on the fractal-web repository.*

# Unreleased

* Added type filters flow modal (\#701);
* Pre-populated the image zarr_url (\#703);
* Added validation for type filters and input types compatibility (\#703);
* Displayed input/output task type in "Type" tab on workflow page (\#691);
* Removed dataset filters modal, implemented editing of dataset filters directly inside the image list table (\#691);
* Refactored code to new filters format and sent selected filters in run workflow modal (\#686);
* Displayed list of images that would be processed by the first task of a job (\#686);
* Added environment variable `ENABLE_INTERACTIVE_ATTRIBUTE_FILTERS`, to enable attribute filters on the "Run workflow" modal (\#686);
* Fixed findings based on `zizmor 1.0.1` audit (\#687).

# 1.14.0
Expand Down
110 changes: 1 addition & 109 deletions __tests__/v2/CreateDatasetModal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,112 +81,12 @@ describe('CreateDatasetModal', () => {
await fireEvent.input(result.getByRole('textbox', { name: 'Zarr dir' }), {
target: { value: '/tmp' }
});
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'my-value' } });
await fireEvent.click(result.getByRole('button', { name: 'Save' }));
expect(fetch).toHaveBeenLastCalledWith(
'/api/v2/project/1/dataset',
expect.objectContaining({
body: JSON.stringify({
name: 'my dataset',
filters: {
attributes: { 'my-key': 'my-value' },
types: {}
},
zarr_dir: '/tmp'
})
})
);
});

it('create dataset with number filter', async () => {
mockFetch(null);
const createDatasetCallback = vi.fn();
const result = render(CreateDatasetModal, {
props: { createDatasetCallback }
});
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
target: { value: 'my dataset' }
});
await fireEvent.input(result.getByRole('textbox', { name: 'Zarr dir' }), {
target: { value: '/tmp' }
});
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: '123' } });
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'number' } });
await fireEvent.click(result.getByRole('button', { name: 'Save' }));
expect(fetch).toHaveBeenLastCalledWith(
'/api/v2/project/1/dataset',
expect.objectContaining({
body: JSON.stringify({
name: 'my dataset',
filters: {
attributes: { 'my-key': 123 },
types: {}
},
zarr_dir: '/tmp'
})
})
);
});

it('create dataset with type filter set to false', async () => {
mockFetch(null);
const createDatasetCallback = vi.fn();
const result = render(CreateDatasetModal, {
props: { createDatasetCallback }
});
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
target: { value: 'my dataset' }
});
await fireEvent.input(result.getByRole('textbox', { name: 'Zarr dir' }), {
target: { value: '/tmp' }
});
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.click(result.getByRole('button', { name: 'Save' }));
expect(fetch).toHaveBeenLastCalledWith(
'/api/v2/project/1/dataset',
expect.objectContaining({
body: JSON.stringify({
name: 'my dataset',
filters: {
attributes: {},
types: { 'my-key': false }
},
zarr_dir: '/tmp'
})
})
);
});

it('create dataset with type filter set to true', async () => {
mockFetch(null);
const createDatasetCallback = vi.fn();
const result = render(CreateDatasetModal, {
props: { createDatasetCallback }
});
await fireEvent.input(result.getByRole('textbox', { name: 'Dataset Name' }), {
target: { value: 'my dataset' }
});
await fireEvent.input(result.getByRole('textbox', { name: 'Zarr dir' }), {
target: { value: '/tmp' }
});
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.click(result.getByLabelText('Value for my-key'));
await fireEvent.click(result.getByRole('button', { name: 'Save' }));
expect(fetch).toHaveBeenLastCalledWith(
'/api/v2/project/1/dataset',
expect.objectContaining({
body: JSON.stringify({
name: 'my dataset',
filters: {
attributes: {},
types: { 'my-key': true }
},
zarr_dir: '/tmp'
})
})
Expand All @@ -207,11 +107,7 @@ describe('CreateDatasetModal', () => {
'/api/v2/project/1/dataset',
expect.objectContaining({
body: JSON.stringify({
name: 'my dataset',
filters: {
attributes: {},
types: {}
}
name: 'my dataset'
})
})
);
Expand Down Expand Up @@ -262,10 +158,6 @@ describe('CreateDatasetModal', () => {
expect.objectContaining({
body: JSON.stringify({
name: 'my dataset',
filters: {
attributes: {},
types: {}
},
zarr_dir: 'foo'
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, it, expect } from 'vitest';
import { fireEvent, render } from '@testing-library/svelte';

import AttributesTypesForm from '../../src/lib/components/v2/projects/datasets/AttributesTypesForm.svelte';
import ImageAttributesTypesForm from '../../src/lib/components/v2/projects/datasets/ImageAttributesTypesForm.svelte';
import { tick } from 'svelte';

describe('AttributesTypesForm', () => {
it('init with existing filters', async () => {
const result = render(AttributesTypesForm);
it('init with existing values', async () => {
const result = render(ImageAttributesTypesForm);
result.component.init(
{
key1: 'value1',
Expand All @@ -28,46 +28,46 @@ describe('AttributesTypesForm', () => {
expect(values[1]).eq('42');
});

it('add and remove attribute filter', async () => {
const result = render(AttributesTypesForm);
it('add and remove attribute', async () => {
const result = render(ImageAttributesTypesForm);
expect(result.queryAllByPlaceholderText('Key').length).eq(0);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
expect(result.queryAllByPlaceholderText('Key').length).eq(1);
await fireEvent.click(result.getByRole('button', { name: 'Remove attribute filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Remove attribute' }));
expect(result.queryAllByPlaceholderText('Key').length).eq(0);
});

it('add and remove type filter', async () => {
const result = render(AttributesTypesForm);
it('add and remove type', async () => {
const result = render(ImageAttributesTypesForm);
expect(result.queryAllByPlaceholderText('Key').length).eq(0);
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add type' }));
expect(result.queryAllByPlaceholderText('Key').length).eq(1);
await fireEvent.click(result.getByRole('button', { name: 'Remove type filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Remove type' }));
expect(result.queryAllByPlaceholderText('Key').length).eq(0);
});

it('validate missing attribute filter key', async () => {
const result = render(AttributesTypesForm);
it('validate missing attribute key', async () => {
const result = render(ImageAttributesTypesForm);
result.component.init({}, {});
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
expect(result.component.validateFields()).false;
await tick();
expect(result.getByText('Key is required')).toBeDefined();
});

it('validate missing attribute filter value', async () => {
const result = render(AttributesTypesForm);
it('validate missing attribute value', async () => {
const result = render(ImageAttributesTypesForm);
result.component.init({}, {});
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
expect(result.component.validateFields()).false;
await tick();
expect(result.getByText('Value is required')).toBeDefined();
});

it('validate invalid number', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'number' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'foo' } });
Expand All @@ -77,8 +77,8 @@ describe('AttributesTypesForm', () => {
});

it('switch to number attribute from string containing a numeric value (number is preserved)', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: '42' } });
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'number' } });
Expand All @@ -87,8 +87,8 @@ describe('AttributesTypesForm', () => {
});

it('switch to number attribute from string containing text (number is reset)', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'foo' } });
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'number' } });
Expand All @@ -97,17 +97,17 @@ describe('AttributesTypesForm', () => {
});

it('switch to boolean attribute, default to false', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'boolean' } });
expect(result.getByLabelText('Value').value).eq('false');
expect(result.component.validateFields()).true;
});

it('switch to boolean attribute from string equals to "true", true is set', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'true' } });
await fireEvent.change(result.getByLabelText('Type'), { target: { value: 'boolean' } });
Expand All @@ -116,11 +116,11 @@ describe('AttributesTypesForm', () => {
});

it('validate duplicated attribute key', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'foo' } });
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.queryAllByPlaceholderText('Key')[1], {
target: { value: 'my-key' }
});
Expand All @@ -133,32 +133,32 @@ describe('AttributesTypesForm', () => {
});

it('allow same key for attribute and type', async () => {
const result = render(AttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute filter' }));
const result = render(ImageAttributesTypesForm);
await fireEvent.click(result.getByRole('button', { name: 'Add attribute' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.input(result.getByPlaceholderText('Value'), { target: { value: 'foo' } });
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add type' }));
await fireEvent.input(result.queryAllByPlaceholderText('Key')[1], {
target: { value: 'my-key' }
});
expect(result.component.validateFields()).true;
});

it('validate missing type filter key', async () => {
const result = render(AttributesTypesForm);
it('validate missing type key', async () => {
const result = render(ImageAttributesTypesForm);
result.component.init({}, {});
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add type' }));
expect(result.component.validateFields()).false;
await tick();
expect(result.getByText('Key is required')).toBeDefined();
});

it('validate duplicated type filter key', async () => {
const result = render(AttributesTypesForm);
it('validate duplicated type key', async () => {
const result = render(ImageAttributesTypesForm);
result.component.init({}, {});
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add type' }));
await fireEvent.input(result.getByPlaceholderText('Key'), { target: { value: 'my-key' } });
await fireEvent.click(result.getByRole('button', { name: 'Add type filter' }));
await fireEvent.click(result.getByRole('button', { name: 'Add type' }));
await fireEvent.input(result.queryAllByPlaceholderText('Key')[1], {
target: { value: 'my-key' }
});
Expand Down
Loading

0 comments on commit a36c012

Please sign in to comment.