Skip to content

Commit 5400bf3

Browse files
authored
Merge pull request #434 from fractal-analytics-platform/api-v2
API V2 Support
2 parents 9addbfc + 5566483 commit 5400bf3

File tree

179 files changed

+12183
-463
lines changed

Some content is hidden

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

179 files changed

+12183
-463
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
*Note: Numbers like (\#123) point to closed Pull Requests on the fractal-web repository.*
22

3+
# Unreleased
4+
5+
* Supported fractal-server API V2 (\#434):
6+
* added menu switch to support legacy and current API;
7+
* Dataset V2 CRUD with attribute and type filters;
8+
* new Dataset page with image list and filters;
9+
* updated Single Task form to handle parallel and non parallel fields;
10+
* updated workflow task form to handle parallel and non parallel arguments;
11+
* handled V2 import and export of workflow task arguments;
12+
* handled V2 version workflow task version update;
13+
314
# 0.10.2
415

516
* Added search functionality on jobs table filters (\#424).

__tests__/Paginator.test.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { fireEvent, render } from '@testing-library/svelte';
3+
4+
import Paginator from '../src/lib/components/common/Paginator.svelte';
5+
6+
describe('Paginator', () => {
7+
it('display without ellipsis', () => {
8+
const result = render(Paginator, {
9+
props: { pageSize: 10, totalCount: 70, currentPage: 4, onPageChange: () => {} }
10+
});
11+
expect(getPageItems(result)).toEqual(['«', '1', '2', '3', '4', '5', '6', '7', '»']);
12+
});
13+
it('display ellipsis at beginning', () => {
14+
const result = render(Paginator, {
15+
props: { pageSize: 10, totalCount: 1000, currentPage: 99, onPageChange: () => {} }
16+
});
17+
expect(getPageItems(result)).toEqual(['«', '1', '...', '96', '97', '98', '99', '100', '»']);
18+
});
19+
it('display ellipsis at end', () => {
20+
const result = render(Paginator, {
21+
props: { pageSize: 10, totalCount: 1000, currentPage: 2, onPageChange: () => {} }
22+
});
23+
expect(getPageItems(result)).toEqual(['«', '1', '2', '3', '4', '5', '...', '100', '»']);
24+
});
25+
it('display ellipsis both at beginning and end', () => {
26+
const result = render(Paginator, {
27+
props: { pageSize: 10, totalCount: 1000, currentPage: 50, onPageChange: () => {} }
28+
});
29+
expect(getPageItems(result)).toEqual([
30+
'«',
31+
'1',
32+
'...',
33+
'47',
34+
'48',
35+
'49',
36+
'50',
37+
'51',
38+
'52',
39+
'53',
40+
'...',
41+
'100',
42+
'»'
43+
]);
44+
});
45+
it('omit ellipsis at end bewteen consecutive numbers', () => {
46+
const result = render(Paginator, {
47+
props: { pageSize: 10, totalCount: 70, currentPage: 3, onPageChange: () => {} }
48+
});
49+
expect(getPageItems(result)).toEqual(['«', '1', '2', '3', '4', '5', '6', '7', '»']);
50+
});
51+
it('omit ellipsis at begninning bewteen consecutive numbers', () => {
52+
const result = render(Paginator, {
53+
props: { pageSize: 10, totalCount: 70, currentPage: 5, onPageChange: () => {} }
54+
});
55+
expect(getPageItems(result)).toEqual(['«', '1', '2', '3', '4', '5', '6', '7', '»']);
56+
});
57+
it('go to previous page', async () => {
58+
const onPageChange = vi.fn();
59+
const result = render(Paginator, {
60+
props: { pageSize: 10, totalCount: 100, currentPage: 3, onPageChange }
61+
});
62+
await fireEvent.click(result.getByLabelText('Previous'));
63+
expect(onPageChange).toHaveBeenCalledWith(2, 10);
64+
});
65+
it('go to next page', async () => {
66+
const onPageChange = vi.fn();
67+
const result = render(Paginator, {
68+
props: { pageSize: 10, totalCount: 100, currentPage: 3, onPageChange }
69+
});
70+
await fireEvent.click(result.getByLabelText('Next'));
71+
expect(onPageChange).toHaveBeenCalledWith(4, 10);
72+
});
73+
it('change page size', async () => {
74+
const onPageChange = vi.fn();
75+
const result = render(Paginator, {
76+
props: { pageSize: 10, totalCount: 100, currentPage: 3, onPageChange }
77+
});
78+
await fireEvent.change(result.getByRole('combobox'), { target: { value: '50' } });
79+
await fireEvent.click(result.getByLabelText('Next'));
80+
expect(onPageChange).toHaveBeenCalledWith(1, 50);
81+
});
82+
it('hide pages if total results is zero', async () => {
83+
const result = render(Paginator, {
84+
props: { pageSize: 10, totalCount: 0, currentPage: 3, onPageChange: () => {} }
85+
});
86+
expect(result.queryByLabelText('Previous')).toBeNull();
87+
});
88+
it('previous is disabled on first page', async () => {
89+
const result = render(Paginator, {
90+
props: { pageSize: 10, totalCount: 3, currentPage: 1, onPageChange: () => {} }
91+
});
92+
expect(getPageItems(result)).toEqual(['«', '1', '»']);
93+
expect(result.queryByLabelText('Previous').disabled).true;
94+
});
95+
it('next is disabled on last page', async () => {
96+
const result = render(Paginator, {
97+
props: { pageSize: 10, totalCount: 3, currentPage: 1, onPageChange: () => {} }
98+
});
99+
expect(getPageItems(result)).toEqual(['«', '1', '»']);
100+
expect(result.queryByLabelText('Next').disabled).true;
101+
});
102+
});
103+
104+
/**
105+
*
106+
* @param {import('@testing-library/svelte').RenderResult} result
107+
*/
108+
function getPageItems(result) {
109+
const itemElements = result.getByRole('list').querySelectorAll('.page-item');
110+
const items = [];
111+
for (const element of itemElements) {
112+
if (element instanceof HTMLElement && element.textContent) {
113+
items.push(element.textContent.trim());
114+
}
115+
}
116+
return items;
117+
}

__tests__/TimestampCell.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ describe('TimestampCell', () => {
2020

2121
it('handles valid timestamp', async () => {
2222
const result = render(TimestampCell, {
23-
props: { timestamp: '2024-02-09T10:35:56.237579+00:00' }
23+
props: { timestamp: '2024-12-19T10:35:56.237579+00:00' }
2424
});
25-
expect(result.container.textContent).eq('9/2/2024 11:35:56');
25+
expect(result.container.textContent).eq('19/12/2024 11:35:56');
2626
});
2727
});

__tests__/component_utilities.test.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ it('should order tasks by owner, then by name, then by version', () => {
99
const tasks = [
1010
{ name: 'task1', owner: 'owner1', version: '0.0.1' },
1111
{ name: 'task2', owner: 'owner1', version: '0.0.1' },
12-
{ name: 'task3', owner: 'owner1', version: '0.0.2' },
13-
{ name: 'task4', owner: 'owner2', version: '0.0.1' },
14-
{ name: 'task5', owner: 'owner2', version: '0.0.2' },
15-
{ name: 'task6', owner: 'owner2', version: '0.0.2' },
12+
{ name: 'TASK3', owner: 'owner1', version: '0.0.2' },
13+
{ name: 'task4', owner: 'OWNER2', version: '0.0.1' },
14+
{ name: 'task5', owner: 'OWNER2', version: '0.0.2' },
15+
{ name: 'TASK6', owner: 'OWNER2', version: '0.0.2' },
1616
{ name: 'task7', owner: 'admin', version: '0.0.1' },
1717
{ name: 'task8', owner: 'owner3', version: '0.0.2' },
18-
{ name: 'task9', owner: 'owner3', version: '0.0.2' }
18+
{ name: 'TASK9', owner: 'owner3', version: '0.0.2' }
1919
];
2020

2121
const sortedTasks = orderTasksByOwnerThenByNameThenByVersion(tasks);
@@ -24,26 +24,26 @@ it('should order tasks by owner, then by name, then by version', () => {
2424
{ name: 'task7', owner: 'admin', version: '0.0.1' },
2525
{ name: 'task1', owner: 'owner1', version: '0.0.1' },
2626
{ name: 'task2', owner: 'owner1', version: '0.0.1' },
27-
{ name: 'task3', owner: 'owner1', version: '0.0.2' },
28-
{ name: 'task4', owner: 'owner2', version: '0.0.1' },
29-
{ name: 'task5', owner: 'owner2', version: '0.0.2' },
30-
{ name: 'task6', owner: 'owner2', version: '0.0.2' },
27+
{ name: 'TASK3', owner: 'owner1', version: '0.0.2' },
28+
{ name: 'task4', owner: 'OWNER2', version: '0.0.1' },
29+
{ name: 'task5', owner: 'OWNER2', version: '0.0.2' },
30+
{ name: 'TASK6', owner: 'OWNER2', version: '0.0.2' },
3131
{ name: 'task8', owner: 'owner3', version: '0.0.2' },
32-
{ name: 'task9', owner: 'owner3', version: '0.0.2' }
32+
{ name: 'TASK9', owner: 'owner3', version: '0.0.2' }
3333
]);
3434

35-
const sortedTasks2 = orderTasksByOwnerThenByNameThenByVersion(tasks, 'owner2');
35+
const sortedTasks2 = orderTasksByOwnerThenByNameThenByVersion(tasks, 'OWNER2');
3636

3737
expect(sortedTasks2).toEqual([
38-
{ name: 'task4', owner: 'owner2', version: '0.0.1' },
39-
{ name: 'task5', owner: 'owner2', version: '0.0.2' },
40-
{ name: 'task6', owner: 'owner2', version: '0.0.2' },
38+
{ name: 'task4', owner: 'OWNER2', version: '0.0.1' },
39+
{ name: 'task5', owner: 'OWNER2', version: '0.0.2' },
40+
{ name: 'TASK6', owner: 'OWNER2', version: '0.0.2' },
4141
{ name: 'task7', owner: 'admin', version: '0.0.1' },
4242
{ name: 'task1', owner: 'owner1', version: '0.0.1' },
4343
{ name: 'task2', owner: 'owner1', version: '0.0.1' },
44-
{ name: 'task3', owner: 'owner1', version: '0.0.2' },
44+
{ name: 'TASK3', owner: 'owner1', version: '0.0.2' },
4545
{ name: 'task8', owner: 'owner3', version: '0.0.2' },
46-
{ name: 'task9', owner: 'owner3', version: '0.0.2' }
46+
{ name: 'TASK9', owner: 'owner3', version: '0.0.2' }
4747
]);
4848
});
4949

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { it, expect, vi, beforeEach } from 'vitest';
2+
import { reloadVersionedPage } from '../src/lib/common/selected_api_version';
3+
4+
vi.mock('$app/navigation', () => {
5+
return { goto: vi.fn() };
6+
});
7+
8+
import { goto } from '$app/navigation';
9+
10+
beforeEach(() => {
11+
vi.resetAllMocks();
12+
});
13+
14+
it('should reload versioned page (v1 -> v2)', async () => {
15+
await reloadVersionedPage('/v1/projects', 'v2');
16+
expect(goto).toHaveBeenCalledWith('/v2/projects');
17+
});
18+
19+
it('should reload versioned page (v2 -> v1)', async () => {
20+
await reloadVersionedPage('/v2/projects', 'v1');
21+
expect(goto).toHaveBeenCalledWith('/v1/projects');
22+
});
23+
24+
it('should ignore page without version', async () => {
25+
await reloadVersionedPage('/admin', 'v2');
26+
expect(goto).not.toHaveBeenCalled();
27+
});

__tests__/JSchema.test.js renamed to __tests__/v1/JSchema.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it, expect } from 'vitest';
22
import { fireEvent, render, screen } from '@testing-library/svelte';
33

4-
import JSchema from '../src/lib/components/common/jschema/JSchema.svelte';
4+
import JSchema from '../../src/lib/components/v1/workflow/JSchema.svelte';
55

66
describe('JSchema', () => {
77
it('Required NumberProperty with title', async () => {

__tests__/JobLogsModal.test.js renamed to __tests__/v1/JobLogsModal.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ global.window.bootstrap = {
1919
// Mocking fetch
2020
global.fetch = vi.fn();
2121

22-
import JobLogsModal from '../src/lib/components/jobs/JobLogsModal.svelte';
22+
import JobLogsModal from '../../src/lib/components/v1/jobs/JobLogsModal.svelte';
2323

2424
describe('JobLogsModal', async () => {
2525
it('display error log fully highlighted', async () => {

__tests__/JobsList.test.js renamed to __tests__/v1/JobsList.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { fireEvent, render } from '@testing-library/svelte';
33
import userEvent from '@testing-library/user-event';
44
import { within, waitFor } from '@testing-library/dom'
55
import { readable } from 'svelte/store';
6-
import { data } from './mock/jobs-list';
6+
import { data } from '../mock/jobs-list';
77

88
// Mocking the page store
99
vi.mock('$app/stores', () => {
@@ -28,7 +28,7 @@ global.window.location = {
2828
};
2929

3030
// The component to be tested must be imported after the mock setup
31-
import JobsList from '../src/lib/components/jobs/JobsList.svelte';
31+
import JobsList from '../../src/lib/components/v1/jobs/JobsList.svelte';
3232

3333
describe('JobsList', () => {
3434
it('display, filter and sort jobs', async () => {

__tests__/TaskCollection.test.js renamed to __tests__/v1/TaskCollection.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ vi.mock('$env/dynamic/public', () => {
1717
});
1818

1919
// The component to be tested must be imported after the mock setup
20-
import TaskCollection from '../src/lib/components/tasks/TaskCollection.svelte';
20+
import TaskCollection from '../../src/lib/components/v1/tasks/TaskCollection.svelte';
2121

2222
describe('TaskCollection', () => {
2323
it('collect tasks with pinned package versions', async () => {

__tests__/VersionUpdate.test.js renamed to __tests__/v1/VersionUpdate.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function createFetchResponse(data) {
2424
}
2525

2626
// The component to be tested must be imported after the mock setup
27-
import VersionUpdate from '../src/lib/components/workflow/VersionUpdate.svelte';
27+
import VersionUpdate from '../../src/lib/components/v1/workflow/VersionUpdate.svelte';
2828

2929
const task3ArgsSchema = {
3030
title: 'MyTask',

0 commit comments

Comments
 (0)