Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion web/src/components/Incidents/AlertsChart/AlertsChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
generateDateArray,
generateAlertsDateArray,
getCurrentTime,
roundDateToInterval,
} from '../utils';
import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -164,7 +165,9 @@ const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => {
const endDate =
datum.alertstate === 'firing'
? '---'
: dateTimeFormatter(i18n.language).format(new Date(datum.y));
: dateTimeFormatter(i18n.language).format(
roundDateToInterval(new Date(datum.y)),
);

const alertName = datum.silenced ? `${datum.name} (silenced)` : datum.name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
calculateIncidentsChartDomain,
createIncidentsChartBars,
generateDateArray,
roundDateToInterval,
} from '../utils';
import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -178,7 +179,9 @@ const IncidentsChart = ({
const startDate = dateTimeFormatter(i18n.language).format(new Date(datum.y0));
const endDate = datum.firing
? '---'
: dateTimeFormatter(i18n.language).format(new Date(datum.y));
: dateTimeFormatter(i18n.language).format(
roundDateToInterval(new Date(datum.y)),
);
const components = formatComponentList(datum.componentList);

return `${t('Severity')}: ${t(datum.name)}
Expand Down
28 changes: 27 additions & 1 deletion web/src/components/Incidents/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { insertPaddingPointsForChart } from './utils';
import { insertPaddingPointsForChart, roundDateToInterval } from './utils';

describe('insertPaddingPointsForChart', () => {
describe('edge cases', () => {
Expand Down Expand Up @@ -287,3 +287,29 @@ describe('insertPaddingPointsForChart', () => {
});
});
});

describe('roundDateToInterval', () => {
describe('exact 5-minute boundaries', () => {
it('should return unchanged date for 23:55:00', () => {
const date = new Date('2026-01-26T23:55:00.000Z');
const rounded = roundDateToInterval(date);
expect(rounded.getTime()).toBe(date.getTime());
});
});

describe('rounding to nearest 5-minute boundary', () => {
it('should round 22:57:00 down to 22:55:00', () => {
const date = new Date('2026-01-26T22:57:00.000Z');
const rounded = roundDateToInterval(date);
const expected = new Date('2026-01-26T22:55:00.000Z');
expect(rounded.getTime()).toBe(expected.getTime());
});

it('should round 22:59:00 up to 23:00:00', () => {
const date = new Date('2026-01-26T22:59:00.000Z');
const rounded = roundDateToInterval(date);
const expected = new Date('2026-01-26T23:00:00.000Z');
expect(rounded.getTime()).toBe(expected.getTime());
});
});
});
20 changes: 20 additions & 0 deletions web/src/components/Incidents/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ export const getCurrentTime = (): number => {
return Math.floor(now / intervalMs) * intervalMs;
};

/**
* Rounds a Date to the nearest 5-minute boundary for display purposes.
* This is used in tooltips to show cleaner, rounded timestamps instead of precise
* interval boundaries that may differ by seconds.
*
* For example:
* - 22:57:00 -> 22:55:00 (rounds down)
* - 22:59:00 -> 23:00:00 (rounds up)
* - 23:30:00 -> 23:30:00 (already at boundary)
* - 23:29:59 -> 23:30:00 (rounds up)
*
* @param date - The Date object to round
* @returns A new Date object rounded to the nearest 5-minute boundary
*/
export const roundDateToInterval = (date: Date): Date => {
const intervalMs = PROMETHEUS_QUERY_INTERVAL_SECONDS * 1000;
const roundedMs = Math.round(date.getTime() / intervalMs) * intervalMs;
return new Date(roundedMs);
};

/**
* Determines if an incident or alert is resolved based on the time elapsed since the last data point.
*
Expand Down