Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1e99a04

Browse files
committed
Add UI for update user roles
1 parent d5e749f commit 1e99a04

File tree

6 files changed

+146
-10
lines changed

6 files changed

+146
-10
lines changed

site/src/api/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,8 @@ export const getSiteRoles = async (): Promise<Array<TypesGen.Role>> => {
163163
const response = await axios.get<Array<TypesGen.Role>>(`/api/v2/users/roles`)
164164
return response.data
165165
}
166+
167+
export const updateUserRoles = async (
168+
roles: TypesGen.Role["name"][],
169+
userId: TypesGen.User["id"],
170+
): Promise<TypesGen.User> => axios.put(`/api/v2/users/${userId}/roles`, { roles })
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Checkbox from "@material-ui/core/Checkbox"
2+
import MenuItem from "@material-ui/core/MenuItem"
3+
import Select from "@material-ui/core/Select"
4+
import makeStyles from "@material-ui/styles/makeStyles"
5+
import React from "react"
6+
import { Role } from "../../api/typesGenerated"
7+
8+
export interface RoleSelectProps {
9+
roles: Role[]
10+
selectedRoles: Role[]
11+
onChange: (roles: Role["name"][]) => void
12+
loading?: boolean
13+
}
14+
15+
export const RoleSelect: React.FC<RoleSelectProps> = ({ roles, selectedRoles, loading, onChange }) => {
16+
const styles = useStyles()
17+
const value = selectedRoles.map((r) => r.name)
18+
const renderValue = () => selectedRoles.map((r) => r.display_name).join(", ")
19+
20+
return (
21+
<Select
22+
multiple
23+
value={value}
24+
renderValue={renderValue}
25+
variant="outlined"
26+
className={styles.select}
27+
onChange={(e) => {
28+
const { value } = e.currentTarget
29+
onChange(value as string[])
30+
}}
31+
>
32+
{roles.map((r) => {
33+
const isChecked = selectedRoles.some((selectedRole) => selectedRole.name === r.name)
34+
35+
return (
36+
<MenuItem key={r.name} value={r.name} disabled={loading}>
37+
<Checkbox color="primary" checked={isChecked} /> {r.display_name}
38+
</MenuItem>
39+
)
40+
})}
41+
</Select>
42+
)
43+
}
44+
45+
const useStyles = makeStyles(() => ({
46+
select: {
47+
margin: 0,
48+
},
49+
}))

site/src/components/UsersTable/UsersTable.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import Box from "@material-ui/core/Box"
2-
import MenuItem from "@material-ui/core/MenuItem"
3-
import Select from "@material-ui/core/Select"
42
import Table from "@material-ui/core/Table"
53
import TableBody from "@material-ui/core/TableBody"
64
import TableCell from "@material-ui/core/TableCell"
@@ -14,6 +12,7 @@ import { TableHeaderRow } from "../TableHeaders/TableHeaders"
1412
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
1513
import { TableTitle } from "../TableTitle/TableTitle"
1614
import { UserCell } from "../UserCell/UserCell"
15+
import { RoleSelect } from "./RoleSelect"
1716

1817
export const Language = {
1918
pageTitle: "Users",
@@ -29,10 +28,19 @@ export interface UsersTableProps {
2928
users: UserResponse[]
3029
onSuspendUser: (user: UserResponse) => void
3130
onResetUserPassword: (user: UserResponse) => void
31+
onUpdateUserRoles: (user: UserResponse, roles: TypesGen.Role["name"][]) => void
3232
roles: TypesGen.Role[]
33+
isUpdatingUserRoles?: boolean
3334
}
3435

35-
export const UsersTable: React.FC<UsersTableProps> = ({ users, roles, onSuspendUser, onResetUserPassword }) => {
36+
export const UsersTable: React.FC<UsersTableProps> = ({
37+
users,
38+
roles,
39+
onSuspendUser,
40+
onResetUserPassword,
41+
onUpdateUserRoles,
42+
isUpdatingUserRoles,
43+
}) => {
3644
return (
3745
<Table>
3846
<TableHead>
@@ -51,13 +59,12 @@ export const UsersTable: React.FC<UsersTableProps> = ({ users, roles, onSuspendU
5159
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "}
5260
</TableCell>
5361
<TableCell>
54-
<Select multiple value={[]}>
55-
{roles.map((r) => (
56-
<MenuItem key={r.name} value={r.name}>
57-
{r.display_name}
58-
</MenuItem>
59-
))}
60-
</Select>
62+
<RoleSelect
63+
roles={roles}
64+
selectedRoles={u.roles}
65+
loading={isUpdatingUserRoles}
66+
onChange={(roles) => onUpdateUserRoles(u, roles)}
67+
/>
6168
</TableCell>
6269
<TableCell>
6370
<TableRowMenu

site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,15 @@ export const UsersPage: React.FC = () => {
6969
onResetUserPassword={(user) => {
7070
usersSend({ type: "RESET_USER_PASSWORD", userId: user.id })
7171
}}
72+
onUpdateUserRoles={(user, roles) => {
73+
usersSend({
74+
type: "UPDATE_USER_ROLES",
75+
userId: user.id,
76+
roles,
77+
})
78+
}}
7279
error={getUsersError}
80+
isUpdatingUserRoles={usersState.matches("updatingUserRoles")}
7381
/>
7482

7583
<ConfirmDialog

site/src/pages/UsersPage/UsersPageView.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ export interface UsersPageViewProps {
1717
openUserCreationDialog: () => void
1818
onSuspendUser: (user: UserResponse) => void
1919
onResetUserPassword: (user: UserResponse) => void
20+
onUpdateUserRoles: (user: UserResponse, roles: TypesGen.Role["name"][]) => void
2021
roles: TypesGen.Role[]
2122
error?: unknown
23+
isUpdatingUserRoles?: boolean
2224
}
2325

2426
export const UsersPageView: React.FC<UsersPageViewProps> = ({
@@ -27,7 +29,9 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
2729
openUserCreationDialog,
2830
onSuspendUser,
2931
onResetUserPassword,
32+
onUpdateUserRoles,
3033
error,
34+
isUpdatingUserRoles,
3135
}) => {
3236
return (
3337
<Stack spacing={4}>
@@ -40,7 +44,9 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
4044
users={users}
4145
onSuspendUser={onSuspendUser}
4246
onResetUserPassword={onResetUserPassword}
47+
onUpdateUserRoles={onUpdateUserRoles}
4348
roles={roles}
49+
isUpdatingUserRoles={isUpdatingUserRoles}
4450
/>
4551
)}
4652
</Margins>

site/src/xServices/users/usersXService.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const Language = {
1212
suspendUserError: "Error on suspend the user.",
1313
resetUserPasswordSuccess: "Successfully updated the user password.",
1414
resetUserPasswordError: "Error on reset the user password.",
15+
updateUserRolesSuccess: "Successfully updated the user roles.",
16+
updateUserRolesError: "Error on update the user roles.",
1517
}
1618

1719
export interface UsersContext {
@@ -27,6 +29,9 @@ export interface UsersContext {
2729
userIdToResetPassword?: TypesGen.User["id"]
2830
resetUserPasswordError?: Error | unknown
2931
newUserPassword?: string
32+
// Update user roles
33+
userIdToUpdateRoles?: TypesGen.User["id"]
34+
updateUserRolesError?: Error | unknown
3035
}
3136

3237
export type UsersEvent =
@@ -40,6 +45,8 @@ export type UsersEvent =
4045
| { type: "RESET_USER_PASSWORD"; userId: TypesGen.User["id"] }
4146
| { type: "CONFIRM_USER_PASSWORD_RESET" }
4247
| { type: "CANCEL_USER_PASSWORD_RESET" }
48+
// Update roles events
49+
| { type: "UPDATE_USER_ROLES"; userId: TypesGen.User["id"]; roles: TypesGen.Role["name"][] }
4350

4451
export const usersMachine = createMachine(
4552
{
@@ -60,6 +67,9 @@ export const usersMachine = createMachine(
6067
updateUserPassword: {
6168
data: undefined
6269
}
70+
updateUserRoles: {
71+
data: TypesGen.User
72+
}
6373
},
6474
},
6575
id: "usersState",
@@ -80,6 +90,10 @@ export const usersMachine = createMachine(
8090
target: "confirmUserPasswordReset",
8191
actions: ["assignUserIdToResetPassword", "generateRandomPassword"],
8292
},
93+
UPDATE_USER_ROLES: {
94+
target: "updatingUserRoles",
95+
actions: ["assignUserIdToUpdateRoles"],
96+
},
8397
},
8498
},
8599
gettingUsers: {
@@ -166,6 +180,21 @@ export const usersMachine = createMachine(
166180
},
167181
},
168182
},
183+
updatingUserRoles: {
184+
entry: "clearUpdateUserRolesError",
185+
invoke: {
186+
src: "updateUserRoles",
187+
id: "updateUserRoles",
188+
onDone: {
189+
target: "idle",
190+
actions: ["displayUpdateRolesSuccess", "updateUserRolesInTheList"],
191+
},
192+
onError: {
193+
target: "idle",
194+
actions: ["assignUpdateRolesError", "displayUpdateRolesErrorMessage"],
195+
},
196+
},
197+
},
169198
error: {
170199
on: {
171200
GET_USERS: "gettingUsers",
@@ -198,6 +227,13 @@ export const usersMachine = createMachine(
198227

199228
return API.updateUserPassword(context.newUserPassword, context.userIdToResetPassword)
200229
},
230+
updateUserRoles: (context, event) => {
231+
if (!context.userIdToUpdateRoles) {
232+
throw new Error("userIdToUpdateRoles is undefined")
233+
}
234+
235+
return API.updateUserRoles(event.roles, context.userIdToUpdateRoles)
236+
},
201237
},
202238
guards: {
203239
isFormError: (_, event) => isApiError(event.data),
@@ -215,6 +251,9 @@ export const usersMachine = createMachine(
215251
assignUserIdToResetPassword: assign({
216252
userIdToResetPassword: (_, event) => event.userId,
217253
}),
254+
assignUserIdToUpdateRoles: assign({
255+
userIdToUpdateRoles: (_, event) => event.userId,
256+
}),
218257
clearGetUsersError: assign((context: UsersContext) => ({
219258
...context,
220259
getUsersError: undefined,
@@ -232,6 +271,9 @@ export const usersMachine = createMachine(
232271
assignResetUserPasswordError: assign({
233272
resetUserPasswordError: (_, event) => event.data,
234273
}),
274+
assignUpdateRolesError: assign({
275+
updateUserRolesError: (_, event) => event.data,
276+
}),
235277
clearCreateUserError: assign((context: UsersContext) => ({
236278
...context,
237279
createUserError: undefined,
@@ -242,6 +284,9 @@ export const usersMachine = createMachine(
242284
clearResetUserPasswordError: assign({
243285
resetUserPasswordError: (_) => undefined,
244286
}),
287+
clearUpdateUserRolesError: assign({
288+
updateUserRolesError: (_) => undefined,
289+
}),
245290
displayCreateUserSuccess: () => {
246291
displaySuccess(Language.createUserSuccess)
247292
},
@@ -257,9 +302,25 @@ export const usersMachine = createMachine(
257302
displayResetPasswordErrorMessage: () => {
258303
displayError(Language.resetUserPasswordError)
259304
},
305+
displayUpdateRolesSuccess: () => {
306+
displayError(Language.updateUserRolesSuccess)
307+
},
308+
displayUpdateRolesErrorMessage: () => {
309+
displayError(Language.updateUserRolesError)
310+
},
260311
generateRandomPassword: assign({
261312
newUserPassword: (_) => generateRandomString(12),
262313
}),
314+
updateUserRolesInTheList: assign({
315+
users: ({ users }, event) => {
316+
if (!users) {
317+
return users
318+
}
319+
320+
const updatedUser = event.data
321+
return users.map((u) => (u.id === updatedUser.id ? updatedUser : u))
322+
},
323+
}),
263324
},
264325
},
265326
)

0 commit comments

Comments
 (0)