Skip to content

Commit

Permalink
feat(auth): add delete user ui (#4609)
Browse files Browse the repository at this point in the history
  • Loading branch information
Parker-Stafford authored Sep 13, 2024
1 parent bbcc24f commit 14df155
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 13 deletions.
2 changes: 1 addition & 1 deletion app/src/components/auth/DeleteAPIKeyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function DeleteAPIKeyButton({

const onDelete = () => {
setDialog(
<Dialog title="Delete System Key">
<Dialog title="Delete API Key">
<View padding="size-200">
<Text color="danger">
{`Are you sure you want to delete this key? This cannot be undone and will disable all uses of this key.`}
Expand Down
112 changes: 112 additions & 0 deletions app/src/pages/settings/DeleteUserButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { ReactNode, useCallback, useState } from "react";
import { graphql, useMutation } from "react-relay";

import {
Button,
Dialog,
DialogContainer,
Flex,
Icon,
Icons,
Text,
View,
} from "@arizeai/components";

import { useNotifyError, useNotifySuccess } from "@phoenix/contexts";

import { DeleteUserButtonMutation } from "./__generated__/DeleteUserButtonMutation.graphql";

export function DeleteUserButton({
userId,
onDeleted,
}: {
userId: string;
onDeleted: () => void;
}) {
const [dialog, setDialog] = useState<ReactNode>(null);

const [commit, isCommitting] = useMutation<DeleteUserButtonMutation>(graphql`
mutation DeleteUserButtonMutation($input: DeleteUsersInput!) {
deleteUsers(input: $input)
}
`);

const notifySuccess = useNotifySuccess();
const notifyError = useNotifyError();

const handleDelete = useCallback(() => {
commit({
variables: {
input: {
userIds: [userId],
},
},
onCompleted: () => {
notifySuccess({
title: "User deleted",
message: "User has been deleted.",
});
onDeleted();
},
onError: (error) => {
notifyError({
title: "Failed to delete user",
message: error.message,
});
},
});
}, [commit, notifyError, notifySuccess, onDeleted, userId]);

const onDelete = () => {
setDialog(
<Dialog title="Delete User">
<View padding="size-200">
<Text color="danger">
{`Are you sure you want to delete this user? This action cannot be undone. The user will be permanently blocked, and cannot be reactivated with the same email or username.`}
</Text>
</View>
<View
paddingEnd="size-200"
paddingTop="size-100"
paddingBottom="size-100"
borderTopColor="light"
borderTopWidth="thin"
>
<Flex direction="row" justifyContent="end">
<Button
variant="danger"
onClick={() => {
handleDelete();
setDialog(null);
}}
>
Delete user
</Button>
</Flex>
</View>
</Dialog>
);
};
return (
<>
<Button
variant="danger"
size="compact"
icon={<Icon svg={<Icons.TrashOutline />} />}
aria-label="Delete User"
onClick={() => {
onDelete();
}}
disabled={isCommitting}
/>
<DialogContainer
isDismissable
onDismiss={() => {
setDialog(null);
}}
>
{dialog}
</DialogContainer>
</>
);
}
48 changes: 43 additions & 5 deletions app/src/pages/settings/UsersTable.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import React, { useMemo } from "react";
import { graphql, useFragment } from "react-relay";
import React, { startTransition, useMemo } from "react";
import { graphql, useRefetchableFragment } from "react-relay";
import {
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";

import { Icon, Icons } from "@arizeai/components";
import { Flex, Icon, Icons } from "@arizeai/components";

import { tableCSS } from "@phoenix/components/table/styles";
import { TableEmpty } from "@phoenix/components/table/TableEmpty";
import { TimestampCell } from "@phoenix/components/table/TimestampCell";

import { UsersTable_users$key } from "./__generated__/UsersTable_users.graphql";
import { UsersTableQuery } from "./__generated__/UsersTableQuery.graphql";
import { DeleteUserButton } from "./DeleteUserButton";

const isDefaultAdminUser = (user: {
email: string;
username?: string | null;
}) => user.email === "admin@localhost" || user.username === "admin";

export function UsersTable({ query }: { query: UsersTable_users$key }) {
const data = useFragment<UsersTable_users$key>(
const [data, refetch] = useRefetchableFragment<
UsersTableQuery,
UsersTable_users$key
>(
graphql`
fragment UsersTable_users on Query {
fragment UsersTable_users on Query
@refetchable(queryName: "UsersTableQuery") {
users {
edges {
user: node {
id
email
username
createdAt
Expand All @@ -38,6 +50,7 @@ export function UsersTable({ query }: { query: UsersTable_users$key }) {

const tableData = useMemo(() => {
return data.users.edges.map(({ user }) => ({
id: user.id,
email: user.email,
username: user.username,
createdAt: user.createdAt,
Expand Down Expand Up @@ -65,6 +78,31 @@ export function UsersTable({ query }: { query: UsersTable_users$key }) {
accessorKey: "createdAt",
cell: TimestampCell,
},
{
header: "",
accessorKey: "id",
size: 10,
cell: ({ row }) => {
if (isDefaultAdminUser(row.original)) {
return null;
}
return (
<Flex direction="row" justifyContent="end" width="100%">
<DeleteUserButton
userId={row.original.id}
onDeleted={() => {
startTransition(() => {
refetch({}, { fetchPolicy: "network-only" });
});
}}
/>
</Flex>
);
},
meta: {
textAlign: "right",
},
},
],
data: tableData,
getCoreRowModel: getCoreRowModel(),
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions app/src/pages/settings/__generated__/UsersCardQuery.graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 14df155

Please sign in to comment.