Skip to content

Commit 771bdf1

Browse files
committed
Added list of users in the profile page
1 parent 449caac commit 771bdf1

File tree

4 files changed

+138
-45
lines changed

4 files changed

+138
-45
lines changed

src/lib/server/api/auth_api.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,22 @@ export async function getProfileInfo(fetch) {
241241

242242
return await response.json();
243243
}
244+
245+
/**
246+
* @param {typeof fetch} fetch
247+
* @param {number} profileId
248+
* @returns {Promise<Array<import('fractal-components/types/api').User>>}
249+
*/
250+
export async function getProfileUsers(fetch, profileId) {
251+
logger.debug(`Retrieving users of profile ${profileId}`);
252+
const response = await fetch(`${env.FRACTAL_SERVER_HOST}/auth/users/?profile_id=${profileId}`, {
253+
method: 'GET',
254+
credentials: 'include'
255+
});
256+
257+
if (!response.ok) {
258+
await responseError(response);
259+
}
260+
261+
return await response.json();
262+
}
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1+
import { getProfileUsers } from '$lib/server/api/auth_api';
12
import { getProfile, getResource } from '$lib/server/api/v2/admin_api';
23
import { getLogger } from '$lib/server/logger.js';
34

45
const logger = getLogger('admin view profile page');
56

67
export async function load({ fetch, params }) {
7-
logger.debug('Loading resource %d', params.resourceId);
8-
const resource = await getResource(fetch, Number(params.resourceId));
9-
logger.debug('Loading profile %d', params.profileId);
10-
const profile = await getProfile(fetch, Number(params.profileId));
8+
const resourceId = Number(params.resourceId);
9+
const profileId = Number(params.profileId);
10+
11+
logger.debug('Loading resource %d', resourceId);
12+
const resource = await getResource(fetch, resourceId);
13+
logger.debug('Loading profile %d', profileId);
14+
const profile = await getProfile(fetch, profileId);
15+
const users = await getProfileUsers(fetch, profileId);
1116
return {
1217
resource,
13-
profile
18+
profile,
19+
users
1420
};
1521
}
Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<script>
22
import { page } from '$app/state';
3+
import { sortUserByEmailComparator } from '$lib/common/user_utilities';
34
45
/** @type {import('fractal-components/types/api').Resource} */
56
const resource = $derived(page.data.resource);
67
/** @type {import('fractal-components/types/api').Profile} */
78
const profile = $derived(page.data.profile);
9+
/** @type {Array<import('fractal-components/types/api').User>} */
10+
const users = $derived(page.data.users.sort(sortUserByEmailComparator));
811
</script>
912

1013
<div class="container mt-3">
@@ -36,50 +39,91 @@
3639
</div>
3740

3841
<div class="container">
39-
<div class="col-md-10 col-lg-8">
40-
<table class="table">
41-
<tbody>
42-
<tr>
43-
<th>Profile ID</th>
44-
<td>{profile.id}</td>
45-
</tr>
46-
<tr>
47-
<th>Profile Name</th>
48-
<td>{profile.name}</td>
49-
</tr>
50-
<tr>
51-
<th>Resource ID</th>
52-
<td>{profile.resource_id}</td>
53-
</tr>
54-
<tr>
55-
<th>Resource Type</th>
56-
<td>{profile.resource_type}</td>
57-
</tr>
58-
{#if resource.type === 'slurm_sudo' || resource.type === 'slurm_ssh'}
42+
<div class="row">
43+
<div class="col-md-10 col-lg-8 mt-3">
44+
<table class="table">
45+
<tbody>
5946
<tr>
60-
<th>Username</th>
61-
<td>{profile.username || '-'}</td>
47+
<th>Profile ID</th>
48+
<td>{profile.id}</td>
6249
</tr>
63-
{/if}
64-
{#if resource.type === 'slurm_ssh'}
6550
<tr>
66-
<th>SSH key path</th>
67-
<td>{profile.ssh_key_path || '-'}</td>
51+
<th>Profile Name</th>
52+
<td>{profile.name}</td>
6853
</tr>
69-
{/if}
70-
{#if resource.type === 'slurm_ssh'}
7154
<tr>
72-
<th>Jobs remote dir</th>
73-
<td>{profile.jobs_remote_dir || '-'}</td>
55+
<th>Resource ID</th>
56+
<td>{profile.resource_id}</td>
7457
</tr>
75-
{/if}
76-
{#if resource.type === 'slurm_ssh'}
7758
<tr>
78-
<th>Tasks remote dir</th>
79-
<td>{profile.tasks_remote_dir || '-'}</td>
59+
<th>Resource Type</th>
60+
<td>{profile.resource_type}</td>
8061
</tr>
81-
{/if}
82-
</tbody>
83-
</table>
62+
{#if resource.type === 'slurm_sudo' || resource.type === 'slurm_ssh'}
63+
<tr>
64+
<th>Username</th>
65+
<td>{profile.username || '-'}</td>
66+
</tr>
67+
{/if}
68+
{#if resource.type === 'slurm_ssh'}
69+
<tr>
70+
<th>SSH key path</th>
71+
<td>{profile.ssh_key_path || '-'}</td>
72+
</tr>
73+
{/if}
74+
{#if resource.type === 'slurm_ssh'}
75+
<tr>
76+
<th>Jobs remote dir</th>
77+
<td>{profile.jobs_remote_dir || '-'}</td>
78+
</tr>
79+
{/if}
80+
{#if resource.type === 'slurm_ssh'}
81+
<tr>
82+
<th>Tasks remote dir</th>
83+
<td>{profile.tasks_remote_dir || '-'}</td>
84+
</tr>
85+
{/if}
86+
</tbody>
87+
</table>
88+
</div>
89+
</div>
90+
<div class="row">
91+
<div class="col-md-10 col-lg-8">
92+
{#if users.length > 0}
93+
<div class="accordion mt-3 ms-3 me-5 mb-5" id="accordion-users">
94+
<div class="accordion-item">
95+
<h2 class="accordion-header">
96+
<button
97+
class="accordion-button collapsed"
98+
type="button"
99+
data-bs-toggle="collapse"
100+
data-bs-target="#collapse-users"
101+
aria-expanded="false"
102+
aria-controls="collapseThree"
103+
>
104+
Users ({users.length})
105+
</button>
106+
</h2>
107+
<div
108+
id="collapse-users"
109+
class="accordion-collapse collapse"
110+
data-bs-parent="#accordion-users"
111+
>
112+
<div class="accordion-body">
113+
<ul>
114+
{#each users as user (user.id)}
115+
<li>
116+
<a href="/v2/admin/users/{user.id}">{user.email}</a>
117+
</li>
118+
{/each}
119+
</ul>
120+
</div>
121+
</div>
122+
</div>
123+
</div>
124+
{:else}
125+
<p class="ms-3 mt-3">There are no users associated with this profile</p>
126+
{/if}
127+
</div>
84128
</div>
85129
</div>

tests/v2/associate_profile_to_user.spec.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,15 @@ test('Associate a profile to a user', async ({ page }) => {
4444
await waitPageLoading(page);
4545
});
4646

47+
const profileUrl =
48+
(await page
49+
.getByRole('row', { name: randomProfileName })
50+
.getByRole('link', { name: 'Info' })
51+
.getAttribute('href')) || '';
52+
53+
const randomEmail = Math.random().toString(36).substring(7) + '@example.com';
54+
4755
await test.step('Create test user and associate it with new profile', async () => {
48-
const randomEmail = Math.random().toString(36).substring(7) + '@example.com';
4956
await page.goto('/v2/admin/users/register');
5057
await waitPageLoading(page);
5158
await page.getByRole('textbox', { name: 'E-mail' }).fill(randomEmail);
@@ -58,8 +65,19 @@ test('Associate a profile to a user', async ({ page }) => {
5865
await waitPageLoading(page);
5966
});
6067

61-
await test.step('Reload the page and verify the selected profile', async () => {
62-
await page.reload();
68+
await test.step('Go to the profile page and check the listed users', async () => {
69+
await page.goto(profileUrl);
70+
await waitPageLoading(page);
71+
await page.getByRole('button', { name: 'Users (1)' }).click();
72+
const userLink = page.getByRole('link', { name: randomEmail });
73+
await expect(userLink).toBeVisible();
74+
await userLink.click();
75+
await page.waitForURL(/\/v2\/admin\/users\/\d+$/);
76+
await waitPageLoading(page);
77+
});
78+
79+
await test.step('Go to user editing page and verify the selected profile', async () => {
80+
await page.getByRole('link', { name: 'Edit' }).click();
6381
await waitPageLoading(page);
6482
await expect(
6583
page
@@ -90,6 +108,12 @@ test('Associate a profile to a user', async ({ page }) => {
90108
).toHaveText('Select profile...');
91109
});
92110

111+
await test.step('Go to the profile page and check the listed users', async () => {
112+
await page.goto(profileUrl);
113+
await waitPageLoading(page);
114+
await expect(page.getByText('There are no users associated with this profile')).toBeVisible();
115+
});
116+
93117
await test.step('Delete profile', async () => {
94118
await page.goto('/v2/admin/resources');
95119
await waitPageLoading(page);

0 commit comments

Comments
 (0)