Skip to content

Commit

Permalink
Admin user management (#1393)
Browse files Browse the repository at this point in the history
part of #1022

    Allow updating the show_on_leaderboard field.
    Add raw JSON of the user object.
    Add a new user message table.
    Also fixed style issue: hard to see reaction count when the user also reacted.
    Rename MessageTable to MessageConversation.
  • Loading branch information
notmd committed Feb 10, 2023
1 parent 4ab5f58 commit aaa1276
Show file tree
Hide file tree
Showing 26 changed files with 409 additions and 135 deletions.
18 changes: 18 additions & 0 deletions website/package-lock.json

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

1 change: 1 addition & 0 deletions website/package.json
Expand Up @@ -45,6 +45,7 @@
"axios": "^1.2.1",
"boolean": "^3.2.0",
"clsx": "^1.2.1",
"date-fns": "^2.29.3",
"eslint": "8.29.0",
"eslint-config-next": "13.0.6",
"eslint-plugin-simple-import-sort": "^8.0.0",
Expand Down
10 changes: 10 additions & 0 deletions website/src/components/DataTable/DataTableAction.tsx
@@ -0,0 +1,10 @@
import { forwardRef, IconButton, IconButtonProps } from "@chakra-ui/react";
import { LucideIcon } from "lucide-react";

export type DataTableActionProps = Omit<IconButtonProps, "icon" | "size"> & { icon: LucideIcon };

// need to use forwardRef from Charka to support `as` props
// https://chakra-ui.com/community/recipes/as-prop
export const DataTableAction = forwardRef<DataTableActionProps, "button">((props: DataTableActionProps, ref) => {
return <IconButton size="sm" {...props} icon={<props.icon size="20"></props.icon>} ref={ref} />;
});
40 changes: 40 additions & 0 deletions website/src/components/DataTable/useCursorPagination.ts
@@ -0,0 +1,40 @@
import { useState } from "react";

export interface CursorPaginationState {
/**
* The user's `display_name` used for pagination.
*/
cursor: string;

/**
* The pagination direction.
*/
direction: "forward" | "back";
}

export const useCursorPagination = () => {
const [pagination, setPagination] = useState<CursorPaginationState>({ cursor: "", direction: "forward" });

const toPreviousPage = (data: undefined | { prev?: string; next?: string }) => {
setPagination({
cursor: data?.prev || "",
direction: "back",
});
};

const toNextPage = (data: undefined | { prev?: string; next?: string }) => {
setPagination({
cursor: data?.next || "",
direction: "forward",
});
};

const resetCursor = () => setPagination((old) => ({ ...old, cursor: "" }));

return {
pagination,
toNextPage,
toPreviousPage,
resetCursor,
};
};
12 changes: 12 additions & 0 deletions website/src/components/JsonCard.tsx
@@ -0,0 +1,12 @@
import { Card, CardBody } from "@chakra-ui/card";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const JsonCard = ({ children }: { children: any }) => {
return (
<Card variant="json">
<CardBody overflowX="auto">
<pre>{JSON.stringify(children, null, 2)}</pre>
</CardBody>
</Card>
);
};
110 changes: 110 additions & 0 deletions website/src/components/Messages/AdminMessageTable.tsx
@@ -0,0 +1,110 @@
import { Avatar } from "@chakra-ui/avatar";
import { Badge, Flex } from "@chakra-ui/layout";
import { Tooltip } from "@chakra-ui/react";
import { createColumnHelper } from "@tanstack/table-core";
import { formatDistanceToNow, formatISO9075 } from "date-fns";
import { Eye } from "lucide-react";
import NextLink from "next/link";
import { ROUTES } from "src/lib/routes";
import { Message } from "src/types/Conversation";
import { isKnownEmoji } from "src/types/Emoji";
import { StrictOmit } from "src/types/utils";

import { DataTable, DataTableProps } from "../DataTable/DataTable";
import { DataTableAction } from "../DataTable/DataTableAction";
import { MessageEmojiButton } from "./MessageEmojiButton";

const columnHelper = createColumnHelper<Message>();

const columns = [
columnHelper.accessor("text", {
cell: ({ getValue, row }) => {
const limit = 80;
const text = getValue();
const renderText = text.length > limit ? `${text.slice(0, limit)}...` : text;
return (
<Flex alignItems="center">
<Avatar
size="xs"
mr="2"
src={`${row.original.is_assistant ? "/images/logos/logo.png" : "/images/temp-avatars/av1.jpg"}`}
></Avatar>
{renderText}
{row.original.deleted && (
<Badge colorScheme="red" ml="1">
Deleted
</Badge>
)}
</Flex>
);
},
}),
columnHelper.accessor("lang", {
header: "Language",
cell: ({ getValue }) => <Badge>{getValue()}</Badge>,
}),
columnHelper.accessor("emojis", {
header: "Reactions",
cell: ({ getValue, row }) => {
const emojis = getValue();

emojis["+1"] = emojis["+1"] || 0;
emojis["-1"] = emojis["-1"] || 0;

return (
<Flex gap="2">
{Object.entries(emojis)
.filter(([emoji]) => isKnownEmoji(emoji))
.sort(([emoji]) => -emoji)
.map(([emoji, count]) => {
return (
<MessageEmojiButton
key={emoji}
emoji={{ name: emoji, count }}
checked={row.original.user_emojis.includes(emoji)}
userReacted={false}
userIsAuthor={false}
sx={{
":disabled": {
opacity: 1,
},
}}
/>
);
})}
</Flex>
);
},
}),
columnHelper.accessor("created_date", {
header: "Date",
cell: ({ getValue }) => {
return <DateDiff>{getValue()}</DateDiff>;
},
}),
columnHelper.accessor((row) => row.id, {
header: "Actions",
cell: ({ getValue }) => (
<DataTableAction
as={NextLink}
href={ROUTES.ADMIN_MESSAGE_DETAIL(getValue())}
icon={Eye}
aria-label="View message"
/>
),
}),
];
// TODO move this to somewhere
const DateDiff = ({ children }: { children: string | Date | number }) => {
const date = new Date(children);
const diff = formatDistanceToNow(date, { addSuffix: true });
return (
<Tooltip label={formatISO9075(date)} placement="top">
{diff}
</Tooltip>
);
};

export const AdminMessageTable = (props: StrictOmit<DataTableProps<Message>, "columns">) => {
return <DataTable columns={columns} {...props}></DataTable>;
};
Expand Up @@ -2,12 +2,12 @@ import { SessionProvider } from "next-auth/react";
import React from "react";
import { Message } from "src/types/Conversation";

import { MessageTable } from "./MessageTable";
import { MessageConversation } from "./MessageConversation";

// eslint-disable-next-line import/no-anonymous-default-export
export default {
title: "Messages/MessageTable",
component: MessageTable,
component: MessageConversation,
};

const Template = ({
Expand All @@ -21,7 +21,7 @@ const Template = ({
}) => {
return (
<SessionProvider>
<MessageTable messages={messages} enableLink={enableLink} highlightLastMessage={highlightLastMessage} />;
<MessageConversation messages={messages} enableLink={enableLink} highlightLastMessage={highlightLastMessage} />;
</SessionProvider>
);
};
Expand Down
Expand Up @@ -2,13 +2,13 @@ import { Stack } from "@chakra-ui/react";
import { MessageTableEntry } from "src/components/Messages/MessageTableEntry";
import { Message } from "src/types/Conversation";

interface MessageTableProps {
interface MessageConversationProps {
messages: Message[];
enableLink?: boolean;
highlightLastMessage?: boolean;
}

export function MessageTable({ messages, enableLink, highlightLastMessage }: MessageTableProps) {
export function MessageConversation({ messages, enableLink, highlightLastMessage }: MessageConversationProps) {
return (
<Stack spacing="4">
{messages.map((message, idx) => (
Expand Down
9 changes: 6 additions & 3 deletions website/src/components/Messages/MessageEmojiButton.tsx
@@ -1,15 +1,16 @@
import { Button } from "@chakra-ui/react";
import { Button, ButtonProps } from "@chakra-ui/react";
import { useHasRole } from "src/hooks/auth/useHasRole";
import { MessageEmoji } from "src/types/Conversation";
import { emojiIcons } from "src/types/Emoji";

interface MessageEmojiButtonProps {
emoji: MessageEmoji;
checked?: boolean;
onClick: () => void;
onClick?: () => void;
userIsAuthor: boolean;
disabled?: boolean;
userReacted: boolean;
sx?: ButtonProps["sx"];
}

export const MessageEmojiButton = ({
Expand All @@ -19,6 +20,7 @@ export const MessageEmojiButton = ({
userIsAuthor,
disabled,
userReacted,
sx,
}: MessageEmojiButtonProps) => {
const EmojiIcon = emojiIcons.get(emoji.name);
const isAdmin = useHasRole("admin");
Expand All @@ -42,8 +44,9 @@ export const MessageEmojiButton = ({
":hover": {
backgroundColor: isDisabled ? "transparent" : undefined,
},
...sx,
}}
color={isDisabled ? "gray.500" : undefined}
color={isDisabled ? (checked ? "gray.700" : "gray.500") : undefined}
>
<EmojiIcon style={{ height: "1em" }} />
{showCount && <span style={{ marginInlineEnd: "0.25em" }}>{emoji.count}</span>}
Expand Down
4 changes: 2 additions & 2 deletions website/src/components/Tasks/CreateTask.tsx
@@ -1,7 +1,7 @@
import { Box, Stack, Text, useColorModeValue } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import { useState } from "react";
import { MessageTable } from "src/components/Messages/MessageTable";
import { MessageConversation } from "src/components/Messages/MessageConversation";
import { TrackedTextarea } from "src/components/Survey/TrackedTextarea";
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
import { TaskSurveyProps } from "src/components/Tasks/Task";
Expand Down Expand Up @@ -44,7 +44,7 @@ export const CreateTask = ({
<TaskHeader taskType={taskType} />
{task.type !== TaskType.initial_prompt && (
<Box mt="4" borderRadius="lg" bg={cardColor} className="p-3 sm:p-6">
<MessageTable messages={task.conversation.messages} highlightLastMessage />
<MessageConversation messages={task.conversation.messages} highlightLastMessage />
</Box>
)}
</>
Expand Down
4 changes: 2 additions & 2 deletions website/src/components/Tasks/EvaluateTask.tsx
@@ -1,6 +1,6 @@
import { Box, useColorModeValue } from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { MessageTable } from "src/components/Messages/MessageTable";
import { MessageConversation } from "src/components/Messages/MessageConversation";
import { Sortable } from "src/components/Sortable/Sortable";
import { SurveyCard } from "src/components/Survey/SurveyCard";
import { TaskSurveyProps } from "src/components/Tasks/Task";
Expand Down Expand Up @@ -47,7 +47,7 @@ export const EvaluateTask = ({
<SurveyCard>
<TaskHeader taskType={taskType} />
<Box mt="4" p="6" borderRadius="lg" bg={cardColor}>
<MessageTable messages={messages} highlightLastMessage />
<MessageConversation messages={messages} highlightLastMessage />
</Box>
<Sortable
items={task[sortables]}
Expand Down
4 changes: 2 additions & 2 deletions website/src/components/Tasks/LabelTask/LabelTask.tsx
Expand Up @@ -2,7 +2,7 @@ import { Box, useBoolean, useColorModeValue } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import { useEffect, useState } from "react";
import { LabelInputGroup } from "src/components/Messages/LabelInputGroup";
import { MessageTable } from "src/components/Messages/MessageTable";
import { MessageConversation } from "src/components/Messages/MessageConversation";
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
import { TaskSurveyProps } from "src/components/Tasks/Task";
import { TaskHeader } from "src/components/Tasks/TaskHeader";
Expand Down Expand Up @@ -57,7 +57,7 @@ export const LabelTask = ({
<>
<TaskHeader taskType={taskType} />
<Box mt="4" p={[4, 6]} borderRadius="lg" bg={cardColor}>
<MessageTable messages={task.conversation.messages} highlightLastMessage />
<MessageConversation messages={task.conversation.messages} highlightLastMessage />
</Box>
</>
<LabelInputGroup
Expand Down
40 changes: 0 additions & 40 deletions website/src/components/UserMessagesCell/UserMessagesCell.tsx

This file was deleted.

1 change: 0 additions & 1 deletion website/src/components/UserMessagesCell/index.tsx

This file was deleted.

0 comments on commit aaa1276

Please sign in to comment.