Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added dashboard for Alert Quality #1977

Open
wants to merge 66 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
a6b43f8
Added UI for Alert Quality dashboard
vikashsprem Sep 21, 2024
9631330
Merge branch 'main' into main
Matvey-Kuk Sep 22, 2024
010054b
Merge branch 'main' into main
talboren Sep 24, 2024
7e6103e
Merge branch 'keephq:main' into main
vikashsprem Sep 26, 2024
177d2d6
Added installed providers
vikashsprem Sep 26, 2024
ada4328
feat: added the quality metrics stats logic in backend and minor modi…
rajeshj11 Sep 26, 2024
8b70d51
Merge pull request #1 from rajeshj11/feat-1779-main
vikashsprem Sep 27, 2024
a3b7b79
feat: added the providers filter tabs and custom filters
rajeshj11 Sep 27, 2024
9523bc8
Merge pull request #2 from rajeshj11/feat-1779-main
vikashsprem Sep 27, 2024
6e9b293
feat: intergated the custom filters
rajeshj11 Sep 27, 2024
7430f43
chore: chnage the time frame to 7 days as default
rajeshj11 Sep 27, 2024
595b6cb
Merge pull request #3 from rajeshj11/feat-1779-main
vikashsprem Sep 27, 2024
4ca62b8
feat: move the quality table report to dashbaord.
rajeshj11 Sep 28, 2024
c267f40
Merge pull request #4 from rajeshj11/feat-1779-main
vikashsprem Sep 28, 2024
f25b5fb
Fixes some warning
vikashsprem Sep 28, 2024
7215dbe
fix: dashboard issue
rajeshj11 Sep 28, 2024
5f02f2c
Merge branch 'main' into feat-1779-main
rajeshj11 Sep 28, 2024
928564e
Merge pull request #5 from rajeshj11/feat-1779-main
vikashsprem Sep 28, 2024
b1161eb
Fix build issue
vikashsprem Sep 28, 2024
29fdff4
fix: lint issues
rajeshj11 Sep 29, 2024
b1b3fa5
Merge pull request #6 from rajeshj11/feat-1779-main
rajeshj11 Sep 29, 2024
9fcc1ad
Merge branch 'main' into main
rajeshj11 Sep 29, 2024
4e49eb0
feat: add more fields options give alteast 3 options to select
rajeshj11 Sep 29, 2024
738ac61
Merge branch 'keephq:main' into main
vikashsprem Sep 29, 2024
cab673c
fix: menu click issue
rajeshj11 Sep 29, 2024
bb4a6e1
chore: minor style change
rajeshj11 Sep 29, 2024
cae90a5
chore: minor comment
rajeshj11 Sep 29, 2024
a236b95
chore: reverted the flex-1 change
rajeshj11 Sep 29, 2024
750c536
Merge pull request #7 from rajeshj11/feat-1779-main
vikashsprem Sep 29, 2024
b416a89
Merge branch 'main' into main
Matvey-Kuk Sep 29, 2024
934ced6
Merge branch 'main' into main
talboren Sep 30, 2024
153a40a
Merge branch 'main' into main
rajeshj11 Sep 30, 2024
611d8a4
Merge branch 'main' into main
rajeshj11 Oct 1, 2024
8f4c1ac
Merge branch 'main' into main
rajeshj11 Oct 2, 2024
792b164
Merge branch 'main' into main
rajeshj11 Oct 6, 2024
35ff5ba
Merge branch 'main' into main
rajeshj11 Oct 7, 2024
d90e52d
chore: handle the feedback changes
rajeshj11 Oct 7, 2024
35a8216
Merge branch 'main' into feat-1779-main
rajeshj11 Oct 7, 2024
ad78c09
Merge pull request #8 from rajeshj11/feat-1779-main
vikashsprem Oct 7, 2024
9b58a14
chore:handle the date filter and removed the tab filters in the table
rajeshj11 Oct 7, 2024
789d6ba
chore:minor dependency check
rajeshj11 Oct 7, 2024
874bc17
Merge pull request #9 from rajeshj11/feat-1779-main
rajeshj11 Oct 7, 2024
43fc32e
Merge branch 'main' into main
rajeshj11 Oct 7, 2024
fc4ca1f
Merge branch 'main' into feat-1779-main
rajeshj11 Oct 7, 2024
557f1e9
Merge pull request #10 from rajeshj11/feat-1779-main
rajeshj11 Oct 7, 2024
7e43700
Merge branch 'main' into main
rajeshj11 Oct 8, 2024
37a7ee2
Merge branch 'main' into main
rajeshj11 Oct 8, 2024
a2f091b
Merge branch 'main' into main
vikashsprem Oct 8, 2024
c6c71eb
Merge branch 'main' into main
rajeshj11 Oct 9, 2024
34d7385
Merge branch 'main' into main
rajeshj11 Oct 9, 2024
26abf15
Merge branch 'main' into main
vikashsprem Oct 9, 2024
099a361
Merge branch 'main' into main
rajeshj11 Oct 10, 2024
6e2bdcd
Merge branch 'main' into main
rajeshj11 Oct 10, 2024
728bdaf
Merge branch 'main' into main
talboren Oct 11, 2024
6ef9815
Merge branch 'main' into main
rajeshj11 Oct 13, 2024
02efe3a
Merge branch 'main' into main
rajeshj11 Oct 13, 2024
751c0e9
Merge branch 'main' into main
rajeshj11 Oct 14, 2024
9148db9
Merge branch 'main' into main
rajeshj11 Oct 14, 2024
9d01b08
chore: remove unwanted page
rajeshj11 Oct 14, 2024
02c07a8
Merge pull request #11 from rajeshj11/feat-1779-main
rajeshj11 Oct 14, 2024
1c984da
Merge branch 'main' into main
rajeshj11 Oct 15, 2024
e1e83b7
Merge branch 'main' into main
rajeshj11 Oct 16, 2024
7b334c9
Merge branch 'main' into main
talboren Oct 16, 2024
4ee1b88
Merge branch 'main' into main
rajeshj11 Oct 17, 2024
21fdaa5
Merge branch 'main' into main
vikashsprem Oct 17, 2024
97ae969
Merge branch 'main' into main
rajeshj11 Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 329 additions & 0 deletions keep-ui/app/alerts/alert-quality-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
"use client"; // Add this line at the top to make this a Client Component

import React, {
useState,
useEffect,
Dispatch,
SetStateAction,
useMemo,
} from "react";
import { GenericTable } from "@/components/table/GenericTable";
import { useAlertQualityMetrics } from "utils/hooks/useAlertQuality";
import { useProviders } from "utils/hooks/useProviders";
import { Provider, ProvidersResponse } from "app/providers/providers";
import { TabGroup, TabList, Tab } from "@tremor/react";
import { GenericFilters } from "@/components/filters/GenericFilters";
import { useSearchParams } from "next/navigation";
import { AlertKnownKeys } from "./models";

const tabs = [
{ name: "All", value: "all" },
{ name: "Installed", value: "installed" },
];

const ALERT_QUALITY_FILTERS = [
{
type: "date",
key: "time_stamp",
value: "",
name: "Last received",
},
];

export const FilterTabs = ({
tabs,
setTab,
tab,
}: {
tabs: { name: string; value: string }[];
setTab: Dispatch<SetStateAction<number>>;
tab: number;
}) => {
return (
<div className="max-w-lg space-y-12 pt-6">
<TabGroup
index={tab}
onIndexChange={(index: number) => {
setTab(index);
}}
>
<TabList variant="solid" color="black" className="bg-gray-300">
{tabs.map((tabItem) => (
<Tab key={tabItem.value}>{tabItem.name}</Tab>
))}
</TabList>
</TabGroup>
</div>
);
};

interface AlertMetricQuality {
alertsReceived: number;
alertsCorrelatedToIncidentsPercentage: number;
alertsWithSeverityPercentage: number;
[key: string]: number;
}

type FinalAlertQuality = (Provider &
AlertMetricQuality & { provider_display_name: string })[];
interface Pagination {
limit: number;
offset: number;
}

const QualityTable = ({
providersMeta,
alertsQualityMetrics,
isDashBoard,
setFields,
fieldsValue,
}: {
providersMeta: ProvidersResponse | undefined;
alertsQualityMetrics: Record<string, Record<string, any>> | undefined;
isDashBoard?: boolean;
setFields: (fields: string | string[] | Record<string, string>) => void;
fieldsValue: string | string[] | Record<string, string>;
}) => {
const [pagination, setPagination] = useState<Pagination>({
limit: 10,
offset: 0,
});
const customFieldFilter = {
type: "select",
key: "fields",
value: isDashBoard ? fieldsValue : "",
name: "Field",
options: AlertKnownKeys.map((key) => ({ value: key, label: key })),
// only_one: true,
searchParamsNotNeed: isDashBoard,
can_select: 3,
setFilter: setFields,
};
const searchParams = useSearchParams();
const entries = searchParams ? Array.from(searchParams.entries()) : [];
const params = entries.reduce((acc, [key, value]) => {
if (key in acc) {
if (Array.isArray(acc[key])) {
acc[key] = [...acc[key], value];
return acc;
} else {
acc[key] = [acc[key] as string, value];
}
return acc;
}
acc[key] = value;
return acc;
}, {} as Record<string, string | string[]>);
function toArray(value: string | string[]) {
if (!value) {
return [];
}

if (!Array.isArray(value) && value) {
return [value];
}

return value;
}
const fields = toArray(
params?.["fields"] || (fieldsValue as string | string[]) || []
) as string[];
const [tab, setTab] = useState(1);

const handlePaginationChange = (newLimit: number, newOffset: number) => {
setPagination({ limit: newLimit, offset: newOffset });
};

useEffect(() => {
handlePaginationChange(10, 0);
}, [tab, searchParams?.toString()]);

// Construct columns based on the fields selected
const columns = useMemo(() => {
const baseColumns = [
{
header: "Provider Name",
accessorKey: "provider_display_name",
},
{
header: "Alerts Received",
accessorKey: "alertsReceived",
},
{
header: "% of Alerts Correlated to Incidents",
accessorKey: "alertsCorrelatedToIncidentsPercentage",
cell: (info: any) => `${info.getValue().toFixed(2)}%`,
},
];

// Add dynamic columns based on the fields
const dynamicColumns = fields.map((field: string) => ({
header: `% of Alerts Having ${
field.charAt(0).toUpperCase() + field.slice(1)
}`,
accessorKey: `alertsWith${
field.charAt(0).toUpperCase() + field.slice(1)
}Percentage`,
cell: (info: any) => `${info.getValue().toFixed(2)}%`,
}));

return [...baseColumns, ...dynamicColumns];
}, [fields]);

// Process data and include dynamic fields
const finalData = useMemo(() => {
let providers: Provider[] | null = null;

if (!providersMeta || !alertsQualityMetrics) {
return null;
}

switch (tab) {
case 0:
providers = providersMeta?.providers || providers;
break;
case 1:
providers = providersMeta?.installed_providers || providers;
break;
default:
providers = providersMeta?.providers || providers;
break;
}

if (!providers) {
return null;
}

const groupedMetrics: { [key: string]: any } = {};

if (tab === 0) {
// Iterate over each provider in the alertsMetrics object
for (const provider in alertsQualityMetrics) {
const metrics = alertsQualityMetrics[provider];
const providerType = metrics.provider_type;

// If the provider_type doesn't exist in the result, initialize it
if (!groupedMetrics[providerType]) {
groupedMetrics[providerType] = {
total_alerts: 0,
correlated_alerts: 0,
};
}

// Aggregate the values for total_alerts, correlated_alerts, etc.
groupedMetrics[providerType].total_alerts += metrics.total_alerts;
groupedMetrics[providerType].correlated_alerts +=
metrics.correlated_alerts;

fields.forEach((field) => {
const key = `${field}_count`;
groupedMetrics[providerType][key] =
groupedMetrics[providerType][key] || 0;
groupedMetrics[providerType][key] += metrics[key];
});
}
}

const innerData: FinalAlertQuality = providers.map((provider) => {
const providerId = provider.id;
const providerType = provider.type;
const key =`${providerId}_${providerType}`;
const alertQuality = tab ===0 ? groupedMetrics[providerType] : alertsQualityMetrics[key];
const totalAlertsReceived = alertQuality?.total_alerts ?? 0;
const correlated_alerts = alertQuality?.correlated_alerts ?? 0;
const correltedPert =
totalAlertsReceived && correlated_alerts
? (correlated_alerts / totalAlertsReceived) * 100
: 0;
const severityPert = totalAlertsReceived
? ((alertQuality?.severity_count ?? 0) / totalAlertsReceived) * 100
: 0;

// Calculate percentages for dynamic fields
const dynamicFieldPercentages = fields.reduce((acc, field: string) => {
acc[
`alertsWith${
field.charAt(0).toUpperCase() + field.slice(1)
}Percentage`
] = totalAlertsReceived
? ((alertQuality?.[`${field}_count`] ?? 0) / totalAlertsReceived) *
100
: 0;
return acc;
}, {} as Record<string, number>);

return {
...provider,
alertsReceived: totalAlertsReceived,
alertsCorrelatedToIncidentsPercentage: correltedPert,
alertsWithSeverityPercentage: severityPert,
...dynamicFieldPercentages, // Add dynamic field percentages here
provider_display_name:
provider?.details?.name ? `${provider.details.name} (${provider.display_name})` : provider.display_name || "",
} as FinalAlertQuality[number];
});

return innerData;
}, [tab, providersMeta, alertsQualityMetrics, fields]);

return (
<div
className={`flex flex-col gap-2 p-2 px-4 ${isDashBoard ? "h-[90%]" : ""}`}
>
<div>
{!isDashBoard && (
<h1 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4">
Alert Quality Dashboard
</h1>
)}
<div className="flex justify-end items-end mb-4">
{/* if we want to use tabs. we can enable it */}
{/* <FilterTabs tabs={tabs} setTab={setTab} tab={tab} /> */}
<GenericFilters
filters={
isDashBoard
? [customFieldFilter]
: [...ALERT_QUALITY_FILTERS, customFieldFilter]
}
/>
</div>
</div>
{finalData && (
<GenericTable
data={finalData}
columns={columns}
rowCount={finalData?.length}
offset={pagination.offset}
limit={pagination.limit}
onPaginationChange={handlePaginationChange}
dataFetchedAtOneGO={true}
onRowClick={(row) => {
console.log("Row clicked:", row);
}}
/>
)}
</div>
);
};

const AlertQuality = ({ isDashBoard }: { isDashBoard?: boolean }) => {
const [fieldsValue, setFieldsValue] = useState<
string | string[] | Record<string, string>
>("severity");
const { data: providersMeta } = useProviders();
const { data: alertsQualityMetrics, error } = useAlertQualityMetrics(
isDashBoard ? (fieldsValue as string) : ""
);

return (
<QualityTable
providersMeta={providersMeta}
alertsQualityMetrics={alertsQualityMetrics}
isDashBoard={isDashBoard}
setFields={setFieldsValue}
fieldsValue={fieldsValue}
/>
);
};

export default AlertQuality;
Loading
Loading