Skip to content

Commit

Permalink
feat(ui): better time filtering (#2486)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahargl authored Nov 14, 2024
1 parent 5051a0f commit 9e9ffdc
Show file tree
Hide file tree
Showing 6 changed files with 907 additions and 83 deletions.
80 changes: 65 additions & 15 deletions keep-ui/app/alerts/TitleAndFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { DateRangePicker, DateRangePickerValue, Title } from "@tremor/react";
import { AlertDto } from "./models";
import ColumnSelection from "./ColumnSelection";
import { ThemeSelection } from "./ThemeSelection";
import EnhancedDateRangePicker from "@/components/ui/DateRangePicker";
import { useState } from "react";

type Theme = {
[key: string]: string;
Expand All @@ -21,22 +23,65 @@ export const TitleAndFilters = ({
table,
onThemeChange,
}: TableHeaderProps) => {
const onDateRangePickerChange = ({
from: start,
to: end,
}: DateRangePickerValue) => {
table.setColumnFilters((existingFilters) => {
// remove any existing "lastReceived" filters
const filteredArrayFromLastReceived = existingFilters.filter(
({ id }) => id !== "lastReceived"
);
const [timeFrame, setTimeFrame] = useState<{
start: Date | null;
end: Date | null;
paused: boolean;
isFromCalendar: boolean;
}>({
start: null,
end: null,
paused: true,
isFromCalendar: false,
});

return filteredArrayFromLastReceived.concat({
id: "lastReceived",
value: { start, end },
});
const handleTimeFrameChange = (newTimeFrame: {
start: Date | null;
end: Date | null;
paused?: boolean;
isFromCalendar?: boolean;
}) => {
setTimeFrame({
start: newTimeFrame.start,
end: newTimeFrame.end,
paused: newTimeFrame.paused ?? true,
isFromCalendar: newTimeFrame.isFromCalendar ?? false,
});

// Only apply date filter if both start and end dates exist
if (newTimeFrame.start && newTimeFrame.end) {
const adjustedTimeFrame = {
...newTimeFrame,
end: new Date(newTimeFrame.end.getTime()),
paused: newTimeFrame.paused ?? true,
isFromCalendar: newTimeFrame.isFromCalendar ?? false,
};

if (adjustedTimeFrame.isFromCalendar) {
adjustedTimeFrame.end.setHours(23, 59, 59, 999);
}

table.setColumnFilters((existingFilters) => {
const filteredArrayFromLastReceived = existingFilters.filter(
({ id }) => id !== "lastReceived"
);

return filteredArrayFromLastReceived.concat({
id: "lastReceived",
value: {
start: adjustedTimeFrame.start,
end: adjustedTimeFrame.end,
},
});
});
} else {
// Remove date filter if no dates are selected
table.setColumnFilters((existingFilters) =>
existingFilters.filter(({ id }) => id !== "lastReceived")
);
}

table.resetRowSelection();
table.resetPagination();
};

Expand All @@ -46,8 +91,13 @@ export const TitleAndFilters = ({
<Title className="capitalize inline">{presetName}</Title>
</div>
<div className="grid grid-cols-[auto_auto] grid-rows-[auto_auto] gap-4">
<DateRangePicker
onValueChange={onDateRangePickerChange}
<EnhancedDateRangePicker
timeFrame={timeFrame}
setTimeFrame={handleTimeFrameChange}
hasPlay={false}
hasRewind={false}
hasForward={false}
hasZoomOut={false}
enableYearNavigation
/>
<div className="flex items-center">
Expand Down
15 changes: 14 additions & 1 deletion keep-ui/app/alerts/alert-table-alert-facets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
className,
table,
}) => {
const timeRangeFilter = table
.getState()
.columnFilters.find((filter) => filter.id === "lastReceived");

const timeRange = timeRangeFilter?.value as
| { start: Date; end: Date; isFromCalendar: boolean }
| undefined;

const presetName = window.location.pathname.split("/").pop() || "default";

const [isModalOpen, setIsModalOpen] = useLocalStorage<boolean>(
Expand Down Expand Up @@ -69,7 +77,12 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
};

const getFacetValues = (key: keyof AlertDto | string): FacetValue[] => {
const filteredAlerts = getFilteredAlertsForFacet(alerts, facetFilters, key);
const filteredAlerts = getFilteredAlertsForFacet(
alerts,
facetFilters,
key,
timeRange
);
const valueMap = new Map<string, number>();
let nullCount = 0;

Expand Down
26 changes: 22 additions & 4 deletions keep-ui/app/alerts/alert-table-facet-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,39 @@ import {
BellSlashIcon,
FireIcon,
} from "@heroicons/react/24/outline";
import { isQuickPresetRange } from "@/components/ui/DateRangePicker";

export const getFilteredAlertsForFacet = (
alerts: AlertDto[],
facetFilters: FacetFilters,
excludeFacet: string
): AlertDto[] => {
currentFacetKey: string,
timeRange?: { start: Date; end: Date; isFromCalendar: boolean }
) => {
return alerts.filter((alert) => {
// Only apply time range filter if both start and end dates exist
if (timeRange?.start && timeRange?.end) {
const lastReceived = new Date(alert.lastReceived);
const rangeStart = new Date(timeRange.start);
const rangeEnd = new Date(timeRange.end);

if (!isQuickPresetRange(timeRange)) {
rangeEnd.setHours(23, 59, 59, 999);
}

if (lastReceived < rangeStart || lastReceived > rangeEnd) {
return false;
}
}

// Then apply facet filters, excluding the current facet
return Object.entries(facetFilters).every(([facetKey, includedValues]) => {
if (facetKey === excludeFacet || includedValues.length === 0) {
// Skip filtering by current facet to avoid circular dependency
if (facetKey === currentFacetKey || includedValues.length === 0) {
return true;
}

let value;
if (facetKey.includes(".")) {
// Handle nested keys like "labels.job"
const [parentKey, childKey] = facetKey.split(".");
const parentValue = alert[parentKey as keyof AlertDto];

Expand Down
63 changes: 0 additions & 63 deletions keep-ui/app/alerts/alert-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,69 +210,6 @@ export function AlertTable({
});
});

const handleFacetSelect = (
facetKey: string,
value: string,
exclusive: boolean,
isAllOnly: boolean = false
) => {
setFacetFilters((prev) => {
// Handle All/Only button clicks
if (isAllOnly) {
if (value === "") {
// Reset to include all values (empty array)
return {
...prev,
[facetKey]: [],
};
}

if (exclusive) {
// Only include this value
return {
...prev,
[facetKey]: [value],
};
}
}

// Handle regular checkbox clicks
const currentValues = prev[facetKey] || [];

if (currentValues.length === 0) {
// If no filters, clicking one value means we want to exclude that value
// So we need to include all OTHER values
const allValues = new Set(
alerts
.map((alert) => {
const val = alert[facetKey as keyof AlertDto];
return Array.isArray(val) ? val : [String(val)];
})
.flat()
);
return {
...prev,
[facetKey]: Array.from(allValues).filter((v) => v !== value),
};
}

if (currentValues.includes(value)) {
// Remove value if it's already included
const newValues = currentValues.filter((v) => v !== value);
return {
...prev,
[facetKey]: newValues,
};
} else {
// Add value if it's not included
return {
...prev,
[facetKey]: [...currentValues, value],
};
}
});
};

const table = useReactTable({
data: filteredAlerts,
columns: columns,
Expand Down
Loading

0 comments on commit 9e9ffdc

Please sign in to comment.