diff --git a/package-lock.json b/package-lock.json index a159e96b..ac07d86a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "tailwind-merge": "^2.5.5", "tailwind-variants": "^0.3.0", "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.1", "zustand": "^5.0.3" }, "devDependencies": { @@ -12564,7 +12565,6 @@ "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 45af9315..0592f1c1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "tailwind-merge": "^2.5.5", "tailwind-variants": "^0.3.0", "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.1", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/src/features/alerts/components/search-field-alerts.tsx b/src/features/alerts/components/search-field-alerts.tsx new file mode 100644 index 00000000..7d084de6 --- /dev/null +++ b/src/features/alerts/components/search-field-alerts.tsx @@ -0,0 +1,31 @@ +import { + FieldGroup, + Input, + SearchField, + SearchFieldClearButton, +} from "@stacklok/ui-kit"; +import { useAlertsFilterSearchParams } from "../hooks/use-alerts-filter-search-params"; +import { SearchMd } from "@untitled-ui/icons-react"; + +export function SearchFieldAlerts() { + const { setSearch, state } = useAlertsFilterSearchParams(); + + return ( + setSearch(value.toLowerCase().trim())} + > + + } + /> + + + + ); +} diff --git a/src/features/alerts/components/switch-malicious-alerts-filter.tsx b/src/features/alerts/components/switch-malicious-alerts-filter.tsx new file mode 100644 index 00000000..78f4e99e --- /dev/null +++ b/src/features/alerts/components/switch-malicious-alerts-filter.tsx @@ -0,0 +1,34 @@ +import { TooltipTrigger, Switch, Tooltip } from "@stacklok/ui-kit"; +import { + AlertsFilterView, + useAlertsFilterSearchParams, +} from "../hooks/use-alerts-filter-search-params"; + +export function SwitchMaliciousAlertsFilter() { + const { setView, state } = useAlertsFilterSearchParams(); + + return ( + + { + switch (isSelected) { + case true: + return setView(AlertsFilterView.MALICIOUS); + case false: + return setView(AlertsFilterView.ALL); + default: + return isSelected satisfies never; + } + }} + > + Malicious Packages + + + +

Filter by malicious packages

+
+
+ ); +} diff --git a/src/features/alerts/components/table-alerts.tsx b/src/features/alerts/components/table-alerts.tsx index 899ddbe4..683a8be4 100644 --- a/src/features/alerts/components/table-alerts.tsx +++ b/src/features/alerts/components/table-alerts.tsx @@ -2,33 +2,28 @@ import { formatDistanceToNow } from "date-fns"; import { Cell, Column, - Input, Row, - SearchField, Table, TableBody, - FieldGroup, TableHeader, - SearchFieldClearButton, Badge, Button, ResizableTableContainer, } from "@stacklok/ui-kit"; -import { Switch } from "@stacklok/ui-kit"; import { AlertConversation, QuestionType } from "@/api/generated"; -import { Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; import { sanitizeQuestionPrompt, parsingPromptText, getIssueDetectedType, } from "@/lib/utils"; import { useAlertSearch } from "@/hooks/useAlertSearch"; -import { useCallback } from "react"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import { useFilteredAlerts } from "@/hooks/useAlertsData"; +import { useNavigate } from "react-router-dom"; import { useClientSidePagination } from "@/hooks/useClientSidePagination"; import { TableAlertTokenUsage } from "./table-alert-token-usage"; -import { Key01, PackageX, SearchMd } from "@untitled-ui/icons-react"; +import { Key01, PackageX } from "@untitled-ui/icons-react"; +import { SearchFieldAlerts } from "./search-field-alerts"; +import { useQueryGetWorkspaceAlertTable } from "../hooks/use-query-get-workspace-alerts-table"; +import { SwitchMaliciousAlertsFilter } from "./switch-malicious-alerts-filter"; const getTitle = (alert: AlertConversation) => { const prompt = alert.conversation; @@ -80,18 +75,9 @@ function IssueDetectedCellContent({ alert }: { alert: AlertConversation }) { } export function TableAlerts() { - const { - isMaliciousFilterActive, - setIsMaliciousFilterActive, - setSearch, - search, - page, - nextPage, - prevPage, - } = useAlertSearch(); + const { page, nextPage, prevPage } = useAlertSearch(); const navigate = useNavigate(); - const [searchParams, setSearchParams] = useSearchParams(); - const { data: filteredAlerts = [] } = useFilteredAlerts(); + const { data: filteredAlerts = [] } = useQueryGetWorkspaceAlertTable(); const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination( filteredAlerts, @@ -99,37 +85,6 @@ export function TableAlerts() { 15, ); - const handleToggleFilter = useCallback( - (isChecked: boolean) => { - if (isChecked) { - searchParams.set("maliciousPkg", "true"); - searchParams.delete("search"); - setSearch(""); - } else { - searchParams.delete("maliciousPkg"); - } - setSearchParams(searchParams); - setIsMaliciousFilterActive(isChecked); - }, - [setSearchParams, setSearch, searchParams, setIsMaliciousFilterActive], - ); - - const handleSearch = useCallback( - (value: string) => { - if (value) { - searchParams.set("search", value); - searchParams.delete("maliciousPkg"); - setSearch(value); - setIsMaliciousFilterActive(false); - } else { - searchParams.delete("search"); - setSearch(""); - } - setSearchParams(searchParams); - }, - [searchParams, setIsMaliciousFilterActive, setSearch, setSearchParams], - ); - return ( <>
@@ -141,37 +96,8 @@ export function TableAlerts() {
-
- - - Malicious Packages - - - -

Filter by malicious packages

-
-
-
- handleSearch(value.toLowerCase().trim())} - > - - } - /> - - - + +
diff --git a/src/features/alerts/hooks/use-alerts-filter-search-params.ts b/src/features/alerts/hooks/use-alerts-filter-search-params.ts new file mode 100644 index 00000000..6fe5c200 --- /dev/null +++ b/src/features/alerts/hooks/use-alerts-filter-search-params.ts @@ -0,0 +1,52 @@ +import { useCallback } from "react"; +import { useSearchParams } from "react-router-dom"; +import { z } from "zod"; + +export enum AlertsFilterView { + ALL = "all", + MALICIOUS = "malicious", + SECRETS = "secrets", +} + +const alertsFilterSchema = z.object({ + search: z.string().optional(), + view: z.nativeEnum(AlertsFilterView), +}); + +type AlertsFilterSchema = z.output; + +const DEFAULT_FILTER: AlertsFilterSchema = { + view: AlertsFilterView.ALL, +}; + +export const useAlertsFilterSearchParams = () => { + const [searchParams, setSearchParams] = useSearchParams( + new URLSearchParams(DEFAULT_FILTER), + ); + + const setView = useCallback( + (view: AlertsFilterView) => { + setSearchParams((prev) => { + if (view) prev.set("view", view); + if (!view) prev.delete("view"); + return prev; + }); + }, + [setSearchParams], + ); + + const setSearch = useCallback( + (query: string | null) => { + setSearchParams((prev) => { + if (query !== null) prev.set("search", query); + if (query == null || query === "") prev.delete("search"); + return prev; + }); + }, + [setSearchParams], + ); + + const state = alertsFilterSchema.parse(Object.fromEntries(searchParams)); + + return { state, setView, setSearch }; +}; diff --git a/src/features/alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts b/src/features/alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts index ecb44d84..d3c4e2ff 100644 --- a/src/features/alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts +++ b/src/features/alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts @@ -1,15 +1,13 @@ -import { - AlertConversation, - V1GetWorkspaceAlertsResponse, -} from "@/api/generated"; -import { filterAlertsCritical } from "../lib/filter-alerts-critical"; +import { V1GetWorkspaceAlertsResponse } from "@/api/generated"; import { isAlertMalicious } from "../lib/is-alert-malicious"; import { useQueryGetWorkspaceAlerts } from "./use-query-get-workspace-alerts"; +import { multiFilter } from "@/lib/multi-filter"; +import { isAlertCritical } from "../lib/is-alert-critical"; // NOTE: This needs to be a stable function reference to enable memo-isation of // the select operation on each React re-render. -function select(data: V1GetWorkspaceAlertsResponse): AlertConversation[] { - return filterAlertsCritical(data).filter(isAlertMalicious); +function select(data: V1GetWorkspaceAlertsResponse) { + return multiFilter(data, [isAlertCritical, isAlertMalicious]); } export function useQueryGetWorkspaceAlertsMaliciousPkg() { diff --git a/src/features/alerts/hooks/use-query-get-workspace-alerts-secrets.ts b/src/features/alerts/hooks/use-query-get-workspace-alerts-secrets.ts index ee5b7a47..5e617173 100644 --- a/src/features/alerts/hooks/use-query-get-workspace-alerts-secrets.ts +++ b/src/features/alerts/hooks/use-query-get-workspace-alerts-secrets.ts @@ -1,15 +1,13 @@ -import { - V1GetWorkspaceAlertsResponse, - AlertConversation, -} from "@/api/generated"; -import { filterAlertsCritical } from "../lib/filter-alerts-critical"; +import { V1GetWorkspaceAlertsResponse } from "@/api/generated"; import { isAlertSecret } from "../lib/is-alert-secret"; import { useQueryGetWorkspaceAlerts } from "./use-query-get-workspace-alerts"; +import { multiFilter } from "@/lib/multi-filter"; +import { isAlertCritical } from "../lib/is-alert-critical"; // NOTE: This needs to be a stable function reference to enable memo-isation of -// the select operation on each React re-render. -function select(data: V1GetWorkspaceAlertsResponse): AlertConversation[] { - return filterAlertsCritical(data).filter(isAlertSecret); +// the select operation on each React re-render +function select(data: V1GetWorkspaceAlertsResponse) { + return multiFilter(data, [isAlertCritical, isAlertSecret]); } export function useQueryGetWorkspaceAlertSecrets() { diff --git a/src/features/alerts/hooks/use-query-get-workspace-alerts-table.ts b/src/features/alerts/hooks/use-query-get-workspace-alerts-table.ts new file mode 100644 index 00000000..42094205 --- /dev/null +++ b/src/features/alerts/hooks/use-query-get-workspace-alerts-table.ts @@ -0,0 +1,43 @@ +import { V1GetWorkspaceAlertsResponse } from "@/api/generated"; +import { useQueryGetWorkspaceAlerts } from "./use-query-get-workspace-alerts"; +import { useCallback } from "react"; +import { + AlertsFilterView, + useAlertsFilterSearchParams, +} from "./use-alerts-filter-search-params"; +import { multiFilter } from "@/lib/multi-filter"; +import { isAlertCritical } from "../lib/is-alert-critical"; +import { isAlertMalicious } from "../lib/is-alert-malicious"; +import { isAlertSecret } from "../lib/is-alert-secret"; +import { doesAlertIncludeSearch } from "../lib/does-alert-include-search"; +import { isAlertConversation } from "../lib/is-alert-conversation"; + +const FILTER: Record< + AlertsFilterView, + (alert: V1GetWorkspaceAlertsResponse[number]) => boolean +> = { + all: () => true, + malicious: isAlertMalicious, + secrets: isAlertSecret, +}; + +export function useQueryGetWorkspaceAlertTable() { + const { state } = useAlertsFilterSearchParams(); + + // NOTE: This needs to be a stable function reference to enable memo-isation + // of the select operation on each React re-render. + const select = useCallback( + (data: V1GetWorkspaceAlertsResponse) => { + return multiFilter(data, [ + isAlertCritical, + isAlertConversation, + FILTER[state.view], + ]).filter((alert) => doesAlertIncludeSearch(alert, state.search ?? null)); + }, + [state.search, state.view], + ); + + return useQueryGetWorkspaceAlerts({ + select, + }); +} diff --git a/src/features/alerts/lib/does-alert-include-search.ts b/src/features/alerts/lib/does-alert-include-search.ts new file mode 100644 index 00000000..8b91b89b --- /dev/null +++ b/src/features/alerts/lib/does-alert-include-search.ts @@ -0,0 +1,38 @@ +import { + AlertConversation, + V1GetWorkspaceAlertsResponse, +} from "@/api/generated"; + +export function doesAlertIncludeSearch( + alert: V1GetWorkspaceAlertsResponse[number], + searchQuery: string | null, +): alert is AlertConversation { + if (alert == null) return false; + if (searchQuery === null) return true; + + const trigger_type: string = alert.trigger_type; + const trigger_string: string | null = + typeof alert.trigger_string === "string" ? alert.trigger_string : null; + + let malicious_pkg_name: string | null = null; + let malicious_pkg_type: string | null = null; + + if ( + alert.trigger_string !== null && + typeof alert.trigger_string === "object" && + "name" in alert.trigger_string && + typeof alert.trigger_string.name === "string" && + "type" in alert.trigger_string && + typeof alert.trigger_string.type === "string" + ) { + malicious_pkg_name = alert.trigger_string.name; + malicious_pkg_type = alert.trigger_string.type; + } + + return [ + trigger_type, + trigger_string, + malicious_pkg_name, + malicious_pkg_type, + ].some((i) => i?.toLowerCase().includes(searchQuery)); +} diff --git a/src/features/alerts/lib/filter-alerts-critical.ts b/src/features/alerts/lib/filter-alerts-critical.ts deleted file mode 100644 index 5537728d..00000000 --- a/src/features/alerts/lib/filter-alerts-critical.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - AlertConversation, - V1GetWorkspaceAlertsResponse, -} from "@/api/generated"; - -export function filterAlertsCritical( - alerts: V1GetWorkspaceAlertsResponse | undefined, -): AlertConversation[] { - return (alerts ?? []) - .filter( - (alert): alert is AlertConversation => - alert !== null && - alert.trigger_category === "critical" && - alert?.conversation.question_answers.every( - (item) => item.answer && item.question, - ), - ) - .sort( - (a, b) => - new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), - ); -} diff --git a/src/features/alerts/lib/is-alert-conversation.ts b/src/features/alerts/lib/is-alert-conversation.ts new file mode 100644 index 00000000..fdcf23cc --- /dev/null +++ b/src/features/alerts/lib/is-alert-conversation.ts @@ -0,0 +1,14 @@ +import { + AlertConversation, + V1GetWorkspaceAlertsResponse, +} from "@/api/generated"; + +export function isAlertConversation( + alert: V1GetWorkspaceAlertsResponse[number], +): alert is AlertConversation { + return Boolean( + alert?.conversation.question_answers.every( + (item) => item.answer && item.question, + ), + ); +} diff --git a/src/features/alerts/lib/is-alert-critical.ts b/src/features/alerts/lib/is-alert-critical.ts new file mode 100644 index 00000000..d4ea3e46 --- /dev/null +++ b/src/features/alerts/lib/is-alert-critical.ts @@ -0,0 +1,10 @@ +import { + AlertConversation, + V1GetWorkspaceAlertsResponse, +} from "@/api/generated"; + +export function isAlertCritical( + alert: V1GetWorkspaceAlertsResponse[number], +): alert is AlertConversation { + return alert !== null && alert.trigger_category === "critical"; +} diff --git a/src/features/alerts/lib/is-alert-malicious.ts b/src/features/alerts/lib/is-alert-malicious.ts index 77ad4f2a..2c9e10ed 100644 --- a/src/features/alerts/lib/is-alert-malicious.ts +++ b/src/features/alerts/lib/is-alert-malicious.ts @@ -1,14 +1,13 @@ import { AlertConversation } from "@/api/generated"; -export function isAlertMalicious({ - trigger_string, - trigger_category, -}: AlertConversation) { +export function isAlertMalicious( + alert: AlertConversation | null, +): alert is AlertConversation { return ( - trigger_category === "critical" && - trigger_string !== null && - typeof trigger_string === "object" && - "status" in trigger_string && - trigger_string.status === "malicious" + alert?.trigger_category === "critical" && + alert.trigger_string !== null && + typeof alert.trigger_string === "object" && + "status" in alert.trigger_string && + alert.trigger_string.status === "malicious" ); } diff --git a/src/features/alerts/lib/is-alert-secret.ts b/src/features/alerts/lib/is-alert-secret.ts index dbaf329a..9e7fd87a 100644 --- a/src/features/alerts/lib/is-alert-secret.ts +++ b/src/features/alerts/lib/is-alert-secret.ts @@ -1,8 +1,8 @@ -import { AlertConversation } from "@/api/generated"; +import { V1GetWorkspaceAlertsResponse } from "@/api/generated"; -export function isAlertSecret({ - trigger_type, - trigger_category, -}: AlertConversation) { - return trigger_category === "critical" && trigger_type === "codegate-secrets"; +export function isAlertSecret(alert: V1GetWorkspaceAlertsResponse[number]) { + return ( + alert?.trigger_category === "critical" && + alert.trigger_type === "codegate-secrets" + ); } diff --git a/src/hooks/useAlertsData.ts b/src/hooks/useAlertsData.ts deleted file mode 100644 index b2e966ad..00000000 --- a/src/hooks/useAlertsData.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { - AlertConversation, - v1GetWorkspaceAlerts, - V1GetWorkspaceAlertsData, -} from "@/api/generated"; -import { getMaliciousPackage } from "@/lib/utils"; -import { MaliciousPkgType, TriggerType } from "@/types"; -import { useAlertSearch } from "./useAlertSearch"; -import { useActiveWorkspaceName } from "@/features/workspace/hooks/use-active-workspace-name"; - -const fetchAlerts = async ( - options: V1GetWorkspaceAlertsData, -): Promise => { - const { data } = await v1GetWorkspaceAlerts(options); - - const results = (data ?? []) - .filter((alert): alert is AlertConversation => alert !== null) - .filter((alert) => alert.trigger_category === "critical") - .filter((alert) => - alert?.conversation.question_answers.every( - (item) => item.answer && item.question, - ), - ) - .sort( - (a, b) => - new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), - ); - - return results; -}; - -/** @deprecated — incrementally aiming to replace this with more granular - * approach - AMG */ -export const useAlertsData = ({ ...args } = {}) => { - const { data: activeWorkspaceName } = useActiveWorkspaceName(); - - const options: V1GetWorkspaceAlertsData = { - path: { - workspace_name: activeWorkspaceName ?? "default", - }, - }; - - return useQuery({ - queryKey: ["useAlertsData", options], - queryFn: () => fetchAlerts(options), - ...args, - }); -}; - -export const useFilteredAlerts = () => { - const { isMaliciousFilterActive, search } = useAlertSearch(); - - return useAlertsData({ - // NOTE: Inlined select will run on every render, we'll remove this over - // time though - AMG - select: ( - data: Exclude["data"], undefined>, - ) => { - return data - .filter((alert) => { - const maliciousPkg = getMaliciousPackage(alert.trigger_string); - const maliciousPkgName = - typeof maliciousPkg === "object" - ? maliciousPkg?.type - : maliciousPkg; - - const maliciousPkgType = - typeof maliciousPkg === "object" - ? maliciousPkg?.name - : maliciousPkg; - - return ( - maliciousPkgName?.toLowerCase().includes(search) || - maliciousPkgType?.toLowerCase().includes(search) || - alert.trigger_type?.toLowerCase().includes(search) - ); - }) - .filter((alert) => { - if (!isMaliciousFilterActive) { - return true; - } - - return ( - typeof alert.trigger_string === "object" && - (alert.trigger_type as TriggerType) === "codegate-context-retriever" - ); - }) - .sort( - (a, b) => - new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), - ); - }, - }); -}; - -export function useMaliciousPackagesChartData() { - const { data: alerts = [] } = useAlertsData(); - - return alerts - .filter((item) => typeof item.trigger_string === "object") - .filter((item) => item.trigger_type === "codegate-context-retriever") - .map((item) => item.trigger_string as MaliciousPkgType); -} diff --git a/src/lib/multi-filter.ts b/src/lib/multi-filter.ts new file mode 100644 index 00000000..59a847bc --- /dev/null +++ b/src/lib/multi-filter.ts @@ -0,0 +1,20 @@ +/** + * Utility function for chaining multiple predicates when filtering an array, + * avoiding iterating over an array more than once. Intended to aid in composing + * filtering behavior, particularly when querying data from the server with react-query. + * + * Not intended to be used to chain filters together, but rather to apply + * multiple filter criteria to an array in a single pass. + * + * @example + * const data = [0,1,2,3,4,5] + * const result = multiFilter(data, [(d) => !!d, (d) => d > 3]) + */ +export function multiFilter( + array: T[] | undefined, + predicates: ((i: T) => boolean)[], +): T[] { + if (!array) return []; + + return array.filter((i) => predicates.every((p) => p(i) === true)); +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b1e13234..07a499a6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +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-alert-malicious"; -import { MaliciousPkgType, TriggerType } from "@/types"; import { format, isToday, isYesterday } from "date-fns"; const ONE_DAY_MS = 24 * 60 * 60 * 1000; @@ -97,45 +96,6 @@ export function groupPromptsByRelativeDate(prompts: Conversation[]) { return grouped; } -export function getAllIssues(alerts: AlertConversation[]) { - const groupedTriggerCounts = alerts.reduce>( - (acc, alert) => { - const triggerType: TriggerType = alert.trigger_type; - if (triggerType) { - acc[triggerType] = (acc[triggerType] || 0) + 1; - } - return acc; - }, - {}, - ); - - const maxCount = Math.max(...Object.values(groupedTriggerCounts)); - - const sortedTagCounts = Object.entries(groupedTriggerCounts).sort( - ([, countA], [, countB]) => countB - countA, - ); - return { maxCount, sortedTagCounts }; -} - -export function getMaliciousPackages() { - const packageCounts = ([] as { packages: [] }[]).reduce< - Record - >((acc, prompt) => { - (prompt?.packages ?? []).forEach((pkg) => { - acc[pkg] = (acc[pkg] || 0) + 1; - }); - return acc; - }, {}); - - const chartData = Object.entries(packageCounts).map(([pkg, count]) => ({ - id: pkg, - label: pkg, - value: count, - })); - - return chartData; -} - export function sanitizeQuestionPrompt({ question, answer, @@ -164,20 +124,6 @@ export function sanitizeQuestionPrompt({ } } -export function getMaliciousPackage( - value: AlertConversation["trigger_string"], -): string | (MaliciousPkgType & { [key: string]: string }) | null { - if (typeof value === "string") { - return value; - } - - if (typeof value === "object" && value !== null) { - return value as MaliciousPkgType; - } - - return null; -} - export function getIssueDetectedType( alert: AlertConversation, ): "malicious_package" | "leaked_secret" | null { diff --git a/src/routes/route-dashboard.tsx b/src/routes/route-dashboard.tsx index 4f469fdd..c8345091 100644 --- a/src/routes/route-dashboard.tsx +++ b/src/routes/route-dashboard.tsx @@ -1,31 +1,10 @@ import { Separator } from "@stacklok/ui-kit"; -import { useEffect } from "react"; -import { useSearchParams } from "react-router-dom"; -import { useAlertsData } from "@/hooks/useAlertsData"; -import { useAlertSearch } from "@/hooks/useAlertSearch"; import { TableAlerts } from "@/features/alerts/components/table-alerts"; import { AlertsSummaryMaliciousPkg } from "@/features/alerts/components/alerts-summary-malicious-pkg"; import { AlertsSummaryWorkspaceTokenUsage } from "@/features/alerts/components/alerts-summary-workspace-token-usage"; import { AlertsSummaryMaliciousSecrets } from "@/features/alerts/components/alerts-summary-secrets"; export function RouteDashboard() { - const [searchParams] = useSearchParams(); - - const { setIsMaliciousFilterActive, setSearch } = useAlertSearch(); - - const { data: alerts = [] } = useAlertsData(); - - useEffect(() => { - const isMaliciousFilterActive = searchParams.get("maliciousPkg") === "true"; - const searchFilterParam = searchParams.get("search"); - if (isMaliciousFilterActive && alerts.length > 0) { - setIsMaliciousFilterActive(true); - } - if (searchFilterParam && alerts.length > 0) { - setSearch(searchFilterParam); - } - }, [searchParams, setIsMaliciousFilterActive, setSearch, alerts]); - return (