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
15 changes: 13 additions & 2 deletions web/src/components/Incidents/AlertsChart/AlertsChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,18 @@ const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => {

const chartData: AlertsChartBar[][] = useMemo(() => {
if (!Array.isArray(alertsData) || alertsData.length === 0) return [];
return alertsData.map((alert) => createAlertsChartBars(alert));

// Group alerts by identity so intervals of the same alert share the same row
const groupedByIdentity = new Map<string, typeof alertsData>();
for (const alert of alertsData) {
const key = [alert.alertname, alert.namespace, alert.severity].join('|');
if (!groupedByIdentity.has(key)) {
groupedByIdentity.set(key, []);
}
groupedByIdentity.get(key)!.push(alert);
}

return Array.from(groupedByIdentity.values()).map((alerts) => createAlertsChartBars(alerts));
}, [alertsData]);

useEffect(() => {
Expand Down Expand Up @@ -161,7 +172,7 @@ const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => {
if (datum.nodata) {
return '';
}
const startDate = dateTimeFormatter(i18n.language).format(new Date(datum.y0));
const startDate = dateTimeFormatter(i18n.language).format(datum.startDate);
const endDate =
datum.alertstate === 'firing'
? '---'
Expand Down
29 changes: 24 additions & 5 deletions web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
calculateIncidentsChartDomain,
createIncidentsChartBars,
generateDateArray,
removeTrailingPaddingFromSeveritySegments,
roundDateToInterval,
} from '../utils';
import { dateTimeFormatter, timeFormatter } from '../../console/utils/datetime';
Expand Down Expand Up @@ -89,10 +90,29 @@ const IncidentsChart = ({
? incidentsData.filter((incident) => incident.group_id === selectedGroupId)
: incidentsData;

// Create chart bars and sort by original x values to maintain proper order
const chartBars = filteredIncidents.map((incident) =>
createIncidentsChartBars(incident, dateValues),
// Group incidents by group_id so split severity segments share the same row
const incidentsByGroupId = new Map<string, typeof filteredIncidents>();
for (const incident of filteredIncidents) {
const existing = incidentsByGroupId.get(incident.group_id);
if (existing) {
existing.push(incident);
} else {
incidentsByGroupId.set(incident.group_id, [incident]);
}
}

// When an incident changes severity, its segments share the same row.
// Non-last segments have trailing padding (+300s) that overlaps with the
// next segment's leading padding (-300s). Remove the trailing padding
// value from non-last segments to prevent visual overlap.
const adjustedGroups = Array.from(incidentsByGroupId.values()).map((group) =>
removeTrailingPaddingFromSeveritySegments(group),
);

// Create chart bars per group and sort by original x values
const chartBars = adjustedGroups
.map((group) => createIncidentsChartBars(group, dateValues))
.filter((bars) => bars.length > 0);
chartBars.sort((a, b) => a[0].x - b[0].x);

// Reassign consecutive x values to eliminate gaps between bars
Expand All @@ -102,7 +122,6 @@ const IncidentsChart = ({
useEffect(() => {
setIsLoading(false);
}, [incidentsData]);

useEffect(() => {
setChartContainerHeight(chartData?.length < 5 ? 300 : chartData?.length * 60);
setChartHeight(chartData?.length < 5 ? 250 : chartData?.length * 55);
Expand Down Expand Up @@ -176,7 +195,7 @@ const IncidentsChart = ({
if (datum.nodata) {
return '';
}
const startDate = dateTimeFormatter(i18n.language).format(new Date(datum.y0));
const startDate = dateTimeFormatter(i18n.language).format(datum.startDate);
const endDate = datum.firing
? '---'
: dateTimeFormatter(i18n.language).format(
Expand Down
21 changes: 15 additions & 6 deletions web/src/components/Incidents/IncidentsDetailsRowTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ const IncidentsDetailsRowTable = ({ alerts }: IncidentsDetailsRowTableProps) =>
const sortedAndMappedAlerts = useMemo(() => {
if (alerts && alerts.length > 0) {
return [...alerts]
.sort(
(a: IncidentsDetailsAlert, b: IncidentsDetailsAlert) =>
a.alertsStartFiring - b.alertsStartFiring,
)
.sort((a: IncidentsDetailsAlert, b: IncidentsDetailsAlert) => {
const aStart = a.firstTimestamp > 0 ? a.firstTimestamp : a.alertsStartFiring;
const bStart = b.firstTimestamp > 0 ? b.firstTimestamp : b.alertsStartFiring;
return aStart - bStart;
})
.map((alertDetails: IncidentsDetailsAlert, rowIndex) => {
return (
<Tr key={rowIndex}>
Expand All @@ -45,13 +46,21 @@ const IncidentsDetailsRowTable = ({ alerts }: IncidentsDetailsRowTableProps) =>
<SeverityBadge severity={alertDetails.severity} />
</Td>
<Td dataLabel="expanded-details-firingstart">
<Timestamp timestamp={String(alertDetails.alertsStartFiring * 1000)} />
<Timestamp
timestamp={new Date(
(alertDetails.firstTimestamp > 0
? alertDetails.firstTimestamp
: alertDetails.alertsStartFiring) * 1000,
).toISOString()}
/>
</Td>
<Td dataLabel="expanded-details-firingend">
{!alertDetails.resolved ? (
'---'
) : (
<Timestamp timestamp={String(alertDetails.alertsEndFiring * 1000)} />
<Timestamp
timestamp={new Date(alertDetails.alertsEndFiring * 1000).toISOString()}
/>
)}
</Td>
<Td dataLabel="expanded-details-alertstate">
Expand Down
105 changes: 56 additions & 49 deletions web/src/components/Incidents/IncidentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
onIncidentFiltersSelect,
parseUrlParams,
updateBrowserUrl,
DAY_MS,
} from './utils';
import { groupAlertsForTable, convertToAlerts } from './processAlerts';
import { CompressArrowsAltIcon, CompressIcon, FilterIcon } from '@patternfly/react-icons';
Expand Down Expand Up @@ -231,52 +232,54 @@ const IncidentsPage = () => {
}, [incidentsActiveFilters.days]);

useEffect(() => {
(async () => {
const currentTime = incidentsLastRefreshTime;
Promise.all(
timeRanges.map(async (range) => {
const response = await fetchDataForIncidentsAndAlerts(
safeFetch,
range,
createAlertsQuery(incidentForAlertProcessing),
);
return response.data.result;
}),
)
.then((results) => {
const prometheusResults = results.flat();
const alerts = convertToAlerts(
prometheusResults,
incidentForAlertProcessing,
currentTime,
);
if (incidentForAlertProcessing.length === 0) {
return;
}

const currentTime = incidentsLastRefreshTime;

// Always fetch 15 days of alert data so firstTimestamp is computed from full history
const fetchTimeRanges = getIncidentsTimeRanges(15 * DAY_MS, currentTime);

Promise.all(
fetchTimeRanges.map(async (range) => {
const response = await fetchDataForIncidentsAndAlerts(
safeFetch,
range,
createAlertsQuery(incidentForAlertProcessing),
);
return response.data.result;
}),
)
.then((alertsResults) => {
const prometheusResults = alertsResults.flat();
const alerts = convertToAlerts(
prometheusResults,
incidentForAlertProcessing,
currentTime,
daysSpan,
);
dispatch(
setAlertsData({
alertsData: alerts,
}),
);
if (rules && alerts) {
dispatch(
setAlertsData({
alertsData: alerts,
setAlertsTableData({
alertsTableData: groupAlertsForTable(alerts, rules),
}),
);
if (rules && alerts) {
dispatch(
setAlertsTableData({
alertsTableData: groupAlertsForTable(alerts, rules),
}),
);
}
if (!isEmpty(filteredData)) {
dispatch(setAlertsAreLoading({ alertsAreLoading: false }));
} else {
dispatch(setAlertsAreLoading({ alertsAreLoading: true }));
}
})
.catch((err) => {
// eslint-disable-next-line no-console
console.log(err);

dispatch(setAlertsAreLoading({ alertsAreLoading: false }));
setLoadError(err);
});
})();
}, [incidentForAlertProcessing]);
}
dispatch(setAlertsAreLoading({ alertsAreLoading: false }));
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
dispatch(setAlertsAreLoading({ alertsAreLoading: false }));
setLoadError(err);
});
}, [incidentForAlertProcessing, rules, daysSpan]);

useEffect(() => {
if (!isInitialized) return;
Expand All @@ -293,30 +296,34 @@ const IncidentsPage = () => {
? incidentsActiveFilters.days[0].split(' ')[0] + 'd'
: '',
);
const calculatedTimeRanges = getIncidentsTimeRanges(daysDuration, currentTime);

const isGroupSelected = !!selectedGroupId;
const incidentsQuery = isGroupSelected
? `cluster_health_components_map{group_id='${selectedGroupId}'}`
: 'cluster_health_components_map';

// Always fetch 15 days of data so firstTimestamp is computed from full history
const fetchTimeRanges = getIncidentsTimeRanges(15 * DAY_MS, currentTime);

Promise.all(
calculatedTimeRanges.map(async (range) => {
fetchTimeRanges.map(async (range) => {
const response = await fetchDataForIncidentsAndAlerts(safeFetch, range, incidentsQuery);
return response.data.result;
}),
)
.then((results) => {
const prometheusResults = results.flat();
const incidents = convertToIncidents(prometheusResults, currentTime);
.then((incidentsResults) => {
const prometheusResults = incidentsResults.flat();
const incidents = convertToIncidents(prometheusResults, currentTime, daysDuration);

// Update the raw, unfiltered incidents state
dispatch(setIncidents({ incidents }));

const filteredData = filterIncident(incidentsActiveFilters, incidents);

// Filter the incidents and dispatch
dispatch(
setFilteredIncidentsData({
filteredIncidentsData: filterIncident(incidentsActiveFilters, incidents),
filteredIncidentsData: filteredData,
}),
);

Expand Down
6 changes: 4 additions & 2 deletions web/src/components/Incidents/IncidentsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const IncidentsTable = () => {
if (!alert.alertsExpandedRowData || alert.alertsExpandedRowData.length === 0) {
return 0;
}
return Math.min(...alert.alertsExpandedRowData.map((alertData) => alertData.alertsStartFiring));
return Math.min(...alert.alertsExpandedRowData.map((alertData) => alertData.firstTimestamp));
};

if (isEmpty(alertsTableData) || alertsAreLoading || isEmpty(incidentsActiveFilters.groupId)) {
Expand Down Expand Up @@ -180,7 +180,9 @@ export const IncidentsTable = () => {
)}
</Td>
<Td dataLabel={columnNames.startDate}>
<Timestamp timestamp={String(getMinStartDate(alert) * 1000)} />
<Timestamp
timestamp={new Date(getMinStartDate(alert) * 1000).toISOString()}
/>
</Td>
<Td
dataLabel={columnNames.state}
Expand Down
6 changes: 5 additions & 1 deletion web/src/components/Incidents/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type Timestamps = [number, string];

export type SpanDates = Array<number>;

export type AlertsIntervalsArray = [number, number, 'data' | 'nodata'];
export type AlertsIntervalsArray = [number, number, 'data' | 'nodata', number?];

export type Incident = {
component: string;
Expand All @@ -15,10 +15,12 @@ export type Incident = {
src_severity: string;
src_alertname: string;
src_namespace: string;
severity: any;
silenced: boolean;
x: number;
values: Array<Timestamps>;
metric: Metric;
firstTimestamp: number;
};

// Define the interface for Metric
Expand Down Expand Up @@ -47,6 +49,7 @@ export type Alert = {
severity: Severity;
silenced: boolean;
x: number;
firstTimestamp: number;
values: Array<Timestamps>;
alertsExpandedRowData?: Array<Alert>;
};
Expand Down Expand Up @@ -101,6 +104,7 @@ export type IncidentsDetailsAlert = {
resolved: boolean;
severity: Severity;
x: number;
firstTimestamp: number;
values: Array<Timestamps>;
silenced: boolean;
rule: {
Expand Down
Loading