Skip to content

Commit

Permalink
Merge pull request #3 from rajeshj11/feat-1779-main
Browse files Browse the repository at this point in the history
feat: intergated the custom filters
  • Loading branch information
vikashsprem authored Sep 27, 2024
2 parents 9523bc8 + 7430f43 commit 595b6cb
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 54 deletions.
139 changes: 96 additions & 43 deletions keep-ui/app/alerts/quality/alert-quality-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ import React, {
import { GenericTable } from "@/components/table/GenericTable";
import { useAlertQualityMetrics } from "utils/hooks/useAlertQuality";
import { useProviders } from "utils/hooks/useProviders";
import {
Provider,
Providers,
ProvidersResponse,
} from "app/providers/providers";
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";

const tabs = [
{ name: "All", value: "alltime" },
Expand All @@ -33,16 +30,16 @@ const ALERT_QUALITY_FILTERS = [
},
{
type: "select",
key: "field",
key: "fields",
value: "",
name: "Field",
options: [
{ value: "team", label: "Team" },
{ value: "application", label: "Application" },
{ value: "subsystem", label: "Subsystem" },
{ value: "severity", label: "Severity" },
{ value: "priority", label: "Priority" },
{ value: "product", label: "Product" },
{ value: "department", label: "Department" },
{ value: "assignee", label: "Affected users" },
{ value: "service", label: "Service" },
],
only_one: true
},
];

Expand All @@ -64,7 +61,7 @@ export const FilterTabs = ({
}}
>
<TabList variant="solid" color="black" className="bg-gray-300">
{tabs.map((tabItem, index) => (
{tabs.map((tabItem) => (
<Tab key={tabItem.value}>{tabItem.name}</Tab>
))}
</TabList>
Expand All @@ -73,13 +70,14 @@ export const FilterTabs = ({
);
};

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

type FinalAlertQuality = (Provider & AlertMetricQuality)[];
interface Pagination {
limit: number;
offset: number;
Expand All @@ -96,6 +94,33 @@ const QualityTable = ({
limit: 10,
offset: 0,
});
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"] || []) as string[];
const [tab, setTab] = useState(0);

const handlePaginationChange = (newLimit: number, newOffset: number) => {
Expand All @@ -104,29 +129,46 @@ const QualityTable = ({

useEffect(() => {
handlePaginationChange(10, 0);
},[tab])

const columns = [
{
header: "Provider Name",
accessorKey: "display_name",
},
{
header: "Alerts Received",
accessorKey: "alertsReceived",
},
{
header: "% of Alerts Correlated to Incidents",
accessorKey: "alertsCorrelatedToIncidentsPercentage",
cell: (info: any) => `${info.getValue().toFixed(2)}%`,
},
{
header: "% of Alerts Having Severity", //we are considering critical and warning as severe
accessorKey: "alertsWithSeverityPercentage",
}, [tab, searchParams?.toString()]);

// Construct columns based on the fields selected
const columns = useMemo(() => {
const baseColumns = [
{
header: "Provider Name",
accessorKey: "display_name",
},
{
header: "Alerts Received",
accessorKey: "alertsReceived",
},
{
header: "% of Alerts Correlated to Incidents",
accessorKey: "alertsCorrelatedToIncidentsPercentage",
cell: (info: any) => `${info.getValue().toFixed(2)}%`,
},
{
header: "% of Alerts Having Severity",
accessorKey: "alertsWithSeverityPercentage",
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;

Expand All @@ -142,7 +184,7 @@ const QualityTable = ({
providers = providersMeta?.installed_providers || providers;
break;
case 2:
providers = providersMeta.linked_providers || providers;
providers = providersMeta?.linked_providers || providers;
break;
default:
providers = providersMeta?.providers || providers;
Expand All @@ -153,9 +195,7 @@ const QualityTable = ({
return null;
}

const innerData: Providers & ProviderAlertQuality[] = [];

providers.forEach((provider) => {
const innerData: FinalAlertQuality = providers.map((provider) => {
const providerType = provider.type;
const alertQuality = alertsQualityMetrics[providerType];
const totalAlertsReceived = alertQuality?.total_alerts ?? 0;
Expand All @@ -167,16 +207,30 @@ const QualityTable = ({
const severityPert = totalAlertsReceived
? ((alertQuality?.severity_count ?? 0) / totalAlertsReceived) * 100
: 0;
innerData.push({

// 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
} as FinalAlertQuality[number];
});

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

return (
<>
Expand All @@ -185,7 +239,6 @@ const QualityTable = ({
</h1>
<div className="flex justify-between items-end mb-4">
<FilterTabs tabs={tabs} setTab={setTab} tab={tab} />
{/* TODO: filters are not working need to intergate with backend logic */}
<GenericFilters filters={ALERT_QUALITY_FILTERS} />
</div>
{finalData && (
Expand Down
18 changes: 14 additions & 4 deletions keep-ui/components/filters/GenericFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Filter = {
options?: { value: string; label: string }[];
name: string;
icon?: IconType;
only_one?: boolean;
};

interface FiltersProps {
Expand All @@ -27,6 +28,7 @@ interface PopoverContentProps {
filterRef: React.MutableRefObject<Filter[]>;
filterKey: string;
type: string;
only_one?: boolean;
}

function toArray(value: string | string[]) {
Expand All @@ -42,9 +44,11 @@ function toArray(value: string | string[]) {
function CustomSelect({
filter,
setLocalFilter,
only_one
}: {
filter: Filter | null;
setLocalFilter: (value: string | string[]) => void;
only_one?: boolean
}) {
const filterKey = filter?.key || "";
const [selectedOptions, setSelectedOptions] = useState<Set<string>>(
Expand All @@ -59,9 +63,13 @@ function CustomSelect({

const handleCheckboxChange = (option: string, checked: boolean) => {
setSelectedOptions((prev) => {
const updatedOptions = new Set(prev);
let updatedOptions = new Set(prev);
if (checked) {
updatedOptions.add(option);
if(only_one){
updatedOptions = new Set([option]);
}else{
updatedOptions.add(option);
}
} else {
updatedOptions.delete(option);
}
Expand Down Expand Up @@ -168,6 +176,7 @@ const PopoverContent: React.FC<PopoverContentProps> = ({
filterRef,
filterKey,
type,
only_one
}) => {
// Initialize local state for selected options

Expand Down Expand Up @@ -224,7 +233,7 @@ const PopoverContent: React.FC<PopoverContentProps> = ({
switch (type) {
case "select":
return (
<CustomSelect filter={localFilter} setLocalFilter={handleLocalFilter} />
<CustomSelect filter={localFilter} setLocalFilter={handleLocalFilter} only_one={only_one}/>
);
case "date":
return <CustomDate filter={localFilter} handleDate={handleDate} />;
Expand Down Expand Up @@ -312,7 +321,7 @@ export const GenericFilters: React.FC<FiltersProps> = ({ filters }) => {
return (
<div className="relative flex flex-col md:flex-row lg:flex-row gap-4 items-center">
{filters &&
filters?.map(({ key, type, name, icon }) => {
filters?.map(({ key, type, name, icon, only_one }) => {
//only type==select and date need popover i guess other text and textarea can be handled different. for now handling select and date
icon = icon ?? type === "date" ? MdOutlineDateRange : GoPlusCircle;
return (
Expand All @@ -325,6 +334,7 @@ export const GenericFilters: React.FC<FiltersProps> = ({ filters }) => {
filterRef={filterRef}
filterKey={key}
type={type}
only_one = {!!only_one}
/>
}
onApply={() => setApply(true)}
Expand Down
5 changes: 4 additions & 1 deletion keep-ui/utils/hooks/useAlertQuality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { getApiURL } from "../apiUrl";
import { SWRConfiguration } from "swr";
import { fetcher } from "../fetcher";
import useSWRImmutable from "swr/immutable";
import { useSearchParams } from "next/navigation";

export const useAlertQualityMetrics = (options: SWRConfiguration = {}) => {
const { data: session } = useSession();
const apiUrl = getApiURL();
const searchParams = useSearchParams();
const filters = searchParams?.toString();

// TODO: Proper type needs to be defined.
return useSWRImmutable<Record<string, Record<string, any>>>(
() => (session ? `${apiUrl}/alerts/quality/metrics` : null),
() => (session ? `${apiUrl}/alerts/quality/metrics${filters?.length ? `?${filters}` : ""}` : null),
(url) => fetcher(url, session?.accessToken),
options
);
Expand Down
14 changes: 11 additions & 3 deletions keep/api/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -3286,23 +3286,30 @@ def change_incident_status_by_id(
def get_alerts_metrics_by_provider(
tenant_id: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None
end_date: Optional[datetime] = None,
fields: Optional[List[str]] = []
) -> Dict[str, Dict[str, Any]]:

# Set default dates to the last 30 days if not provided
if start_date is None:
start_date = datetime.now() - timedelta(days=30)
start_date = datetime.now() - timedelta(days=7)
if end_date is None:
end_date = datetime.now()

dynamic_field_sums = [
func.sum(case([(func.json_extract(Alert.event, f'$.{field}').isnot(None), 1)], else_=0)).label(f"{field}_count")
for field in fields
]

#if the below query is not perfomring well, we can try to optimise the query using Venn Diagram or similar(for now we are using the below query)
with Session(engine) as session:
results = (
session.query(
Alert.provider_type,
func.count(Alert.id).label("total_alerts"),
func.sum(case([(AlertToIncident.alert_id.isnot(None), 1)], else_=0)).label("correlated_alerts"),
func.sum(case([(func.json_extract(Alert.event, '$.severity').in_(['critical', 'warning']), 1)], else_=0)).label("severity_count")
func.sum(case([(func.json_extract(Alert.event, '$.severity').in_(['critical', 'warning']), 1)], else_=0)).label("severity_count"),
*dynamic_field_sums
)
.outerjoin(AlertToIncident, Alert.id == AlertToIncident.alert_id)
.filter(
Expand All @@ -3320,6 +3327,7 @@ def get_alerts_metrics_by_provider(
"total_alerts": row.total_alerts,
"correlated_alerts": row.correlated_alerts,
"severity_count": row.severity_count,
**{f"{field}_count": getattr(row, f"{field}_count") for field in fields} # Add field-specific counts
}
for row in results
}
Loading

0 comments on commit 595b6cb

Please sign in to comment.