Skip to content

Commit 2765917

Browse files
feat(alerts): tabs for filtering table (#234)
* feat(alerts): tabs for filtering table * fix: header spacing bug * fix: wrong count in tab
1 parent 9911a7a commit 2765917

12 files changed

+361
-206
lines changed

src/App.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ function App() {
1717
<div className="flex-1 flex flex-col overflow-hidden">
1818
<Header />
1919

20-
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-3">
20+
<div
21+
className="flex-1 overflow-y-auto p-6 flex flex-col gap-3"
22+
style={{ scrollbarGutter: "stable" }}
23+
>
2124
<Page />
2225
</div>
2326
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { server } from "@/mocks/msw/node";
2+
import { http, HttpResponse } from "msw";
3+
import { makeMockAlert } from "../../mocks/alert.mock";
4+
import { render, waitFor } from "@/lib/test-utils";
5+
import { TabsAlerts } from "../tabs-alerts";
6+
7+
test("shows correct count of all packages", async () => {
8+
server.use(
9+
http.get("*/workspaces/:name/alerts", () => {
10+
return HttpResponse.json([
11+
...Array.from({ length: 13 }).map(() =>
12+
makeMockAlert({ type: "secret" }),
13+
),
14+
...Array.from({ length: 13 }).map(() =>
15+
makeMockAlert({ type: "malicious" }),
16+
),
17+
]);
18+
}),
19+
);
20+
21+
const { getByRole } = render(
22+
<TabsAlerts>
23+
<div>foo</div>
24+
</TabsAlerts>,
25+
);
26+
27+
await waitFor(() => {
28+
expect(getByRole("tab", { name: /all/i })).toHaveTextContent("26");
29+
});
30+
});
31+
32+
test("shows correct count of malicious packages", async () => {
33+
server.use(
34+
http.get("*/workspaces/:name/alerts", () => {
35+
return HttpResponse.json(
36+
Array.from({ length: 13 }).map(() =>
37+
makeMockAlert({ type: "malicious" }),
38+
),
39+
);
40+
}),
41+
);
42+
43+
const { getByRole } = render(
44+
<TabsAlerts>
45+
<div>foo</div>
46+
</TabsAlerts>,
47+
);
48+
49+
await waitFor(() => {
50+
expect(getByRole("tab", { name: /malicious/i })).toHaveTextContent("13");
51+
});
52+
});
53+
54+
test("shows correct count of secret packages", async () => {
55+
server.use(
56+
http.get("*/workspaces/:name/alerts", () => {
57+
return HttpResponse.json(
58+
Array.from({ length: 13 }).map(() => makeMockAlert({ type: "secret" })),
59+
);
60+
}),
61+
);
62+
63+
const { getByRole } = render(
64+
<TabsAlerts>
65+
<div>foo</div>
66+
</TabsAlerts>,
67+
);
68+
69+
await waitFor(() => {
70+
expect(getByRole("tab", { name: /secrets/i })).toHaveTextContent("13");
71+
});
72+
});

src/features/alerts/components/search-field-alerts.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { useAlertsFilterSearchParams } from "../hooks/use-alerts-filter-search-params";
88
import { SearchMd } from "@untitled-ui/icons-react";
99

10-
export function SearchFieldAlerts() {
10+
export function SearchFieldAlerts({ className }: { className?: string }) {
1111
const { setSearch, state } = useAlertsFilterSearchParams();
1212

1313
return (
@@ -16,6 +16,7 @@ export function SearchFieldAlerts() {
1616
aria-label="Search alerts"
1717
value={state.search ?? ""}
1818
onChange={(value) => setSearch(value.toLowerCase().trim())}
19+
className={className}
1920
>
2021
<FieldGroup>
2122
<Input

src/features/alerts/components/switch-malicious-alerts-filter.tsx

-34
This file was deleted.

src/features/alerts/components/table-alerts.tsx

+65-80
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
Table,
77
TableBody,
88
TableHeader,
9-
Badge,
109
Button,
1110
ResizableTableContainer,
1211
Link,
@@ -20,15 +19,14 @@ import {
2019
parsingPromptText,
2120
getIssueDetectedType,
2221
} from "@/lib/utils";
23-
import { useAlertSearch } from "@/hooks/useAlertSearch";
2422
import { useNavigate } from "react-router-dom";
2523
import { useClientSidePagination } from "@/hooks/useClientSidePagination";
2624
import { TableAlertTokenUsage } from "./table-alert-token-usage";
27-
import { Key01, LinkExternal02, PackageX } from "@untitled-ui/icons-react";
28-
import { useListWorkspaces } from "@/features/workspace/hooks/use-list-workspaces";
29-
import { SearchFieldAlerts } from "./search-field-alerts";
30-
import { SwitchMaliciousAlertsFilter } from "./switch-malicious-alerts-filter";
25+
3126
import { useQueryGetWorkspaceAlertTable } from "../hooks/use-query-get-workspace-alerts-table";
27+
import { useAlertsFilterSearchParams } from "../hooks/use-alerts-filter-search-params";
28+
import { useListWorkspaces } from "@/features/workspace/hooks/use-list-workspaces";
29+
import { Key01, LinkExternal02, PackageX } from "@untitled-ui/icons-react";
3230

3331
const getTitle = (alert: AlertConversation) => {
3432
const prompt = alert.conversation;
@@ -150,10 +148,12 @@ function EmptyState({
150148
}
151149

152150
export function TableAlerts() {
153-
const { page, nextPage, prevPage } = useAlertSearch();
154151
const navigate = useNavigate();
152+
const { state, prevPage, nextPage } = useAlertsFilterSearchParams();
153+
155154
const { data: filteredAlerts = [], isLoading: isLoadingAlerts } =
156155
useQueryGetWorkspaceAlertTable();
156+
157157
const {
158158
data: { workspaces } = { workspaces: [] },
159159
isLoading: isLoadingWorkspaces,
@@ -163,86 +163,71 @@ export function TableAlerts() {
163163

164164
const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination(
165165
filteredAlerts,
166-
page,
166+
state.page,
167167
15,
168168
);
169169

170170
return (
171171
<>
172-
<div className="flex mb-2 mx-2 justify-between w-[calc(100vw-20rem)]">
173-
<div className="flex gap-2 items-center">
174-
<h2 className="font-bold text-lg">All Alerts</h2>
175-
<Badge size="sm" variant="inverted" data-testid="alerts-count">
176-
{filteredAlerts.length}
177-
</Badge>
178-
</div>
179-
180-
<div className="flex items-center gap-8">
181-
<SwitchMaliciousAlertsFilter />
182-
<SearchFieldAlerts />
183-
</div>
184-
</div>
185-
<div className="overflow-x-auto">
186-
<ResizableTableContainer>
187-
<Table data-testid="alerts-table" aria-label="Alerts table">
188-
<TableHeader>
189-
<Row>
190-
<Column isRowHeader width={150}>
191-
Time
192-
</Column>
193-
<Column width={150}>Type</Column>
194-
<Column>Event</Column>
195-
<Column width={325}>Issue Detected</Column>
196-
<Column width={200}>Token usage</Column>
197-
</Row>
198-
</TableHeader>
199-
<TableBody
200-
renderEmptyState={() =>
201-
isLoading ? (
202-
<div>Loading alerts</div>
203-
) : (
204-
<EmptyState hasMultipleWorkspaces={workspaces.length > 1} />
205-
)
206-
}
207-
>
208-
{dataView.map((alert) => {
209-
return (
210-
<Row
211-
key={alert.alert_id}
212-
className="h-20"
213-
onAction={() =>
214-
navigate(`/prompt/${alert.conversation.chat_id}`)
215-
}
216-
>
217-
<Cell className="truncate">
218-
{formatDistanceToNow(new Date(alert.timestamp), {
219-
addSuffix: true,
220-
})}
221-
</Cell>
222-
<Cell className="truncate">
223-
<TypeCellContent alert={alert} />
224-
</Cell>
225-
<Cell className="truncate">{getTitle(alert)}</Cell>
226-
<Cell>
227-
<div className="truncate flex gap-2 items-center">
228-
<IssueDetectedCellContent alert={alert} />
229-
</div>
230-
</Cell>
231-
<Cell>
232-
<TableAlertTokenUsage
233-
usage={alert.conversation.token_usage_agg}
234-
/>
235-
</Cell>
236-
</Row>
237-
);
238-
})}
239-
</TableBody>
240-
</Table>
241-
</ResizableTableContainer>
242-
</div>
172+
<ResizableTableContainer>
173+
<Table data-testid="alerts-table" aria-label="Alerts table">
174+
<TableHeader>
175+
<Row>
176+
<Column isRowHeader width={150}>
177+
Time
178+
</Column>
179+
<Column width={150}>Type</Column>
180+
<Column>Event</Column>
181+
<Column width={325}>Issue Detected</Column>
182+
<Column width={200}>Token usage</Column>
183+
</Row>
184+
</TableHeader>
185+
<TableBody
186+
renderEmptyState={() =>
187+
isLoading ? (
188+
<div>Loading alerts</div>
189+
) : (
190+
<EmptyState hasMultipleWorkspaces={workspaces.length > 1} />
191+
)
192+
}
193+
>
194+
{dataView.map((alert) => {
195+
return (
196+
<Row
197+
key={alert.alert_id}
198+
className="h-20"
199+
onAction={() =>
200+
navigate(`/prompt/${alert.conversation.chat_id}`)
201+
}
202+
>
203+
<Cell className="truncate">
204+
{formatDistanceToNow(new Date(alert.timestamp), {
205+
addSuffix: true,
206+
})}
207+
</Cell>
208+
<Cell className="truncate">
209+
<TypeCellContent alert={alert} />
210+
</Cell>
211+
<Cell className="truncate">{getTitle(alert)}</Cell>
212+
<Cell>
213+
<div className="truncate flex gap-2 items-center">
214+
<IssueDetectedCellContent alert={alert} />
215+
</div>
216+
</Cell>
217+
<Cell>
218+
<TableAlertTokenUsage
219+
usage={alert.conversation.token_usage_agg}
220+
/>
221+
</Cell>
222+
</Row>
223+
);
224+
})}
225+
</TableBody>
226+
</Table>
227+
</ResizableTableContainer>
243228

244229
<div className="flex justify-center w-full p-4">
245-
<div className="flex gap-2">
230+
<div className="grid grid-cols-2 gap-2">
246231
<Button isDisabled={!hasPreviousPage} onPress={prevPage}>
247232
Previous
248233
</Button>

0 commit comments

Comments
 (0)