diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx index 6f2616c6..10fab666 100644 --- a/src/components/empty-state.tsx +++ b/src/components/empty-state.tsx @@ -30,8 +30,8 @@ export function EmptyState({ actions: [ReactNode, ReactNode?] | null; }) { return ( -
- +
+ {title} 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 9f97b2c7..0461b54b 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._, + search: P.any, + view: P.any, + isLoading: false, }, () => , ) @@ -200,7 +194,8 @@ export function TableAlertsEmptyState() { hasWorkspaceMessages: true, hasMultipleWorkspaces: P.any, search: P.string.select(), - view: P._, + view: P.any, + isLoading: false, }, (search) => , ) @@ -208,8 +203,9 @@ export function TableAlertsEmptyState() { { hasWorkspaceMessages: false, hasMultipleWorkspaces: P.any, - search: P._, + search: P.any, view: P.any, + isLoading: false, }, () => , ) @@ -217,8 +213,9 @@ export function TableAlertsEmptyState() { { hasWorkspaceMessages: true, hasMultipleWorkspaces: P.any, - search: P._, + search: P.any, view: AlertsFilterView.MALICIOUS, + isLoading: false, }, () => , ) @@ -227,8 +224,9 @@ export function TableAlertsEmptyState() { hasWorkspaceMessages: true, hasMultipleWorkspaces: P.any, view: AlertsFilterView.SECRETS, + isLoading: false, }, () => , ) - .otherwise(() => ); + .otherwise(() => ); } diff --git a/src/features/dashboard-messages/components/table-messages.tsx b/src/features/dashboard-messages/components/table-messages.tsx index 6d5e63d4..166238e0 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, + TableMessagesEmptyState, +} 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) => ( 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" )} >