Skip to content

Commit 1076d48

Browse files
committed
Users' roles can be updated
Signed-off-by: Aashir Siddiqui <aashir_sidiki@hotmail.com>
1 parent fd5b3f4 commit 1076d48

File tree

11 files changed

+617
-28
lines changed

11 files changed

+617
-28
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright contributors to the Galasa project
3+
*
4+
* SPDX-License-Identifier: EPL-2.0
5+
*/
6+
import EditUserBreadCrumb from '@/components/common/EditUserBreadCrumb';
7+
import PageTile from '@/components/PageTile';
8+
import UserRoleSection from '@/components/users/UserRoleSection';
9+
import React from 'react';
10+
import { UserData, UsersAPIApi } from '@/generated/galasaapi';
11+
import * as Constants from "@/utils/constants";
12+
import { createAuthenticatedApiConfiguration } from '@/utils/api';
13+
14+
// In order to extract query param on server-side
15+
type UsersPageProps = {
16+
params: {}; // No dynamic route parameters
17+
searchParams: { [key: string]: string | string[] | undefined };
18+
};
19+
20+
export default function EditUserPage({searchParams} : UsersPageProps) {
21+
22+
const apiConfig = createAuthenticatedApiConfiguration();
23+
const loginIdFromQueryParam = searchParams.loginId as string;
24+
25+
const fetchUserFromApiServer = async () => {
26+
27+
let user: UserData = {};
28+
const usersApiClient = new UsersAPIApi(apiConfig);
29+
30+
const usersReponse = await usersApiClient.getUserByLoginId(Constants.CLIENT_API_VERSION, loginIdFromQueryParam);
31+
32+
if (usersReponse && usersReponse.length > 0) {
33+
user = structuredClone(usersReponse[0]);
34+
}
35+
36+
return user;
37+
};
38+
39+
40+
return (
41+
<main id="content">
42+
<EditUserBreadCrumb />
43+
<PageTile title={"Edit User"} />
44+
<UserRoleSection userProfilePromise={fetchUserFromApiServer()}/>
45+
</main>
46+
);
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright contributors to the Galasa project
3+
*
4+
* SPDX-License-Identifier: EPL-2.0
5+
*/
6+
7+
import { UsersAPIApi } from "@/generated/galasaapi";
8+
import { createAuthenticatedApiConfiguration } from "@/utils/api";
9+
import { NextRequest, NextResponse } from "next/server";
10+
import * as Constants from "@/utils/constants";
11+
12+
13+
export const dynamic = 'force-dynamic';
14+
15+
export async function PUT(request: NextRequest) {
16+
17+
const apiConfig = createAuthenticatedApiConfiguration();
18+
const usersApiClient = new UsersAPIApi(apiConfig);
19+
const requestBody = await request.json();
20+
21+
if (!requestBody.userNumber || !requestBody.roleDetails?.role) {
22+
return new NextResponse('Role ID and User Number are required', { status: 400 });
23+
}
24+
25+
try {
26+
// Attempt to update the user
27+
await usersApiClient.updateUser(
28+
requestBody.userNumber,
29+
requestBody.roleDetails,
30+
Constants.CLIENT_API_VERSION
31+
);
32+
return new NextResponse(null, { status: 200 });
33+
} catch (error: any) {
34+
// Log the error for debugging
35+
console.error(error);
36+
37+
// Check for specific error codes
38+
if (error.response?.status === 403) {
39+
return new NextResponse('Forbidden: You do not have permission to update this user.', { status: 403 });
40+
} else if (error.response?.status === 404) {
41+
return new NextResponse('Not Found: The specified user does not exist.', { status: 404 });
42+
}
43+
44+
// Fallback for other errors
45+
return new NextResponse('Internal Server Error', { status: 500 });
46+
}
47+
}

galasa-ui/src/app/users/page.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,46 @@ import React from 'react';
1010
import * as Constants from "@/utils/constants";
1111
import BreadCrumb from '@/components/common/BreadCrumb';
1212
import PageTile from '@/components/PageTile';
13-
import UsersList from '@/components/users/UsersTable';
13+
import UsersTable from '@/components/users/UsersTable';
1414

1515
export const dynamic = 'force-dynamic';
1616

17-
function UsersPage() {
17+
export default function UsersPage() {
1818

1919
const apiConfig = createAuthenticatedApiConfiguration();
2020
const fetchAllUsersFromApiServer = async () => {
2121

22-
let users : UserData[] = [];
22+
let users: UserData[] = [];
2323

2424
const usersApiClient = new UsersAPIApi(apiConfig);
2525
const usersReponse = await usersApiClient.getUserByLoginId(Constants.CLIENT_API_VERSION);
2626

27-
if(usersReponse && usersReponse.length >= 1){
27+
if (usersReponse && usersReponse.length >= 1) {
2828
users = structuredClone(usersReponse);
2929
}
3030

3131
return users;
3232

3333
};
3434

35+
const fetchCurrentUserFromApiServer = async () => {
36+
37+
let user: UserData= {};
38+
const usersApiClient = new UsersAPIApi(apiConfig);
39+
const usersReponse = await usersApiClient.getUserByLoginId(Constants.CLIENT_API_VERSION, "me");
40+
41+
if(usersReponse && usersReponse.length > 0){
42+
user = structuredClone(usersReponse[0]);
43+
}
44+
45+
return user;
46+
};
47+
3548
return (
3649
<main id="content">
3750
<BreadCrumb />
3851
<PageTile title={"Users"} />
39-
<UsersList usersListPromise={fetchAllUsersFromApiServer()}/>
52+
<UsersTable usersListPromise={fetchAllUsersFromApiServer()} currentUserPromise={fetchCurrentUserFromApiServer()}/>
4053
</main>
4154
);
4255
}
43-
44-
export default UsersPage;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright contributors to the Galasa project
3+
*
4+
* SPDX-License-Identifier: EPL-2.0
5+
*/
6+
7+
'use client';
8+
9+
import React from 'react';
10+
import { Breadcrumb, BreadcrumbItem, Theme } from "@carbon/react";
11+
import "@/styles/global.scss";
12+
import styles from "@/styles/BreadCrumb.module.css";
13+
14+
export default function EditUserBreadCrumb() {
15+
return (
16+
<Theme theme="g10">
17+
<Breadcrumb className={styles.crumbContainer}>
18+
<BreadcrumbItem isCurrentPage={false} href="/">Home</BreadcrumbItem>
19+
<BreadcrumbItem isCurrentPage={false} href="/users">Users</BreadcrumbItem>
20+
</Breadcrumb>
21+
</Theme>
22+
);
23+
}

galasa-ui/src/components/profile/ProfileDetails.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
import ErrorPage from "@/app/error/page";
99
import { UserData } from "@/generated/galasaapi";
1010
import styles from "@/styles/MyProfile.module.css";
11+
import { ProfileDetailsProps } from "@/utils/interfaces";
1112
import { Loading } from "@carbon/react";
1213
import { useEffect, useState } from "react";
1314

14-
interface ProfileDetailsProps {
15-
userProfilePromise: Promise<UserData>;
16-
}
1715

1816
export default function ProfileDetails({ userProfilePromise }: ProfileDetailsProps) {
1917
const WEB_UI_CLIENT_NAME = "web-ui";
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright contributors to the Galasa project
3+
*
4+
* SPDX-License-Identifier: EPL-2.0
5+
*/
6+
"use client";
7+
import React, { useEffect, useState } from 'react';
8+
import { ProfileDetailsProps } from '@/utils/interfaces';
9+
import { UserData } from '@/generated/galasaapi';
10+
import styles from "@/styles/UserRole.module.css";
11+
import { ButtonSet, Button, Dropdown, Loading } from '@carbon/react';
12+
import ErrorPage from '@/app/error/page';
13+
import { InlineNotification } from '@carbon/react';
14+
15+
interface DropdownItem {
16+
name: string,
17+
description: string
18+
}
19+
20+
export default function UserRoleSection({ userProfilePromise }: ProfileDetailsProps) {
21+
22+
const [userProfile, setUserProfile] = useState<UserData>({});
23+
const [isLoading, setIsLoading] = useState(false);
24+
const [isError, setIsError] = useState(false);
25+
const [role, setRole] = useState({
26+
id: "",
27+
name: "",
28+
description: ""
29+
});
30+
const [isSaveBtnDisabled, setIsSaveBtnDisabled] = useState(true);
31+
const [isResetBtnDisabled, setIsResetBtnDisabled] = useState(true);
32+
const [isToastVisible, setIsToastVisible] = useState(false);
33+
34+
const items = [
35+
{
36+
id: "1",
37+
name: 'tester',
38+
description: "Test developer and runner"
39+
}, {
40+
id: "2",
41+
name: 'admin',
42+
description: "Administrator access"
43+
}, {
44+
id: "0",
45+
name: 'deactivated',
46+
description: "User has no access"
47+
}
48+
];
49+
50+
useEffect(() => {
51+
const loadUserProfile = async () => {
52+
setIsError(false);
53+
setIsLoading(true);
54+
55+
try {
56+
57+
const loadedProfile = await userProfilePromise;
58+
setUserProfile(loadedProfile);
59+
setRole({ id: loadedProfile.synthetic?.role?.metadata?.id!, name: loadedProfile.synthetic?.role?.metadata?.name!, description: loadedProfile.synthetic?.role?.metadata?.description! });
60+
61+
} catch (err) {
62+
setIsError(true);
63+
console.error(err);
64+
} finally {
65+
setIsLoading(false);
66+
}
67+
};
68+
69+
loadUserProfile();
70+
}, [userProfilePromise]);
71+
72+
const changeUserRole = (e: any) => {
73+
74+
setRole({
75+
id: e.selectedItem.id,
76+
name: e.selectedItem.name,
77+
description: e.selectedItem.description
78+
});
79+
80+
setIsResetBtnDisabled(false);
81+
setIsSaveBtnDisabled(false);
82+
83+
};
84+
85+
const resetRole = () => {
86+
87+
setRole({
88+
id: userProfile.synthetic?.role?.metadata?.id!,
89+
name: userProfile.synthetic?.role?.metadata?.name!,
90+
description: userProfile.synthetic?.role?.metadata?.description!
91+
});
92+
93+
setIsResetBtnDisabled(true);
94+
setIsSaveBtnDisabled(true);
95+
96+
};
97+
98+
const updateUserRole = async () => {
99+
100+
const requestBody = {
101+
roleDetails : {
102+
role: role.id
103+
},
104+
userNumber: userProfile.id
105+
};
106+
107+
try {
108+
const response = await fetch('/users/edit/updateUserRole', {
109+
method: "PUT",
110+
headers: {
111+
"Content-Type": "application/json",
112+
},
113+
body: JSON.stringify(requestBody)
114+
});
115+
116+
if (response.ok) {
117+
setIsResetBtnDisabled(true);
118+
setIsSaveBtnDisabled(true);
119+
setIsToastVisible(true);
120+
}
121+
} catch (err) {
122+
setIsError(true);
123+
}
124+
125+
};
126+
127+
if (isLoading) {
128+
return <Loading small={false} active={isLoading} />;
129+
}
130+
131+
if (isError) {
132+
return <ErrorPage />;
133+
}
134+
135+
return (
136+
<div className={styles.roleDetails}>
137+
<div className={styles.roleDetailsContainer}>
138+
<h2>{userProfile.loginId}</h2>
139+
<h4>User Role</h4>
140+
<p>The actions a user can or cannot on this Galasa service is controlled by their user role.</p>
141+
<div className={styles.dropdownContainer}>
142+
<Dropdown onChange={(e: any) => changeUserRole(e)} style={{ "width": "35%" }} size="lg" id="default" helperText={role.description} label={role.name} items={items} itemToString={(item: DropdownItem) => item ? item.name : ''} />
143+
<ButtonSet className={styles.buttonSet}>
144+
<Button onClick={resetRole} disabled={isResetBtnDisabled} kind="secondary">Reset</Button>
145+
<Button onClick={updateUserRole} disabled={isSaveBtnDisabled} kind="primary">Save</Button>
146+
</ButtonSet>
147+
</div>
148+
149+
{
150+
isToastVisible && <InlineNotification inline={true} onClose={() => setIsToastVisible(false)} lowContrast={true} kind="success" title="Success" subtitle="User role was updated successfully." />
151+
}
152+
153+
</div>
154+
</div>
155+
);
156+
}

0 commit comments

Comments
 (0)