Skip to content

Commit be99509

Browse files
fix: error displayed when activating workspace (#305)
* fix(table empty state): smaller size for loader * fix(empty state): spacing tweaks requested by James * fix(workspace selector): navigate back to home on activating workspace * fix(table empty state): default to loading state, handle error state separately * fix: tidy ups to empty state
1 parent ccbce0c commit be99509

File tree

5 files changed

+59
-42
lines changed

5 files changed

+59
-42
lines changed

src/components/empty-state.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export function EmptyState({
3030
actions: [ReactNode, ReactNode?] | null;
3131
}) {
3232
return (
33-
<div className="max-w-[40rem] mx-auto py-20 flex items-center flex-col text-center text-balance">
34-
<Illustration className="size-28" />
33+
<div className="max-w-[40rem] mx-auto py-32 flex items-center justify-center flex-col text-center text-balance">
34+
<Illustration className="size-32 mb-4" />
3535
<Heading level={4} className="font-bold text-gray-900 mb-2">
3636
{title}
3737
</Heading>

src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx

+16-14
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { useSearchParams } from "react-router-dom";
66
import { delay, http, HttpHandler, HttpResponse } from "msw";
77
import { mockAlert } from "../../../../mocks/msw/mockers/alert.mock";
88
import { AlertsFilterView } from "../../hooks/use-messages-filter-search-params";
9-
import { TableMessages } from "../table-messages";
109
import { hrefs } from "@/lib/hrefs";
1110
import { mswEndpoint } from "@/test/msw-endpoint";
11+
import { TableMessagesEmptyState } from "../table-messages-empty-state";
1212

1313
enum IllustrationTestId {
1414
ALERT = "illustration-alert",
@@ -48,7 +48,7 @@ type TestCase = {
4848
vi.mock("react-router-dom", async () => {
4949
const original =
5050
await vi.importActual<typeof import("react-router-dom")>(
51-
"react-router-dom"
51+
"react-router-dom",
5252
);
5353
return {
5454
...original,
@@ -59,7 +59,7 @@ vi.mock("react-router-dom", async () => {
5959
vi.mock("@stacklok/ui-kit", async () => {
6060
const original =
6161
await vi.importActual<typeof import("@stacklok/ui-kit")>(
62-
"@stacklok/ui-kit"
62+
"@stacklok/ui-kit",
6363
);
6464
return {
6565
...original,
@@ -116,7 +116,7 @@ const TEST_CASES: TestCase[] = [
116116
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
117117
() => {
118118
return HttpResponse.json([]);
119-
}
119+
},
120120
),
121121
],
122122
searchParams: {
@@ -158,9 +158,9 @@ const TEST_CASES: TestCase[] = [
158158
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
159159
() => {
160160
return HttpResponse.json(
161-
Array.from({ length: 10 }, () => mockAlert({ type: "malicious" }))
161+
Array.from({ length: 10 }, () => mockAlert({ type: "malicious" })),
162162
);
163-
}
163+
},
164164
),
165165
],
166166
searchParams: { search: "foo-bar", view: AlertsFilterView.ALL },
@@ -202,7 +202,7 @@ const TEST_CASES: TestCase[] = [
202202
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
203203
() => {
204204
return HttpResponse.json([]);
205-
}
205+
},
206206
),
207207
],
208208
searchParams: {
@@ -248,9 +248,9 @@ const TEST_CASES: TestCase[] = [
248248
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
249249
() => {
250250
return HttpResponse.json(
251-
Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" }))
251+
Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" })),
252252
);
253-
}
253+
},
254254
),
255255
],
256256
searchParams: {
@@ -291,10 +291,10 @@ const TEST_CASES: TestCase[] = [
291291
() => {
292292
return HttpResponse.json(
293293
Array.from({ length: 10 }).map(() =>
294-
mockAlert({ type: "malicious" })
295-
)
294+
mockAlert({ type: "malicious" }),
295+
),
296296
);
297-
}
297+
},
298298
),
299299
],
300300
searchParams: {
@@ -321,11 +321,13 @@ test.each(TEST_CASES)("$testDescription", async (testCase) => {
321321
() => {},
322322
]);
323323

324-
const { getByText, getByRole, getByTestId } = render(<TableMessages />);
324+
const { getByText, getByRole, getByTestId } = render(
325+
<TableMessagesEmptyState />,
326+
);
325327

326328
await waitFor(() => {
327329
expect(
328-
getByRole("heading", { level: 4, name: testCase.expected.title })
330+
getByRole("heading", { level: 4, name: testCase.expected.title }),
329331
).toBeVisible();
330332
expect(getByText(testCase.expected.body)).toBeVisible();
331333
expect(getByTestId(testCase.expected.illustrationTestId)).toBeVisible();

src/features/dashboard-messages/components/table-messages-empty-state.tsx

+17-19
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ import {
2020
} from "../hooks/use-messages-filter-search-params";
2121
import { match, P } from "ts-pattern";
2222
import { useQueryGetWorkspaceMessages } from "@/hooks/use-query-get-workspace-messages";
23+
import { twMerge } from "tailwind-merge";
2324

2425
function EmptyStateLoading() {
2526
return (
2627
<EmptyState
2728
title={emptyStateStrings.title.loading}
2829
body={emptyStateStrings.body.loading}
29-
illustration={Loader}
30+
illustration={(props) => (
31+
<Loader {...props} className={twMerge(props.className, "!size-16")} />
32+
)}
3033
actions={null}
3134
/>
3235
);
@@ -117,7 +120,7 @@ function EmptyStateSecrets() {
117120
);
118121
}
119122

120-
function EmptyStateError() {
123+
export function EmptyStateError() {
121124
return (
122125
<EmptyState
123126
title={emptyStateStrings.title.anErrorOccurred}
@@ -157,7 +160,7 @@ type MatchInput = {
157160
view: AlertsFilterView | null;
158161
};
159162

160-
export function TableAlertsEmptyState() {
163+
export function TableMessagesEmptyState() {
161164
const { state, setSearch } = useMessagesFilterSearchParams();
162165

163166
const { data: messages = [], isLoading: isMessagesLoading } =
@@ -176,22 +179,13 @@ export function TableAlertsEmptyState() {
176179
search: state.search || null,
177180
view: state.view,
178181
})
179-
.with(
180-
{
181-
isLoading: true,
182-
hasWorkspaceMessages: P._,
183-
hasMultipleWorkspaces: P._,
184-
search: P._,
185-
view: P._,
186-
},
187-
() => <EmptyStateLoading />,
188-
)
189182
.with(
190183
{
191184
hasWorkspaceMessages: false,
192185
hasMultipleWorkspaces: false,
193-
search: P._,
194-
view: P._,
186+
search: P.any,
187+
view: P.any,
188+
isLoading: false,
195189
},
196190
() => <EmptyStateGetStarted />,
197191
)
@@ -200,25 +194,28 @@ export function TableAlertsEmptyState() {
200194
hasWorkspaceMessages: true,
201195
hasMultipleWorkspaces: P.any,
202196
search: P.string.select(),
203-
view: P._,
197+
view: P.any,
198+
isLoading: false,
204199
},
205200
(search) => <EmptyStateSearch search={search} setSearch={setSearch} />,
206201
)
207202
.with(
208203
{
209204
hasWorkspaceMessages: false,
210205
hasMultipleWorkspaces: P.any,
211-
search: P._,
206+
search: P.any,
212207
view: P.any,
208+
isLoading: false,
213209
},
214210
() => <EmptyStateNoMessagesInWorkspace />,
215211
)
216212
.with(
217213
{
218214
hasWorkspaceMessages: true,
219215
hasMultipleWorkspaces: P.any,
220-
search: P._,
216+
search: P.any,
221217
view: AlertsFilterView.MALICIOUS,
218+
isLoading: false,
222219
},
223220
() => <EmptyStateMalicious />,
224221
)
@@ -227,8 +224,9 @@ export function TableAlertsEmptyState() {
227224
hasWorkspaceMessages: true,
228225
hasMultipleWorkspaces: P.any,
229226
view: AlertsFilterView.SECRETS,
227+
isLoading: false,
230228
},
231229
() => <EmptyStateSecrets />,
232230
)
233-
.otherwise(() => <EmptyStateError />);
231+
.otherwise(() => <EmptyStateLoading />);
234232
}

src/features/dashboard-messages/components/table-messages.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import { TableAlertTokenUsage } from "./table-alert-token-usage";
1717

1818
import { useMessagesFilterSearchParams } from "../hooks/use-messages-filter-search-params";
1919
import { Key01, PackageX } from "@untitled-ui/icons-react";
20-
import { TableAlertsEmptyState } from "./table-messages-empty-state";
20+
import {
21+
EmptyStateError,
22+
TableMessagesEmptyState,
23+
} from "./table-messages-empty-state";
2124
import { hrefs } from "@/lib/hrefs";
2225
import { isAlertMalicious } from "../../../lib/is-alert-malicious";
2326
import { isAlertSecret } from "../../../lib/is-alert-secret";
@@ -145,7 +148,7 @@ function CellRenderer({
145148
export function TableMessages() {
146149
const { state, prevPage, nextPage } = useMessagesFilterSearchParams();
147150

148-
const { data = [] } = useQueryGetWorkspaceMessagesTable();
151+
const { data = [], isError } = useQueryGetWorkspaceMessagesTable();
149152
const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination(
150153
data,
151154
state.page,
@@ -160,7 +163,11 @@ export function TableMessages() {
160163
{(column) => <Column {...column} id={column.id} />}
161164
</TableHeader>
162165
<TableBody
163-
renderEmptyState={() => <TableAlertsEmptyState />}
166+
renderEmptyState={() => {
167+
if (isError) return <EmptyStateError />;
168+
169+
return <TableMessagesEmptyState />;
170+
}}
164171
items={dataView}
165172
>
166173
{(row) => (

src/features/header/components/header-active-workspace-selector.tsx

+14-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@ import { hrefs } from "@/lib/hrefs";
1919
import { twMerge } from "tailwind-merge";
2020
import ChevronDown from "@untitled-ui/icons-react/build/cjs/ChevronDown";
2121
import { SearchMd, Settings01 } from "@untitled-ui/icons-react";
22+
import { useLocation, useNavigate } from "react-router-dom";
23+
24+
const ROUTES_REQUIRING_REDIRECT = [/^\/$/, /^\/prompt\/(.*)$/];
2225

2326
export function HeaderActiveWorkspaceSelector() {
2427
const queryClient = useQueryClient();
2528

29+
const navigate = useNavigate();
30+
const location = useLocation();
31+
const { pathname } = location;
32+
2633
const { data: workspacesResponse } = useQueryListWorkspaces();
2734
const { mutateAsync: activateWorkspace } = useMutationActivateWorkspace();
2835

@@ -32,13 +39,16 @@ export function HeaderActiveWorkspaceSelector() {
3239
const [searchWorkspace, setSearchWorkspace] = useState("");
3340
const workspaces = workspacesResponse?.workspaces ?? [];
3441
const filteredWorkspaces = workspaces.filter((workspace) =>
35-
workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase()),
42+
workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase())
3643
);
3744

3845
const handleWorkspaceClick = (name: string) => {
3946
activateWorkspace({ body: { name } }).then(() => {
4047
// eslint-disable-next-line no-restricted-syntax
4148
queryClient.invalidateQueries({ refetchType: "all" }); // Global setting, refetch **everything**
49+
if (ROUTES_REQUIRING_REDIRECT.some((route) => route.test(pathname))) {
50+
navigate("/");
51+
}
4252
setIsOpen(false);
4353
});
4454
};
@@ -86,7 +96,7 @@ export function HeaderActiveWorkspaceSelector() {
8696
{
8797
"!bg-gray-900 hover:bg-gray-900 !text-gray-25 hover:!text-gray-25":
8898
item.is_active,
89-
},
99+
}
90100
)}
91101
>
92102
<span className="block truncate">{item.name}</span>
@@ -100,12 +110,12 @@ export function HeaderActiveWorkspaceSelector() {
100110
"ml-auto size-6 group-hover/selector:opacity-100 opacity-0 transition-opacity",
101111
item.is_active
102112
? "hover:bg-gray-800 pressed:bg-gray-700"
103-
: "hover:bg-gray-50 hover:text-primary",
113+
: "hover:bg-gray-50 hover:text-primary"
104114
)}
105115
>
106116
<Settings01
107117
className={twMerge(
108-
item.is_active ? "text-gray-25" : "text-secondary",
118+
item.is_active ? "text-gray-25" : "text-secondary"
109119
)}
110120
/>
111121
</LinkButton>

0 commit comments

Comments
 (0)