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

Skip to content

Commit 3f770e1

Browse files
fix: User permissions on UI (#1570)
1 parent 4eb0bb6 commit 3f770e1

File tree

7 files changed

+88
-60
lines changed

7 files changed

+88
-60
lines changed

site/src/components/Navbar/Navbar.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ beforeEach(() => {
1111
})
1212

1313
describe("Navbar", () => {
14-
describe("when user has permission to read all users", () => {
14+
describe("when user has permission to update users", () => {
1515
it("displays the admin menu", async () => {
1616
const checkUserPermissionsSpy = jest.spyOn(API, "checkUserPermissions").mockResolvedValueOnce({
17-
[checks.readAllUsers]: true,
17+
[checks.updateUsers]: true,
1818
})
1919

2020
renderWithAuth(<Navbar />)
@@ -25,10 +25,10 @@ describe("Navbar", () => {
2525
})
2626
})
2727

28-
describe("when user has NO permission to read all users", () => {
28+
describe("when user has NO permission to update users", () => {
2929
it("does not display the admin menu", async () => {
3030
const checkUserPermissionsSpy = jest.spyOn(API, "checkUserPermissions").mockResolvedValueOnce({
31-
[checks.readAllUsers]: false,
31+
[checks.updateUsers]: false,
3232
})
3333
renderWithAuth(<Navbar />)
3434

site/src/components/Navbar/Navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const Navbar: React.FC = () => {
1111
const permissions = useSelector(xServices.authXService, selectPermissions)
1212
// When we have more options in the admin dropdown we may want to check this
1313
// for more permissions
14-
const displayAdminDropdown = !!permissions?.readAllUsers
14+
const displayAdminDropdown = !!permissions?.updateUsers
1515
const onSignOut = () => authSend("SIGN_OUT")
1616

1717
return <NavbarView user={me} onSignOut={onSignOut} displayAdminDropdown={displayAdminDropdown} />

site/src/components/UsersTable/UsersTable.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export const Example = Template.bind({})
1414
Example.args = {
1515
users: [MockUser, MockUser2],
1616
roles: MockSiteRoles,
17+
canEditUsers: false,
18+
}
19+
20+
export const Editable = Template.bind({})
21+
Editable.args = {
22+
users: [MockUser, MockUser2],
23+
roles: MockSiteRoles,
24+
canEditUsers: true,
1725
}
1826

1927
export const Empty = Template.bind({})

site/src/components/UsersTable/UsersTable.tsx

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ import React from "react"
88
import * as TypesGen from "../../api/typesGenerated"
99
import { EmptyState } from "../EmptyState/EmptyState"
1010
import { RoleSelect } from "../RoleSelect/RoleSelect"
11-
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
1211
import { TableLoader } from "../TableLoader/TableLoader"
1312
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
14-
import { TableTitle } from "../TableTitle/TableTitle"
1513
import { UserCell } from "../UserCell/UserCell"
1614

1715
export const Language = {
@@ -28,6 +26,8 @@ export interface UsersTableProps {
2826
users?: TypesGen.User[]
2927
roles?: TypesGen.Role[]
3028
isUpdatingUserRoles?: boolean
29+
canEditUsers?: boolean
30+
isLoading?: boolean
3131
onSuspendUser: (user: TypesGen.User) => void
3232
onResetUserPassword: (user: TypesGen.User) => void
3333
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
@@ -40,52 +40,57 @@ export const UsersTable: React.FC<UsersTableProps> = ({
4040
onResetUserPassword,
4141
onUpdateUserRoles,
4242
isUpdatingUserRoles,
43+
canEditUsers,
44+
isLoading,
4345
}) => {
44-
const isLoading = !users || !roles
45-
4646
return (
4747
<Table>
4848
<TableHead>
49-
<TableTitle title={Language.usersTitle} />
50-
<TableHeaderRow>
51-
<TableCell size="small">{Language.usernameLabel}</TableCell>
52-
<TableCell size="small">{Language.rolesLabel}</TableCell>
49+
<TableRow>
50+
<TableCell>{Language.usernameLabel}</TableCell>
51+
<TableCell>{Language.rolesLabel}</TableCell>
5352
{/* 1% is a trick to make the table cell width fit the content */}
54-
<TableCell size="small" width="1%" />
55-
</TableHeaderRow>
53+
{canEditUsers && <TableCell width="1%" />}
54+
</TableRow>
5655
</TableHead>
5756
<TableBody>
5857
{isLoading && <TableLoader />}
59-
{users &&
60-
roles &&
58+
{!isLoading &&
59+
users &&
6160
users.map((u) => (
6261
<TableRow key={u.id}>
6362
<TableCell>
6463
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "}
6564
</TableCell>
6665
<TableCell>
67-
<RoleSelect
68-
roles={roles}
69-
selectedRoles={u.roles}
70-
loading={isUpdatingUserRoles}
71-
onChange={(roles) => onUpdateUserRoles(u, roles)}
72-
/>
73-
</TableCell>
74-
<TableCell>
75-
<TableRowMenu
76-
data={u}
77-
menuItems={[
78-
{
79-
label: Language.suspendMenuItem,
80-
onClick: onSuspendUser,
81-
},
82-
{
83-
label: Language.resetPasswordMenuItem,
84-
onClick: onResetUserPassword,
85-
},
86-
]}
87-
/>
66+
{canEditUsers ? (
67+
<RoleSelect
68+
roles={roles ?? []}
69+
selectedRoles={u.roles}
70+
loading={isUpdatingUserRoles}
71+
onChange={(roles) => onUpdateUserRoles(u, roles)}
72+
/>
73+
) : (
74+
<>{u.roles.map((r) => r.display_name).join(", ")}</>
75+
)}
8876
</TableCell>
77+
{canEditUsers && (
78+
<TableCell>
79+
<TableRowMenu
80+
data={u}
81+
menuItems={[
82+
{
83+
label: Language.suspendMenuItem,
84+
onClick: onSuspendUser,
85+
},
86+
{
87+
label: Language.resetPasswordMenuItem,
88+
onClick: onResetUserPassword,
89+
},
90+
]}
91+
/>
92+
</TableCell>
93+
)}
8994
</TableRow>
9095
))}
9196

site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useActor } from "@xstate/react"
1+
import { useActor, useSelector } from "@xstate/react"
22
import React, { useContext, useEffect } from "react"
33
import { useNavigate } from "react-router"
44
import { ConfirmDialog } from "../../components/ConfirmDialog/ConfirmDialog"
55
import { ResetPasswordDialog } from "../../components/ResetPasswordDialog/ResetPasswordDialog"
6+
import { selectPermissions } from "../../xServices/auth/authSelectors"
67
import { XServiceContext } from "../../xServices/StateContext"
78
import { UsersPageView } from "./UsersPageView"
89

@@ -12,39 +13,38 @@ export const Language = {
1213
suspendDialogMessagePrefix: "Do you want to suspend the user",
1314
}
1415

15-
const useRoles = () => {
16-
const xServices = useContext(XServiceContext)
17-
const [rolesState, rolesSend] = useActor(xServices.siteRolesXService)
18-
const { roles } = rolesState.context
19-
20-
/**
21-
* Fetch roles on component mount
22-
*/
23-
useEffect(() => {
24-
rolesSend({
25-
type: "GET_ROLES",
26-
})
27-
}, [rolesSend])
28-
29-
return roles
30-
}
31-
3216
export const UsersPage: React.FC = () => {
3317
const xServices = useContext(XServiceContext)
3418
const [usersState, usersSend] = useActor(xServices.usersXService)
19+
const [rolesState, rolesSend] = useActor(xServices.siteRolesXService)
3520
const { users, getUsersError, userIdToSuspend, userIdToResetPassword, newUserPassword } = usersState.context
3621
const navigate = useNavigate()
3722
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend)
3823
const userToResetPassword = users?.find((u) => u.id === userIdToResetPassword)
39-
const roles = useRoles()
24+
const permissions = useSelector(xServices.authXService, selectPermissions)
25+
const canEditUsers = permissions && permissions.updateUsers
26+
const { roles } = rolesState.context
27+
// Is loading if
28+
// - permissions are not loaded or
29+
// - users are not loaded or
30+
// - the user can edit the users but the roles are not loaded yet
31+
const isLoading = !permissions || !users || (canEditUsers && !roles)
4032

41-
/**
42-
* Fetch users on component mount
43-
*/
33+
// Fetch users on component mount
4434
useEffect(() => {
4535
usersSend("GET_USERS")
4636
}, [usersSend])
4737

38+
// Fetch roles on component mount
39+
useEffect(() => {
40+
// Only fetch the roles if the user has permission for it
41+
if (canEditUsers) {
42+
rolesSend({
43+
type: "GET_ROLES",
44+
})
45+
}
46+
}, [canEditUsers, rolesSend])
47+
4848
return (
4949
<>
5050
<UsersPageView
@@ -68,6 +68,8 @@ export const UsersPage: React.FC = () => {
6868
}}
6969
error={getUsersError}
7070
isUpdatingUserRoles={usersState.matches("updatingUserRoles")}
71+
isLoading={isLoading}
72+
canEditUsers={canEditUsers}
7173
/>
7274

7375
<ConfirmDialog

site/src/pages/UsersPage/UsersPageView.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface UsersPageViewProps {
1616
roles?: TypesGen.Role[]
1717
error?: unknown
1818
isUpdatingUserRoles?: boolean
19+
canEditUsers?: boolean
20+
isLoading?: boolean
1921
openUserCreationDialog: () => void
2022
onSuspendUser: (user: TypesGen.User) => void
2123
onResetUserPassword: (user: TypesGen.User) => void
@@ -31,6 +33,8 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
3133
onUpdateUserRoles,
3234
error,
3335
isUpdatingUserRoles,
36+
canEditUsers,
37+
isLoading,
3438
}) => {
3539
return (
3640
<Stack spacing={4}>
@@ -46,6 +50,8 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
4650
onResetUserPassword={onResetUserPassword}
4751
onUpdateUserRoles={onUpdateUserRoles}
4852
isUpdatingUserRoles={isUpdatingUserRoles}
53+
canEditUsers={canEditUsers}
54+
isLoading={isLoading}
4955
/>
5056
)}
5157
</Margins>

site/src/xServices/auth/authXService.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const Language = {
1111

1212
export const checks = {
1313
readAllUsers: "readAllUsers",
14+
updateUsers: "updateUsers",
1415
createTemplates: "createTemplates",
1516
} as const
1617

@@ -21,6 +22,12 @@ export const permissionsToCheck = {
2122
},
2223
action: "read",
2324
},
25+
[checks.updateUsers]: {
26+
object: {
27+
resource_type: "user",
28+
},
29+
action: "update",
30+
},
2431
[checks.createTemplates]: {
2532
object: {
2633
resource_type: "template",

0 commit comments

Comments
 (0)