Skip to content

Commit 5d78699

Browse files
authored
Merge pull request #379 from fractal-analytics-platform/performance-improvements
Improved performance and some minor issues
2 parents 8daa6c6 + 4a220b1 commit 5d78699

Some content is hidden

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

43 files changed

+728
-382
lines changed

CHANGELOG.md

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

3+
# Unreleased
4+
5+
* Added search project field on projects list page (\#379).
6+
* Used modal to create new project (\#379).
7+
* Sorted users in admin-area page (\#379).
8+
* Exposed stop-job and download-logs in admin-area jobs page (\#379).
9+
* Displayed number of rows in admin jobs page (\#379).
10+
* Fixed semver circular dependency issue (\#379).
11+
* Fixed issue with version sorting (\#379).
12+
* Sorted datasets by name (\#379).
13+
* Fixed accessibility issue (\#379).
14+
* Improved performance reducing the number of API calls on project, workflow and dataset pages (\#379).
15+
316
# 0.7.2
417

518
Note: with this release, `PUBLIC_OAUTH_CLIENT_NAME` becomes a required

__tests__/semver.test.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { it, expect } from 'vitest';
2-
import semver from 'semver';
2+
import valid from 'semver/functions/valid';
33
import { greatestVersionAsc } from '../src/lib/common/component_utilities';
44

55
it('should be able to loosely validate versions', () => {
@@ -20,14 +20,14 @@ it('should be able to loosely validate versions', () => {
2020
};
2121

2222
cases.forEach(([input, expected]) => {
23-
const validatedVersion = semver.valid(input, validationOptions);
23+
const validatedVersion = valid(input, validationOptions);
2424
expect(validatedVersion).toBe(expected);
2525
});
2626
});
2727

2828
it('should sort versions correctly', () => {
29-
3029
const versions = [
30+
{ version: '2' },
3131
{ version: '0.10.0c0' },
3232
{ version: '0.10.0b4' },
3333
{ version: '0.10.0' },
@@ -37,12 +37,18 @@ it('should sort versions correctly', () => {
3737
{ version: '0.10.0a0' },
3838
{ version: '1.0.0rc4.dev7' },
3939
{ version: '0.10.0beta5' },
40-
{ version: '0.10.0alpha0' }
40+
{ version: '0.10.0alpha0' },
41+
{ version: '0.1.2' },
42+
{ version: '0.1.dev27+g1458b59' },
43+
{ version: '0.2.0a0' }
4144
];
4245

43-
const sortedVersions = versions.sort(greatestVersionAsc);
46+
const sortedVersions = [...versions].sort(greatestVersionAsc);
4447

45-
expect(sortedVersions).toEqual([
48+
const expectedSortedVersions = [
49+
{ version: '0.1.dev27+g1458b59' },
50+
{ version: '0.1.2' },
51+
{ version: '0.2.0a0' },
4652
{ version: '0.10.0a0' },
4753
{ version: '0.10.0a2' },
4854
{ version: '0.10.0alpha0' },
@@ -52,6 +58,19 @@ it('should sort versions correctly', () => {
5258
{ version: '0.10.0c0' },
5359
{ version: '0.10.0' },
5460
{ version: '1.0.0rc4.dev7' },
55-
{ version: '1.0.0' }
56-
]);
57-
});
61+
{ version: '1.0.0' },
62+
{ version: '2' }
63+
];
64+
65+
expect(sortedVersions).toEqual(expectedSortedVersions);
66+
67+
// Test that the sorting works also with 'v' prefix
68+
const v_versions = versions.map((v) => ({ version: `v${v.version}` }));
69+
const v_expectedSortedVersions = expectedSortedVersions.map((v) => ({
70+
version: `v${v.version}`
71+
}));
72+
73+
const v_sortedVersions = [...v_versions].sort(greatestVersionAsc);
74+
75+
expect(v_sortedVersions).toEqual(v_expectedSortedVersions);
76+
});

__tests__/user_utilities.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { it, expect } from 'vitest';
2+
import { sortUsers } from '$lib/components/admin/user_utilities';
3+
4+
it('should sort user by current superuser, then by superuser, then by email', () => {
5+
let users = [
6+
{ id: 5, is_superuser: false, email: '[email protected]' },
7+
{ id: 4, is_superuser: false, email: '[email protected]' },
8+
{ id: 3, is_superuser: true, email: '[email protected]' },
9+
{ id: 2, is_superuser: true, email: '[email protected]' },
10+
{ id: 1, is_superuser: true, email: '[email protected]' }
11+
];
12+
13+
sortUsers(users, 1);
14+
15+
expect(users[0].id).eq(1);
16+
expect(users[1].id).eq(2);
17+
expect(users[2].id).eq(3);
18+
expect(users[3].id).eq(4);
19+
expect(users[4].id).eq(5);
20+
21+
users = [
22+
{ id: 1, is_superuser: true, email: '[email protected]' },
23+
{ id: 2, is_superuser: true, email: '[email protected]' },
24+
{ id: 3, is_superuser: false, email: '[email protected]' }
25+
];
26+
27+
sortUsers(users, 2);
28+
29+
expect(users[0].id).eq(2);
30+
expect(users[1].id).eq(1);
31+
expect(users[2].id).eq(3);
32+
});

src/lib/common/component_utilities.js

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import { marked } from 'marked';
22
import DOMPurify from 'dompurify';
3-
import semver from 'semver';
3+
import coerce from 'semver/functions/coerce';
4+
import gte from 'semver/functions/gte';
5+
import lte from 'semver/functions/lte';
6+
import valid from 'semver/functions/valid';
47

58
/**
69
* @param {import('$lib/types').Task} t1
710
* @param {import('$lib/types').Task} t2
811
* @returns {-1|0|1}
912
*/
1013
export function greatestVersionAsc(t1, t2) {
11-
const semverValidationOptions = {
12-
loose: true,
13-
includePrerelease: true
14-
};
15-
const t1Version = semver.valid(t1.version, semverValidationOptions);
16-
const t2Version = semver.valid(t2.version, semverValidationOptions);
14+
const t1Version = validateVersion(t1.version);
15+
const t2Version = validateVersion(t2.version);
1716
if (t1Version !== null && t2Version !== null) {
18-
const t1VersionLt = semver.lte(t1Version, t2Version);
17+
const t1VersionLt = lte(t1Version, t2Version);
1918
return t1VersionLt ? -1 : 1;
2019
}
2120
return 0;
@@ -27,19 +26,32 @@ export function greatestVersionAsc(t1, t2) {
2726
* @returns {-1|0|1}
2827
*/
2928
export function greatestVersionDesc(t1, t2) {
30-
const semverValidationOptions = {
31-
loose: true,
32-
includePrerelease: true
33-
};
34-
const t1Version = semver.valid(t1.version, semverValidationOptions);
35-
const t2Version = semver.valid(t2.version, semverValidationOptions);
29+
const t1Version = validateVersion(t1.version);
30+
const t2Version = validateVersion(t2.version);
3631
if (t1Version !== null && t2Version !== null) {
37-
const t1VersionGt = semver.gte(t1Version, t2Version);
32+
const t1VersionGt = gte(t1Version, t2Version);
3833
return t1VersionGt ? -1 : 1;
3934
}
4035
return 0;
4136
}
4237

38+
const semverValidationOptions = {
39+
loose: true,
40+
includePrerelease: true
41+
};
42+
43+
/**
44+
* @param {string} version
45+
* @returns {string | null}
46+
*/
47+
function validateVersion(version) {
48+
return (
49+
valid(version, semverValidationOptions) ||
50+
valid(coerce(version), semverValidationOptions) ||
51+
null
52+
);
53+
}
54+
4355
/**
4456
* @param {import('$lib/types').Task} t1
4557
* @param {import('$lib/types').Task} t2
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @param {Array<import('$lib/types').User & {id: number}>} users
3+
* @param {number} currentAdminId
4+
*/
5+
export function sortUsers(users, currentAdminId) {
6+
users.sort((a, b) => {
7+
// current admin is always first
8+
if (a.id === currentAdminId) {
9+
return -1;
10+
}
11+
if (b.id === currentAdminId) {
12+
return 1;
13+
}
14+
// prioritize superusers
15+
if (a.is_superuser && !b.is_superuser) {
16+
return -1;
17+
}
18+
if (!a.is_superuser && b.is_superuser) {
19+
return 1;
20+
}
21+
// then sort by email
22+
return a.email < b.email ? -1 : 1;
23+
});
24+
return users;
25+
}

src/lib/components/common/jschema/PropertyDiscriminator.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import ArrayProperty from '$lib/components/common/jschema/ArrayProperty.svelte';
66
import ObjectProperty from '$lib/components/common/jschema/ObjectProperty.svelte';
77
8+
/** @type {any|undefined} */
89
export let schemaProperty = undefined;
910
10-
const key = schemaProperty.key;
11+
const key = schemaProperty?.key;
1112
1213
</script>
1314
@@ -40,4 +41,4 @@
4041
<p>Unable to display schema property | unknown property type</p>
4142
</div>
4243
43-
{/if}
44+
{/if}

src/lib/components/common/jschema/schema_management.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ export default class SchemaManager {
22
keySeparator = '###';
33
propertiesMap = new Map();
44
hasUnsavedChanges = false;
5-
onPropertyChanges = () => {
6-
};
5+
/** @type {(hasChanges: boolean) => void} */
6+
onPropertyChanges = () => {};
77

88
constructor(schema, schemaData) {
99
this.loadSchema(schema);

src/lib/components/jobs/JobsList.svelte

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@
9292
}
9393
9494
console.log('Stop running job');
95-
const response = await fetch(`/api/v1/project/${job.project_id}/job/${job.id}/stop`, {
95+
let stopJobUrl = admin
96+
? `/api/admin/job/${job.id}/stop`
97+
: `/api/v1/project/${job.project_id}/job/${job.id}/stop`;
98+
const response = await fetch(stopJobUrl, {
9699
method: 'GET',
97100
credentials: 'include'
98101
});
@@ -133,6 +136,17 @@
133136
updateJobsTimeout = setTimeout(updateJobsInBackground, updateJobsInterval);
134137
}
135138
139+
/**
140+
* @param {import('$lib/types').ApplyWorkflow} row
141+
*/
142+
function getDownloadUrl(row) {
143+
if (admin) {
144+
return `/api/admin/job/${row.id}/download`;
145+
} else {
146+
return `/api/v1/project/${row.project_id}/job/${row.id}/download`;
147+
}
148+
}
149+
136150
onMount(() => {
137151
updateJobsTimeout = setTimeout(updateJobsInBackground, updateJobsInterval);
138152
});
@@ -292,17 +306,17 @@
292306
<i class="bi-list-columns-reverse" />
293307
Logs
294308
</button>
295-
{#if row.project_id !== null && row.user_email === $page.data.userInfo.email}
309+
{#if (admin && row.id) || (row.project_id !== null && row.user_email === $page.data.userInfo.email)}
296310
<a
297311
class="btn btn-light"
298-
href={`/api/v1/project/${row.project_id}/job/${row.id}/download`}
312+
href={getDownloadUrl(row)}
299313
download={`${row.id}_logs.zip`}
300314
>
301315
<i class="bi-arrow-down-circle" />
302316
</a>
303317
{/if}
304318
{/if}
305-
{#if row.status === 'running' && !admin}
319+
{#if row.status === 'running'}
306320
<button class="btn btn-danger" on:click={() => handleJobCancel(row)}>
307321
<i class="bi-x-circle" /> Cancel
308322
</button>

src/lib/components/projects/CreateUpdateDatasetModal.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import { onMount, tick } from 'svelte';
55
import Modal from '../common/Modal.svelte';
66
7-
/** @type {(dataset: (import('$lib/types').Dataset)) => void} */
7+
/** @type {(dataset: import('$lib/types').Dataset) => void} */
88
export let createDatasetCallback;
9-
/** @type {(dataset: (import('$lib/types').Dataset)) => void} */
9+
/** @type {(dataset: import('$lib/types').Dataset) => void} */
1010
export let updateDatasetCallback;
1111
1212
/** @type {Modal} */
@@ -177,6 +177,9 @@
177177
return result;
178178
}
179179
180+
/**
181+
* @returns {Promise<import('$lib/types').Dataset>}
182+
*/
180183
async function callUpdateDataset() {
181184
const projectId = $page.params.projectId;
182185
const headers = new Headers();
@@ -376,6 +379,7 @@
376379
});
377380
}),
378381
project_id: originalDataset.project_id,
382+
project: originalDataset.project,
379383
history: []
380384
};
381385
}

0 commit comments

Comments
 (0)