From 40950d44c8c7c8278a47d37d731743b18ba8c446 Mon Sep 17 00:00:00 2001 From: rioloc Date: Wed, 17 Dec 2025 15:05:23 +0100 Subject: [PATCH] feat: fix misaligned start dates by always loading 15 days --- .../Incidents/AlertsChart/AlertsChart.tsx | 12 +++++++++--- .../IncidentsChart/IncidentsChart.tsx | 10 ++++++++-- web/src/components/Incidents/IncidentsPage.tsx | 11 ++++++++--- web/src/components/Incidents/processAlerts.ts | 13 ++++++++++++- .../Incidents/processIncidents.spec.ts | 18 ------------------ .../components/Incidents/processIncidents.ts | 12 ++---------- web/src/components/Incidents/utils.ts | 18 ++++++++++++++++++ 7 files changed, 57 insertions(+), 37 deletions(-) diff --git a/web/src/components/Incidents/AlertsChart/AlertsChart.tsx b/web/src/components/Incidents/AlertsChart/AlertsChart.tsx index 43dd87584..9c1138949 100644 --- a/web/src/components/Incidents/AlertsChart/AlertsChart.tsx +++ b/web/src/components/Incidents/AlertsChart/AlertsChart.tsx @@ -32,6 +32,7 @@ import { generateDateArray, generateAlertsDateArray, getCurrentTime, + isInTimeWindow, } from '../utils'; import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime'; import { useTranslation } from 'react-i18next'; @@ -41,7 +42,7 @@ import { MonitoringState } from '../../../store/store'; import { isEmpty } from 'lodash-es'; import { DataTestIDs } from '../../data-test'; -const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => { +const AlertsChart = ({ theme, daysSpan }: { theme: 'light' | 'dark'; daysSpan: number }) => { const dispatch = useDispatch(); const [chartContainerHeight, setChartContainerHeight] = useState(); const [chartHeight, setChartHeight] = useState(); @@ -72,8 +73,13 @@ const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => { const chartData: AlertsChartBar[][] = useMemo(() => { if (!Array.isArray(alertsData) || alertsData.length === 0) return []; - return alertsData.map((alert) => createAlertsChartBars(alert)); - }, [alertsData]); + return alertsData + .filter((alert) => { + const lastTimestamp = alert.values[alert.values.length - 1][0]; + return isInTimeWindow(lastTimestamp, daysSpan, currentTime); + }) + .map((alert) => createAlertsChartBars(alert)); + }, [alertsData, currentTime, daysSpan]); useEffect(() => { setChartContainerHeight(chartData?.length < 5 ? 300 : chartData?.length * 55); diff --git a/web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx b/web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx index b5d27ad1e..216faf95a 100644 --- a/web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx +++ b/web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx @@ -33,6 +33,7 @@ import { calculateIncidentsChartDomain, createIncidentsChartBars, generateDateArray, + isInTimeWindow, } from '../utils'; import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime'; import { useTranslation } from 'react-i18next'; @@ -84,10 +85,15 @@ const IncidentsChart = ({ const chartData = useMemo(() => { if (!Array.isArray(incidentsData) || incidentsData.length === 0) return []; - const filteredIncidents = selectedGroupId + const groupFilteredIncidents = selectedGroupId ? incidentsData.filter((incident) => incident.group_id === selectedGroupId) : incidentsData; + const filteredIncidents = groupFilteredIncidents.filter((incident) => { + const lastTimestamp = incident.values[incident.values.length - 1][0]; + return isInTimeWindow(lastTimestamp, chartDays * (60 * 60 * 24 * 1000), currentTime); + }); + // Create chart bars and sort by original x values to maintain proper order const chartBars = filteredIncidents.map((incident) => createIncidentsChartBars(incident, dateValues), @@ -96,7 +102,7 @@ const IncidentsChart = ({ // Reassign consecutive x values to eliminate gaps between bars return chartBars.map((bars, index) => bars.map((bar) => ({ ...bar, x: index + 1 }))); - }, [incidentsData, dateValues, selectedGroupId]); + }, [incidentsData, dateValues, selectedGroupId, currentTime, chartDays]); useEffect(() => { setIsLoading(false); diff --git a/web/src/components/Incidents/IncidentsPage.tsx b/web/src/components/Incidents/IncidentsPage.tsx index c8557b24e..8142c10d4 100644 --- a/web/src/components/Incidents/IncidentsPage.tsx +++ b/web/src/components/Incidents/IncidentsPage.tsx @@ -265,7 +265,12 @@ const IncidentsPage = () => { if (rules && alertsData) { dispatch( setAlertsTableData({ - alertsTableData: groupAlertsForTable(alertsData, rules), + alertsTableData: groupAlertsForTable( + alertsData, + rules, + incidentsLastRefreshTime, + daysSpan, + ), }), ); } @@ -604,7 +609,7 @@ const IncidentsPage = () => { { /> - + )} diff --git a/web/src/components/Incidents/processAlerts.ts b/web/src/components/Incidents/processAlerts.ts index 921a4a49f..0845e3f3e 100644 --- a/web/src/components/Incidents/processAlerts.ts +++ b/web/src/components/Incidents/processAlerts.ts @@ -290,8 +290,19 @@ export function convertToAlerts( export const groupAlertsForTable = ( alerts: Array, alertingRulesData: Array, + currentTime: number, + daysSpan: number, ): Array => { - const groupedAlerts = alerts.reduce((acc: Array, alert) => { + const filteredAlerts = alerts.filter((alert) => { + const endTime = alert.values[alert.values.length - 1][0]; + const delta = (currentTime - daysSpan) / 1000; + if (endTime < delta) { + return false; + } + return true; + }); + + const groupedAlerts = filteredAlerts.reduce((acc: Array, alert) => { const { component, alertstate, severity, layer, alertname, silenced } = alert; const existingGroup = acc.find((group) => group.component === component); const rule = alertingRulesData?.find((rule) => alertname === rule.name); diff --git a/web/src/components/Incidents/processIncidents.spec.ts b/web/src/components/Incidents/processIncidents.spec.ts index ab6ccd631..293aee09c 100644 --- a/web/src/components/Incidents/processIncidents.spec.ts +++ b/web/src/components/Incidents/processIncidents.spec.ts @@ -607,24 +607,6 @@ describe('getIncidentsTimeRanges', () => { const now = getCurrentTime(); describe('basic functionality', () => { - it('should return single range for timespan less than one day', () => { - const timespan = 12 * 60 * 60 * 1000; // 12 hours - const result = getIncidentsTimeRanges(timespan, now); - - expect(result).toHaveLength(1); - expect(result[0].duration).toBe(ONE_DAY); - }); - - it('should split longer timespans into daily chunks', () => { - const timespan = 3 * ONE_DAY; // 3 days - const result = getIncidentsTimeRanges(timespan, now); - - expect(result.length).toBeGreaterThan(1); - result.forEach((range) => { - expect(range.duration).toBe(ONE_DAY); - }); - }); - it('should use provided maxEndTime', () => { const maxEndTime = new Date('2024-01-01T00:00:00Z').getTime(); const timespan = ONE_DAY; diff --git a/web/src/components/Incidents/processIncidents.ts b/web/src/components/Incidents/processIncidents.ts index 803123f17..f621b626d 100644 --- a/web/src/components/Incidents/processIncidents.ts +++ b/web/src/components/Incidents/processIncidents.ts @@ -167,16 +167,8 @@ export const getIncidentsTimeRanges = ( currentTime: number, ): Array<{ endTime: number; duration: number }> => { const ONE_DAY = 24 * 60 * 60 * 1000; // 24 hours in milliseconds - const startTime = currentTime - timespan; - const timeRanges = [{ endTime: startTime + ONE_DAY, duration: ONE_DAY }]; - - while (timeRanges[timeRanges.length - 1].endTime < currentTime) { - const lastRange = timeRanges[timeRanges.length - 1]; - const nextEndTime = lastRange.endTime + ONE_DAY; - timeRanges.push({ endTime: nextEndTime, duration: ONE_DAY }); - } - - return timeRanges; + const FIFTEEN_DAYS = 15 * ONE_DAY; + return [{ endTime: currentTime, duration: FIFTEEN_DAYS }]; }; /** diff --git a/web/src/components/Incidents/utils.ts b/web/src/components/Incidents/utils.ts index a778c0ffa..f1f2a3630 100644 --- a/web/src/components/Incidents/utils.ts +++ b/web/src/components/Incidents/utils.ts @@ -72,6 +72,24 @@ export const isResolved = (lastTimestamp: number, currentTime: number): boolean return delta >= threshold; }; +/** + * Checks if the last timestamp is in the time window. + * @param lastTimestamp - The last timestamp in the incident/alert (in seconds) + * @param daysSpan - The number of days in the time window (in milliseconds). + * @param currentTime - The current time in milliseconds. + * @returns true if the last timestamp is in the time window, false otherwise. + */ +export const isInTimeWindow = ( + lastTimestamp: number, + daysSpan: number, + currentTime: number, +): boolean => { + // convert chartDays to ms and then convert the result to seconds + const timeWindow = (currentTime - daysSpan) / 1000; + // if endTime is lower than currentTime-chartDays, return false else true + return lastTimestamp >= timeWindow; +}; + /** * Inserts padding data points to ensure the chart renders correctly. * This allows the chart to properly render events, especially single data points.