Skip to content

Commit 5a93f10

Browse files
vikashspremMatvey-Kuktalborenrajesh-jonnalagadda
authored
feat: Added dashboard for Alert Quality (#1977)
Signed-off-by: Rajesh Jonnalagadda <[email protected]> Co-authored-by: Matvey Kukuy <[email protected]> Co-authored-by: Tal <[email protected]> Co-authored-by: Rajesh Jonnalagadda <[email protected]> Co-authored-by: Rajesh Jonnalagadda <[email protected]>
1 parent e1e7eff commit 5a93f10

16 files changed

+975
-145
lines changed
+361
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
"use client"; // Add this line at the top to make this a Client Component
2+
3+
import React, {
4+
useState,
5+
useEffect,
6+
Dispatch,
7+
SetStateAction,
8+
useMemo,
9+
} from "react";
10+
import { GenericTable } from "@/components/table/GenericTable";
11+
import { useAlertQualityMetrics } from "utils/hooks/useAlertQuality";
12+
import { useProviders } from "utils/hooks/useProviders";
13+
import { Provider, ProvidersResponse } from "app/providers/providers";
14+
import { TabGroup, TabList, Tab } from "@tremor/react";
15+
import { GenericFilters } from "@/components/filters/GenericFilters";
16+
import { useSearchParams } from "next/navigation";
17+
import { AlertKnownKeys } from "./models";
18+
import { createColumnHelper, DisplayColumnDef } from "@tanstack/react-table";
19+
20+
const tabs = [
21+
{ name: "All", value: "all" },
22+
{ name: "Installed", value: "installed" },
23+
{ name: "Linked", value: "linked" },
24+
];
25+
26+
const ALERT_QUALITY_FILTERS = [
27+
{
28+
type: "date",
29+
key: "time_stamp",
30+
value: "",
31+
name: "Last received",
32+
},
33+
];
34+
35+
export const FilterTabs = ({
36+
tabs,
37+
setTab,
38+
tab,
39+
}: {
40+
tabs: { name: string; value: string }[];
41+
setTab: Dispatch<SetStateAction<number>>;
42+
tab: number;
43+
}) => {
44+
return (
45+
<div className="max-w-lg space-y-12 pt-6">
46+
<TabGroup
47+
index={tab}
48+
onIndexChange={(index: number) => {
49+
setTab(index);
50+
}}
51+
>
52+
<TabList variant="solid" color="black" className="bg-gray-300">
53+
{tabs.map((tabItem) => (
54+
<Tab key={tabItem.value}>{tabItem.name}</Tab>
55+
))}
56+
</TabList>
57+
</TabGroup>
58+
</div>
59+
);
60+
};
61+
62+
interface AlertMetricQuality {
63+
alertsReceived: number;
64+
alertsCorrelatedToIncidentsPercentage: number;
65+
alertsWithSeverityPercentage: number;
66+
[key: string]: number;
67+
}
68+
69+
type FinalAlertQuality = Provider &
70+
AlertMetricQuality & { provider_display_name: string };
71+
interface Pagination {
72+
limit: number;
73+
offset: number;
74+
}
75+
76+
const QualityTable = ({
77+
providersMeta,
78+
alertsQualityMetrics,
79+
isDashBoard,
80+
setFields,
81+
fieldsValue,
82+
}: {
83+
providersMeta: ProvidersResponse | undefined;
84+
alertsQualityMetrics: Record<string, Record<string, any>> | undefined;
85+
isDashBoard?: boolean;
86+
setFields: (fields: string | string[] | Record<string, string>) => void;
87+
fieldsValue: string | string[] | Record<string, string>;
88+
}) => {
89+
const [pagination, setPagination] = useState<Pagination>({
90+
limit: 10,
91+
offset: 0,
92+
});
93+
const customFieldFilter = {
94+
type: "select",
95+
key: "fields",
96+
value: isDashBoard ? fieldsValue : "",
97+
name: "Field",
98+
options: AlertKnownKeys.map((key) => ({ value: key, label: key })),
99+
// only_one: true,
100+
searchParamsNotNeed: isDashBoard,
101+
can_select: 3,
102+
setFilter: setFields,
103+
};
104+
const searchParams = useSearchParams();
105+
const entries = searchParams ? Array.from(searchParams.entries()) : [];
106+
const columnHelper = createColumnHelper<FinalAlertQuality>();
107+
108+
const params = entries.reduce(
109+
(acc, [key, value]) => {
110+
if (key in acc) {
111+
if (Array.isArray(acc[key])) {
112+
acc[key] = [...acc[key], value];
113+
return acc;
114+
} else {
115+
acc[key] = [acc[key] as string, value];
116+
}
117+
return acc;
118+
}
119+
acc[key] = value;
120+
return acc;
121+
},
122+
{} as Record<string, string | string[]>
123+
);
124+
function toArray(value: string | string[]) {
125+
if (!value) {
126+
return [];
127+
}
128+
129+
if (!Array.isArray(value) && value) {
130+
return [value];
131+
}
132+
133+
return value;
134+
}
135+
const fields = toArray(
136+
params?.["fields"] || (fieldsValue as string | string[]) || []
137+
) as string[];
138+
const [tab, setTab] = useState(0);
139+
140+
const handlePaginationChange = (newLimit: number, newOffset: number) => {
141+
setPagination({ limit: newLimit, offset: newOffset });
142+
};
143+
144+
useEffect(() => {
145+
handlePaginationChange(10, 0);
146+
}, [tab, searchParams?.toString()]);
147+
148+
// Construct columns based on the fields selected
149+
const columns = useMemo(() => {
150+
const baseColumns = [
151+
columnHelper.display({
152+
id: "provider_display_name",
153+
header: "Provider Name",
154+
cell: ({ row }) => {
155+
const displayName = row.original.provider_display_name;
156+
return (
157+
<div className="flex flex-col gap-2">
158+
<div>{displayName}</div>
159+
<div>id: {row.original.id}</div>
160+
<div>type: {row.original.type}</div>
161+
</div>
162+
);
163+
},
164+
}),
165+
columnHelper.accessor("alertsReceived", {
166+
id: "alertsReceived",
167+
header: "Alerts Received",
168+
}),
169+
columnHelper.display({
170+
id: "alertsCorrelatedToIncidentsPercentage",
171+
header: "% of Alerts Correlated to Incidents",
172+
cell: ({ row }) => {
173+
return `${row.original.alertsCorrelatedToIncidentsPercentage.toFixed(2)}%`;
174+
},
175+
}),
176+
] as DisplayColumnDef<FinalAlertQuality>[];
177+
178+
// Add dynamic columns based on the fields
179+
const dynamicColumns = fields.map((field: string) =>
180+
columnHelper.accessor(
181+
`alertsWith${field.charAt(0).toUpperCase() + field.slice(1)}Percentage`,
182+
{
183+
id: `alertsWith${
184+
field.charAt(0).toUpperCase() + field.slice(1)
185+
}Percentage`,
186+
header: `% of Alerts Having ${
187+
field.charAt(0).toUpperCase() + field.slice(1)
188+
}`,
189+
cell: (info: any) => `${info.getValue().toFixed(2)}%`,
190+
}
191+
)
192+
) as DisplayColumnDef<FinalAlertQuality>[];
193+
194+
return [
195+
...baseColumns,
196+
...dynamicColumns,
197+
] as DisplayColumnDef<FinalAlertQuality>[];
198+
}, [fields]);
199+
200+
// Process data and include dynamic fields
201+
const finalData = useMemo(() => {
202+
let providers: Provider[] | null = null;
203+
204+
if (!providersMeta || !alertsQualityMetrics) {
205+
return null;
206+
}
207+
208+
switch (tab) {
209+
case 0:
210+
providers = [
211+
...providersMeta?.installed_providers,
212+
...providersMeta?.linked_providers,
213+
];
214+
break;
215+
case 1:
216+
providers = providersMeta?.installed_providers || [];
217+
break;
218+
case 2:
219+
providers = providersMeta?.linked_providers || [];
220+
break;
221+
default:
222+
providers = [
223+
...providersMeta?.installed_providers,
224+
...providersMeta?.linked_providers,
225+
];
226+
break;
227+
}
228+
229+
if (!providers) {
230+
return null;
231+
}
232+
233+
function getProviderDisplayName(provider: Provider) {
234+
return (
235+
(provider?.details?.name
236+
? `${provider.details.name} (${provider.display_name})`
237+
: provider.display_name) || provider.type
238+
);
239+
}
240+
241+
const innerData: FinalAlertQuality[] = providers.map((provider) => {
242+
const providerId = provider.id;
243+
const providerType = provider.type;
244+
const key = `${providerId}_${providerType}`;
245+
const alertQuality = alertsQualityMetrics[key];
246+
const totalAlertsReceived = alertQuality?.total_alerts ?? 0;
247+
const correlated_alerts = alertQuality?.correlated_alerts ?? 0;
248+
const correltedPert =
249+
totalAlertsReceived && correlated_alerts
250+
? (correlated_alerts / totalAlertsReceived) * 100
251+
: 0;
252+
const severityPert = totalAlertsReceived
253+
? ((alertQuality?.severity_count ?? 0) / totalAlertsReceived) * 100
254+
: 0;
255+
256+
// Calculate percentages for dynamic fields
257+
const dynamicFieldPercentages = fields.reduce(
258+
(acc, field: string) => {
259+
acc[
260+
`alertsWith${
261+
field.charAt(0).toUpperCase() + field.slice(1)
262+
}Percentage`
263+
] = totalAlertsReceived
264+
? ((alertQuality?.[`${field}_count`] ?? 0) / totalAlertsReceived) *
265+
100
266+
: 0;
267+
return acc;
268+
},
269+
{} as Record<string, number>
270+
);
271+
272+
return {
273+
...provider,
274+
alertsReceived: totalAlertsReceived,
275+
alertsCorrelatedToIncidentsPercentage: correltedPert,
276+
alertsWithSeverityPercentage: severityPert,
277+
...dynamicFieldPercentages, // Add dynamic field percentages here
278+
provider_display_name: getProviderDisplayName(provider),
279+
} as FinalAlertQuality;
280+
});
281+
282+
return innerData;
283+
}, [tab, providersMeta, alertsQualityMetrics, fields]);
284+
285+
return (
286+
<div
287+
className={`flex flex-col gap-2 p-2 px-4 ${isDashBoard ? "h-[90%]" : ""}`}
288+
>
289+
<div>
290+
{!isDashBoard && (
291+
<h1 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4">
292+
Alert Quality Dashboard
293+
</h1>
294+
)}
295+
<div className="flex items-end mb-4">
296+
<div className="flex-1">
297+
<FilterTabs tabs={tabs} setTab={setTab} tab={tab} />
298+
</div>
299+
<GenericFilters
300+
filters={
301+
isDashBoard
302+
? [customFieldFilter]
303+
: [...ALERT_QUALITY_FILTERS, customFieldFilter]
304+
}
305+
/>
306+
</div>
307+
</div>
308+
{finalData && (
309+
<GenericTable<FinalAlertQuality>
310+
data={finalData}
311+
columns={columns}
312+
rowCount={finalData?.length}
313+
offset={pagination.offset}
314+
limit={pagination.limit}
315+
onPaginationChange={handlePaginationChange}
316+
dataFetchedAtOneGO={true}
317+
onRowClick={(row) => {
318+
console.log("Row clicked:", row);
319+
}}
320+
/>
321+
)}
322+
</div>
323+
);
324+
};
325+
326+
const AlertQuality = ({
327+
isDashBoard,
328+
filters,
329+
setFilters,
330+
}: {
331+
isDashBoard?: boolean;
332+
filters: {
333+
[x: string]: string | string[];
334+
};
335+
setFilters: any;
336+
}) => {
337+
const fieldsValue = filters?.fields || "";
338+
const { data: providersMeta } = useProviders();
339+
const { data: alertsQualityMetrics, error } = useAlertQualityMetrics(
340+
isDashBoard ? (fieldsValue as string | string[]) : ""
341+
);
342+
343+
return (
344+
<QualityTable
345+
providersMeta={providersMeta}
346+
alertsQualityMetrics={alertsQualityMetrics}
347+
isDashBoard={isDashBoard}
348+
setFields={(field) => {
349+
setFilters((filters: any) => {
350+
return {
351+
...filters,
352+
fields: field,
353+
};
354+
});
355+
}}
356+
fieldsValue={fieldsValue}
357+
/>
358+
);
359+
};
360+
361+
export default AlertQuality;

0 commit comments

Comments
 (0)