Skip to content

Commit 289646d

Browse files
authored
Merge pull request #564 from fractal-analytics-platform/user-settings
User settings
2 parents eeed425 + f0654ca commit 289646d

35 files changed

+1117
-593
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ LOG_LEVEL_FILE=info
1919
LOG_LEVEL_CONSOLE=warn
2020

2121
FRACTAL_API_V1_MODE=include
22+
FRACTAL_RUNNER_BACKEND=local
2223
#WARNING_BANNER_PATH=/path/to/banner.txt

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ LOG_LEVEL_FILE=debug
1818
LOG_LEVEL_CONSOLE=info
1919

2020
FRACTAL_API_V1_MODE=include
21+
FRACTAL_RUNNER_BACKEND=local
2122
#WARNING_BANNER_PATH=/path/to/banner.txt

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Unreleased
44

5+
* Added "My Settings" page and updated admin user editor (\#564);
56
* Fixed duplicated entries in owner dropdown (\#562);
67
* Added "View plate" button on dataset page (\#562);
78
* Improved stability of end to end tests (\#560);

__tests__/v2/RunWorkflowModal.test.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import { describe, it, expect, vi } from 'vitest';
22
import { fireEvent, render } from '@testing-library/svelte';
3-
import { readable } from 'svelte/store';
43

54
// Mocking fetch
65
global.fetch = vi.fn();
76

8-
// Mocking the page store
9-
vi.mock('$app/stores', () => {
10-
return {
11-
page: readable({
12-
data: {
13-
userInfo: { slurm_accounts: [] }
14-
}
15-
})
16-
};
7+
fetch.mockResolvedValue({
8+
ok: true,
9+
status: 200,
10+
json: () => new Promise((resolve) => resolve({ slurm_accounts: [] }))
1711
});
1812

1913
// Mocking bootstrap.Modal

__tests__/v2/UserEditor.test.js

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { describe, it, beforeEach, expect, vi } from 'vitest';
2+
import { render, screen } from '@testing-library/svelte';
3+
import userEvent from '@testing-library/user-event';
4+
import { readable } from 'svelte/store';
5+
6+
// Mocking fetch
7+
global.fetch = vi.fn();
8+
9+
// Mocking the page store
10+
vi.mock('$app/stores', () => {
11+
return {
12+
page: readable({
13+
data: {
14+
userInfo: {
15+
id: 2
16+
}
17+
}
18+
})
19+
};
20+
});
21+
22+
// The component to be tested must be imported after the mock setup
23+
import UserEditor from '../../src/lib/components/v2/admin/UserEditor.svelte';
24+
25+
describe('UserEditor', () => {
26+
beforeEach(() => {
27+
fetch.mockClear();
28+
});
29+
30+
const selectedUser = {
31+
id: 1,
32+
group_ids: []
33+
};
34+
35+
const initialSettings = {
36+
id: 1,
37+
slurm_accounts: [],
38+
slurm_user: null,
39+
cache_dir: null,
40+
ssh_host: null,
41+
ssh_username: null,
42+
ssh_private_key_path: null,
43+
ssh_tasks_dir: null,
44+
ssh_jobs_dir: null
45+
};
46+
47+
it('Update settings with slurm runner backend - success', async () => {
48+
const user = userEvent.setup();
49+
50+
render(UserEditor, {
51+
props: {
52+
runnerBackend: 'slurm',
53+
user: selectedUser,
54+
settings: { ...initialSettings },
55+
save: () => {}
56+
}
57+
});
58+
59+
const mockRequest = fetch.mockResolvedValue({
60+
ok: true,
61+
status: 200,
62+
json: () =>
63+
new Promise((resolve) =>
64+
resolve({
65+
...initialSettings,
66+
slurm_user: 'user',
67+
cache_dir: '/path/to/cache/dir'
68+
})
69+
)
70+
});
71+
72+
await user.type(screen.getByRole('textbox', { name: 'SLURM user' }), 'user');
73+
await user.type(screen.getByRole('textbox', { name: 'Cache dir' }), '/path/to/cache/dir');
74+
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
75+
await screen.findByText('Settings successfully updated');
76+
77+
expect(mockRequest).toHaveBeenCalledWith(
78+
expect.anything(),
79+
expect.objectContaining({
80+
body: JSON.stringify({
81+
slurm_user: 'user',
82+
cache_dir: '/path/to/cache/dir',
83+
slurm_accounts: []
84+
})
85+
})
86+
);
87+
});
88+
89+
it('Update settings with slurm runner backend - validation error', async () => {
90+
const user = userEvent.setup();
91+
92+
render(UserEditor, {
93+
props: {
94+
runnerBackend: 'slurm',
95+
user: selectedUser,
96+
settings: { ...initialSettings },
97+
save: () => {}
98+
}
99+
});
100+
101+
const mockRequest = fetch.mockResolvedValue({
102+
ok: false,
103+
status: 422,
104+
json: () =>
105+
new Promise((resolve) =>
106+
resolve({
107+
detail: [
108+
{
109+
loc: ['body', 'cache_dir'],
110+
msg: 'mocked_error',
111+
type: 'value_error'
112+
}
113+
]
114+
})
115+
)
116+
});
117+
118+
await user.type(screen.getByRole('textbox', { name: 'Cache dir' }), 'xxx');
119+
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
120+
await screen.findByText('mocked_error');
121+
122+
expect(mockRequest).toHaveBeenCalledWith(
123+
expect.anything(),
124+
expect.objectContaining({
125+
body: JSON.stringify({
126+
cache_dir: 'xxx',
127+
slurm_accounts: []
128+
})
129+
})
130+
);
131+
});
132+
133+
it('Update settings with slurm_ssh runner backend - success', async () => {
134+
const user = userEvent.setup();
135+
136+
render(UserEditor, {
137+
props: {
138+
runnerBackend: 'slurm_ssh',
139+
user: selectedUser,
140+
settings: { ...initialSettings },
141+
save: () => {}
142+
}
143+
});
144+
145+
const mockRequest = fetch.mockResolvedValue({
146+
ok: true,
147+
status: 200,
148+
json: () =>
149+
new Promise((resolve) =>
150+
resolve({
151+
...initialSettings,
152+
ssh_host: 'localhost',
153+
ssh_username: 'username',
154+
ssh_private_key_path: '/path/to/private/key',
155+
ssh_tasks_dir: '/path/to/tasks/dir',
156+
ssh_jobs_dir: '/path/to/jobs/dir'
157+
})
158+
)
159+
});
160+
161+
await user.type(screen.getByRole('textbox', { name: 'SSH host' }), 'localhost');
162+
await user.type(screen.getByRole('textbox', { name: 'SSH username' }), 'username');
163+
await user.type(screen.getByRole('textbox', { name: 'SSH Private Key Path' }), 'xxx');
164+
await user.type(screen.getByRole('textbox', { name: 'SSH Tasks Dir' }), 'yyy');
165+
await user.type(screen.getByRole('textbox', { name: 'SSH Jobs Dir' }), 'zzz');
166+
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
167+
await screen.findByText('Settings successfully updated');
168+
169+
expect(mockRequest).toHaveBeenCalledWith(
170+
expect.anything(),
171+
expect.objectContaining({
172+
body: JSON.stringify({
173+
ssh_host: 'localhost',
174+
ssh_username: 'username',
175+
ssh_private_key_path: 'xxx',
176+
ssh_tasks_dir: 'yyy',
177+
ssh_jobs_dir: 'zzz',
178+
slurm_accounts: []
179+
})
180+
})
181+
);
182+
});
183+
184+
it('Update settings with slurm_ssh runner backend - validation error', async () => {
185+
const user = userEvent.setup();
186+
187+
render(UserEditor, {
188+
props: {
189+
runnerBackend: 'slurm_ssh',
190+
user: selectedUser,
191+
settings: { ...initialSettings },
192+
save: () => {}
193+
}
194+
});
195+
196+
const mockRequest = fetch.mockResolvedValue({
197+
ok: false,
198+
status: 422,
199+
json: () =>
200+
new Promise((resolve) =>
201+
resolve({
202+
detail: [
203+
{
204+
loc: ['body', 'ssh_private_key_path'],
205+
msg: 'mock_error_ssh_private_key_path',
206+
type: 'value_error'
207+
},
208+
{
209+
loc: ['body', 'ssh_tasks_dir'],
210+
msg: 'mock_error_ssh_tasks_dir',
211+
type: 'value_error'
212+
},
213+
{
214+
loc: ['body', 'ssh_jobs_dir'],
215+
msg: 'mock_error_ssh_jobs_dir',
216+
type: 'value_error'
217+
}
218+
]
219+
})
220+
)
221+
});
222+
223+
await user.type(screen.getByRole('textbox', { name: 'SSH host' }), 'localhost');
224+
await user.type(screen.getByRole('textbox', { name: 'SSH username' }), 'username');
225+
await user.type(
226+
screen.getByRole('textbox', { name: 'SSH Private Key Path' }),
227+
'/path/to/private/key'
228+
);
229+
await user.type(screen.getByRole('textbox', { name: 'SSH Tasks Dir' }), '/path/to/tasks/dir');
230+
await user.type(screen.getByRole('textbox', { name: 'SSH Jobs Dir' }), '/path/to/jobs/dir');
231+
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
232+
await screen.findByText('mock_error_ssh_private_key_path');
233+
await screen.findByText('mock_error_ssh_tasks_dir');
234+
await screen.findByText('mock_error_ssh_jobs_dir');
235+
236+
expect(mockRequest).toHaveBeenCalledWith(
237+
expect.anything(),
238+
expect.objectContaining({
239+
body: JSON.stringify({
240+
ssh_host: 'localhost',
241+
ssh_username: 'username',
242+
ssh_private_key_path: '/path/to/private/key',
243+
ssh_tasks_dir: '/path/to/tasks/dir',
244+
ssh_jobs_dir: '/path/to/jobs/dir',
245+
slurm_accounts: []
246+
})
247+
})
248+
);
249+
});
250+
});

__tests__/v2/workflow_page.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ describe('Workflow page', () => {
6363
log: 'Exception error occurred while creating job folder and subfolders.\nOriginal error: test'
6464
}
6565
];
66+
case '/api/auth/current-user/settings':
67+
return { slurm_accounts: [] };
6668
default:
6769
throw Error(`Unexpected API call: ${url}`);
6870
}

docs/environment-variables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The following environment variables can be used to configure fractal-web.
1717
* `LOG_LEVEL_FILE`: the log level of logs that will be written to the file; the default value is `info`;
1818
* `LOG_LEVEL_CONSOLE`: the log level of logs that will be written to the console; the default value is `warn`;
1919
* `FRACTAL_API_V1_MODE`: include/exclude V1 pages and version switcher; supported values are: `include`, `include_read_only`, `exclude`; the default value is `include`;
20+
* `FRACTAL_RUNNER_BACKEND`: specifies which runner backend is used; supported values are: `local`, `local_experimental`, `slurm`, `slurm_ssh`; the default value is `local`;
2021
* `PUBLIC_FRACTAL_VIZARR_VIEWER_URL`: URL to [fractal-vizarr-viewer](https://github.com/fractal-analytics-platform/fractal-vizarr-viewer) service (e.g. http://localhost:3000/vizarr for testing);
2122
* `WARNING_BANNER_PATH`: specifies the path to a text file containing the warning banner message displayed on the site; the banner is used to inform users about important issues, such as external resources downtime or maintenance alerts; if the variable is empty or unset no banner is displayed.
2223

docs/quickstart.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export LOG_FILE=fractal-web.log
5656
# export LOG_LEVEL_CONSOLE=warn
5757

5858
export FRACTAL_API_V1_MODE=include
59+
export FRACTAL_RUNNER_BACKEND=local
5960

6061
#export PUBLIC_FRACTAL_VIZARR_VIEWER_URL=
6162
#export WARNING_BANNER_PATH=

playwright.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export default defineConfig({
107107

108108
webServer: [
109109
{
110-
command: './tests/start-test-server.sh 2.5.0a1',
110+
command: './tests/start-test-server.sh 2.6.0a1',
111111
port: 8000,
112112
waitForPort: true,
113113
stdout: 'pipe',
@@ -123,7 +123,7 @@ export default defineConfig({
123123
},
124124
{
125125
command:
126-
'npm run build && LOG_LEVEL_CONSOLE=debug ORIGIN=http://localhost:5173 PORT=5173 node build',
126+
'npm run build && LOG_LEVEL_CONSOLE=debug ORIGIN=http://localhost:5173 PORT=5173 FRACTAL_RUNNER_BACKEND=slurm node build',
127127
port: 5173,
128128
stdout: 'pipe',
129129
reuseExistingServer: !process.env.CI

0 commit comments

Comments
 (0)