-
Notifications
You must be signed in to change notification settings - Fork 894
feat: add user groups column to users table #10284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
cb51aa8
a33fe0c
cfd1807
d278c20
2fdbd65
0c9525e
2e2bf08
b8a73f5
52cb1bf
df9b25c
a29f395
fd8dafc
3b11414
083bb16
0061dc0
25767c6
c2aea9b
534cf82
f8d8de5
3ec6823
85d4de0
376d20c
b9d2b72
dc2fedd
1624981
81f252b
6cc07b4
f73ba71
c646d9f
c46b42a
3b36858
49570b0
d18641e
40846c6
c678223
c642ef8
774e4d5
d2cc9cc
33af1ce
ccb213f
88eaf4a
271b704
8cf2324
2e4ce81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,10 @@ | ||
import { User } from "api/typesGenerated"; | ||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"; | ||
import { nonInitialPage } from "components/PaginationWidget/utils"; | ||
import { useMe } from "hooks/useMe"; | ||
import { usePermissions } from "hooks/usePermissions"; | ||
import { FC, ReactNode, useState } from "react"; | ||
import { Helmet } from "react-helmet-async"; | ||
import { useSearchParams, useNavigate } from "react-router-dom"; | ||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; | ||
import { ResetPasswordDialog } from "./ResetPasswordDialog"; | ||
import { pageTitle } from "utils/page"; | ||
import { UsersPageView } from "./UsersPageView"; | ||
import { useStatusFilterMenu } from "./UsersFilter"; | ||
import { useFilter } from "components/Filter/filter"; | ||
import { useDashboard } from "components/Dashboard/DashboardProvider"; | ||
import { useMutation, useQuery, useQueryClient } from "react-query"; | ||
import { type FC, type ReactNode, useState } from "react"; | ||
|
||
import { type User } from "api/typesGenerated"; | ||
import { roles } from "api/queries/roles"; | ||
import { groupsByUserId } from "api/queries/groups"; | ||
import { getErrorMessage } from "api/errors"; | ||
import { deploymentConfig } from "api/queries/deployment"; | ||
import { prepareQuery } from "utils/filters"; | ||
import { usePagination } from "hooks"; | ||
import { | ||
users, | ||
suspendUser, | ||
|
@@ -27,38 +14,55 @@ import { | |
updateRoles, | ||
authMethods, | ||
} from "api/queries/users"; | ||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; | ||
import { getErrorMessage } from "api/errors"; | ||
|
||
import { useMutation, useQuery, useQueryClient } from "react-query"; | ||
import { useSearchParams, useNavigate } from "react-router-dom"; | ||
import { useOrganizationId, usePagination } from "hooks"; | ||
import { useMe } from "hooks/useMe"; | ||
import { usePermissions } from "hooks/usePermissions"; | ||
import { useStatusFilterMenu } from "./UsersFilter"; | ||
import { useFilter } from "components/Filter/filter"; | ||
import { useDashboard } from "components/Dashboard/DashboardProvider"; | ||
import { generateRandomString } from "utils/random"; | ||
import { prepareQuery } from "utils/filters"; | ||
|
||
import { Helmet } from "react-helmet-async"; | ||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"; | ||
import { nonInitialPage } from "components/PaginationWidget/utils"; | ||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; | ||
import { ResetPasswordDialog } from "./ResetPasswordDialog"; | ||
import { pageTitle } from "utils/page"; | ||
import { UsersPageView } from "./UsersPageView"; | ||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; | ||
|
||
export const UsersPage: FC<{ children?: ReactNode }> = () => { | ||
const queryClient = useQueryClient(); | ||
const navigate = useNavigate(); | ||
|
||
const searchParamsResult = useSearchParams(); | ||
const { entitlements } = useDashboard(); | ||
const [searchParams] = searchParamsResult; | ||
const filter = searchParams.get("filter") ?? ""; | ||
const pagination = usePagination({ | ||
searchParamsResult, | ||
}); | ||
|
||
const pagination = usePagination({ searchParamsResult }); | ||
const usersQuery = useQuery( | ||
users({ | ||
q: prepareQuery(filter), | ||
q: prepareQuery(searchParams.get("filter") ?? ""), | ||
limit: pagination.limit, | ||
offset: pagination.offset, | ||
}), | ||
); | ||
|
||
const organizationId = useOrganizationId(); | ||
const groupsByUserIdQuery = useQuery(groupsByUserId(organizationId)); | ||
const authMethodsQuery = useQuery(authMethods()); | ||
|
||
const { updateUsers: canEditUsers, viewDeploymentValues } = usePermissions(); | ||
const rolesQuery = useQuery(roles()); | ||
const { data: deploymentValues } = useQuery({ | ||
...deploymentConfig(), | ||
enabled: viewDeploymentValues, | ||
}); | ||
// Indicates if oidc roles are synced from the oidc idp. | ||
// Assign 'false' if unknown. | ||
const oidcRoleSyncEnabled = | ||
viewDeploymentValues && | ||
deploymentValues?.config.oidc?.user_role_field !== ""; | ||
|
||
const me = useMe(); | ||
const useFilterResult = useFilter({ | ||
searchParamsResult, | ||
|
@@ -74,36 +78,47 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => { | |
status: option?.value, | ||
}), | ||
}); | ||
const authMethodsQuery = useQuery(authMethods()); | ||
const isLoading = | ||
usersQuery.isLoading || rolesQuery.isLoading || authMethodsQuery.isLoading; | ||
|
||
const [confirmSuspendUser, setConfirmSuspendUser] = useState<User>(); | ||
const [userToSuspend, setUserToSuspend] = useState<User>(); | ||
const suspendUserMutation = useMutation(suspendUser(queryClient)); | ||
|
||
const [confirmActivateUser, setConfirmActivateUser] = useState<User>(); | ||
const [userToActivate, setUserToActivate] = useState<User>(); | ||
const activateUserMutation = useMutation(activateUser(queryClient)); | ||
|
||
const [confirmDeleteUser, setConfirmDeleteUser] = useState<User>(); | ||
const [userToDelete, setUserToDelete] = useState<User>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Way better names 🙏 |
||
const deleteUserMutation = useMutation(deleteUser(queryClient)); | ||
|
||
const [confirmResetPassword, setConfirmResetPassword] = useState<{ | ||
user: User; | ||
newPassword: string; | ||
}>(); | ||
const updatePasswordMutation = useMutation(updatePassword()); | ||
|
||
const updatePasswordMutation = useMutation(updatePassword()); | ||
const updateRolesMutation = useMutation(updateRoles(queryClient)); | ||
|
||
// Indicates if oidc roles are synced from the oidc idp. | ||
// Assign 'false' if unknown. | ||
const oidcRoleSyncEnabled = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this used somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly, I'm not completely sure. I didn't change the code or the comment, and I didn't dig deep enough into some of the components to see where it was actually used |
||
viewDeploymentValues && | ||
deploymentValues?.config.oidc?.user_role_field !== ""; | ||
|
||
const isLoading = | ||
usersQuery.isLoading || | ||
rolesQuery.isLoading || | ||
authMethodsQuery.isLoading || | ||
groupsByUserIdQuery.isLoading; | ||
|
||
return ( | ||
<> | ||
<Helmet> | ||
<title>{pageTitle("Users")}</title> | ||
</Helmet> | ||
|
||
<UsersPageView | ||
oidcRoleSyncEnabled={oidcRoleSyncEnabled} | ||
roles={rolesQuery.data} | ||
users={usersQuery.data?.users} | ||
groupsByUserId={groupsByUserIdQuery.data} | ||
authMethods={authMethodsQuery.data} | ||
onListWorkspaces={(user) => { | ||
navigate( | ||
|
@@ -116,9 +131,9 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => { | |
"/audit?filter=" + encodeURIComponent(`username:${user.username}`), | ||
); | ||
}} | ||
onDeleteUser={setConfirmDeleteUser} | ||
onSuspendUser={setConfirmSuspendUser} | ||
onActivateUser={setConfirmActivateUser} | ||
onDeleteUser={setUserToDelete} | ||
onSuspendUser={setUserToSuspend} | ||
onActivateUser={setUserToActivate} | ||
onResetUserPassword={(user) => { | ||
setConfirmResetPassword({ | ||
user, | ||
|
@@ -147,9 +162,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => { | |
filterProps={{ | ||
filter: useFilterResult, | ||
error: usersQuery.error, | ||
menus: { | ||
status: statusMenu, | ||
}, | ||
menus: { status: statusMenu }, | ||
}} | ||
count={usersQuery.data?.count} | ||
page={pagination.page} | ||
|
@@ -158,75 +171,69 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => { | |
/> | ||
|
||
<DeleteDialog | ||
key={confirmDeleteUser?.username} | ||
isOpen={confirmDeleteUser !== undefined} | ||
key={userToDelete?.username} | ||
isOpen={userToDelete !== undefined} | ||
confirmLoading={deleteUserMutation.isLoading} | ||
name={confirmDeleteUser?.username ?? ""} | ||
name={userToDelete?.username ?? ""} | ||
entity="user" | ||
onCancel={() => setUserToDelete(undefined)} | ||
onConfirm={async () => { | ||
try { | ||
await deleteUserMutation.mutateAsync(confirmDeleteUser!.id); | ||
setConfirmDeleteUser(undefined); | ||
await deleteUserMutation.mutateAsync(userToDelete!.id); | ||
setUserToDelete(undefined); | ||
displaySuccess("Successfully deleted the user."); | ||
} catch (e) { | ||
displayError(getErrorMessage(e, "Error deleting user.")); | ||
} | ||
}} | ||
onCancel={() => { | ||
setConfirmDeleteUser(undefined); | ||
}} | ||
/> | ||
|
||
<ConfirmDialog | ||
type="delete" | ||
hideCancel={false} | ||
open={confirmSuspendUser !== undefined} | ||
open={userToSuspend !== undefined} | ||
confirmLoading={suspendUserMutation.isLoading} | ||
title="Suspend user" | ||
confirmText="Suspend" | ||
onClose={() => setUserToSuspend(undefined)} | ||
onConfirm={async () => { | ||
try { | ||
await suspendUserMutation.mutateAsync(confirmSuspendUser!.id); | ||
setConfirmSuspendUser(undefined); | ||
await suspendUserMutation.mutateAsync(userToSuspend!.id); | ||
setUserToSuspend(undefined); | ||
displaySuccess("Successfully suspended the user."); | ||
} catch (e) { | ||
displayError(getErrorMessage(e, "Error suspending user.")); | ||
} | ||
}} | ||
onClose={() => { | ||
setConfirmSuspendUser(undefined); | ||
}} | ||
description={ | ||
<> | ||
Do you want to suspend the user{" "} | ||
<strong>{confirmSuspendUser?.username ?? ""}</strong>? | ||
<strong>{userToSuspend?.username ?? ""}</strong>? | ||
</> | ||
} | ||
/> | ||
|
||
<ConfirmDialog | ||
type="success" | ||
hideCancel={false} | ||
open={confirmActivateUser !== undefined} | ||
open={userToActivate !== undefined} | ||
confirmLoading={activateUserMutation.isLoading} | ||
title="Activate user" | ||
confirmText="Activate" | ||
onClose={() => setUserToActivate(undefined)} | ||
onConfirm={async () => { | ||
try { | ||
await activateUserMutation.mutateAsync(confirmActivateUser!.id); | ||
setConfirmActivateUser(undefined); | ||
await activateUserMutation.mutateAsync(userToActivate!.id); | ||
setUserToActivate(undefined); | ||
displaySuccess("Successfully activated the user."); | ||
} catch (e) { | ||
displayError(getErrorMessage(e, "Error activating user.")); | ||
} | ||
}} | ||
onClose={() => { | ||
setConfirmActivateUser(undefined); | ||
}} | ||
description={ | ||
<> | ||
Do you want to activate{" "} | ||
<strong>{confirmActivateUser?.username ?? ""}</strong>? | ||
<strong>{userToActivate?.username ?? ""}</strong>? | ||
</> | ||
} | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import TableCell from "@mui/material/TableCell"; | ||
import { GroupsByUserId } from "api/queries/groups"; | ||
import { User } from "api/typesGenerated"; | ||
|
||
type GroupsCellProps = { | ||
user: User; | ||
groupsByUserId: GroupsByUserId | undefined; | ||
}; | ||
|
||
export function GroupsCell({ user, groupsByUserId }: GroupsCellProps) { | ||
return <TableCell>5 Groups</TableCell>; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost all the diffs for this file are me just moving things around to group them better, and renaming some variables for clarity. The major things to look for are any code involving
groupsByUserIdQuery