Skip to content

Commit 44e13ea

Browse files
authored
Merge pull request #450 from fractal-analytics-platform/task-v1v2-compatibility
Tasks V1/V2 compatibility
2 parents 5400bf3 + bfcf950 commit 44e13ea

39 files changed

+1395
-799
lines changed

CHANGELOG.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
# Unreleased
44

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;
5+
* Supported fractal-server API V2:
6+
* added menu switch to support legacy and current API (\#434);
7+
* Dataset V2 CRUD with attribute and type filters (\#434);
8+
* new Dataset page with image list and filters (\#434);
9+
* updated Single Task form to handle parallel and non parallel fields (\#434);
10+
* updated workflow task form to handle parallel and non parallel arguments (\#434);
11+
* handled V2 import and export of workflow task arguments (\#434);
12+
* handled V2 version workflow task version update (\#434);
13+
* added admin "Tasks V1/V2 compatibility" page (\#450);
14+
* supported adding V1 task in V2 workflow (\#450);
15+
* removed read only property from V2 datasets (\#450);
1316

1417
# 0.10.2
1518

__tests__/v2/CreateUpdateDatasetModal.test.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ describe('CreateUpdateDatasetModal', () => {
7373
expect.objectContaining({
7474
body: JSON.stringify({
7575
name: 'my dataset',
76-
read_only: false,
7776
zarr_dir: '/tmp',
7877
filters: {
7978
attributes: { 'my-key': 'my-value' },
@@ -105,7 +104,6 @@ describe('CreateUpdateDatasetModal', () => {
105104
expect.objectContaining({
106105
body: JSON.stringify({
107106
name: 'my dataset',
108-
read_only: false,
109107
zarr_dir: '/tmp',
110108
filters: {
111109
attributes: { 'my-key': 123 },
@@ -135,7 +133,6 @@ describe('CreateUpdateDatasetModal', () => {
135133
expect.objectContaining({
136134
body: JSON.stringify({
137135
name: 'my dataset',
138-
read_only: false,
139136
zarr_dir: '/tmp',
140137
filters: {
141138
attributes: {},
@@ -166,7 +163,6 @@ describe('CreateUpdateDatasetModal', () => {
166163
expect.objectContaining({
167164
body: JSON.stringify({
168165
name: 'my dataset',
169-
read_only: false,
170166
zarr_dir: '/tmp',
171167
filters: {
172168
attributes: {},

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.0a3',
50+
command: './tests/start-test-server.sh 2.0.0a6',
5151
port: 8000,
5252
waitForPort: true,
5353
stdout: 'pipe',
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<script>
2+
/**
3+
* @type {any[]}
4+
*/
5+
export let tasks;
6+
7+
/**
8+
* @param {number} index
9+
*/
10+
function isOldVersion(index) {
11+
if (index === 0) {
12+
return false;
13+
}
14+
const currentTask = tasks[index];
15+
const previousTask = tasks[index - 1];
16+
return previousTask.name === currentTask.name && previousTask.owner === currentTask.owner;
17+
}
18+
19+
/**
20+
* @param {number} index
21+
*/
22+
function isLastOldVersion(index) {
23+
if (!isOldVersion(index)) {
24+
return false;
25+
}
26+
if (index === tasks.length - 1) {
27+
return false;
28+
}
29+
const currentTask = tasks[index];
30+
const nextTask = tasks[index + 1];
31+
return nextTask.name !== currentTask.name || nextTask.owner !== currentTask.owner;
32+
}
33+
34+
/**
35+
* @param {number} index
36+
*/
37+
function isMainVersion(index) {
38+
if (isOldVersion(index)) {
39+
return false;
40+
}
41+
if (index === tasks.length - 1) {
42+
return false;
43+
}
44+
const currentTask = tasks[index];
45+
const nextTask = tasks[index + 1];
46+
return nextTask.name === currentTask.name && nextTask.owner === currentTask.owner;
47+
}
48+
49+
/**
50+
* @param {Event} event
51+
*/
52+
function handleToggleOldVersions(event) {
53+
const element = /** @type {HTMLElement} */ (event.target);
54+
/** @type {HTMLElement|null} */
55+
let row = /** @type {HTMLElement} */ (element.closest('tr'));
56+
if (!row.classList.contains('expanded')) {
57+
closeAllOldVersions(/** @type {HTMLElement} */ (row.closest('table')));
58+
}
59+
toggleOldVersions(row);
60+
}
61+
62+
/**
63+
* @param {HTMLElement} table
64+
*/
65+
function closeAllOldVersions(table) {
66+
const rows = table.querySelectorAll('tr');
67+
for (const row of rows) {
68+
if (row.classList.contains('expanded')) {
69+
toggleOldVersions(row);
70+
}
71+
}
72+
}
73+
74+
/**
75+
* @param {HTMLElement} mainRow
76+
*/
77+
function toggleOldVersions(mainRow) {
78+
mainRow.classList.toggle('expanded');
79+
/** @type {HTMLElement|null} */
80+
let row = mainRow;
81+
while ((row = /** @type {HTMLElement|null} */ (row?.nextSibling))) {
82+
if (!row.classList) {
83+
continue;
84+
}
85+
if (row.classList.contains('old-version')) {
86+
row.classList.toggle('collapsed');
87+
} else {
88+
break;
89+
}
90+
}
91+
}
92+
</script>
93+
94+
<table class="table align-middle">
95+
<slot name="thead" />
96+
<tbody>
97+
{#key tasks}
98+
{#each tasks as task, i}
99+
<tr
100+
class:old-version={isOldVersion(i)}
101+
class:last-old-version={isLastOldVersion(i)}
102+
class:is-main-version={isMainVersion(i)}
103+
class:collapsed={isOldVersion(i)}
104+
>
105+
<slot name="custom-columns-left" {task} />
106+
<td>{isOldVersion(i) ? '' : task.name}</td>
107+
<td>
108+
{task.version || ''}
109+
{#if isMainVersion(i)}
110+
<button class="btn btn-link" on:click={handleToggleOldVersions} aria-label="Expand old versions">
111+
<i class="bi bi-plus-circle" />
112+
</button>
113+
{/if}
114+
</td>
115+
<slot name="custom-columns-right" {task} />
116+
</tr>
117+
{/each}
118+
{/key}
119+
</tbody>
120+
</table>
121+
122+
<style>
123+
:global(.is-main-version.expanded td) {
124+
border-bottom-style: dashed;
125+
}
126+
127+
:global(.old-version.collapsed) {
128+
display: none;
129+
}
130+
131+
:global(.old-version) {
132+
display: table-row;
133+
}
134+
135+
:global(.old-version td) {
136+
border-bottom-style: dashed;
137+
}
138+
139+
:global(.last-old-version td) {
140+
border-bottom-style: solid;
141+
}
142+
</style>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<script>
2+
import { formatMarkdown } from '$lib/common/component_utilities';
3+
4+
/** @type {import("$lib/types").Task} */
5+
export let task;
6+
</script>
7+
8+
<ul class="list-group">
9+
<li class="list-group-item list-group-item-light fw-bold">Name</li>
10+
<li class="list-group-item">{task.name}</li>
11+
<li class="list-group-item list-group-item-light fw-bold">Version</li>
12+
<li class="list-group-item">{task.version || ''}</li>
13+
<li class="list-group-item list-group-item-light fw-bold">Docs Link</li>
14+
<li class="list-group-item">
15+
{#if task.docs_link}
16+
<a href={task.docs_link} target="_blank">{task.docs_link}</a>
17+
{:else}
18+
-
19+
{/if}
20+
</li>
21+
<li class="list-group-item list-group-item-light fw-bold">Docs Info</li>
22+
<li class="list-group-item">
23+
{#if task.docs_info}
24+
{@html formatMarkdown(task.docs_info)}
25+
{:else}
26+
-
27+
{/if}
28+
</li>
29+
<li class="list-group-item list-group-item-light fw-bold">Owner</li>
30+
<li class="list-group-item">{task.owner || ''}</li>
31+
<li class="list-group-item list-group-item-light fw-bold">Command</li>
32+
<li class="list-group-item">
33+
<code>{task.command}</code>
34+
</li>
35+
<li class="list-group-item list-group-item-light fw-bold">Source</li>
36+
<li class="list-group-item">
37+
<code>{task.source}</code>
38+
</li>
39+
<li class="list-group-item list-group-item-light fw-bold">Input Type</li>
40+
<li class="list-group-item">
41+
<code>{task.input_type}</code>
42+
</li>
43+
<li class="list-group-item list-group-item-light fw-bold">Output Type</li>
44+
<li class="list-group-item">
45+
<code>{task.output_type}</code>
46+
</li>
47+
<li class="list-group-item list-group-item-light fw-bold">Args Schema Version</li>
48+
<li class="list-group-item">
49+
{task.args_schema_version || ''}
50+
</li>
51+
<li class="list-group-item list-group-item-light fw-bold">Args Schema</li>
52+
<li class="list-group-item">
53+
{#if task.args_schema}
54+
<code>
55+
<pre>{JSON.stringify(task.args_schema, null, 2)}</pre>
56+
</code>
57+
{:else}
58+
-
59+
{/if}
60+
</li>
61+
</ul>

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

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
let selectedUpdateVersion = '';
2121
let originalArgs = '';
2222
let argsToBeFixed = '';
23+
let needChanges = false;
2324
let argsToBeFixedValidJson = true;
2425
/** @type {import('ajv').ErrorObject[] | null} */
2526
let validationErrors = null;
@@ -111,6 +112,7 @@
111112
validationErrors = null;
112113
} else {
113114
argsToBeFixed = JSON.stringify(args, null, 2);
115+
needChanges = true;
114116
validationErrors = validator.getErrors();
115117
}
116118
}
@@ -243,32 +245,34 @@
243245
</ul>
244246
</div>
245247
{/if}
246-
{#if argsToBeFixed}
248+
{#if originalArgs}
247249
{#if !validationErrors}
248250
<div class="alert alert-success mt-3">The arguments are valid</div>
249251
{/if}
250-
<label class="form-label" for="fix-arguments">Fix the arguments:</label>
251-
<textarea
252-
class="form-control"
253-
id="fix-arguments"
254-
class:is-invalid={!argsToBeFixedValidJson}
255-
bind:value={argsToBeFixed}
256-
rows="20"
257-
/>
258-
{#if !argsToBeFixedValidJson}
259-
<div class="invalid-feedback">Invalid JSON</div>
252+
{#if needChanges}
253+
<label class="form-label" for="fix-arguments">Fix the arguments:</label>
254+
<textarea
255+
class="form-control"
256+
id="fix-arguments"
257+
class:is-invalid={!argsToBeFixedValidJson}
258+
bind:value={argsToBeFixed}
259+
rows="20"
260+
/>
261+
{#if !argsToBeFixedValidJson}
262+
<div class="invalid-feedback">Invalid JSON</div>
263+
{/if}
264+
<button type="button" class="btn btn-warning mt-3" on:click={check}> Check </button>
265+
&nbsp;
266+
<button
267+
type="button"
268+
class="btn btn-secondary mt-3"
269+
on:click={cancel}
270+
disabled={argsToBeFixed === originalArgs}
271+
>
272+
Cancel
273+
</button>
274+
&nbsp;
260275
{/if}
261-
<button type="button" class="btn btn-warning mt-3" on:click={check}> Check </button>
262-
&nbsp;
263-
<button
264-
type="button"
265-
class="btn btn-secondary mt-3"
266-
on:click={cancel}
267-
disabled={argsToBeFixed === originalArgs}
268-
>
269-
Cancel
270-
</button>
271-
&nbsp;
272276
{/if}
273277
<button type="button" class="btn btn-primary mt-3" on:click={update} disabled={!canBeUpdated}>
274278
Update

src/lib/components/v1/workflow/version-checker.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@ import { AlertError } from '$lib/common/errors';
33

44
/**
55
* @param {import('$lib/types').Task} task
6+
* @param {boolean|false=} v2
67
* @returns {Promise<import('$lib/types').Task[]>} the list of update candidates for the given task
78
*/
8-
export async function getNewVersions(task) {
9-
const updateCandidates = await getAllNewVersions([task]);
9+
export async function getNewVersions(task, v2) {
10+
const updateCandidates = await getAllNewVersions([task], v2);
1011
return updateCandidates[task.id];
1112
}
1213

1314
/**
1415
* @param {import('$lib/types').Task[]} tasks
16+
* @param {boolean|false=} v2
1517
* @returns {Promise<{ [id: string]: import('$lib/types').Task[] }>} the list of update candidates, for each task received as input
1618
*/
17-
export async function getAllNewVersions(tasks) {
18-
console.log('Checking for new versions');
19-
const response = await fetch(`/api/v1/task`);
19+
export async function getAllNewVersions(tasks, v2 = false) {
20+
const response = await fetch(v2 ? `/api/v2/task-legacy` : `/api/v1/task`);
2021

2122
if (!response.ok) {
2223
throw new AlertError(await response.json());

0 commit comments

Comments
 (0)