From 001139a2bac4d70950be6934dcfa202d5a1592a3 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 13:10:31 +0100 Subject: [PATCH 01/19] rename timestamp coloumn to time --- src/components/AlertsTable.tsx | 2 +- src/routes/__tests__/route-dashboard.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 7bb2c613..31d7b4a3 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -169,7 +169,7 @@ export function AlertsTable() { Trigger Token File Code - Timestamp + Time diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 1f6d30bf..755f327b 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -162,7 +162,7 @@ describe("Dashboard", () => { ).toBeVisible(); expect( screen.getByRole("columnheader", { - name: /timestamp/i, + name: /time/i, }), ).toBeVisible(); From 4c2c60463ccb0343370bf6715c1fdf165912e443 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 13:11:16 +0100 Subject: [PATCH 02/19] make 'time' the first column --- src/components/AlertsTable.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 31d7b4a3..ca5aa715 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -163,18 +163,26 @@ export function AlertsTable() { + Time Trigger Type Trigger Token File Code - Time {dataView.map((alert) => ( + +
+ {format(new Date(alert.timestamp ?? ""), "y/MM/dd")} +
+
+ {format(new Date(alert.timestamp ?? ""), "hh:mm:ss a")} +
+
{alert.trigger_type} {wrapObjectOutput(alert.trigger_string)} @@ -191,14 +199,6 @@ export function AlertsTable() { "N/A" )} - -
- {format(new Date(alert.timestamp ?? ""), "y/MM/dd")} -
-
- {format(new Date(alert.timestamp ?? ""), "hh:mm:ss a")} -
-
))}
From 474f9a201671524680531892342ab9f6fa46cd2a Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 13:38:00 +0100 Subject: [PATCH 03/19] use relative date time format in alerts table --- src/components/AlertsTable.tsx | 56 +++++++++---------- src/routes/__tests__/route-dashboard.test.tsx | 7 +-- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index ca5aa715..396a7ceb 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -1,4 +1,4 @@ -import { format } from "date-fns"; +import { formatDistanceToNow } from "date-fns"; import { Cell, Column, @@ -173,34 +173,32 @@ export function AlertsTable() { - {dataView.map((alert) => ( - - -
- {format(new Date(alert.timestamp ?? ""), "y/MM/dd")} -
-
- {format(new Date(alert.timestamp ?? ""), "hh:mm:ss a")} -
-
- {alert.trigger_type} - - {wrapObjectOutput(alert.trigger_string)} - - - {alert.code_snippet?.filepath || "N/A"} - - - {alert.code_snippet?.code ? ( -
-                      {alert.code_snippet.code}
-                    
- ) : ( - "N/A" - )} -
-
- ))} + {dataView + .map((alert) => ( + + + {formatDistanceToNow(new Date(alert.timestamp), { + addSuffix: true, + })} + + {alert.trigger_type} + + {wrapObjectOutput(alert.trigger_string)} + + + {alert.code_snippet?.filepath || "N/A"} + + + {alert.code_snippet?.code ? ( +
+                        {alert.code_snippet.code}
+                      
+ ) : ( + "N/A" + )} +
+
+ ))}
diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 755f327b..a64a0d18 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -183,8 +183,7 @@ describe("Dashboard", () => { 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\/14/i)).toBeVisible(); - expect(within(firstRow).getByTestId(/time/i)).toBeVisible(); + expect(within(firstRow).getByText(/[0-9]+.*ago/i)).toBeVisible(); // check trigger_string null expect(within(secondRow).getAllByText(/n\/a/i).length).toEqual(3); @@ -312,8 +311,8 @@ describe("Dashboard", () => { "row", )[2] as HTMLElement; - expect(within(firstRow).getByText(/2025\/01\/14/i)).toBeVisible(); - expect(within(secondRow).getByText(/2025\/01\/07/i)).toBeVisible(); + expect(within(firstRow).getByText(/[0-9]+.*ago/i)).toBeVisible(); + expect(within(secondRow).getByText(/[0-9]+.*ago/i)).toBeVisible(); }); it("only displays a limited number of items in the table", async () => { From e68e4c28895d9b6b39565dcb2a6ffe066fb180f9 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 13:39:27 +0100 Subject: [PATCH 04/19] rename trigger type column to type --- src/components/AlertsTable.tsx | 2 +- src/routes/__tests__/route-dashboard.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 396a7ceb..ce2b064f 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -165,7 +165,7 @@ export function AlertsTable() { Time - Trigger Type + Type Trigger Token File diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index a64a0d18..2c842ae6 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -141,7 +141,7 @@ describe("Dashboard", () => { expect( screen.getByRole("columnheader", { - name: /trigger type/i, + name: /type/i, }), ).toBeVisible(); expect( From 70ceabbe92782f212e61baa0e949f98dbde4d605 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 13:45:54 +0100 Subject: [PATCH 05/19] clamp trigger markdown to a single lineoverflowoverflow --- src/components/AlertsTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index ce2b064f..b4a29b3a 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -30,8 +30,8 @@ const wrapObjectOutput = (input: AlertConversation["trigger_string"]) => { if (data === null) return "N/A"; if (typeof data === "string") { return ( -
- {data} +
+ {data.split("\n")[0] ?? ""}
); } From fa48c6ed3326cd2182defc49ebecae4b7f844ef5 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 13:49:53 +0100 Subject: [PATCH 06/19] remove code and file columns from alerts table --- src/components/AlertsTable.tsx | 14 -------------- src/routes/__tests__/route-dashboard.test.tsx | 19 ------------------- 2 files changed, 33 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index b4a29b3a..a6c0ee23 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -168,8 +168,6 @@ export function AlertsTable() { Type Trigger Token - File - Code @@ -185,18 +183,6 @@ export function AlertsTable() { {wrapObjectOutput(alert.trigger_string)} - - {alert.code_snippet?.filepath || "N/A"} - - - {alert.code_snippet?.code ? ( -
-                        {alert.code_snippet.code}
-                      
- ) : ( - "N/A" - )} -
))}
diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 2c842ae6..377561bf 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -150,16 +150,6 @@ describe("Dashboard", () => { }), ).toBeVisible(); - expect( - screen.getByRole("columnheader", { - name: /file/i, - }), - ).toBeVisible(); - expect( - screen.getByRole("columnheader", { - name: /code/i, - }), - ).toBeVisible(); expect( screen.getByRole("columnheader", { name: /time/i, @@ -176,17 +166,9 @@ describe("Dashboard", () => { 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(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(/[0-9]+.*ago/i)).toBeVisible(); - - // check trigger_string null - expect(within(secondRow).getAllByText(/n\/a/i).length).toEqual(3); }); it("should render malicious pkg", async () => { @@ -294,7 +276,6 @@ describe("Dashboard", () => { 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(); }); From a956b9efe945fbd71e1c5b627be55d5809122cba Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 14:48:37 +0100 Subject: [PATCH 07/19] rename trigger token column to event --- src/components/AlertsTable.tsx | 2 +- src/routes/__tests__/route-dashboard.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index a6c0ee23..8565bb0c 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -167,7 +167,7 @@ export function AlertsTable() { Type - Trigger Token + Event diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 377561bf..f65b5b82 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -146,7 +146,7 @@ describe("Dashboard", () => { ).toBeVisible(); expect( screen.getByRole("columnheader", { - name: /trigger token/i, + name: /event/i, }), ).toBeVisible(); From bc6542f862e906f8c5299b915c88c439904919ae Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 15:05:05 +0100 Subject: [PATCH 08/19] . --- src/components/AlertsTable.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 8565bb0c..de5be59a 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -18,7 +18,6 @@ import { AlertConversation } from "@/api/generated"; import { Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; import { getMaliciousPackage } from "@/lib/utils"; import { Search } from "lucide-react"; -import { Markdown } from "./Markdown"; import { useAlertSearch } from "@/hooks/useAlertSearch"; import { useCallback } from "react"; import { useSearchParams } from "react-router-dom"; @@ -31,7 +30,7 @@ const wrapObjectOutput = (input: AlertConversation["trigger_string"]) => { if (typeof data === "string") { return (
- {data.split("\n")[0] ?? ""} + {data.split("\n")[0] ?? ""}
); } From 04860b357084e72385c55a1d785076cab276b77e Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 17:37:22 +0100 Subject: [PATCH 09/19] implement event column content correctly --- src/components/AlertsTable.tsx | 53 +++++-------------- src/routes/__tests__/route-dashboard.test.tsx | 30 +++-------- 2 files changed, 20 insertions(+), 63 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index de5be59a..d73faf92 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -16,7 +16,7 @@ import { import { Switch } from "@stacklok/ui-kit"; import { AlertConversation } from "@/api/generated"; import { Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; -import { getMaliciousPackage } from "@/lib/utils"; +import { sanitizeQuestionPrompt, parsingPromptText } from "@/lib/utils"; import { Search } from "lucide-react"; import { useAlertSearch } from "@/hooks/useAlertSearch"; import { useCallback } from "react"; @@ -24,44 +24,17 @@ import { useSearchParams } from "react-router-dom"; import { useFilteredAlerts } from "@/hooks/useAlertsData"; import { useClientSidePagination } from "@/hooks/useClientSidePagination"; -const wrapObjectOutput = (input: AlertConversation["trigger_string"]) => { - const data = getMaliciousPackage(input); - if (data === null) return "N/A"; - if (typeof data === "string") { - return ( -
- {data.split("\n")[0] ?? ""} -
- ); - } - if (!data.type || !data.name) return "N/A"; - - return ( -
- -   - - {data.type}/{data.name} - - {data.status && ( - <> -
- {data.status} - - )} - {data.description && ( - <> -
- {data.description} - - )} -
+const getTitle = (alert: AlertConversation) => { + const prompt = alert.conversation; + const title = parsingPromptText( + sanitizeQuestionPrompt({ + question: prompt.question_answers?.[0]?.question.message ?? "", + answer: prompt.question_answers?.[0]?.answer?.message ?? "", + }), + prompt.conversation_timestamp, ); + + return title; }; export function AlertsTable() { @@ -179,9 +152,7 @@ export function AlertsTable() { })} {alert.trigger_type} - - {wrapObjectOutput(alert.trigger_string)} - + {getTitle(alert)} ))}
diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index f65b5b82..680d6ec2 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -188,19 +188,15 @@ describe("Dashboard", () => { /codegate-context-retriever/i, ), ).toBeVisible(); + }); - expect(screen.getByText(/package:/i)).toBeVisible(); - expect( - screen.getByRole("link", { - name: /pypi\/invokehttp/i, - }), - ).toHaveAttribute( - "href", - "https://www.insight.stacklok.com/report/pypi/invokehttp", - ); - expect( - screen.getByText(/malicious python http for humans\./i), - ).toBeVisible(); + it("renders event column", async () => { + mockAlertsWithMaliciousPkg(); + render(); + + await waitFor(() => { + expect(screen.getByText(/are there malicious/i)).toBeVisible(); + }); }); it("should filter by malicious pkg", async () => { @@ -228,16 +224,6 @@ describe("Dashboard", () => { expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("1"), ); - expect(screen.getByText(/package:/i)).toBeVisible(); - expect( - screen.getByRole("link", { - name: /pypi\/invokehttp/i, - }), - ).toBeVisible(); - expect( - screen.getByText(/malicious python http for humans\./i), - ).toBeVisible(); - userEvent.click( screen.getByRole("switch", { name: /malicious packages/i, From 5e92cbe91fd514e0e5b6853ed16a3d8de1b58eb9 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Thu, 23 Jan 2025 18:58:50 +0100 Subject: [PATCH 10/19] implement type column values --- src/components/AlertsTable.tsx | 15 ++++++++++- src/routes/__tests__/route-dashboard.test.tsx | 25 ++++++++----------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index d73faf92..a9256d72 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -37,6 +37,17 @@ const getTitle = (alert: AlertConversation) => { return title; }; +function TypeCellContent({ alert }: { alert: AlertConversation }) { + const conversationType = alert.conversation.type; + console.log({ conversationType }); + + if (conversationType === "chat") { + return "Chat"; + } + + return "Completion"; +} + export function AlertsTable() { const { isMaliciousFilterActive, @@ -151,7 +162,9 @@ export function AlertsTable() { addSuffix: true, })} - {alert.trigger_type} + + + {getTitle(alert)} ))} diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 680d6ec2..6005e6b6 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -167,7 +167,7 @@ describe("Dashboard", () => { "row", )[1] as HTMLElement; - expect(within(firstRow).getByText(/codegate-secrets/i)).toBeVisible(); + expect(within(firstRow).getByText(/chat/i)).toBeVisible(); expect(within(firstRow).getByText(/[0-9]+.*ago/i)).toBeVisible(); }); @@ -209,10 +209,10 @@ describe("Dashboard", () => { expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("2"); expect( - screen.getByRole("row", { - name: /codegate-secrets/i, - }), - ).toBeVisible(); + screen.getAllByRole("row", { + name: /chat/i, + }).length, + ).toBeGreaterThanOrEqual(1); userEvent.click( screen.getByRole("switch", { @@ -244,15 +244,10 @@ describe("Dashboard", () => { expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("2"); expect( - screen.getByRole("row", { - name: /codegate-secrets/i, - }), - ).toBeVisible(); - expect( - screen.getByRole("row", { - name: /codegate-context-retriever/i, - }), - ).toBeVisible(); + screen.getAllByRole("row", { + name: /chat/i, + }).length, + ).toBeGreaterThanOrEqual(1); await userEvent.type(screen.getByRole("searchbox"), "codegate-secrets"); @@ -262,7 +257,7 @@ describe("Dashboard", () => { const row = within(screen.getByTestId("alerts-table")).getAllByRole( "row", )[1] as HTMLElement; - expect(within(row).getByText(/codegate-secrets/i)).toBeVisible(); + expect(within(row).getByText(/chat/i)).toBeVisible(); }); it("should sort alerts by date desc", async () => { From f7c9c736db1d613ebf7a4baafe96f1f02c140bf0 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 24 Jan 2025 10:34:11 +0100 Subject: [PATCH 11/19] add note about ongoing discussion on message type --- src/components/AlertsTable.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index a9256d72..08ba4ccf 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -38,14 +38,16 @@ const getTitle = (alert: AlertConversation) => { }; function TypeCellContent({ alert }: { alert: AlertConversation }) { + // TODO: this should be improved to better make sure all possible + // values from BE are properly handled + // see discussion: https://discord.com/channels/1184987096302239844/1317203257051054120/1332280487464407071 const conversationType = alert.conversation.type; - console.log({ conversationType }); if (conversationType === "chat") { return "Chat"; } - return "Completion"; + return "Code Suggestion"; } export function AlertsTable() { From 3a778b23e9d935fd97e207b7dcb758acee6c7e4d Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 24 Jan 2025 12:37:33 +0100 Subject: [PATCH 12/19] fix mapping alerts type --- src/components/AlertsTable.tsx | 43 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 08ba4ccf..e0946b48 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -14,7 +14,7 @@ import { Button, } from "@stacklok/ui-kit"; import { Switch } from "@stacklok/ui-kit"; -import { AlertConversation } from "@/api/generated"; +import { AlertConversation, QuestionType } from "@/api/generated"; import { Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; import { sanitizeQuestionPrompt, parsingPromptText } from "@/lib/utils"; import { Search } from "lucide-react"; @@ -38,16 +38,16 @@ const getTitle = (alert: AlertConversation) => { }; function TypeCellContent({ alert }: { alert: AlertConversation }) { - // TODO: this should be improved to better make sure all possible - // values from BE are properly handled - // see discussion: https://discord.com/channels/1184987096302239844/1317203257051054120/1332280487464407071 const conversationType = alert.conversation.type; - if (conversationType === "chat") { - return "Chat"; + switch (conversationType) { + case QuestionType.CHAT: + return "Chat"; + case QuestionType.FIM: + return "Code Suggestion"; + default: + return "Unknown"; } - - return "Code Suggestion"; } export function AlertsTable() { @@ -156,20 +156,19 @@ export function AlertsTable() { - {dataView - .map((alert) => ( - - - {formatDistanceToNow(new Date(alert.timestamp), { - addSuffix: true, - })} - - - - - {getTitle(alert)} - - ))} + {dataView.map((alert) => ( + + + {formatDistanceToNow(new Date(alert.timestamp), { + addSuffix: true, + })} + + + + + {getTitle(alert)} + + ))}
From c07eeac7cfc56aa26f6357bd4a27865da414df1c Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 24 Jan 2025 18:26:05 +0100 Subject: [PATCH 13/19] display detected problem properly --- src/components/AlertsTable.tsx | 37 ++++++++++++++++++- src/lib/utils.ts | 12 ++++++ src/routes/__tests__/route-dashboard.test.tsx | 23 ++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 1301bbc0..d25a6db2 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -16,8 +16,12 @@ import { import { Switch } from "@stacklok/ui-kit"; import { AlertConversation, QuestionType } from "@/api/generated"; import { Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; -import { sanitizeQuestionPrompt, parsingPromptText } from "@/lib/utils"; -import { Search } from "lucide-react"; +import { + sanitizeQuestionPrompt, + parsingPromptText, + getIssueDetectedType, +} from "@/lib/utils"; +import { KeyRoundIcon, PackageX, Search } from "lucide-react"; import { useAlertSearch } from "@/hooks/useAlertSearch"; import { useCallback } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; @@ -50,6 +54,29 @@ function TypeCellContent({ alert }: { alert: AlertConversation }) { } } +function IssueDetectedCellContent({ alert }: { alert: AlertConversation }) { + const issueDetected = getIssueDetectedType(alert); + + switch (issueDetected) { + case "leaked_secret": + return ( + <> + + Blocked secret exposure + + ); + case "malicious_package": + return ( + <> + + Blocked malicious package + + ); + default: + return ""; + } +} + export function AlertsTable() { const { isMaliciousFilterActive, @@ -154,6 +181,7 @@ export function AlertsTable() { Type Event + Issue Detected
@@ -174,6 +202,11 @@ export function AlertsTable() { {getTitle(alert)} + +
+ +
+
))}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 886881d1..262d785e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -175,3 +175,15 @@ export function getMaliciousPackage( return null; } + +export function getIssueDetectedType( + alert: AlertConversation, +): "malicious_package" | "leaked_secret" { + const maliciousPackage = getMaliciousPackage(alert.trigger_string); + + if (maliciousPackage !== null && typeof maliciousPackage === "object") { + return "malicious_package"; + } + + return "leaked_secret"; +} diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 6005e6b6..9e8d8c6f 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -156,6 +156,12 @@ describe("Dashboard", () => { }), ).toBeVisible(); + expect( + screen.getByRole("columnheader", { + name: /issue detected/i, + }), + ).toBeVisible(); + expect( screen.getByRole("switch", { name: /malicious packages/i, @@ -169,6 +175,11 @@ describe("Dashboard", () => { expect(within(firstRow).getByText(/chat/i)).toBeVisible(); expect(within(firstRow).getByText(/[0-9]+.*ago/i)).toBeVisible(); + expect( + screen.getAllByRole("gridcell", { + name: /blocked secret exposure/i, + }).length, + ).toBeGreaterThanOrEqual(1); }); it("should render malicious pkg", async () => { @@ -188,6 +199,12 @@ describe("Dashboard", () => { /codegate-context-retriever/i, ), ).toBeVisible(); + + expect( + screen.getByRole("gridcell", { + name: /blocked malicious package/i, + }), + ).toBeVisible(); }); it("renders event column", async () => { @@ -224,6 +241,12 @@ describe("Dashboard", () => { expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("1"), ); + expect( + screen.queryAllByRole("gridcell", { + name: /blocked secret exposure/i, + }).length, + ).toBe(0); + userEvent.click( screen.getByRole("switch", { name: /malicious packages/i, From b75fc7935c2e4d138a6c344180dfc2b216d1e6aa Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 24 Jan 2025 19:30:43 +0100 Subject: [PATCH 14/19] configure alerts table width --- src/components/AlertsTable.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index d25a6db2..7691a4bc 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -176,12 +176,12 @@ export function AlertsTable() { - Time - - Type + + Time - Event - Issue Detected + Type + Event + Issue Detected From d013d4efd53eda1daaa49375757c08c8dbaa7c8d Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Mon, 27 Jan 2025 08:36:54 +0000 Subject: [PATCH 15/19] fix(getIssueDetectedType): don't default to `leaked_secret` --- .../lib/__tests__/is-alert-malicious.test.ts | 12 ++++++ .../lib/__tests__/is-alert-secret.test.ts | 12 ++++++ src/features/alerts/lib/is-alert-secret.ts | 8 ++++ src/features/alerts/lib/is-malicious.ts | 14 +++++++ .../alerts/mocks/alert-malicious.mock.ts | 37 +++++++++++++++++++ .../alerts/mocks/alert-secret.mock.ts | 32 ++++++++++++++++ src/lib/utils.ts | 13 +++---- 7 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 src/features/alerts/lib/__tests__/is-alert-malicious.test.ts create mode 100644 src/features/alerts/lib/__tests__/is-alert-secret.test.ts create mode 100644 src/features/alerts/lib/is-alert-secret.ts create mode 100644 src/features/alerts/lib/is-malicious.ts create mode 100644 src/features/alerts/mocks/alert-malicious.mock.ts create mode 100644 src/features/alerts/mocks/alert-secret.mock.ts diff --git a/src/features/alerts/lib/__tests__/is-alert-malicious.test.ts b/src/features/alerts/lib/__tests__/is-alert-malicious.test.ts new file mode 100644 index 00000000..55baa39b --- /dev/null +++ b/src/features/alerts/lib/__tests__/is-alert-malicious.test.ts @@ -0,0 +1,12 @@ +import { test, expect } from "vitest"; +import { isAlertMalicious } from "../is-malicious"; +import { ALERT_MALICIOUS } from "../../mocks/alert-malicious.mock"; +import { ALERT_SECRET } from "../../mocks/alert-secret.mock"; + +test("matches malicious alert", () => { + expect(isAlertMalicious(ALERT_MALICIOUS)).toBe(true); +}); + +test("doesn't match secret", () => { + expect(isAlertMalicious(ALERT_SECRET)).toBe(false); +}); diff --git a/src/features/alerts/lib/__tests__/is-alert-secret.test.ts b/src/features/alerts/lib/__tests__/is-alert-secret.test.ts new file mode 100644 index 00000000..8c9bba7e --- /dev/null +++ b/src/features/alerts/lib/__tests__/is-alert-secret.test.ts @@ -0,0 +1,12 @@ +import { test, expect } from "vitest"; +import { ALERT_MALICIOUS } from "../../mocks/alert-malicious.mock"; +import { ALERT_SECRET } from "../../mocks/alert-secret.mock"; +import { isAlertSecret } from "../is-alert-secret"; + +test("matches secret alert", () => { + expect(isAlertSecret(ALERT_SECRET)).toBe(true); +}); + +test("doesn't match secret", () => { + expect(isAlertSecret(ALERT_MALICIOUS)).toBe(false); +}); diff --git a/src/features/alerts/lib/is-alert-secret.ts b/src/features/alerts/lib/is-alert-secret.ts new file mode 100644 index 00000000..dbaf329a --- /dev/null +++ b/src/features/alerts/lib/is-alert-secret.ts @@ -0,0 +1,8 @@ +import { AlertConversation } from "@/api/generated"; + +export function isAlertSecret({ + trigger_type, + trigger_category, +}: AlertConversation) { + return trigger_category === "critical" && trigger_type === "codegate-secrets"; +} diff --git a/src/features/alerts/lib/is-malicious.ts b/src/features/alerts/lib/is-malicious.ts new file mode 100644 index 00000000..77ad4f2a --- /dev/null +++ b/src/features/alerts/lib/is-malicious.ts @@ -0,0 +1,14 @@ +import { AlertConversation } from "@/api/generated"; + +export function isAlertMalicious({ + trigger_string, + trigger_category, +}: AlertConversation) { + return ( + trigger_category === "critical" && + trigger_string !== null && + typeof trigger_string === "object" && + "status" in trigger_string && + trigger_string.status === "malicious" + ); +} diff --git a/src/features/alerts/mocks/alert-malicious.mock.ts b/src/features/alerts/mocks/alert-malicious.mock.ts new file mode 100644 index 00000000..0605ff64 --- /dev/null +++ b/src/features/alerts/mocks/alert-malicious.mock.ts @@ -0,0 +1,37 @@ +import { AlertConversation, QuestionType } from "@/api/generated"; + +export const ALERT_MALICIOUS = { + conversation: { + question_answers: [ + { + question: { + message: + "Context: invokehttp is a Python package available on PyPI ecosystem. However, this package is found to be malicious and must not be used. For additional information refer to https://www.insight.stacklok.com/report/pypi/invokehttp - Package offers this functionality: Python HTTP for Humans.\n \n\n Query: Is invokehttp a malicious package?", + timestamp: "2025-01-14T16:29:49.602403Z", + message_id: "bf92bf3c-fcec-4064-ad02-c792026c3555", + }, + answer: { + message: + "**Warning:** CodeGate detected one or more malicious, deprecated or archived packages.\n- Pkg 1: [https://www.insight.stacklok.com/report/pypi/invokehttp](https://www.insight.stacklok.com/report/pypi/invokehttp)", + timestamp: "2025-01-14T16:29:50.213490Z", + message_id: "7e260699-906e-43dc-a43e-8f288389bd9d", + }, + }, + ], + provider: "copilot", + type: QuestionType.CHAT, + chat_id: "bf92bf3c-fcec-4064-ad02-c792026c3555", + conversation_timestamp: "2025-01-14T16:29:49.602403Z", + }, + alert_id: "bf92bf3c-fcec-4064-ad02-c792026c3555", + code_snippet: null, + trigger_string: { + name: "invokehttp", + type: "pypi", + status: "malicious", + description: "Python HTTP for Humans.", + }, + trigger_type: "codegate-context-retriever", + trigger_category: "critical", + timestamp: "2025-01-14T16:29:49.602403Z", +} satisfies AlertConversation; diff --git a/src/features/alerts/mocks/alert-secret.mock.ts b/src/features/alerts/mocks/alert-secret.mock.ts new file mode 100644 index 00000000..887833d9 --- /dev/null +++ b/src/features/alerts/mocks/alert-secret.mock.ts @@ -0,0 +1,32 @@ +import { AlertConversation, QuestionType } from "@/api/generated"; + +export const ALERT_SECRET = { + conversation: { + question_answers: [ + { + question: { + message: "Analyse this file please", + timestamp: "2025-01-13T17:15:06.942856Z", + message_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", + }, + answer: { + message: + 'The `.env` file you provided contains environment variable definitions that are typically used in applications to configure settings without hardcoding them into the source code. Here\'s a breakdown of each line in the file:\n\n1. **`AUTH_KEYCLOAK_ID="minder-frontend"`**:\n - This variable likely represents the identifier for the Keycloak client used for authentication. It specifies which client (in this case, `minder-frontend`) is being used to authenticate users.\n\n2. **`AUTH_KEYCLOAK_ISSUER="https://auth.sandbox.stacklok.dev/realms/stacklok"`**:\n - This variable specifies the URL of the Keycloak issuer. It points to the Keycloak server\'s realm where the authentication takes place. This URL is used to validate tokens and manage user sessions.\n\n3. **`AUTH_SECRET="minder-ui-secret"`**:\n - This variable holds a secret key used for signing or encrypting tokens. It is crucial for maintaining the security of the authentication process. This secret should be kept confidential and not exposed in public repositories.\n\n4. **`NEXT_PUBLIC_API_URL="https://api.sandbox.stacklok.dev"`**:\n - This variable defines the base URL for the API that the frontend application will interact with. The `NEXT_PUBLIC_` prefix indicates that this variable is accessible in the client-side code, which is typical in Next.js applications.\n\n5. **`NEXT_PUBLIC_BASE_URL="https://cloud.stacklok.com"`**:\n - Similar to the previous variable, this defines the base URL for the application. It may be used for routing or linking to different parts of the application.\n\n6. **`NEXT_PUBLIC_INSIGHT_API_URL=https://trusty.stacklok.dev`**:\n - This variable specifies another API endpoint that the application may use. It is also prefixed with `NEXT_PUBLIC_`, indicating that it is accessible in the client-side code. Note that this variable does not have quotes around the URL, which is acceptable in `.env` files but may lead to inconsistencies if other variables are quoted.\n\n### Summary:\n- The `.env` file is used to store configuration settings for the application, particularly related to authentication and API endpoints.\n- The use of `NEXT_PUBLIC_` indicates that these variables are intended to be exposed to the client-side code in a Next.js application.\n- It is important to ensure that sensitive information, such as `AUTH_SECRET`, is kept secure and not exposed in public repositories or client-side code.\n- Consistency in quoting values is recommended for clarity and to avoid potential issues.', + timestamp: "2025-01-13T17:15:08.537530Z", + message_id: "f1a6201f-0d7f-4c93-bb84-525f2a2d0d3b", + }, + }, + ], + provider: "copilot", + type: QuestionType.CHAT, + chat_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", + conversation_timestamp: "2025-01-13T17:15:06.942856Z", + }, + alert_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", + code_snippet: null, + trigger_string: + "Amazon - Secret Access Key:\n steps:\n - name: Checkout Repository\n uses: REDACTED<$BeldxVmNty++LTH/0/z3/S2sJZVvZC16b/S4On/cKkTuT6p7ygAa+NQkVQ/Yrf8pIV4Aat0L7GaEXuOWN0ITKrq7b0+YfBhNaunpOX0n5ACs+drUjmuj4Vi9uNkbxlNl> # v4\n\n - name: Setup\n uses: ./.github/actions/setup", + trigger_type: "codegate-secrets", + trigger_category: "critical", + timestamp: "2025-01-13T17:15:06.942856Z", +} satisfies AlertConversation; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 262d785e..100451bb 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,6 @@ import { AlertConversation, Conversation } from "@/api/generated/types.gen"; +import { isAlertSecret } from "@/features/alerts/lib/is-alert-secret"; +import { isAlertMalicious } from "@/features/alerts/lib/is-malicious"; import { MaliciousPkgType, TriggerType } from "@/types"; import { format, isToday, isYesterday } from "date-fns"; @@ -178,12 +180,9 @@ export function getMaliciousPackage( export function getIssueDetectedType( alert: AlertConversation, -): "malicious_package" | "leaked_secret" { - const maliciousPackage = getMaliciousPackage(alert.trigger_string); +): "malicious_package" | "leaked_secret" | null { + if (isAlertMalicious(alert)) return "malicious_package"; + if (isAlertSecret(alert)) return "leaked_secret"; - if (maliciousPackage !== null && typeof maliciousPackage === "object") { - return "malicious_package"; - } - - return "leaked_secret"; + return null; } From 360af2c59f9968cde9d61e1d7a3816328bf079db Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Mon, 27 Jan 2025 08:37:08 +0000 Subject: [PATCH 16/19] fix(`AlertsTable`): failing test --- src/components/AlertsTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 7691a4bc..3fc47eef 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -176,10 +176,10 @@ export function AlertsTable() {
+ Time - Time + Type - Type Event Issue Detected From 4c5cbac5cb4fdd055274f38453944a96410187be Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Mon, 27 Jan 2025 08:42:58 +0000 Subject: [PATCH 17/19] chore: tidy up --- src/features/alerts/lib/__tests__/is-alert-secret.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/alerts/lib/__tests__/is-alert-secret.test.ts b/src/features/alerts/lib/__tests__/is-alert-secret.test.ts index 8c9bba7e..a42a77ba 100644 --- a/src/features/alerts/lib/__tests__/is-alert-secret.test.ts +++ b/src/features/alerts/lib/__tests__/is-alert-secret.test.ts @@ -7,6 +7,6 @@ test("matches secret alert", () => { expect(isAlertSecret(ALERT_SECRET)).toBe(true); }); -test("doesn't match secret", () => { +test("doesn't match malicious", () => { expect(isAlertSecret(ALERT_MALICIOUS)).toBe(false); }); From 60bda234b7f9685e630ed0f950c0a4c9d77a21a7 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Mon, 27 Jan 2025 11:16:53 +0100 Subject: [PATCH 18/19] fix table column sizing using --- src/components/AlertsTable.tsx | 77 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 7691a4bc..f709ff07 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -12,6 +12,7 @@ import { SearchFieldClearButton, Badge, Button, + ResizableTableContainer, } from "@stacklok/ui-kit"; import { Switch } from "@stacklok/ui-kit"; import { AlertConversation, QuestionType } from "@/api/generated"; @@ -173,44 +174,46 @@ export function AlertsTable() {
-
- - - - Time - - Type - Event - Issue Detected - - - - {dataView.map((alert) => ( - - navigate(`/prompt/${alert.conversation.chat_id}`) - } - > - - {formatDistanceToNow(new Date(alert.timestamp), { - addSuffix: true, - })} - - - - - {getTitle(alert)} - -
- -
-
+ +
+ + + + Time + + Type + Event + Issue Detected - ))} - -
+ + + {dataView.map((alert) => ( + + navigate(`/prompt/${alert.conversation.chat_id}`) + } + > + + {formatDistanceToNow(new Date(alert.timestamp), { + addSuffix: true, + })} + + + + + {getTitle(alert)} + +
+ +
+
+
+ ))} +
+ +
From 879df3d8f66f49dae3f8a2bdecaa8f152ef86745 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Mon, 27 Jan 2025 13:59:54 +0000 Subject: [PATCH 19/19] chore: more sane mock for alert secrets --- src/features/alerts/mocks/alert-secret.mock.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/features/alerts/mocks/alert-secret.mock.ts b/src/features/alerts/mocks/alert-secret.mock.ts index 887833d9..287d8f99 100644 --- a/src/features/alerts/mocks/alert-secret.mock.ts +++ b/src/features/alerts/mocks/alert-secret.mock.ts @@ -10,8 +10,7 @@ export const ALERT_SECRET = { message_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", }, answer: { - message: - 'The `.env` file you provided contains environment variable definitions that are typically used in applications to configure settings without hardcoding them into the source code. Here\'s a breakdown of each line in the file:\n\n1. **`AUTH_KEYCLOAK_ID="minder-frontend"`**:\n - This variable likely represents the identifier for the Keycloak client used for authentication. It specifies which client (in this case, `minder-frontend`) is being used to authenticate users.\n\n2. **`AUTH_KEYCLOAK_ISSUER="https://auth.sandbox.stacklok.dev/realms/stacklok"`**:\n - This variable specifies the URL of the Keycloak issuer. It points to the Keycloak server\'s realm where the authentication takes place. This URL is used to validate tokens and manage user sessions.\n\n3. **`AUTH_SECRET="minder-ui-secret"`**:\n - This variable holds a secret key used for signing or encrypting tokens. It is crucial for maintaining the security of the authentication process. This secret should be kept confidential and not exposed in public repositories.\n\n4. **`NEXT_PUBLIC_API_URL="https://api.sandbox.stacklok.dev"`**:\n - This variable defines the base URL for the API that the frontend application will interact with. The `NEXT_PUBLIC_` prefix indicates that this variable is accessible in the client-side code, which is typical in Next.js applications.\n\n5. **`NEXT_PUBLIC_BASE_URL="https://cloud.stacklok.com"`**:\n - Similar to the previous variable, this defines the base URL for the application. It may be used for routing or linking to different parts of the application.\n\n6. **`NEXT_PUBLIC_INSIGHT_API_URL=https://trusty.stacklok.dev`**:\n - This variable specifies another API endpoint that the application may use. It is also prefixed with `NEXT_PUBLIC_`, indicating that it is accessible in the client-side code. Note that this variable does not have quotes around the URL, which is acceptable in `.env` files but may lead to inconsistencies if other variables are quoted.\n\n### Summary:\n- The `.env` file is used to store configuration settings for the application, particularly related to authentication and API endpoints.\n- The use of `NEXT_PUBLIC_` indicates that these variables are intended to be exposed to the client-side code in a Next.js application.\n- It is important to ensure that sensitive information, such as `AUTH_SECRET`, is kept secure and not exposed in public repositories or client-side code.\n- Consistency in quoting values is recommended for clarity and to avoid potential issues.', + message: "foo-bar", timestamp: "2025-01-13T17:15:08.537530Z", message_id: "f1a6201f-0d7f-4c93-bb84-525f2a2d0d3b", }, @@ -25,7 +24,7 @@ export const ALERT_SECRET = { alert_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", code_snippet: null, trigger_string: - "Amazon - Secret Access Key:\n steps:\n - name: Checkout Repository\n uses: REDACTED<$BeldxVmNty++LTH/0/z3/S2sJZVvZC16b/S4On/cKkTuT6p7ygAa+NQkVQ/Yrf8pIV4Aat0L7GaEXuOWN0ITKrq7b0+YfBhNaunpOX0n5ACs+drUjmuj4Vi9uNkbxlNl> # v4\n\n - name: Setup\n uses: ./.github/actions/setup", + "Amazon - Secret Access Key:\n steps:\n - name: Checkout Repository\n uses: REDACTED<$foo-bar> # v4\n\n - name: Setup\n uses: ./.github/actions/setup", trigger_type: "codegate-secrets", trigger_category: "critical", timestamp: "2025-01-13T17:15:06.942856Z",