From c94c3f18ed2daa756626103cb7a6c23050866f5c Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Tue, 11 Feb 2025 16:45:45 +0000 Subject: [PATCH 1/5] fix(table empty state): smaller size for loader --- .../components/table-messages-empty-state.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dashboard-messages/components/table-messages-empty-state.tsx b/src/features/dashboard-messages/components/table-messages-empty-state.tsx index 9f97b2c7..72194289 100644 --- a/src/features/dashboard-messages/components/table-messages-empty-state.tsx +++ b/src/features/dashboard-messages/components/table-messages-empty-state.tsx @@ -26,7 +26,7 @@ function EmptyStateLoading() { actions={null} /> ); From 309e4484d20cf31e14039094b036184119ee692c Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Tue, 11 Feb 2025 16:46:06 +0000 Subject: [PATCH 2/5] fix(empty state): spacing tweaks requested by James --- src/components/empty-state.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx index 6f2616c6..12799ecc 100644 --- a/src/components/empty-state.tsx +++ b/src/components/empty-state.tsx @@ -30,12 +30,12 @@ export function EmptyState({ actions: [ReactNode, ReactNode?] | null; }) { return ( -
- +
+ {title} -

{body}

+

{body}

{actions ? : null}
); From 2bbb3ddffb2becaa254ad4116a3e9fb1dcc43f60 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Tue, 11 Feb 2025 16:47:09 +0000 Subject: [PATCH 3/5] fix(workspace selector): navigate back to home on activating workspace --- .../header-active-workspace-selector.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/features/header/components/header-active-workspace-selector.tsx b/src/features/header/components/header-active-workspace-selector.tsx index ea204d6e..89618312 100644 --- a/src/features/header/components/header-active-workspace-selector.tsx +++ b/src/features/header/components/header-active-workspace-selector.tsx @@ -19,10 +19,17 @@ import { hrefs } from "@/lib/hrefs"; import { twMerge } from "tailwind-merge"; import ChevronDown from "@untitled-ui/icons-react/build/cjs/ChevronDown"; import { SearchMd, Settings01 } from "@untitled-ui/icons-react"; +import { useLocation, useNavigate } from "react-router-dom"; + +const ROUTES_REQUIRING_REDIRECT = [/^\/$/, /^\/prompt\/(.*)$/]; export function HeaderActiveWorkspaceSelector() { const queryClient = useQueryClient(); + const navigate = useNavigate(); + const location = useLocation(); + const { pathname } = location; + const { data: workspacesResponse } = useQueryListWorkspaces(); const { mutateAsync: activateWorkspace } = useMutationActivateWorkspace(); @@ -32,13 +39,16 @@ export function HeaderActiveWorkspaceSelector() { const [searchWorkspace, setSearchWorkspace] = useState(""); const workspaces = workspacesResponse?.workspaces ?? []; const filteredWorkspaces = workspaces.filter((workspace) => - workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase()), + workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase()) ); const handleWorkspaceClick = (name: string) => { activateWorkspace({ body: { name } }).then(() => { // eslint-disable-next-line no-restricted-syntax queryClient.invalidateQueries({ refetchType: "all" }); // Global setting, refetch **everything** + if (ROUTES_REQUIRING_REDIRECT.some((route) => route.test(pathname))) { + navigate("/"); + } setIsOpen(false); }); }; @@ -86,7 +96,7 @@ export function HeaderActiveWorkspaceSelector() { { "!bg-gray-900 hover:bg-gray-900 !text-gray-25 hover:!text-gray-25": item.is_active, - }, + } )} > {item.name} @@ -100,12 +110,12 @@ export function HeaderActiveWorkspaceSelector() { "ml-auto size-6 group-hover/selector:opacity-100 opacity-0 transition-opacity", item.is_active ? "hover:bg-gray-800 pressed:bg-gray-700" - : "hover:bg-gray-50 hover:text-primary", + : "hover:bg-gray-50 hover:text-primary" )} > From 8e4ba40212ba95b72fa25a17a5e95fec492c2d03 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Tue, 11 Feb 2025 16:47:34 +0000 Subject: [PATCH 4/5] fix(table empty state): default to loading state, handle error state separately --- .../components/table-messages-empty-state.tsx | 41 +++++++++---------- .../components/table-messages.tsx | 13 ++++-- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/features/dashboard-messages/components/table-messages-empty-state.tsx b/src/features/dashboard-messages/components/table-messages-empty-state.tsx index 72194289..64d7cafb 100644 --- a/src/features/dashboard-messages/components/table-messages-empty-state.tsx +++ b/src/features/dashboard-messages/components/table-messages-empty-state.tsx @@ -20,13 +20,16 @@ import { } from "../hooks/use-messages-filter-search-params"; import { match, P } from "ts-pattern"; import { useQueryGetWorkspaceMessages } from "@/hooks/use-query-get-workspace-messages"; +import { twMerge } from "tailwind-merge"; function EmptyStateLoading() { return ( ( + )} actions={null} /> ); @@ -117,7 +120,7 @@ function EmptyStateSecrets() { ); } -function EmptyStateError() { +export function EmptyStateError() { return ( , - ) - .with( - { - hasWorkspaceMessages: false, hasMultipleWorkspaces: false, - search: P._, - view: P._, + hasWorkspaceMessages: false, + isLoading: false, + search: P.any, + view: P.any, }, () => , ) .with( { - hasWorkspaceMessages: true, hasMultipleWorkspaces: P.any, + hasWorkspaceMessages: true, + isLoading: false, search: P.string.select(), - view: P._, + view: P.any, }, (search) => , ) .with( { + hasMultipleWorkspaces: true, hasWorkspaceMessages: false, - hasMultipleWorkspaces: P.any, - search: P._, - view: P.any, + isLoading: false, + search: P.any, + view: AlertsFilterView.ALL, }, () => , ) .with( { - hasWorkspaceMessages: true, hasMultipleWorkspaces: P.any, - search: P._, + hasWorkspaceMessages: true, + isLoading: false, + search: P.any, view: AlertsFilterView.MALICIOUS, }, () => , @@ -230,5 +227,5 @@ export function TableAlertsEmptyState() { }, () => , ) - .otherwise(() => ); + .otherwise(() => ); } diff --git a/src/features/dashboard-messages/components/table-messages.tsx b/src/features/dashboard-messages/components/table-messages.tsx index 6d5e63d4..d6daa594 100644 --- a/src/features/dashboard-messages/components/table-messages.tsx +++ b/src/features/dashboard-messages/components/table-messages.tsx @@ -17,7 +17,10 @@ import { TableAlertTokenUsage } from "./table-alert-token-usage"; import { useMessagesFilterSearchParams } from "../hooks/use-messages-filter-search-params"; import { Key01, PackageX } from "@untitled-ui/icons-react"; -import { TableAlertsEmptyState } from "./table-messages-empty-state"; +import { + EmptyStateError, + TableAlertsEmptyState, +} from "./table-messages-empty-state"; import { hrefs } from "@/lib/hrefs"; import { isAlertMalicious } from "../../../lib/is-alert-malicious"; import { isAlertSecret } from "../../../lib/is-alert-secret"; @@ -145,7 +148,7 @@ function CellRenderer({ export function TableMessages() { const { state, prevPage, nextPage } = useMessagesFilterSearchParams(); - const { data = [] } = useQueryGetWorkspaceMessagesTable(); + const { data = [], isError } = useQueryGetWorkspaceMessagesTable(); const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination( data, state.page, @@ -160,7 +163,11 @@ export function TableMessages() { {(column) => } } + renderEmptyState={() => { + if (isError) return ; + + return ; + }} items={dataView} > {(row) => ( From ede12a873327098229b573aba72eb64c8ef15f89 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Wed, 12 Feb 2025 08:19:45 +0000 Subject: [PATCH 5/5] fix: tidy ups to empty state --- src/components/empty-state.tsx | 6 ++-- .../table-messages.empty-state.test.tsx | 30 ++++++++++--------- .../components/table-messages-empty-state.tsx | 21 ++++++------- .../components/table-messages.tsx | 4 +-- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx index 12799ecc..10fab666 100644 --- a/src/components/empty-state.tsx +++ b/src/components/empty-state.tsx @@ -30,12 +30,12 @@ export function EmptyState({ actions: [ReactNode, ReactNode?] | null; }) { return ( -
- +
+ {title} -

{body}

+

{body}

{actions ? : null}
); diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx index c48c30a1..27b7d0c2 100644 --- a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx @@ -6,9 +6,9 @@ import { useSearchParams } from "react-router-dom"; import { delay, http, HttpHandler, HttpResponse } from "msw"; import { mockAlert } from "../../../../mocks/msw/mockers/alert.mock"; import { AlertsFilterView } from "../../hooks/use-messages-filter-search-params"; -import { TableMessages } from "../table-messages"; import { hrefs } from "@/lib/hrefs"; import { mswEndpoint } from "@/test/msw-endpoint"; +import { TableMessagesEmptyState } from "../table-messages-empty-state"; enum IllustrationTestId { ALERT = "illustration-alert", @@ -48,7 +48,7 @@ type TestCase = { vi.mock("react-router-dom", async () => { const original = await vi.importActual( - "react-router-dom" + "react-router-dom", ); return { ...original, @@ -59,7 +59,7 @@ vi.mock("react-router-dom", async () => { vi.mock("@stacklok/ui-kit", async () => { const original = await vi.importActual( - "@stacklok/ui-kit" + "@stacklok/ui-kit", ); return { ...original, @@ -116,7 +116,7 @@ const TEST_CASES: TestCase[] = [ mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { return HttpResponse.json([]); - } + }, ), ], searchParams: { @@ -158,9 +158,9 @@ const TEST_CASES: TestCase[] = [ mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { return HttpResponse.json( - Array.from({ length: 10 }, () => mockAlert({ type: "malicious" })) + Array.from({ length: 10 }, () => mockAlert({ type: "malicious" })), ); - } + }, ), ], searchParams: { search: "foo-bar", view: AlertsFilterView.ALL }, @@ -202,7 +202,7 @@ const TEST_CASES: TestCase[] = [ mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { return HttpResponse.json([]); - } + }, ), ], searchParams: { @@ -248,9 +248,9 @@ const TEST_CASES: TestCase[] = [ mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { return HttpResponse.json( - Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" })) + Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" })), ); - } + }, ), ], searchParams: { @@ -291,10 +291,10 @@ const TEST_CASES: TestCase[] = [ () => { return HttpResponse.json( Array.from({ length: 10 }).map(() => - mockAlert({ type: "malicious" }) - ) + mockAlert({ type: "malicious" }), + ), ); - } + }, ), ], searchParams: { @@ -321,11 +321,13 @@ test.each(TEST_CASES)("$testDescription", async (testCase) => { () => {}, ]); - const { getByText, getByRole, getByTestId } = render(); + const { getByText, getByRole, getByTestId } = render( + , + ); await waitFor(() => { expect( - getByRole("heading", { level: 4, name: testCase.expected.title }) + getByRole("heading", { level: 4, name: testCase.expected.title }), ).toBeVisible(); expect(getByText(testCase.expected.body)).toBeVisible(); expect(getByTestId(testCase.expected.illustrationTestId)).toBeVisible(); diff --git a/src/features/dashboard-messages/components/table-messages-empty-state.tsx b/src/features/dashboard-messages/components/table-messages-empty-state.tsx index 64d7cafb..0461b54b 100644 --- a/src/features/dashboard-messages/components/table-messages-empty-state.tsx +++ b/src/features/dashboard-messages/components/table-messages-empty-state.tsx @@ -160,7 +160,7 @@ type MatchInput = { view: AlertsFilterView | null; }; -export function TableAlertsEmptyState() { +export function TableMessagesEmptyState() { const { state, setSearch } = useMessagesFilterSearchParams(); const { data: messages = [], isLoading: isMessagesLoading } = @@ -181,41 +181,41 @@ export function TableAlertsEmptyState() { }) .with( { - hasMultipleWorkspaces: false, hasWorkspaceMessages: false, - isLoading: false, + hasMultipleWorkspaces: false, search: P.any, view: P.any, + isLoading: false, }, () => , ) .with( { - hasMultipleWorkspaces: P.any, hasWorkspaceMessages: true, - isLoading: false, + hasMultipleWorkspaces: P.any, search: P.string.select(), view: P.any, + isLoading: false, }, (search) => , ) .with( { - hasMultipleWorkspaces: true, hasWorkspaceMessages: false, - isLoading: false, + hasMultipleWorkspaces: P.any, search: P.any, - view: AlertsFilterView.ALL, + view: P.any, + isLoading: false, }, () => , ) .with( { - hasMultipleWorkspaces: P.any, hasWorkspaceMessages: true, - isLoading: false, + hasMultipleWorkspaces: P.any, search: P.any, view: AlertsFilterView.MALICIOUS, + isLoading: false, }, () => , ) @@ -224,6 +224,7 @@ export function TableAlertsEmptyState() { hasWorkspaceMessages: true, hasMultipleWorkspaces: P.any, view: AlertsFilterView.SECRETS, + isLoading: false, }, () => , ) diff --git a/src/features/dashboard-messages/components/table-messages.tsx b/src/features/dashboard-messages/components/table-messages.tsx index d6daa594..166238e0 100644 --- a/src/features/dashboard-messages/components/table-messages.tsx +++ b/src/features/dashboard-messages/components/table-messages.tsx @@ -19,7 +19,7 @@ import { useMessagesFilterSearchParams } from "../hooks/use-messages-filter-sear import { Key01, PackageX } from "@untitled-ui/icons-react"; import { EmptyStateError, - TableAlertsEmptyState, + TableMessagesEmptyState, } from "./table-messages-empty-state"; import { hrefs } from "@/lib/hrefs"; import { isAlertMalicious } from "../../../lib/is-alert-malicious"; @@ -166,7 +166,7 @@ export function TableMessages() { renderEmptyState={() => { if (isError) return ; - return ; + return ; }} items={dataView} >