Skip to content

Commit 725b651

Browse files
committed
Add frontend
1 parent baf8f79 commit 725b651

File tree

11 files changed

+142
-6
lines changed

11 files changed

+142
-6
lines changed

coderd/database/dump.sql

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/migrations/000048_userdelete.up.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ ALTER TABLE users
33

44
DROP INDEX idx_users_email;
55
DROP INDEX idx_users_username;
6+
DROP INDEX users_username_lower_idx;
67
CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE deleted = false;
78
CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE deleted = false;
9+
CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE deleted = false;

coderd/database/unique_constraint.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/users_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,16 @@ func TestDeleteUser(t *testing.T) {
266266
_, another := coderdtest.CreateAnotherUserWithUser(t, api, user.OrganizationID)
267267
err := api.DeleteUser(context.Background(), another.ID)
268268
require.NoError(t, err)
269+
// Attempt to create a user with the same email and username, and delete them again.
270+
another, err = api.CreateUser(context.Background(), codersdk.CreateUserRequest{
271+
Email: another.Email,
272+
Username: another.Username,
273+
Password: "testing",
274+
OrganizationID: user.OrganizationID,
275+
})
276+
require.NoError(t, err)
277+
err = api.DeleteUser(context.Background(), another.ID)
278+
require.NoError(t, err)
269279
})
270280
t.Run("NoPermission", func(t *testing.T) {
271281
t.Parallel()

site/src/api/api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ export const suspendUser = async (userId: TypesGen.User["id"]): Promise<TypesGen
295295
return response.data
296296
}
297297

298+
export const deleteUser = async (userId: TypesGen.User["id"]): Promise<undefined> => {
299+
return await axios.delete(`/api/v2/users/${userId}`)
300+
}
301+
298302
// API definition:
299303
// https://github.com/coder/coder/blob/db665e7261f3c24a272ccec48233a3e276878239/coderd/users.go#L33-L53
300304
export const hasFirstUser = async (): Promise<boolean> => {

site/src/components/UsersTable/UsersTable.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ describe("AuditPage", () => {
1010
render(
1111
<UsersTable
1212
onSuspendUser={() => jest.fn()}
13+
onDeleteUser={() => jest.fn()}
14+
onListWorkspaces={() => jest.fn()}
1315
onActivateUser={() => jest.fn()}
1416
onResetUserPassword={() => jest.fn()}
1517
onUpdateUserRoles={() => jest.fn()}

site/src/components/UsersTable/UsersTable.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export interface UsersTableProps {
2424
isLoading?: boolean
2525
onSuspendUser: (user: TypesGen.User) => void
2626
onActivateUser: (user: TypesGen.User) => void
27+
onDeleteUser: (user: TypesGen.User) => void
28+
onListWorkspaces: (user: TypesGen.User) => void
2729
onResetUserPassword: (user: TypesGen.User) => void
2830
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
2931
}
@@ -32,6 +34,8 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
3234
users,
3335
roles,
3436
onSuspendUser,
37+
onDeleteUser,
38+
onListWorkspaces,
3539
onActivateUser,
3640
onResetUserPassword,
3741
onUpdateUserRoles,
@@ -64,6 +68,8 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
6468
canEditUsers={canEditUsers}
6569
isUpdatingUserRoles={isUpdatingUserRoles}
6670
onActivateUser={onActivateUser}
71+
onDeleteUser={onDeleteUser}
72+
onListWorkspaces={onListWorkspaces}
6773
onResetUserPassword={onResetUserPassword}
6874
onSuspendUser={onSuspendUser}
6975
onUpdateUserRoles={onUpdateUserRoles}

site/src/components/UsersTable/UsersTableBody.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
1414
export const Language = {
1515
emptyMessage: "No users found",
1616
suspendMenuItem: "Suspend",
17+
deleteMenuItem: "Delete",
18+
listWorkspacesMenuItem: "View Workspaces",
1719
activateMenuItem: "Activate",
1820
resetPasswordMenuItem: "Reset password",
1921
}
@@ -25,6 +27,8 @@ interface UsersTableBodyProps {
2527
canEditUsers?: boolean
2628
isLoading?: boolean
2729
onSuspendUser: (user: TypesGen.User) => void
30+
onDeleteUser: (user: TypesGen.User) => void
31+
onListWorkspaces: (user: TypesGen.User) => void
2832
onActivateUser: (user: TypesGen.User) => void
2933
onResetUserPassword: (user: TypesGen.User) => void
3034
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
@@ -34,6 +38,8 @@ export const UsersTableBody: FC<React.PropsWithChildren<UsersTableBodyProps>> =
3438
users,
3539
roles,
3640
onSuspendUser,
41+
onDeleteUser,
42+
onListWorkspaces,
3743
onActivateUser,
3844
onResetUserPassword,
3945
onUpdateUserRoles,
@@ -130,10 +136,20 @@ export const UsersTableBody: FC<React.PropsWithChildren<UsersTableBodyProps>> =
130136
onClick: onActivateUser,
131137
},
132138
]
133-
).concat({
134-
label: Language.resetPasswordMenuItem,
135-
onClick: onResetUserPassword,
136-
})
139+
).concat(
140+
{
141+
label: Language.deleteMenuItem,
142+
onClick: onDeleteUser,
143+
},
144+
{
145+
label: Language.listWorkspacesMenuItem,
146+
onClick: onListWorkspaces,
147+
},
148+
{
149+
label: Language.resetPasswordMenuItem,
150+
onClick: onResetUserPassword,
151+
},
152+
)
137153
}
138154
/>
139155
</TableCell>

site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { XServiceContext } from "../../xServices/StateContext"
1111
import { UsersPageView } from "./UsersPageView"
1212

1313
export const Language = {
14+
deleteDialogTitle: "Delete user",
15+
deleteDialogAction: "Delete",
16+
deleteDialogMessagePrefix: "Do you want to delete the user",
1417
suspendDialogTitle: "Suspend user",
1518
suspendDialogAction: "Suspend",
1619
suspendDialogMessagePrefix: "Do you want to suspend the user",
@@ -25,6 +28,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
2528
const {
2629
users,
2730
getUsersError,
31+
userIdToDelete,
2832
userIdToSuspend,
2933
userIdToActivate,
3034
userIdToResetPassword,
@@ -33,6 +37,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
3337
const navigate = useNavigate()
3438
const [searchParams, setSearchParams] = useSearchParams()
3539
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend)
40+
const userToBeDeleted = users?.find((u) => u.id === userIdToDelete)
3641
const userToBeActivated = users?.find((u) => u.id === userIdToActivate)
3742
const userToResetPassword = users?.find((u) => u.id === userIdToResetPassword)
3843

@@ -84,6 +89,12 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
8489
openUserCreationDialog={() => {
8590
navigate("/users/create")
8691
}}
92+
onListWorkspaces={(user) => {
93+
navigate("/workspaces?filter=" + encodeURIComponent(`owner:${user.username}`))
94+
}}
95+
onDeleteUser={(user) => {
96+
usersSend({ type: "DELETE_USER", userId: user.id })
97+
}}
8798
onSuspendUser={(user) => {
8899
usersSend({ type: "SUSPEND_USER", userId: user.id })
89100
}}
@@ -112,6 +123,26 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
112123
}}
113124
/>
114125

126+
<ConfirmDialog
127+
type="delete"
128+
hideCancel={false}
129+
open={usersState.matches("confirmUserDeletion")}
130+
confirmLoading={usersState.matches("deletingUser")}
131+
title={Language.deleteDialogTitle}
132+
confirmText={Language.deleteDialogAction}
133+
onConfirm={() => {
134+
usersSend("CONFIRM_USER_DELETE")
135+
}}
136+
onClose={() => {
137+
usersSend("CANCEL_USER_DELETE")
138+
}}
139+
description={
140+
<>
141+
{Language.deleteDialogMessagePrefix} <strong>{userToBeDeleted?.username}</strong>?
142+
</>
143+
}
144+
/>
145+
115146
<ConfirmDialog
116147
type="delete"
117148
hideCancel={false}

site/src/pages/UsersPage/UsersPageView.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export interface UsersPageViewProps {
2525
isLoading?: boolean
2626
openUserCreationDialog: () => void
2727
onSuspendUser: (user: TypesGen.User) => void
28+
onDeleteUser: (user: TypesGen.User) => void
29+
onListWorkspaces: (user: TypesGen.User) => void
2830
onActivateUser: (user: TypesGen.User) => void
2931
onResetUserPassword: (user: TypesGen.User) => void
3032
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
@@ -36,6 +38,8 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
3638
roles,
3739
openUserCreationDialog,
3840
onSuspendUser,
41+
onDeleteUser,
42+
onListWorkspaces,
3943
onActivateUser,
4044
onResetUserPassword,
4145
onUpdateUserRoles,
@@ -79,6 +83,8 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
7983
users={users}
8084
roles={roles}
8185
onSuspendUser={onSuspendUser}
86+
onDeleteUser={onDeleteUser}
87+
onListWorkspaces={onListWorkspaces}
8288
onActivateUser={onActivateUser}
8389
onResetUserPassword={onResetUserPassword}
8490
onUpdateUserRoles={onUpdateUserRoles}

0 commit comments

Comments
 (0)