Skip to content

Commit c099915

Browse files
committed
Add alert details info
1 parent 1894b7a commit c099915

File tree

7 files changed

+137
-22
lines changed

7 files changed

+137
-22
lines changed

web/locales/en/plugin__netobserv-plugin.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@
225225
"Previous tip": "Previous tip",
226226
"Next tip": "Next tip",
227227
"Close tips": "Close tips",
228+
"Summary": "Summary",
229+
"Navigate to alert details": "Navigate to alert details",
230+
"State": "State",
231+
"Severity": "Severity",
232+
"Labels": "Labels",
233+
"Description": "Description",
228234
"(global)": "(global)",
229235
"No violations found": "No violations found",
230236
"critical issues": "critical issues",
@@ -233,6 +239,7 @@
233239
"pending issues": "pending issues",
234240
"silenced issues": "silenced issues",
235241
"Rule {{ruleName}}: no alert": "Rule {{ruleName}}: no alert",
242+
"No alert for this rule": "No alert for this rule",
236243
"No rules found, health cannot be determined": "No rules found, health cannot be determined",
237244
"Check alert definitions in FlowCollector \"spec.processor.metrics.alertGroups\" and \"spec.processor.metrics.disableAlerts\".": "Check alert definitions in FlowCollector \"spec.processor.metrics.alertGroups\" and \"spec.processor.metrics.disableAlerts\".",
238245
"Make sure that Prometheus and AlertManager are running.": "Make sure that Prometheus and AlertManager are running.",
@@ -254,7 +261,6 @@
254261
"Global rule violations": "Global rule violations",
255262
"Rule violations per node": "Rule violations per node",
256263
"Rule violations per namespace": "Rule violations per namespace",
257-
"Navigate to alert details": "Navigate to alert details",
258264
"No results found": "No results found",
259265
"Clear or reset filters and try again.": "Clear or reset filters and try again.",
260266
"Check for errors in health dashboard. Status endpoint is returning: {{statusError}}": "Check for errors in health dashboard. Status endpoint is returning: {{statusError}}",
@@ -356,7 +362,6 @@
356362
"Filtered sum of packets": "Filtered sum of packets",
357363
"Filtered average speed": "Filtered average speed",
358364
"Bps": "Bps",
359-
"Summary": "Summary",
360365
"Query limit reached": "Query limit reached",
361366
"Filtered flows count": "Filtered flows count",
362367
"Filtered ended conversations count": "Filtered ended conversations count",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as React from 'react';
2+
3+
import { Label, Text, TextContent, TextVariants } from '@patternfly/react-core';
4+
import { useTranslation } from 'react-i18next';
5+
import { Link } from 'react-router-dom';
6+
import { valueFormat } from '../../utils/format';
7+
import { AlertWithRuleName, getAlertFilteredLabels, getAlertLink, getHealthMetadata } from './helper';
8+
9+
export interface AlertDetailsProps {
10+
resourceName: string;
11+
alert: AlertWithRuleName;
12+
}
13+
14+
export const AlertDetails: React.FC<AlertDetailsProps> = ({ resourceName, alert }) => {
15+
const { t } = useTranslation('plugin__netobserv-plugin');
16+
17+
const md = getHealthMetadata(alert.annotations);
18+
19+
return (
20+
<div className="alert-details">
21+
<TextContent>
22+
<AlertDetailsValue title={t('Summary')}>
23+
<Link to={getAlertLink(alert)} title={t('Navigate to alert details')}>
24+
{alert.annotations['summary']}
25+
</Link>
26+
</AlertDetailsValue>
27+
<AlertDetailsValue title={t('State')}>{alert.state}</AlertDetailsValue>
28+
<AlertDetailsValue title={t('Severity')}>{alert.labels.severity}</AlertDetailsValue>
29+
<AlertDetailsValue title={t('Labels')}>
30+
{getAlertFilteredLabels(alert, resourceName).map(kv => (
31+
<Label key={kv[0]}>
32+
{kv[0]}={kv[1]}
33+
</Label>
34+
))}
35+
</AlertDetailsValue>
36+
<AlertDetailsValue title={t('Value')}>
37+
<>
38+
{valueFormat(alert.value as number, 2)}
39+
{md?.threshold && ' > ' + md.threshold + ' ' + md.unit}
40+
</>
41+
</AlertDetailsValue>
42+
<AlertDetailsValue title={t('Description')}>{alert.annotations['description']}</AlertDetailsValue>
43+
</TextContent>
44+
</div>
45+
);
46+
};
47+
48+
export const AlertDetailsValue: React.FC<{ title: string }> = ({ title, children }) => {
49+
return (
50+
<>
51+
<Text component={TextVariants.h4} className="alert-field-title">
52+
{title}
53+
</Text>
54+
<Text>{children}</Text>
55+
</>
56+
);
57+
};

web/src/components/health/health-drawer-container.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ export const HealthDrawerContainer: React.FC<HealthDrawerContainerProps> = ({ ti
8989
</DrawerActions>
9090
</DrawerHead>
9191
{selectedResource && (
92-
<>
92+
<div className="health-gallery-drawer-content">
9393
{selectedPanelView === 'heatmap' ? (
9494
<HealthHeatmap info={selectedResource} />
9595
) : (
9696
<RuleDetails info={selectedResource} />
9797
)}
98-
</>
98+
</div>
9999
)}
100100
</DrawerPanelContent>
101101
}

web/src/components/health/health-heatmap.tsx

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { TextContent } from '@patternfly/react-core';
12
import * as React from 'react';
23
import { useTranslation } from 'react-i18next';
4+
import { valueFormat } from '../../utils/format';
35
import { AlertWithRuleName, ByResource, computeAlertScore, getAllAlerts } from './helper';
46

5-
import { valueFormat } from '../../utils/format';
7+
import { AlertDetails, AlertDetailsValue } from './alert-details';
68
import './heatmap.css';
79

810
export interface HealthHeatmapProps {
@@ -54,6 +56,12 @@ const getCellColors = (value: number, rangeFrom: number, rangeTo: number, colorM
5456

5557
export const HealthHeatmap: React.FC<HealthHeatmapProps> = ({ info }) => {
5658
const { t } = useTranslation('plugin__netobserv-plugin');
59+
const [selectedItem, setSelectedItem] = React.useState<AlertWithRuleName | string | undefined>(undefined);
60+
61+
React.useEffect(() => {
62+
// Reset selection when props.info changes
63+
setSelectedItem(undefined);
64+
}, [info]);
5765

5866
const tooltip = (a: AlertWithRuleName): string => {
5967
let prefix = '';
@@ -71,15 +79,21 @@ export const HealthHeatmap: React.FC<HealthHeatmapProps> = ({ info }) => {
7179
return `${prefix}${a.annotations['summary']} | ${valueInfo}`;
7280
};
7381

74-
type CellInfo = { score: number; colorMap: ColorMap; tooltip: string };
82+
type CellInfo = { score: number; colorMap: ColorMap; tooltip: string; onClick: () => void; selected: boolean };
7583

7684
// Map inactive rules to CellInfo
7785
const inactive: CellInfo[] = [...info.critical.inactive, ...info.warning.inactive, ...info.other.inactive].map(
78-
ruleName => ({ score: 0, colorMap: inactiveColorMap, tooltip: t('Rule {{ruleName}}: no alert', { ruleName }) })
86+
ruleName => ({
87+
score: 0,
88+
colorMap: inactiveColorMap,
89+
tooltip: t('Rule {{ruleName}}: no alert', { ruleName }),
90+
onClick: () => setSelectedItem(ruleName),
91+
selected: selectedItem === ruleName
92+
})
7993
);
8094

8195
// Map active alerts to CellInfo, then concat inactive
82-
const values: CellInfo[] = getAllAlerts(info)
96+
const items: CellInfo[] = getAllAlerts(info)
8397
.map(a => {
8498
const colorMap =
8599
a.labels.severity === 'critical'
@@ -88,26 +102,52 @@ export const HealthHeatmap: React.FC<HealthHeatmapProps> = ({ info }) => {
88102
? warningColorMap
89103
: infoColorMap;
90104
const score = computeAlertScore(a, true);
91-
return { score, colorMap: colorMap, tooltip: tooltip(a) };
105+
return {
106+
score,
107+
colorMap: colorMap,
108+
tooltip: tooltip(a),
109+
onClick: () => setSelectedItem(a),
110+
selected: selectedItem === a
111+
};
92112
})
93113
.concat(inactive)
94114
.slice(0, 24);
95115

96116
// Fill remaining cells for a 5x5 array
97117
let remains = [];
98-
if (values.length < 25) {
99-
remains = Array(25 - values.length).fill(undefined);
118+
if (items.length < 25) {
119+
remains = Array(25 - items.length).fill(undefined);
100120
}
101121

102122
return (
103-
<div className="heatmap">
104-
{values.map((cell, i) => {
105-
const style = getCellColors(cell.score, 0, 1, cell.colorMap);
106-
return <div key={`heatmap_${i}`} className={'cell'} style={style} title={cell.tooltip} />;
107-
})}
108-
{remains.map((_, i) => {
109-
return <div key={`heatmap_remains_${i}`} className={'cell greyed'} />;
110-
})}
111-
</div>
123+
<>
124+
<div className="heatmap">
125+
{items.map((item, i) => {
126+
const style = getCellColors(item.score, 0, 1, item.colorMap);
127+
return (
128+
<div
129+
key={`heatmap_${i}`}
130+
className={'cell' + (item.selected ? ' selected' : '')}
131+
style={style}
132+
title={item.tooltip}
133+
onClick={item.onClick}
134+
/>
135+
);
136+
})}
137+
{remains.map((_, i) => {
138+
return <div key={`heatmap_remains_${i}`} className={'cell greyed'} />;
139+
})}
140+
</div>
141+
<div className="details">
142+
{selectedItem && typeof selectedItem === 'string' && (
143+
<TextContent>
144+
<AlertDetailsValue title={t('No alert for this rule')}>{selectedItem}</AlertDetailsValue>
145+
</TextContent>
146+
)}
147+
{selectedItem && typeof selectedItem !== 'string' && (
148+
<AlertDetails alert={selectedItem} resourceName={info.name} />
149+
)}
150+
</div>
151+
</>
112152
);
113153
};

web/src/components/health/health.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
height: initial;
2828
}
2929

30+
.health-gallery-drawer-content {
31+
padding: 1.5rem;
32+
}
33+
3034
.netobserv-refresh-interval-container {
3135
margin: 0 !important;
3236
}

web/src/components/health/heatmap.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,24 @@
33
grid-template-columns: 2rem 2rem 2rem 2rem 2rem;
44
grid-template-rows: 2rem 2rem 2rem 2rem 2rem;
55
grid-gap: 0.3rem;
6-
margin-left: 50px;
76
}
87

98
.heatmap .cell {
109
border-radius: 5px;
1110
width: 100%;
1211
height: 100%;
12+
cursor: pointer;
13+
}
14+
15+
.heatmap .cell.selected {
16+
border: 2px solid #06c;
1317
}
1418

1519
.heatmap .cell.greyed {
1620
background-color: rgb(106, 110, 115);
21+
cursor: initial;
22+
}
23+
24+
.details {
25+
margin-top: 2rem;
1726
}

web/src/components/health/network-health.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as _ from 'lodash';
55
import { murmur3 } from 'murmurhash-js';
66
import * as React from 'react';
77
import { useTranslation } from 'react-i18next';
8+
import { SilenceMatcher } from '../../api/alert';
89
import { getAlerts, getSilencedAlerts } from '../../api/routes';
910
import { getHTTPErrorDetails } from '../../utils/errors';
1011
import { localStorageHealthRefreshKey, useLocalStorage } from '../../utils/local-storage-hook';
@@ -16,7 +17,6 @@ import HealthError from './health-error';
1617
import { HealthSummary } from './health-summary';
1718
import { buildStats, isSilenced } from './helper';
1819
import { HealthTabTitle } from './tab-title';
19-
import { SilenceMatcher } from '../../api/alert';
2020

2121
import './health.css';
2222

0 commit comments

Comments
 (0)