Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { Card, CardBody, CardTitle, EmptyState, EmptyStateBody } from '@patternf
import { createAlertsChartBars, formatDate, generateDateArray } from '../utils';
import { getResizeObserver } from '@patternfly/react-core';
import { useDispatch, useSelector } from 'react-redux';
import * as _ from 'lodash-es';
import { setAlertsAreLoading } from '../../../actions/observe';
import {
t_global_color_status_danger_default,
Expand All @@ -24,8 +23,8 @@ import {

const AlertsChart = ({ chartDays, theme }) => {
const dispatch = useDispatch();
const [chartContainerHeight, setChartContainerHeight] = React.useState();
const [chartHeight, setChartHeight] = React.useState();
const [chartContainerHeight, setChartContainerHeight] = React.useState<number>(0);
const [chartHeight, setChartHeight] = React.useState<number>();
const alertsData = useSelector((state) =>
state.plugins.mcp.getIn(['incidentsData', 'alertsData']),
);
Expand All @@ -43,12 +42,12 @@ const AlertsChart = ({ chartDays, theme }) => {

const chartData = React.useMemo(() => {
if (!Array.isArray(alertsData) || alertsData.length === 0) return [];
return alertsData.map((alert) => createAlertsChartBars(alert, theme, dateValues));
}, [alertsData, theme, dateValues]);
return alertsData.map((alert) => createAlertsChartBars(alert, dateValues));
}, [alertsData, dateValues]);

React.useEffect(() => {
setChartContainerHeight(chartData?.length < 5 ? 300 : chartData?.length * 60);
setChartHeight(chartData?.length < 5 ? 250 : chartData?.length * 55);
setChartContainerHeight(chartData ? (chartData?.length < 5 ? 300 : chartData?.length * 60) : 0);
setChartHeight(chartData ? (chartData?.length < 5 ? 250 : chartData?.length * 55) : 0);
}, [chartData]);

const selectedIncidentIsVisible = React.useMemo(() => {
Expand Down Expand Up @@ -78,7 +77,7 @@ const AlertsChart = ({ chartDays, theme }) => {
<CardTitle>Alerts Timeline</CardTitle>
{alertsAreLoading ? (
<EmptyState
variant="large"
variant="lg"
style={{
height: '250px',
}}
Expand All @@ -88,7 +87,7 @@ const AlertsChart = ({ chartDays, theme }) => {
) : (
<CardBody
style={{
height: { chartContainerHeight },
height: chartContainerHeight,
width: '100%',
}}
>
Expand All @@ -98,7 +97,7 @@ const AlertsChart = ({ chartDays, theme }) => {
labelComponent={
<ChartTooltip
orientation="top"
dx={({ x, x0 }) => -(x - x0) / 2}
dx={({ x, datum }) => -Math.abs(x - datum.x) / 2}
dy={-5} // Position tooltip so pointer appears above bar
constrainToVisibleArea
labelComponent={<ChartLabel />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@ import { setAlertsAreLoading } from '../../../actions/observe';
const IncidentsChart = ({ incidentsData, chartDays, theme }) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = React.useState(true);
const [chartContainerHeight, setChartContainerHeight] = React.useState();
const [chartHeight, setChartHeight] = React.useState();
const [chartContainerHeight, setChartContainerHeight] = React.useState<number>(0);
const [chartHeight, setChartHeight] = React.useState<number>();
const dateValues = React.useMemo(() => generateDateArray(chartDays), [chartDays]);

const chartData = React.useMemo(() => {
if (!Array.isArray(incidentsData) || incidentsData.length === 0) return [];
return incidentsData.map((incident) => createIncidentsChartBars(incident, theme, dateValues));
}, [incidentsData, theme, dateValues]);
return incidentsData.map((incident) => createIncidentsChartBars(incident, dateValues));
}, [incidentsData, dateValues]);

React.useEffect(() => {
setIsLoading(false);
}, [incidentsData]);

React.useEffect(() => {
setChartContainerHeight(chartData?.length < 5 ? 300 : chartData?.length * 60);
setChartHeight(chartData?.length < 5 ? 250 : chartData?.length * 55);
setChartContainerHeight(chartData ? (chartData?.length < 5 ? 300 : chartData?.length * 60) : 0);
setChartHeight(chartData ? (chartData?.length < 5 ? 250 : chartData?.length * 55) : 0);
}, [chartData]);

const [width, setWidth] = React.useState(0);
Expand Down Expand Up @@ -107,7 +107,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => {
) : (
<CardBody
style={{
height: { chartContainerHeight },
height: chartContainerHeight,
width: '100%',
}}
>
Expand All @@ -117,7 +117,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => {
labelComponent={
<ChartTooltip
orientation="top"
dx={({ x, x0 }) => -(x - x0) / 2}
dx={({ x, datum }) => -Math.abs(x - datum.x) / 2}
dy={-5} // Position tooltip so pointer appears above bar
constrainToVisibleArea
labelComponent={<ChartLabel />}
Expand Down Expand Up @@ -192,7 +192,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => {
//we have several arrays and for each array we make a ChartBar
<ChartBar
data={bar}
key={bar.group_id}
key={bar[0].group_id}
style={{
data: {
fill: ({ datum }) => datum.fill,
Expand All @@ -203,6 +203,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => {
}}
events={[
{
target: 'data',
eventHandlers: {
onClick: (props, datum) => clickHandler(props, datum),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const IncidentsDetailsRowTable = ({ alerts }) => {
}, [memoizedAlerts, alertsWithLabels]);

return (
<Table borders={'compactBorderless'}>
<Table borders={false} variant="compact">
<Thead>
<Tr>
<Th width={25}>{t('Alert Name')}</Th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import withFallback from '../console/console-shared/error/fallbacks/withFallback
import IncidentsChart from './IncidentsChart/IncidentsChart';
import AlertsChart from './AlertsChart/AlertsChart';
import { usePatternFlyTheme } from '../hooks/usePatternflyTheme';
import { DaysFilters, IncidentFilters } from './models';

const IncidentsPage = () => {
const { t } = useTranslation(process.env.I18N_NAMESPACE);
Expand All @@ -69,7 +70,7 @@ const IncidentsPage = () => {
// days span is where we store the value for creating time ranges for
// fetch incidents/alerts based on the length of time ranges
// when days filter changes we set a new days span -> calculate new time range and fetch new data
const [daysSpan, setDaysSpan] = React.useState();
const [daysSpan, setDaysSpan] = React.useState<number>(7);
const [timeRanges, setTimeRanges] = React.useState([]);
// data that is used for processing to serve it to the alerts table and chart
const [incidentForAlertProcessing, setIncidentForAlertProcessing] = React.useState([]);
Expand Down Expand Up @@ -271,9 +272,9 @@ const IncidentsPage = () => {
}
}, [incidentGroupId, timeRanges]);

const onSelect = (_event, value) => {
const onSelect = (_event: React.MouseEvent, value?: string) => {
if (value) {
changeDaysFilter(value, dispatch, incidentsActiveFilters);
changeDaysFilter(value as DaysFilters, dispatch, incidentsActiveFilters);
}

setDaysFilterIsExpanded(false);
Expand All @@ -297,15 +298,20 @@ const IncidentsPage = () => {
id="toolbar-with-filter"
collapseListedFiltersBreakpoint="xl"
clearAllFilters={() =>
onDeleteIncidentFilterChip('', '', incidentsActiveFilters, dispatch)
onDeleteIncidentFilterChip('', undefined, incidentsActiveFilters, dispatch)
}
>
<ToolbarContent>
<ToolbarItem>
<ToolbarFilter
labels={incidentsActiveFilters.incidentFilters}
deleteLabel={(category, chip) =>
onDeleteIncidentFilterChip(category, chip, incidentsActiveFilters, dispatch)
onDeleteIncidentFilterChip(
category as string,
chip as IncidentFilters,
incidentsActiveFilters,
dispatch,
)
}
deleteLabelGroup={() =>
onDeleteGroupIncidentFilterChip(incidentsActiveFilters, dispatch)
Expand All @@ -319,7 +325,12 @@ const IncidentsPage = () => {
isOpen={incidentFilterIsExpanded}
selected={incidentsActiveFilters.incidentFilters}
onSelect={(event, selection) =>
onIncidentFiltersSelect(event, selection, dispatch, incidentsActiveFilters)
onIncidentFiltersSelect(
event,
selection as IncidentFilters,
dispatch,
incidentsActiveFilters,
)
}
onOpenChange={(isOpen) => setIncidentIsExpanded(isOpen)}
toggle={(toggleRef) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import { AlertSeverity, AlertStates } from '@openshift-console/dynamic-plugin-sdk';
import { Bullseye, Card, CardBody, EmptyState, EmptyStateBody } from '@patternfly/react-core';
import { SearchIcon } from '@patternfly/react-icons';
import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import * as _ from 'lodash-es';
import React from 'react';
import { Table, Thead, Tr, Th, Tbody, Td, ExpandableRowContent } from '@patternfly/react-table';
import {
Bullseye,
Card,
CardBody,
EmptyState,
EmptyStateBody,
Label,
} from '@patternfly/react-core';
import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon';
import IncidentsDetailsRowTable from './IncidentsDetailsRowTable';
import { BellIcon, BellSlashIcon, SearchIcon } from '@patternfly/react-icons';
import { useSelector } from 'react-redux';
import * as _ from 'lodash-es';
import { AlertState, AlertStateIcon, SeverityBadge } from '../alerting/AlertUtils';
import { AlertSeverity, AlertStates } from '@openshift-console/dynamic-plugin-sdk';
import { AlertStateIcon, SeverityBadge } from '../alerting/AlertUtils';
import IncidentsDetailsRowTable from './IncidentsDetailsRowTable';

export const IncidentsTable = ({ namespace }) => {
export const IncidentsTable = () => {
const columnNames = {
checkbox: '',
component: 'Component',
Expand Down Expand Up @@ -108,10 +100,7 @@ export const IncidentsTable = ({ namespace }) => {
<Tr isExpanded={isAlertExpanded(alert)}>
<Td width={100} colSpan={6}>
<ExpandableRowContent>
<IncidentsDetailsRowTable
alerts={alert.alertsExpandedRowData}
namespace={namespace}
/>
<IncidentsDetailsRowTable alerts={alert.alertsExpandedRowData} />
</ExpandableRowContent>
</Td>
</Tr>
Expand Down
60 changes: 60 additions & 0 deletions web/src/components/Incidents/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export type Timestamps = [Date, string];

export type SpanDates = Array<Date>;

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

export type Incident = {
component: string;
componentList: Array<string>;
layer: string;
firing: boolean;
group_id: string;
src_severity: string;
src_alertname: string;
src_namespace: string;
x: number;
values: Array<Timestamps>;
metric: Metric;
};

// Define the interface for Metric
export type Metric = {
group_id: string; // The unique ID for grouping
component: string; // Component name
componentList?: string[]; // List of all unique components
[key: string]: any; // Allow other dynamic fields in Metric
};

export type ProcessedIncident = Omit<Incident, 'metric'> & {
informative: boolean;
critical: boolean;
warning: boolean;
resolved: boolean;
firing: boolean;
};

export type Alert = {
alertname: string;
alertsStartFiring: Date;
alertsEndFiring: Date;
alertstate: string;
component: string;
layer: string;
name: string;
namespace: string;
resolved: boolean;
severity: 'critical' | 'warning' | 'info';
x: number;
values: Array<Timestamps>;
};

export type DaysFilters = '1 day' | '3 days' | '7 days' | '15 days';

export type IncidentFilters = 'Critical' | 'Warning' | 'Firing' | 'Informative' | 'Resolved';

export type IncidentFiltersCombined = {
days: Array<DaysFilters>;
incidentFilters: Array<IncidentFilters>;
groupId?: Array<string>;
};
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
/* eslint-disable max-len */

import { sortObjectsByEarliestTimestamp } from './processIncidents';
import { Incident } from './models';
import { sortObjectsByEarliestTimestamp } from './utils';

/**
* Groups alert objects by their `alertname`, `namespace`, and `component` fields and merges their values
* while removing duplicates. Alerts with the same combination of `alertname`, `namespace`, and `component`
* are combined, with values being deduplicated.
*
* @param {Array<Object>} objects - Array of alert objects to be grouped. Each object contains a `metric` field
* with properties such as `alertname`, `namespace`, `component`, and an array of `values`.
* @param {Object} objects[].metric - The metric information of the alert.
* @param {string} objects[].metric.alertname - The name of the alert.
* @param {string} objects[].metric.namespace - The namespace in which the alert is raised.
* @param {string} objects[].metric.component - The component associated with the alert.
* @param {Array<Array<Number | string>>} objects[].values - The array of values corresponding to the alert, where
* each value is a tuple containing a timestamp and a value (e.g., [timestamp, value]).
*
* @returns {Array<Object>} - An array of grouped alert objects. Each object contains a unique combination of
* `alertname`, `namespace`, and `component`, with deduplicated values.
* @returns {Object} return[].metric - The metric information of the grouped alert.
* @returns {Array<Array<Number | string>>} return[].values - The deduplicated array of values for the grouped alert.
*
* @example
* const alerts = [
* { metric: { alertname: "Alert1", namespace: "ns1", component: "comp1" }, values: [[12345, "2"], [12346, "2"]] },
Expand All @@ -29,7 +16,7 @@ import { sortObjectsByEarliestTimestamp } from './processIncidents';
* const groupedAlerts = groupAlerts(alerts);
* // Returns an array where the two alerts are grouped together with deduplicated values.
*/
export function groupAlerts(objects) {
export function groupAlerts(objects: Array<Incident>) {
// Step 1: Filter out all non firing alerts
const filteredObjects = objects.filter((obj) => obj.metric.alertstate === 'firing');
const groupedObjects = new Map();
Expand Down Expand Up @@ -137,7 +124,7 @@ export function groupAlerts(objects) {
* // ]
*/

export function processAlerts(data, selectedIncidents) {
export function processAlerts(data: Array<Incident>, selectedIncidents) {
const firing = groupAlerts(data).filter((alert) => alert.metric.alertname !== 'Watchdog');

// Extract the first and last timestamps from selectedIncidents
Expand All @@ -152,16 +139,16 @@ export function processAlerts(data, selectedIncidents) {
// Filter values based on firstTimestamp and lastTimestamp keep only values within range
const processedValues = alert.values
.map((value) => {
const timestamp = new Date(value[0] * 1000);
const timestamp = new Date(value[0].getTime() * 1000);
return [timestamp, value[1]];
})
.filter(([date]) => date >= firstTimestamp && date <= lastTimestamp);
.filter(([date]) => date >= firstTimestamp && date <= lastTimestamp) as Array<[Date, string]>;

const sortedValues = processedValues.sort((a, b) => a[0] - b[0]);
const sortedValues = processedValues.sort((a, b) => a[0].getTime() - b[0].getTime());

const alertsStartFiring = sortedValues[0][0];
const alertsEndFiring = sortedValues[sortedValues.length - 1][0];
const resolved = new Date() - alertsEndFiring > 10 * 60 * 1000;
const resolved = Date.now() - alertsEndFiring.getTime() > 10 * 60 * 1000;

return {
alertname: alert.metric.alertname,
Expand Down
Loading