Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User settings #564

Merged
merged 8 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ LOG_LEVEL_FILE=info
LOG_LEVEL_CONSOLE=warn

FRACTAL_API_V1_MODE=include
FRACTAL_RUNNER_BACKEND=local
#WARNING_BANNER_PATH=/path/to/banner.txt
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ LOG_LEVEL_FILE=debug
LOG_LEVEL_CONSOLE=info

FRACTAL_API_V1_MODE=include
FRACTAL_RUNNER_BACKEND=local
#WARNING_BANNER_PATH=/path/to/banner.txt
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# Unreleased

* Added "My Settings" page and updated admin user editor (\#564);
* Fixed duplicated entries in owner dropdown (\#562);
* Added "View plate" button on dataset page (\#562);
* Improved stability of end to end tests (\#560);
Expand Down
14 changes: 4 additions & 10 deletions __tests__/v2/RunWorkflowModal.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { describe, it, expect, vi } from 'vitest';
import { fireEvent, render } from '@testing-library/svelte';
import { readable } from 'svelte/store';

// Mocking fetch
global.fetch = vi.fn();

// Mocking the page store
vi.mock('$app/stores', () => {
return {
page: readable({
data: {
userInfo: { slurm_accounts: [] }
}
})
};
fetch.mockResolvedValue({
ok: true,
status: 200,
json: () => new Promise((resolve) => resolve({ slurm_accounts: [] }))
});

// Mocking bootstrap.Modal
Expand Down
250 changes: 250 additions & 0 deletions __tests__/v2/UserEditor.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import { describe, it, beforeEach, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { readable } from 'svelte/store';

// Mocking fetch
global.fetch = vi.fn();

// Mocking the page store
vi.mock('$app/stores', () => {
return {
page: readable({
data: {
userInfo: {
id: 2
}
}
})
};
});

// The component to be tested must be imported after the mock setup
import UserEditor from '../../src/lib/components/v2/admin/UserEditor.svelte';

describe('UserEditor', () => {
beforeEach(() => {
fetch.mockClear();
});

const selectedUser = {
id: 1,
group_ids: []
};

const initialSettings = {
id: 1,
slurm_accounts: [],
slurm_user: null,
cache_dir: null,
ssh_host: null,
ssh_username: null,
ssh_private_key_path: null,
ssh_tasks_dir: null,
ssh_jobs_dir: null
};

it('Update settings with slurm runner backend - success', async () => {
const user = userEvent.setup();

render(UserEditor, {
props: {
runnerBackend: 'slurm',
user: selectedUser,
settings: { ...initialSettings },
save: () => {}
}
});

const mockRequest = fetch.mockResolvedValue({
ok: true,
status: 200,
json: () =>
new Promise((resolve) =>
resolve({
...initialSettings,
slurm_user: 'user',
cache_dir: '/path/to/cache/dir'
})
)
});

await user.type(screen.getByRole('textbox', { name: 'SLURM user' }), 'user');
await user.type(screen.getByRole('textbox', { name: 'Cache dir' }), '/path/to/cache/dir');
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
await screen.findByText('Settings successfully updated');

expect(mockRequest).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
body: JSON.stringify({
slurm_user: 'user',
cache_dir: '/path/to/cache/dir',
slurm_accounts: []
})
})
);
});

it('Update settings with slurm runner backend - validation error', async () => {
const user = userEvent.setup();

render(UserEditor, {
props: {
runnerBackend: 'slurm',
user: selectedUser,
settings: { ...initialSettings },
save: () => {}
}
});

const mockRequest = fetch.mockResolvedValue({
ok: false,
status: 422,
json: () =>
new Promise((resolve) =>
resolve({
detail: [
{
loc: ['body', 'cache_dir'],
msg: 'mocked_error',
type: 'value_error'
}
]
})
)
});

await user.type(screen.getByRole('textbox', { name: 'Cache dir' }), 'xxx');
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
await screen.findByText('mocked_error');

expect(mockRequest).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
body: JSON.stringify({
cache_dir: 'xxx',
slurm_accounts: []
})
})
);
});

it('Update settings with slurm_ssh runner backend - success', async () => {
const user = userEvent.setup();

render(UserEditor, {
props: {
runnerBackend: 'slurm_ssh',
user: selectedUser,
settings: { ...initialSettings },
save: () => {}
}
});

const mockRequest = fetch.mockResolvedValue({
ok: true,
status: 200,
json: () =>
new Promise((resolve) =>
resolve({
...initialSettings,
ssh_host: 'localhost',
ssh_username: 'username',
ssh_private_key_path: '/path/to/private/key',
ssh_tasks_dir: '/path/to/tasks/dir',
ssh_jobs_dir: '/path/to/jobs/dir'
})
)
});

await user.type(screen.getByRole('textbox', { name: 'SSH host' }), 'localhost');
await user.type(screen.getByRole('textbox', { name: 'SSH username' }), 'username');
await user.type(screen.getByRole('textbox', { name: 'SSH Private Key Path' }), 'xxx');
await user.type(screen.getByRole('textbox', { name: 'SSH Tasks Dir' }), 'yyy');
await user.type(screen.getByRole('textbox', { name: 'SSH Jobs Dir' }), 'zzz');
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
await screen.findByText('Settings successfully updated');

expect(mockRequest).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
body: JSON.stringify({
ssh_host: 'localhost',
ssh_username: 'username',
ssh_private_key_path: 'xxx',
ssh_tasks_dir: 'yyy',
ssh_jobs_dir: 'zzz',
slurm_accounts: []
})
})
);
});

it('Update settings with slurm_ssh runner backend - validation error', async () => {
const user = userEvent.setup();

render(UserEditor, {
props: {
runnerBackend: 'slurm_ssh',
user: selectedUser,
settings: { ...initialSettings },
save: () => {}
}
});

const mockRequest = fetch.mockResolvedValue({
ok: false,
status: 422,
json: () =>
new Promise((resolve) =>
resolve({
detail: [
{
loc: ['body', 'ssh_private_key_path'],
msg: 'mock_error_ssh_private_key_path',
type: 'value_error'
},
{
loc: ['body', 'ssh_tasks_dir'],
msg: 'mock_error_ssh_tasks_dir',
type: 'value_error'
},
{
loc: ['body', 'ssh_jobs_dir'],
msg: 'mock_error_ssh_jobs_dir',
type: 'value_error'
}
]
})
)
});

await user.type(screen.getByRole('textbox', { name: 'SSH host' }), 'localhost');
await user.type(screen.getByRole('textbox', { name: 'SSH username' }), 'username');
await user.type(
screen.getByRole('textbox', { name: 'SSH Private Key Path' }),
'/path/to/private/key'
);
await user.type(screen.getByRole('textbox', { name: 'SSH Tasks Dir' }), '/path/to/tasks/dir');
await user.type(screen.getByRole('textbox', { name: 'SSH Jobs Dir' }), '/path/to/jobs/dir');
await user.click(screen.getAllByRole('button', { name: 'Save' })[1]);
await screen.findByText('mock_error_ssh_private_key_path');
await screen.findByText('mock_error_ssh_tasks_dir');
await screen.findByText('mock_error_ssh_jobs_dir');

expect(mockRequest).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
body: JSON.stringify({
ssh_host: 'localhost',
ssh_username: 'username',
ssh_private_key_path: '/path/to/private/key',
ssh_tasks_dir: '/path/to/tasks/dir',
ssh_jobs_dir: '/path/to/jobs/dir',
slurm_accounts: []
})
})
);
});
});
2 changes: 2 additions & 0 deletions __tests__/v2/workflow_page.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ describe('Workflow page', () => {
log: 'Exception error occurred while creating job folder and subfolders.\nOriginal error: test'
}
];
case '/api/auth/current-user/settings':
return { slurm_accounts: [] };
default:
throw Error(`Unexpected API call: ${url}`);
}
Expand Down
1 change: 1 addition & 0 deletions docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The following environment variables can be used to configure fractal-web.
* `LOG_LEVEL_FILE`: the log level of logs that will be written to the file; the default value is `info`;
* `LOG_LEVEL_CONSOLE`: the log level of logs that will be written to the console; the default value is `warn`;
* `FRACTAL_API_V1_MODE`: include/exclude V1 pages and version switcher; supported values are: `include`, `include_read_only`, `exclude`; the default value is `include`;
* `FRACTAL_RUNNER_BACKEND`: specifies which runner backend is used; supported values are: `local`, `local_experimental`, `slurm`, `slurm_ssh`; the default value is `local`;
* `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);
* `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.

Expand Down
1 change: 1 addition & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export LOG_FILE=fractal-web.log
# export LOG_LEVEL_CONSOLE=warn

export FRACTAL_API_V1_MODE=include
export FRACTAL_RUNNER_BACKEND=local

#export PUBLIC_FRACTAL_VIZARR_VIEWER_URL=
#export WARNING_BANNER_PATH=
Expand Down
4 changes: 2 additions & 2 deletions playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export default defineConfig({

webServer: [
{
command: './tests/start-test-server.sh 2.5.0a1',
command: './tests/start-test-server.sh 2.6.0a1',
port: 8000,
waitForPort: true,
stdout: 'pipe',
Expand All @@ -123,7 +123,7 @@ export default defineConfig({
},
{
command:
'npm run build && LOG_LEVEL_CONSOLE=debug ORIGIN=http://localhost:5173 PORT=5173 node build',
'npm run build && LOG_LEVEL_CONSOLE=debug ORIGIN=http://localhost:5173 PORT=5173 FRACTAL_RUNNER_BACKEND=slurm node build',
port: 5173,
stdout: 'pipe',
reuseExistingServer: !process.env.CI
Expand Down
Loading
Loading