Skip to content

Commit 32cba83

Browse files
committed
Load roles and show them in the table
1 parent 0080658 commit 32cba83

File tree

7 files changed

+188
-40
lines changed

7 files changed

+188
-40
lines changed

site/src/api/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,8 @@ export const suspendUser = async (userId: TypesGen.User["id"]): Promise<TypesGen
158158

159159
export const updateUserPassword = async (password: string, userId: TypesGen.User["id"]): Promise<undefined> =>
160160
axios.put(`/api/v2/users/${userId}/password`, { password })
161+
162+
export const getOrganizationRoles = async (organizationId: string): Promise<Array<string>> => {
163+
const response = await axios.get<Array<string>>(`/api/v2/organizations/${organizationId}/members/roles`)
164+
return response.data
165+
}

site/src/components/TableHeaders/TableHeaders.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ export interface TableHeadersProps {
88
hasMenu?: boolean
99
}
1010

11-
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu }) => {
11+
export const TableHeaderRow: React.FC = ({ children }) => {
1212
const styles = useStyles()
13+
return <TableRow className={styles.root}>{children}</TableRow>
14+
}
15+
16+
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu }) => {
1317
return (
14-
<TableRow className={styles.root}>
18+
<TableHeaderRow>
1519
{columns.map((c, idx) => (
1620
<TableCell key={idx} size="small">
1721
{c}
1822
</TableCell>
1923
))}
2024
{/* 1% is a trick to make the table cell width fit the content */}
2125
{hasMenu && <TableCell width="1%" />}
22-
</TableRow>
26+
</TableHeaderRow>
2327
)
2428
}
2529

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
import Box from "@material-ui/core/Box"
2+
import Table from "@material-ui/core/Table"
3+
import TableBody from "@material-ui/core/TableBody"
4+
import TableCell from "@material-ui/core/TableCell"
5+
import TableHead from "@material-ui/core/TableHead"
6+
import TableRow from "@material-ui/core/TableRow"
17
import React from "react"
28
import { UserResponse } from "../../api/types"
39
import { EmptyState } from "../EmptyState/EmptyState"
4-
import { Column, Table } from "../Table/Table"
10+
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
511
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
12+
import { TableTitle } from "../TableTitle/TableTitle"
613
import { UserCell } from "../UserCell/UserCell"
714

815
export const Language = {
@@ -12,48 +19,63 @@ export const Language = {
1219
usernameLabel: "User",
1320
suspendMenuItem: "Suspend",
1421
resetPasswordMenuItem: "Reset password",
22+
rolesLabel: "Roles",
1523
}
1624

17-
const emptyState = <EmptyState message={Language.emptyMessage} />
18-
19-
const columns: Column<UserResponse>[] = [
20-
{
21-
key: "username",
22-
name: Language.usernameLabel,
23-
renderer: (field, data) => {
24-
return <UserCell Avatar={{ username: data.username }} primaryText={data.username} caption={data.email} />
25-
},
26-
},
27-
]
28-
2925
export interface UsersTableProps {
3026
users: UserResponse[]
3127
onSuspendUser: (user: UserResponse) => void
3228
onResetUserPassword: (user: UserResponse) => void
29+
roles: string[]
3330
}
3431

35-
export const UsersTable: React.FC<UsersTableProps> = ({ users, onSuspendUser, onResetUserPassword }) => {
32+
export const UsersTable: React.FC<UsersTableProps> = ({ users, roles, onSuspendUser, onResetUserPassword }) => {
3633
return (
37-
<Table
38-
columns={columns}
39-
data={users}
40-
title={Language.usersTitle}
41-
emptyState={emptyState}
42-
rowMenu={(user) => (
43-
<TableRowMenu
44-
data={user}
45-
menuItems={[
46-
{
47-
label: Language.suspendMenuItem,
48-
onClick: onSuspendUser,
49-
},
50-
{
51-
label: Language.resetPasswordMenuItem,
52-
onClick: onResetUserPassword,
53-
},
54-
]}
55-
/>
56-
)}
57-
/>
34+
<Table>
35+
<TableHead>
36+
<TableTitle title={Language.usersTitle} />
37+
<TableHeaderRow>
38+
<TableCell size="small">{Language.usernameLabel}</TableCell>
39+
<TableCell size="small">{Language.rolesLabel}</TableCell>
40+
{/* 1% is a trick to make the table cell width fit the content */}
41+
<TableCell size="small" width="1%" />
42+
</TableHeaderRow>
43+
</TableHead>
44+
<TableBody>
45+
{users.map((u) => (
46+
<TableRow key={u.id}>
47+
<TableCell>
48+
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "}
49+
</TableCell>
50+
<TableCell>{roles}</TableCell>
51+
<TableCell>
52+
<TableRowMenu
53+
data={u}
54+
menuItems={[
55+
{
56+
label: Language.suspendMenuItem,
57+
onClick: onSuspendUser,
58+
},
59+
{
60+
label: Language.resetPasswordMenuItem,
61+
onClick: onResetUserPassword,
62+
},
63+
]}
64+
/>
65+
</TableCell>
66+
</TableRow>
67+
))}
68+
69+
{users.length === 0 && (
70+
<TableRow>
71+
<TableCell colSpan={999}>
72+
<Box p={4}>
73+
<EmptyState message={Language.emptyMessage} />
74+
</Box>
75+
</TableCell>
76+
</TableRow>
77+
)}
78+
</TableBody>
79+
</Table>
5880
)
5981
}

site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,37 @@ export const Language = {
1313
suspendDialogMessagePrefix: "Do you want to suspend the user",
1414
}
1515

16+
const useRoles = () => {
17+
const xServices = useContext(XServiceContext)
18+
const [authState] = useActor(xServices.authXService)
19+
const [rolesState, rolesSend] = useActor(xServices.rolesXService)
20+
const { roles } = rolesState.context
21+
const { me } = authState.context
22+
23+
useEffect(() => {
24+
if (!me) {
25+
throw new Error("User is not logged in")
26+
}
27+
28+
const organizationId = me.organization_ids[0]
29+
30+
rolesSend({
31+
type: "GET_ROLES",
32+
organizationId,
33+
})
34+
}, [me, rolesSend])
35+
36+
return roles
37+
}
38+
1639
export const UsersPage: React.FC = () => {
1740
const xServices = useContext(XServiceContext)
1841
const [usersState, usersSend] = useActor(xServices.usersXService)
1942
const { users, getUsersError, userIdToSuspend, userIdToResetPassword, newUserPassword } = usersState.context
2043
const navigate = useNavigate()
2144
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend)
2245
const userToResetPassword = users?.find((u) => u.id === userIdToResetPassword)
46+
const roles = useRoles()
2347

2448
/**
2549
* Fetch users on component mount
@@ -28,12 +52,13 @@ export const UsersPage: React.FC = () => {
2852
usersSend("GET_USERS")
2953
}, [usersSend])
3054

31-
if (!users) {
55+
if (!users || !roles) {
3256
return <FullScreenLoader />
3357
} else {
3458
return (
3559
<>
3660
<UsersPageView
61+
roles={roles}
3762
users={users}
3863
openUserCreationDialog={() => {
3964
navigate("/users/create")

site/src/pages/UsersPage/UsersPageView.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ export interface UsersPageViewProps {
1616
openUserCreationDialog: () => void
1717
onSuspendUser: (user: UserResponse) => void
1818
onResetUserPassword: (user: UserResponse) => void
19+
roles: string[]
1920
error?: unknown
2021
}
2122

2223
export const UsersPageView: React.FC<UsersPageViewProps> = ({
2324
users,
25+
roles,
2426
openUserCreationDialog,
2527
onSuspendUser,
2628
onResetUserPassword,
@@ -33,7 +35,12 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
3335
{error ? (
3436
<ErrorSummary error={error} />
3537
) : (
36-
<UsersTable users={users} onSuspendUser={onSuspendUser} onResetUserPassword={onResetUserPassword} />
38+
<UsersTable
39+
users={users}
40+
onSuspendUser={onSuspendUser}
41+
onResetUserPassword={onResetUserPassword}
42+
roles={roles}
43+
/>
3744
)}
3845
</Margins>
3946
</Stack>

site/src/xServices/StateContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useNavigate } from "react-router"
44
import { ActorRefFrom } from "xstate"
55
import { authMachine } from "./auth/authXService"
66
import { buildInfoMachine } from "./buildInfo/buildInfoXService"
7+
import { rolesMachine } from "./roles/rolesXService"
78
import { usersMachine } from "./users/usersXService"
89
import { workspaceMachine } from "./workspace/workspaceXService"
910

@@ -12,6 +13,7 @@ interface XServiceContextType {
1213
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
1314
usersXService: ActorRefFrom<typeof usersMachine>
1415
workspaceXService: ActorRefFrom<typeof workspaceMachine>
16+
rolesXService: ActorRefFrom<typeof rolesMachine>
1517
}
1618

1719
/**
@@ -37,6 +39,7 @@ export const XServiceProvider: React.FC = ({ children }) => {
3739
buildInfoXService: useInterpret(buildInfoMachine),
3840
usersXService: useInterpret(() => usersMachine.withConfig({ actions: { redirectToUsersPage } })),
3941
workspaceXService: useInterpret(workspaceMachine),
42+
rolesXService: useInterpret(rolesMachine),
4043
}}
4144
>
4245
{children}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { assign, createMachine } from "xstate"
2+
import * as API from "../../api"
3+
import { displayError } from "../../components/GlobalSnackbar/utils"
4+
5+
type RolesContext = {
6+
roles?: string[]
7+
getRolesError: Error | unknown
8+
organizationId?: string
9+
}
10+
11+
type RolesEvent = {
12+
type: "GET_ROLES"
13+
organizationId: string
14+
}
15+
16+
export const rolesMachine = createMachine(
17+
{
18+
id: "rolesState",
19+
initial: "idle",
20+
schema: {
21+
context: {} as RolesContext,
22+
events: {} as RolesEvent,
23+
services: {
24+
getRoles: {
25+
data: {} as string[],
26+
},
27+
},
28+
},
29+
tsTypes: {} as import("./rolesXService.typegen").Typegen0,
30+
states: {
31+
idle: {
32+
on: {
33+
GET_ROLES: {
34+
target: "gettingRoles",
35+
actions: ["assignOrganizationId"],
36+
},
37+
},
38+
},
39+
gettingRoles: {
40+
invoke: {
41+
id: "getRoles",
42+
src: "getRoles",
43+
onDone: {
44+
target: "idle",
45+
actions: ["assignRoles"],
46+
},
47+
onError: {
48+
target: "idle",
49+
actions: ["assignGetRolesError", "displayGetRolesError"],
50+
},
51+
},
52+
},
53+
},
54+
},
55+
{
56+
actions: {
57+
assignRoles: assign({
58+
roles: (_, event) => event.data,
59+
}),
60+
assignGetRolesError: assign({
61+
getRolesError: (_, event) => event.data,
62+
}),
63+
assignOrganizationId: assign({
64+
organizationId: (_, event) => event.organizationId,
65+
}),
66+
displayGetRolesError: () => {
67+
displayError("Error on get the roles.")
68+
},
69+
},
70+
services: {
71+
getRoles: (ctx) => {
72+
const { organizationId } = ctx
73+
74+
if (!organizationId) {
75+
throw new Error("organizationId not defined")
76+
}
77+
78+
return API.getOrganizationRoles(organizationId)
79+
},
80+
},
81+
},
82+
)

0 commit comments

Comments
 (0)