Skip to content

Commit a21aab2

Browse files
Merge pull request #529 from falox/fit-alerts-timeline
OU-783: Fit the alerts timeline to the actual data timespan
2 parents 2f449d8 + dd4c9a8 commit a21aab2

File tree

3 files changed

+81
-21
lines changed

3 files changed

+81
-21
lines changed

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ import { useDispatch, useSelector } from 'react-redux';
2626
import { setAlertsAreLoading } from '../../../actions/observe';
2727
import { MonitoringState } from '../../../reducers/observe';
2828
import { IncidentsTooltip } from '../IncidentsTooltip';
29-
import { createAlertsChartBars, formatDate, generateDateArray } from '../utils';
29+
import {
30+
createAlertsChartBars,
31+
formatDate,
32+
generateDateArray,
33+
generateAlertsDateArray,
34+
} from '../utils';
3035

31-
const AlertsChart = ({ chartDays, theme }: { chartDays: number; theme: 'light' | 'dark' }) => {
36+
const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => {
3237
const dispatch = useDispatch();
3338
const [chartContainerHeight, setChartContainerHeight] = useState<number>();
3439
const [chartHeight, setChartHeight] = useState<number>();
@@ -45,12 +50,19 @@ const AlertsChart = ({ chartDays, theme }: { chartDays: number; theme: 'light' |
4550
state.plugins.mcp.getIn(['incidentsData', 'groupId']),
4651
);
4752

48-
const dateValues = useMemo(() => generateDateArray(chartDays), [chartDays]);
53+
// Use dynamic date range based on actual alerts data instead of fixed chartDays
54+
const dateValues = useMemo(() => {
55+
if (!Array.isArray(alertsData) || alertsData.length === 0) {
56+
// Fallback to single day if no alerts data
57+
return generateDateArray(1);
58+
}
59+
return generateAlertsDateArray(alertsData);
60+
}, [alertsData]);
4961

5062
const chartData = useMemo(() => {
5163
if (!Array.isArray(alertsData) || alertsData.length === 0) return [];
52-
return alertsData.map((alert) => createAlertsChartBars(alert, dateValues));
53-
}, [alertsData, dateValues]);
64+
return alertsData.map((alert) => createAlertsChartBars(alert));
65+
}, [alertsData]);
5466

5567
useEffect(() => {
5668
setChartContainerHeight(chartData?.length < 5 ? 300 : chartData?.length * 60);

web/src/components/Incidents/IncidentsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ const IncidentsPage = () => {
515515
/>
516516
</StackItem>
517517
<StackItem>
518-
<AlertsChart chartDays={timeRanges.length} theme={theme} />
518+
<AlertsChart theme={theme} />
519519
</StackItem>
520520
</>
521521
)}

web/src/components/Incidents/utils.ts

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export const createIncidentsChartBars = (incident: Incident, dateArray: SpanDate
171171
return data;
172172
};
173173

174-
function consolidateAndMergeAlertIntervals(data: Alert, dateArray: SpanDates) {
174+
function consolidateAndMergeAlertIntervals(data: Alert) {
175175
if (!data.values || data.values.length === 0) {
176176
return [];
177177
}
@@ -195,24 +195,15 @@ function consolidateAndMergeAlertIntervals(data: Alert, dateArray: SpanDates) {
195195

196196
intervals.push([currentStart, sortedValues[sortedValues.length - 1][0], 'data']);
197197

198-
// Handle gaps before and after the detected intervals
199-
const startBoundary = dateArray[0],
200-
endBoundary = dateArray[dateArray.length - 1];
201-
const firstIntervalStart = intervals[0][0],
202-
lastIntervalEnd = intervals[intervals.length - 1][1];
203-
204-
if (firstIntervalStart > startBoundary) {
205-
intervals.unshift([startBoundary, firstIntervalStart - 1, 'nodata']);
206-
}
207-
if (lastIntervalEnd < endBoundary) {
208-
intervals.push([lastIntervalEnd + 1, endBoundary, 'nodata']);
209-
}
198+
// For dynamic alerts timeline, we don't add padding gaps since the dateArray
199+
// is already calculated to fit the alert data with appropriate padding
200+
// This allows the timeline to focus on the actual alert activity period
210201

211202
return intervals;
212203
}
213204

214-
export const createAlertsChartBars = (alert: Alert, dateValues: SpanDates) => {
215-
const groupedData = consolidateAndMergeAlertIntervals(alert, dateValues);
205+
export const createAlertsChartBars = (alert: Alert) => {
206+
const groupedData = consolidateAndMergeAlertIntervals(alert);
216207
const barChartColorScheme = {
217208
critical: t_global_color_status_danger_default.var,
218209
info: t_global_color_status_info_default.var,
@@ -298,6 +289,63 @@ export function generateDateArray(days: number): Array<number> {
298289
return dateArray;
299290
}
300291

292+
/**
293+
* Generates a dynamic date array based on the actual min/max timestamps from alerts data.
294+
* This creates a focused timeline that spans only the relevant alert activity period.
295+
*
296+
* @param {Array<Alert>} alertsData - Array of alert objects containing timestamp values
297+
* @returns {Array<number>} - Array of timestamp values representing the date range with some padding
298+
*
299+
* @example
300+
* const alertsDateRange = generateAlertsDateArray(alertsData);
301+
* // Returns timestamps spanning from earliest alert minus padding to latest alert plus padding
302+
*/
303+
export function generateAlertsDateArray(alertsData: Array<Alert>): Array<number> {
304+
if (!Array.isArray(alertsData) || alertsData.length === 0) {
305+
// Fallback to current day if no alerts data
306+
const now = new Date();
307+
now.setHours(0, 0, 0, 0);
308+
return [now.getTime() / 1000];
309+
}
310+
311+
let minTimestamp = Infinity;
312+
let maxTimestamp = -Infinity;
313+
314+
// Find min and max timestamps across all alerts
315+
alertsData.forEach((alert) => {
316+
if (alert.values && Array.isArray(alert.values)) {
317+
alert.values.forEach(([timestamp]) => {
318+
minTimestamp = Math.min(minTimestamp, timestamp);
319+
maxTimestamp = Math.max(maxTimestamp, timestamp);
320+
});
321+
}
322+
});
323+
324+
// Handle edge case where no valid timestamps found
325+
if (minTimestamp === Infinity || maxTimestamp === -Infinity) {
326+
const now = new Date();
327+
now.setHours(0, 0, 0, 0);
328+
return [now.getTime() / 1000];
329+
}
330+
331+
// Add some padding to make the timeline more readable
332+
// Padding: 10% of the time span, minimum 1 hour, maximum 24 hours
333+
const timeSpan = maxTimestamp - minTimestamp;
334+
const paddingSeconds = Math.max(3600, Math.min(86400, timeSpan * 0.1));
335+
336+
const paddedMin = minTimestamp - paddingSeconds;
337+
const paddedMax = maxTimestamp + paddingSeconds;
338+
339+
// Generate array with exactly 2 timestamps: min (start) and max (end)
340+
const dateArray: Array<number> = [];
341+
342+
// Always show exactly 2 ticks for clean X-axis: start and end only
343+
dateArray.push(paddedMin); // Min date (start)
344+
dateArray.push(paddedMax); // Max date (end)
345+
346+
return dateArray;
347+
}
348+
301349
/**
302350
* Filters incidents based on the specified filters.
303351
*

0 commit comments

Comments
 (0)