Skip to content

Commit 452e160

Browse files
skynetigorshahargl
andauthored
feat: Add columns configuration for alerts preset widget (keephq#4503)
Co-authored-by: Shahar Glazner <[email protected]>
1 parent 6b64ab2 commit 452e160

File tree

5 files changed

+300
-90
lines changed

5 files changed

+300
-90
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useFacetPotentialFields } from "@/features/filter/hooks";
2+
import { MultiSelect, MultiSelectItem } from "@tremor/react";
3+
import React, { useEffect, useMemo, useState } from "react";
4+
import { defaultColumns } from "./constants";
5+
6+
interface ColumnsSelectionProps {
7+
selectedColumns?: string[];
8+
onChange: (selected: string[]) => void;
9+
}
10+
11+
const ColumnsSelection: React.FC<ColumnsSelectionProps> = ({
12+
selectedColumns,
13+
onChange,
14+
}) => {
15+
const [selectedColumnsState, setSelectedColumnsState] = useState<Set<string>>(
16+
new Set(selectedColumns || defaultColumns)
17+
);
18+
const { data } = useFacetPotentialFields("alerts");
19+
20+
useEffect(
21+
() => onChange(Array.from(selectedColumnsState)),
22+
[selectedColumnsState]
23+
);
24+
25+
const sortedOptions = useMemo(() => {
26+
return data?.toSorted((first, second) => {
27+
const inSetA = selectedColumnsState.has(first);
28+
const inSetB = selectedColumnsState.has(second);
29+
30+
if (inSetA && !inSetB) return -1;
31+
if (!inSetA && inSetB) return 1;
32+
33+
return first.localeCompare(second);
34+
});
35+
}, [data, selectedColumnsState]);
36+
37+
return (
38+
<MultiSelect
39+
placeholder="Select alert columns"
40+
value={Array.from(selectedColumnsState)}
41+
onValueChange={(selected) => setSelectedColumnsState(new Set(selected))}
42+
>
43+
{sortedOptions?.map((field) => (
44+
<MultiSelectItem key={field} value={field}>
45+
{field}
46+
</MultiSelectItem>
47+
))}
48+
</MultiSelect>
49+
);
50+
};
51+
52+
export default ColumnsSelection;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const defaultColumns = [
2+
"severity",
3+
"status",
4+
"source",
5+
"name",
6+
"description",
7+
"lastReceived",
8+
];

keep-ui/app/(keep)/dashboard/widget-types/preset/preset-grid-item.tsx

Lines changed: 8 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@ import { usePresetAlertsCount } from "@/features/presets/custom-preset-links";
44
import { useDashboardPreset } from "@/utils/hooks/useDashboardPresets";
55
import { Button, Icon } from "@tremor/react";
66
import { FireIcon } from "@heroicons/react/24/outline";
7-
import { DynamicImageProviderIcon } from "@/components/ui";
8-
import { getStatusColor, getStatusIcon } from "@/shared/lib/status-utils";
9-
import { SeverityBorderIcon, UISeverity } from "@/shared/ui";
10-
import { severityMapping } from "@/entities/alerts/model";
117
import * as Tooltip from "@radix-ui/react-tooltip";
128
import Skeleton from "react-loading-skeleton";
139
import "react-loading-skeleton/dist/skeleton.css";
1410
import { useRouter } from "next/navigation";
1511
import TimeAgo from "react-timeago";
1612
import { useSearchParams } from "next/navigation";
13+
import WidgetAlertsTable from "./widget-alerts-table";
1714

1815
interface GridItemProps {
1916
item: WidgetData;
@@ -43,6 +40,7 @@ const PresetGridItem: React.FC<GridItemProps> = ({ item }) => {
4340
() => [timeRangeCel, presetCel].filter(Boolean).join(" && "),
4441
[presetCel, timeRangeCel]
4542
);
43+
4644
const {
4745
alerts,
4846
totalCount: presetAlertsCount,
@@ -98,82 +96,6 @@ const PresetGridItem: React.FC<GridItemProps> = ({ item }) => {
9896
return `rgb(${r}, ${g}, ${b}, ${alpha})`;
9997
}
10098

101-
function renderLastAlertsGrid() {
102-
if (isLoading) {
103-
return (
104-
<>
105-
{Array.from({ length: countOfLastAlerts }).map((_, index) => (
106-
<div
107-
key={index}
108-
className="flex flex-row min-h-7 h-7 items-center gap-2"
109-
>
110-
<Skeleton containerClassName="h-4 w-1" />
111-
<Skeleton containerClassName="h-4 w-4" />
112-
<Skeleton containerClassName="h-4 w-4" />
113-
<Skeleton containerClassName="h-4 flex-1" />
114-
<Skeleton containerClassName="h-4 flex-1" />
115-
</div>
116-
))}
117-
</>
118-
);
119-
}
120-
121-
if (presetAlertsCount === 0) {
122-
let emptyStateText = "No alerts matching this preset";
123-
124-
if (timeRangeCel) {
125-
emptyStateText = "No alerts matching this preset and time range";
126-
}
127-
128-
return (
129-
<div className="flex items-center justify-center h-10">
130-
{emptyStateText}
131-
</div>
132-
);
133-
}
134-
135-
return (
136-
<>
137-
{alerts?.map((alert) => (
138-
<div
139-
key={alert.fingerprint}
140-
className="flex flex-row min-h-7 h-7 items-center gap-2"
141-
>
142-
<SeverityBorderIcon
143-
severity={
144-
(severityMapping[Number(alert.severity)] ||
145-
alert.severity) as UISeverity
146-
}
147-
/>
148-
<Icon
149-
icon={getStatusIcon(alert.status)}
150-
size="sm"
151-
color={getStatusColor(alert.status)}
152-
className="!p-0"
153-
/>
154-
<div>
155-
<DynamicImageProviderIcon
156-
className="inline-block"
157-
alt={(alert as any).providerType}
158-
height={16}
159-
width={16}
160-
title={(alert as any).providerType}
161-
providerType={(alert as any).providerType}
162-
src={`/icons/${(alert as any).providerType}-icon.png`}
163-
/>
164-
</div>
165-
<div className="flex-1 truncate text-xs" title={alert.name}>
166-
{alert.name} (<TimeAgo date={alert.lastReceived} />)
167-
</div>
168-
<div className="flex-1 truncate text-xs" title={alert.description}>
169-
{alert.description}
170-
</div>
171-
</div>
172-
))}
173-
</>
174-
);
175-
}
176-
17799
function renderCEL() {
178100
if (!presetCel) {
179101
return;
@@ -279,14 +201,12 @@ const PresetGridItem: React.FC<GridItemProps> = ({ item }) => {
279201
</div>
280202
</div>
281203
{countOfLastAlerts > 0 && (
282-
<div
283-
style={{
284-
background: isLoading ? undefined : hexToRgb(getColor(), 0.1),
285-
}}
286-
className="bg-opacity-25 flex flex-col overflow-y-auto overflow-x-hidden auto-rows-auto border rounded-md p-2"
287-
>
288-
{renderLastAlertsGrid()}
289-
</div>
204+
<WidgetAlertsTable
205+
presetName={preset?.name as string}
206+
alerts={isLoading ? undefined : alerts}
207+
columns={(item as any)?.presetColumns}
208+
background={isLoading ? undefined : hexToRgb(getColor(), 0.1)}
209+
/>
290210
)}
291211
</div>
292212
);

keep-ui/app/(keep)/dashboard/widget-types/preset/preset-widget-form.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
Subtitle,
99
TextInput,
1010
} from "@tremor/react";
11-
import { useEffect, useMemo } from "react";
11+
import { useEffect, useMemo, useState } from "react";
1212
import {
1313
Controller,
1414
get,
@@ -17,6 +17,7 @@ import {
1717
useFieldArray,
1818
} from "react-hook-form";
1919
import { LayoutItem, Threshold } from "../../types";
20+
import ColumnsSelection from "./columns-selection";
2021

2122
interface PresetForm {
2223
selectedPreset: string;
@@ -51,6 +52,9 @@ export const PresetWidgetForm: React.FC<PresetWidgetFormProps> = ({
5152
],
5253
},
5354
});
55+
const [presetColumns, setPresetColumns] = useState<string[] | undefined>(
56+
editingItem ? editingItem.presetColumns : undefined
57+
);
5458

5559
const { fields, append, remove, move, replace } = useFieldArray({
5660
control,
@@ -63,12 +67,13 @@ export const PresetWidgetForm: React.FC<PresetWidgetFormProps> = ({
6367
return {
6468
countOfLastAlerts: parseInt(formValues.countOfLastAlerts || "0"),
6569
selectedPreset: presets.find((p) => p.id === formValues.selectedPreset),
70+
presetColumns,
6671
thresholds: formValues.thresholds?.map((t) => ({
6772
...t,
6873
value: parseInt(t.value?.toString() as string, 10) || 0,
6974
})),
7075
};
71-
}, [formValues]);
76+
}, [formValues, presetColumns]);
7277

7378
function getLayoutValues(): LayoutItem {
7479
if (editingItem) {
@@ -95,6 +100,7 @@ export const PresetWidgetForm: React.FC<PresetWidgetFormProps> = ({
95100
...normalizedFormValues.selectedPreset,
96101
countOfLastAlerts: normalizedFormValues.countOfLastAlerts,
97102
},
103+
presetColumns: normalizedFormValues.presetColumns,
98104
thresholds: normalizedFormValues.thresholds,
99105
},
100106
isValid
@@ -175,6 +181,10 @@ export const PresetWidgetForm: React.FC<PresetWidgetFormProps> = ({
175181
)}
176182
/>
177183
</div>
184+
<ColumnsSelection
185+
selectedColumns={presetColumns}
186+
onChange={(selectedColumns) => setPresetColumns(selectedColumns)}
187+
></ColumnsSelection>
178188
<div className="mb-4">
179189
<div className="flex items-center justify-between">
180190
<Subtitle>Thresholds</Subtitle>

0 commit comments

Comments
 (0)