Skip to content

Commit 1cc531b

Browse files
Merge pull request #748 from rioloc/feat/rounded-end-times
OU-1205: feat: force rounded dates for consecutive intervals
2 parents a933f76 + fe41654 commit 1cc531b

4 files changed

Lines changed: 55 additions & 3 deletions

File tree

web/src/components/Incidents/AlertsChart/AlertsChart.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
generateDateArray,
3333
generateAlertsDateArray,
3434
getCurrentTime,
35+
roundDateToInterval,
3536
} from '../utils';
3637
import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime';
3738
import { useTranslation } from 'react-i18next';
@@ -164,7 +165,9 @@ const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => {
164165
const endDate =
165166
datum.alertstate === 'firing'
166167
? '---'
167-
: dateTimeFormatter(i18n.language).format(new Date(datum.y));
168+
: dateTimeFormatter(i18n.language).format(
169+
roundDateToInterval(new Date(datum.y)),
170+
);
168171

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

web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
calculateIncidentsChartDomain,
3434
createIncidentsChartBars,
3535
generateDateArray,
36+
roundDateToInterval,
3637
} from '../utils';
3738
import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime';
3839
import { useTranslation } from 'react-i18next';
@@ -178,7 +179,9 @@ const IncidentsChart = ({
178179
const startDate = dateTimeFormatter(i18n.language).format(new Date(datum.y0));
179180
const endDate = datum.firing
180181
? '---'
181-
: dateTimeFormatter(i18n.language).format(new Date(datum.y));
182+
: dateTimeFormatter(i18n.language).format(
183+
roundDateToInterval(new Date(datum.y)),
184+
);
182185
const components = formatComponentList(datum.componentList);
183186

184187
return `${t('Severity')}: ${t(datum.name)}

web/src/components/Incidents/utils.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { insertPaddingPointsForChart } from './utils';
1+
import { insertPaddingPointsForChart, roundDateToInterval } from './utils';
22

33
describe('insertPaddingPointsForChart', () => {
44
describe('edge cases', () => {
@@ -287,3 +287,29 @@ describe('insertPaddingPointsForChart', () => {
287287
});
288288
});
289289
});
290+
291+
describe('roundDateToInterval', () => {
292+
describe('exact 5-minute boundaries', () => {
293+
it('should return unchanged date for 23:55:00', () => {
294+
const date = new Date('2026-01-26T23:55:00.000Z');
295+
const rounded = roundDateToInterval(date);
296+
expect(rounded.getTime()).toBe(date.getTime());
297+
});
298+
});
299+
300+
describe('rounding to nearest 5-minute boundary', () => {
301+
it('should round 22:57:00 down to 22:55:00', () => {
302+
const date = new Date('2026-01-26T22:57:00.000Z');
303+
const rounded = roundDateToInterval(date);
304+
const expected = new Date('2026-01-26T22:55:00.000Z');
305+
expect(rounded.getTime()).toBe(expected.getTime());
306+
});
307+
308+
it('should round 22:59:00 up to 23:00:00', () => {
309+
const date = new Date('2026-01-26T22:59:00.000Z');
310+
const rounded = roundDateToInterval(date);
311+
const expected = new Date('2026-01-26T23:00:00.000Z');
312+
expect(rounded.getTime()).toBe(expected.getTime());
313+
});
314+
});
315+
});

web/src/components/Incidents/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ export const getCurrentTime = (): number => {
5454
return Math.floor(now / intervalMs) * intervalMs;
5555
};
5656

57+
/**
58+
* Rounds a Date to the nearest 5-minute boundary for display purposes.
59+
* This is used in tooltips to show cleaner, rounded timestamps instead of precise
60+
* interval boundaries that may differ by seconds.
61+
*
62+
* For example:
63+
* - 22:57:00 -> 22:55:00 (rounds down)
64+
* - 22:59:00 -> 23:00:00 (rounds up)
65+
* - 23:30:00 -> 23:30:00 (already at boundary)
66+
* - 23:29:59 -> 23:30:00 (rounds up)
67+
*
68+
* @param date - The Date object to round
69+
* @returns A new Date object rounded to the nearest 5-minute boundary
70+
*/
71+
export const roundDateToInterval = (date: Date): Date => {
72+
const intervalMs = PROMETHEUS_QUERY_INTERVAL_SECONDS * 1000;
73+
const roundedMs = Math.round(date.getTime() / intervalMs) * intervalMs;
74+
return new Date(roundedMs);
75+
};
76+
5777
/**
5878
* Determines if an incident or alert is resolved based on the time elapsed since the last data point.
5979
*

0 commit comments

Comments
 (0)