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

Merged
merged 74 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 71 commits
Commits
Show all changes
74 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…
rajesh-jonnalagadda 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
rajesh-jonnalagadda Sep 27, 2024
9523bc8
Merge pull request #2 from rajeshj11/feat-1779-main
vikashsprem Sep 27, 2024
6e9b293
feat: intergated the custom filters
rajesh-jonnalagadda Sep 27, 2024
7430f43
chore: chnage the time frame to 7 days as default
rajesh-jonnalagadda 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.
rajesh-jonnalagadda 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
rajesh-jonnalagadda Sep 28, 2024
5f02f2c
Merge branch 'main' into feat-1779-main
rajesh-jonnalagadda 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
rajesh-jonnalagadda Sep 29, 2024
b1b3fa5
Merge pull request #6 from rajeshj11/feat-1779-main
rajesh-jonnalagadda Sep 29, 2024
9fcc1ad
Merge branch 'main' into main
rajesh-jonnalagadda Sep 29, 2024
4e49eb0
feat: add more fields options give alteast 3 options to select
rajesh-jonnalagadda Sep 29, 2024
738ac61
Merge branch 'keephq:main' into main
vikashsprem Sep 29, 2024
cab673c
fix: menu click issue
rajesh-jonnalagadda Sep 29, 2024
bb4a6e1
chore: minor style change
rajesh-jonnalagadda Sep 29, 2024
cae90a5
chore: minor comment
rajesh-jonnalagadda Sep 29, 2024
a236b95
chore: reverted the flex-1 change
rajesh-jonnalagadda 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
rajesh-jonnalagadda Sep 30, 2024
611d8a4
Merge branch 'main' into main
rajesh-jonnalagadda Oct 1, 2024
8f4c1ac
Merge branch 'main' into main
rajesh-jonnalagadda Oct 2, 2024
792b164
Merge branch 'main' into main
rajesh-jonnalagadda Oct 6, 2024
35ff5ba
Merge branch 'main' into main
rajesh-jonnalagadda Oct 7, 2024
d90e52d
chore: handle the feedback changes
rajesh-jonnalagadda Oct 7, 2024
35a8216
Merge branch 'main' into feat-1779-main
rajesh-jonnalagadda 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
rajesh-jonnalagadda Oct 7, 2024
789d6ba
chore:minor dependency check
rajesh-jonnalagadda Oct 7, 2024
874bc17
Merge pull request #9 from rajeshj11/feat-1779-main
rajesh-jonnalagadda Oct 7, 2024
43fc32e
Merge branch 'main' into main
rajesh-jonnalagadda Oct 7, 2024
fc4ca1f
Merge branch 'main' into feat-1779-main
rajesh-jonnalagadda Oct 7, 2024
557f1e9
Merge pull request #10 from rajeshj11/feat-1779-main
rajesh-jonnalagadda Oct 7, 2024
7e43700
Merge branch 'main' into main
rajesh-jonnalagadda Oct 8, 2024
37a7ee2
Merge branch 'main' into main
rajesh-jonnalagadda Oct 8, 2024
a2f091b
Merge branch 'main' into main
vikashsprem Oct 8, 2024
c6c71eb
Merge branch 'main' into main
rajesh-jonnalagadda Oct 9, 2024
34d7385
Merge branch 'main' into main
rajesh-jonnalagadda Oct 9, 2024
26abf15
Merge branch 'main' into main
vikashsprem Oct 9, 2024
099a361
Merge branch 'main' into main
rajesh-jonnalagadda Oct 10, 2024
6e2bdcd
Merge branch 'main' into main
rajesh-jonnalagadda Oct 10, 2024
728bdaf
Merge branch 'main' into main
talboren Oct 11, 2024
6ef9815
Merge branch 'main' into main
rajesh-jonnalagadda Oct 13, 2024
02efe3a
Merge branch 'main' into main
rajesh-jonnalagadda Oct 13, 2024
751c0e9
Merge branch 'main' into main
rajesh-jonnalagadda Oct 14, 2024
9148db9
Merge branch 'main' into main
rajesh-jonnalagadda Oct 14, 2024
9d01b08
chore: remove unwanted page
rajesh-jonnalagadda Oct 14, 2024
02c07a8
Merge pull request #11 from rajeshj11/feat-1779-main
rajesh-jonnalagadda Oct 14, 2024
1c984da
Merge branch 'main' into main
rajesh-jonnalagadda Oct 15, 2024
e1e83b7
Merge branch 'main' into main
rajesh-jonnalagadda Oct 16, 2024
7b334c9
Merge branch 'main' into main
talboren Oct 16, 2024
4ee1b88
Merge branch 'main' into main
rajesh-jonnalagadda Oct 17, 2024
21fdaa5
Merge branch 'main' into main
vikashsprem Oct 17, 2024
97ae969
Merge branch 'main' into main
rajesh-jonnalagadda Oct 18, 2024
45cf659
Merge branch 'main' into main
rajesh-jonnalagadda Oct 19, 2024
fffafaf
Merge branch 'main' into main
rajesh-jonnalagadda Oct 20, 2024
b9e46be
Merge branch 'main' into main
rajesh-jonnalagadda Oct 20, 2024
2c836c0
chore: fix feedback changes
rajesh-jonnalagadda Oct 20, 2024
9912fac
Merge pull request #12 from rajeshj11/feat-1779-main
vikashsprem Oct 20, 2024
8e3b1cc
Merge branch 'main' into main
rajesh-jonnalagadda Oct 20, 2024
981abef
chore: add default filters in dashboard config
rajesh-jonnalagadda Oct 20, 2024
52ce668
Merge pull request #13 from rajeshj11/feat-1779-main
vikashsprem Oct 20, 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
345 changes: 345 additions & 0 deletions keep-ui/app/alerts/alert-quality-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
"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";
import { createColumnHelper, DisplayColumnDef } from "@tanstack/react-table";

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

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 columnHelper = createColumnHelper<FinalAlertQuality>();

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(0);

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 = [
columnHelper.display({
id: "provider_display_name",
header: "Provider Name",
cell: ({ row }) => {
const displayName = row.original.provider_display_name;
return (
<div className="flex flex-col gap-2">
<div>{displayName}</div>
<div>id: {row.original.id}</div>
<div>type: {row.original.type}</div>
</div>
);
},
}),
columnHelper.accessor("alertsReceived", {
id: "alertsReceived",
header: "Alerts Received",
}),
columnHelper.display({
id: "alertsCorrelatedToIncidentsPercentage",
header: "% of Alerts Correlated to Incidents",
cell: ({ row }) => {
return `${row.original.alertsCorrelatedToIncidentsPercentage.toFixed(2)}%`;
},
}),
] as DisplayColumnDef<FinalAlertQuality>[];

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

return [
...baseColumns,
...dynamicColumns,
] as DisplayColumnDef<FinalAlertQuality>[];
}, [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?.installed_providers,
...providersMeta?.linked_providers,
];
break;
case 1:
providers = providersMeta?.installed_providers || [];
break;
case 2:
providers = providersMeta?.linked_providers || [];
break;
default:
providers = [
...providersMeta?.installed_providers,
...providersMeta?.linked_providers,
];
break;
}

if (!providers) {
return null;
}

function getProviderDisplayName(provider: Provider) {
return (
(provider?.details?.name
? `${provider.details.name} (${provider.display_name})`
: provider.display_name) || provider.type
);
}

const innerData: FinalAlertQuality[] = providers.map((provider) => {
const providerId = provider.id;
const providerType = provider.type;
const key = `${providerId}_${providerType}`;
const alertQuality = 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: getProviderDisplayName(provider),
} as FinalAlertQuality;
});

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 items-end mb-4">
<div className="flex-1">
<FilterTabs tabs={tabs} setTab={setTab} tab={tab} />
</div>
<GenericFilters
filters={
isDashBoard
? [customFieldFilter]
: [...ALERT_QUALITY_FILTERS, customFieldFilter]
}
/>
</div>
</div>
{finalData && (
<GenericTable<FinalAlertQuality>
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