Skip to content

Commit

Permalink
feat: health check card (#62)
Browse files Browse the repository at this point in the history
* feat: initial work on health-check card

* feat: tidy up health check card

* feat: add ability to control polling interval

* fix: use correct referrer type for error UI links

* fix: add default health-check endpoint handler

* test: fix failing tests after introducing second table to dashboard page
  • Loading branch information
alex-mcgovern authored Jan 15, 2025
1 parent 78bdfb9 commit f2673bc
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 51 deletions.
27 changes: 27 additions & 0 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/react-query": "^5.64.1",
"@types/prismjs": "^1.26.5",
"@types/react-syntax-highlighter": "^15.5.13",
"class-variance-authority": "^0.7.1",
Expand Down Expand Up @@ -81,4 +82,4 @@
"overrides": {
"vite": "^6.0.1"
}
}
}
18 changes: 7 additions & 11 deletions src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
import { useSearchParams } from "react-router-dom";
import { AlertConversation } from "@/api/generated";
import { getMaliciousPackage } from "@/lib/utils";
import { CardCodegateStatus } from "@/features/dashboard/components/card-codegate-status";

const wrapObjectOutput = (input: AlertConversation["trigger_string"]) => {
const data = getMaliciousPackage(input);
Expand Down Expand Up @@ -127,16 +128,11 @@ export function Dashboard() {

return (
<div className="flex-col">
<div className="flex flex-wrap items-center gap-4 w-full">
<div className="min-w-80 w-1/3 h-60">
<BarChart data={alerts} loading={loading} />
</div>
<div className="min-w-80 w-1/4 h-60">
<PieChart data={maliciousPackages} loading={loading} />
</div>
<div className="relative w-[370px] h-60">
<LineChart data={alerts} loading={loading} />
</div>
<div className="grid 2xl:grid-cols-4 sm:grid-cols-2 grid-cols-1 items-stretch gap-4 w-full">
<CardCodegateStatus />
<BarChart data={alerts} loading={loading} />
<PieChart data={maliciousPackages} loading={loading} />
<LineChart data={alerts} loading={loading} />
</div>

<Separator className="my-8" />
Expand Down Expand Up @@ -193,7 +189,7 @@ export function Dashboard() {
</div>
</div>
<div className="overflow-x-auto">
<Table>
<Table data-testid="alerts-table">
<TableHeader>
<TableRow>
<TableHead className="w-[150px]">Trigger Type</TableHead>
Expand Down
26 changes: 15 additions & 11 deletions src/components/__tests__/Dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,21 @@ describe("Dashboard", () => {
).toBeVisible();
expect(screen.getByRole("searchbox")).toBeVisible();

const row = screen.getAllByRole("row")[1] as HTMLElement;
const firstRow = within(screen.getByTestId("alerts-table")).getAllByRole(
"row",
)[1] as HTMLElement;
const secondRow = within(screen.getByTestId("alerts-table")).getAllByRole(
"row",
)[2] as HTMLElement;

expect(within(row).getByText(/ghp_token/i)).toBeVisible();
expect(within(row).getByText(/codegate-secrets/i)).toBeVisible();
expect(within(row).getAllByText(/n\/a/i).length).toEqual(2);
expect(within(row).getByText(/2025\/01\/07/i)).toBeVisible();
expect(within(row).getByTestId(/time/i)).toBeVisible();
expect(within(firstRow).getByText(/ghp_token/i)).toBeVisible();
expect(within(firstRow).getByText(/codegate-secrets/i)).toBeVisible();
expect(within(firstRow).getAllByText(/n\/a/i).length).toEqual(2);
expect(within(firstRow).getByText(/2025\/01\/07/i)).toBeVisible();
expect(within(firstRow).getByTestId(/time/i)).toBeVisible();

// check trigger_string null
expect(
within(screen.getAllByRole("row")[2] as HTMLElement).getAllByText(/n\/a/i)
.length,
).toEqual(3);
expect(within(secondRow).getAllByText(/n\/a/i).length).toEqual(3);
});

it("should render malicious pkg", async () => {
Expand Down Expand Up @@ -271,7 +273,9 @@ describe("Dashboard", () => {
await waitFor(() =>
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("1"),
);
const row = screen.getAllByRole("row")[1] as HTMLElement;
const row = within(screen.getByTestId("alerts-table")).getAllByRole(
"row",
)[1] as HTMLElement;
expect(within(row).getByText(/ghp_token/i)).toBeVisible();
expect(within(row).getByText(/codegate-secrets/i)).toBeVisible();
});
Expand Down
41 changes: 24 additions & 17 deletions src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";

import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";

const Card = React.forwardRef<
HTMLDivElement,
Expand All @@ -10,12 +10,12 @@ const Card = React.forwardRef<
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
className,
)}
{...props}
/>
))
Card.displayName = "Card"
));
Card.displayName = "Card";

const CardHeader = React.forwardRef<
HTMLDivElement,
Expand All @@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
className={cn("flex flex-col space-y-1 p-4", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
));
CardHeader.displayName = "CardHeader";

const CardTitle = React.forwardRef<
HTMLDivElement,
Expand All @@ -37,12 +37,12 @@ const CardTitle = React.forwardRef<
ref={ref}
className={cn(
"text-xl font-semibold leading-none tracking-tight",
className
className,
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
));
CardTitle.displayName = "CardTitle";

const CardDescription = React.forwardRef<
HTMLDivElement,
Expand All @@ -53,16 +53,16 @@ const CardDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
));
CardDescription.displayName = "CardDescription";

const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-4 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
));
CardContent.displayName = "CardContent";

const CardFooter = React.forwardRef<
HTMLDivElement,
Expand All @@ -73,7 +73,14 @@ const CardFooter = React.forwardRef<
className={cn("flex items-center p-4 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
));
CardFooter.displayName = "CardFooter";

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { server } from "@/mocks/msw/node";
import { http, HttpResponse } from "msw";
import { expect } from "vitest";
import { CardCodegateStatus } from "../card-codegate-status";
import { render, waitFor } from "@/lib/test-utils";

const renderComponent = () => render(<CardCodegateStatus />);

describe("CardCodegateStatus", () => {
test("renders 'healthy' state", async () => {
server.use(
http.get("*/health", () => HttpResponse.json({ status: "healthy" })),
);

const { getByText } = renderComponent();

await waitFor(
() => {
expect(getByText(/healthy/i)).toBeVisible();
},
{ timeout: 10_000 },
);
});

test("renders 'unhealthy' state", async () => {
server.use(http.get("*/health", () => HttpResponse.json({ status: null })));

const { getByText } = renderComponent();

await waitFor(
() => {
expect(getByText(/unhealthy/i)).toBeVisible();
},
{ timeout: 10_000 },
);
});

test("renders 'error' state", async () => {
server.use(http.get("*/health", () => HttpResponse.error()));

const { getByText } = renderComponent();

await waitFor(
() => {
expect(getByText(/an error occurred/i)).toBeVisible();
},
{ timeout: 10_000 },
);
});
});
Loading

0 comments on commit f2673bc

Please sign in to comment.