Skip to content

Commit 612e8f4

Browse files
committed
Links per alert
1 parent 0e8bdb3 commit 612e8f4

File tree

6 files changed

+111
-73
lines changed

6 files changed

+111
-73
lines changed

pkg/handler/alertingmock/alerting_mock.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"math/rand/v2"
77
"net/http"
8+
"net/url"
89
"strings"
910

1011
"github.com/prometheus/common/model"
@@ -125,12 +126,14 @@ func createRule(probability float64, name, severity string, threshold, upperBoun
125126
}
126127
jsonNodeLbl = fmt.Sprintf(`"nodeLabels":[%s],`, strings.Join(quotedLbl, ","))
127128
}
129+
searchURL := "https://duckduckgo.com/?q=" + url.PathEscape(name)
128130
annotations["netobserv_io_network_health"] = model.LabelValue(fmt.Sprintf(
129-
`{%s%s"threshold":"%d","upperBound":"%d","unit":"%%"}`,
131+
`{%s%s"threshold":"%d","upperBound":"%d","unit":"%%","links":[{"name":"Search the web", "url": "%s"}]}`,
130132
jsonNsLbl,
131133
jsonNodeLbl,
132134
threshold,
133135
upperBound,
136+
searchURL,
134137
))
135138
ruleLabels := labels.Clone()
136139
ruleLabels["prometheus"] = "openshift-monitoring/k8s"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ActionsColumn, Td, Tr } from '@patternfly/react-table';
2+
import * as React from 'react';
3+
4+
import { Label, Tooltip } from '@patternfly/react-core';
5+
import { useTranslation } from 'react-i18next';
6+
import { valueFormat } from '../../utils/format';
7+
import { HealthColorSquare } from './health-color-square';
8+
import { AlertWithRuleName, getAlertFilteredLabels, getAlertLink, getHealthMetadata } from './helper';
9+
10+
export interface AlertRowProps {
11+
resourceName: string;
12+
alert: AlertWithRuleName;
13+
wide: boolean;
14+
}
15+
16+
export const AlertRow: React.FC<AlertRowProps> = ({ resourceName, alert, wide }) => {
17+
const { t } = useTranslation('plugin__netobserv-plugin');
18+
19+
const md = getHealthMetadata(alert.annotations);
20+
const labels = getAlertFilteredLabels(alert, resourceName);
21+
const links = [
22+
{
23+
name: t('Navigate to alert details'),
24+
url: getAlertLink(alert)
25+
}
26+
];
27+
if (md?.links) {
28+
links.push(...md?.links);
29+
}
30+
31+
return (
32+
<Tr>
33+
{wide && (
34+
<Td>
35+
<AlertSummaryCell alert={alert} showTooltip={false} />
36+
</Td>
37+
)}
38+
<Td noPadding={!wide}>{alert.state}</Td>
39+
<Td>{alert.labels.severity}</Td>
40+
<Td>
41+
{labels.length === 0
42+
? t('None')
43+
: labels.map(kv => (
44+
<Label key={kv[0]}>
45+
{kv[0]}={kv[1]}
46+
</Label>
47+
))}
48+
</Td>
49+
<Td>
50+
{valueFormat(alert.value as number, 2)}
51+
{md?.threshold && ' > ' + md.threshold + ' ' + md.unit}
52+
</Td>
53+
{wide && <Td>{alert.annotations['description']}</Td>}
54+
<Td noPadding>
55+
<ActionsColumn
56+
isDisabled={links.length === 0}
57+
items={links.map(l => {
58+
return {
59+
title: <a href={l.url}>{l.name}</a>
60+
};
61+
})}
62+
/>
63+
</Td>
64+
</Tr>
65+
);
66+
};
67+
68+
export const AlertSummaryCell: React.FC<{ alert: AlertWithRuleName; showTooltip: boolean }> = ({
69+
alert,
70+
showTooltip
71+
}) => {
72+
return (
73+
<>
74+
<HealthColorSquare alert={alert} />
75+
{showTooltip ? (
76+
<Tooltip content={alert.annotations['description']}>
77+
<span>{alert.annotations['summary']}</span>
78+
</Tooltip>
79+
) : (
80+
<>{alert.annotations['summary']}</>
81+
)}
82+
</>
83+
);
84+
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const HealthDrawerContainer: React.FC<HealthDrawerContainerProps> = ({ ti
109109
</DrawerHead>
110110
{selectedResource && (
111111
<div className="health-gallery-drawer-content">
112-
<RuleDetails info={selectedResource} header={false} />
112+
<RuleDetails info={selectedResource} wide={false} />
113113
</div>
114114
)}
115115
</DrawerPanelContent>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const HealthGlobal: React.FC<HealthGlobalProps> = ({ info, isDark }) => {
4343
<HealthCard isDark={isDark} stats={info} isSelected={false} />
4444
</GridItem>
4545
<GridItem span={9}>
46-
<RuleDetails info={info} header={true} />
46+
<RuleDetails info={info} wide={true} />
4747
</GridItem>
4848
</Grid>
4949
)}

web/src/components/health/helper.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ type RuleWithMetadata = Rule & {
3434
};
3535

3636
type HealthMetadata = {
37-
threshold: string;
38-
thresholdF: number;
39-
upperBound: string;
40-
upperBoundF: number;
41-
unit: string;
37+
threshold?: string;
38+
thresholdF?: number;
39+
upperBound?: string;
40+
upperBoundF?: number;
41+
unit?: string;
4242
nodeLabels?: string[];
4343
namespaceLabels?: string[];
44+
links?: { name: string; url: string }[];
4445
};
4546

4647
type ScoreDetail = {
@@ -52,8 +53,8 @@ export const getHealthMetadata = (annotations: PrometheusLabels): HealthMetadata
5253
if ('netobserv_io_network_health' in annotations) {
5354
const md = (JSON.parse(annotations['netobserv_io_network_health']) as HealthMetadata) || undefined;
5455
if (md) {
55-
md.thresholdF = parseFloat(md.threshold) || 0;
56-
md.upperBoundF = parseFloat(md.upperBound) || 0;
56+
md.thresholdF = parseFloat(md.threshold || '0') || 0;
57+
md.upperBoundF = parseFloat(md.upperBound || '100') || 100;
5758
}
5859
return md;
5960
}

web/src/components/health/rule-details.tsx

Lines changed: 13 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,50 @@
11
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
22
import * as React from 'react';
33

4-
import { Label } from '@patternfly/react-core';
54
import { useTranslation } from 'react-i18next';
6-
import { Link } from 'react-router-dom';
7-
import { valueFormat } from '../../utils/format';
8-
import { HealthColorSquare } from './health-color-square';
9-
import { ByResource, getAlertFilteredLabels, getAlertLink, getAllAlerts, getHealthMetadata } from './helper';
5+
import { AlertRow, AlertSummaryCell } from './alert-row';
6+
import { ByResource, getAllAlerts } from './helper';
107

118
export interface RuleDetailsProps {
129
info: ByResource;
13-
header: boolean;
10+
wide: boolean;
1411
}
1512

16-
export const RuleDetails: React.FC<RuleDetailsProps> = ({ info, header }) => {
13+
export const RuleDetails: React.FC<RuleDetailsProps> = ({ info, wide }) => {
1714
const { t } = useTranslation('plugin__netobserv-plugin');
1815

1916
const allAlerts = getAllAlerts(info);
2017

2118
return (
2219
<Table data-test-rows-count={allAlerts.length} aria-label="Detailed alerting rules" variant="compact">
23-
{header && (
20+
{wide && (
2421
<Thead>
2522
<Th>{t('Summary')}</Th>
2623
<Th>{t('State')}</Th>
2724
<Th>{t('Severity')}</Th>
2825
<Th>{t('Labels')}</Th>
2926
<Th>{t('Value')}</Th>
3027
<Th>{t('Description')}</Th>
28+
<Th screenReaderText="Links" />
3129
</Thead>
3230
)}
33-
{header ? (
31+
{wide ? (
3432
<Tbody>
35-
{allAlerts.map((a, i) => {
36-
const md = getHealthMetadata(a.annotations);
37-
const labels = getAlertFilteredLabels(a, info.name);
38-
return (
39-
<Tr key={'detailed-rules-row-' + i}>
40-
<Td>
41-
<HealthColorSquare alert={a} />
42-
<Link to={getAlertLink(a)} title={t('Navigate to alert details')}>
43-
{a.annotations['summary']}
44-
</Link>
45-
</Td>
46-
<Td>{a.state}</Td>
47-
<Td>{a.labels.severity}</Td>
48-
<Td>
49-
{labels.length === 0
50-
? t('None')
51-
: labels.map(kv => (
52-
<Label key={kv[0]}>
53-
{kv[0]}={kv[1]}
54-
</Label>
55-
))}
56-
</Td>
57-
<Td>
58-
{valueFormat(a.value as number, 2)}
59-
{md?.threshold && ' > ' + md.threshold + ' ' + md.unit}
60-
</Td>
61-
<Td>{a.annotations['description']}</Td>
62-
</Tr>
63-
);
64-
})}
33+
{allAlerts.map((a, i) => (
34+
<AlertRow key={'detailed-rules-row-' + i} alert={a} resourceName={info.name} wide={wide} />
35+
))}
6536
</Tbody>
6637
) : (
6738
allAlerts.map((a, i) => {
68-
// in non-detailed mode, alert summaries take full cols span, and the other fields are displayed below; requires to have Tbody within Tr.
69-
const md = getHealthMetadata(a.annotations);
70-
const labels = getAlertFilteredLabels(a, info.name);
39+
// in non-detailed mode, alert summaries take full cols span, and the other fields are displayed below; requires to have a Tbody for each row.
7140
return (
7241
<Tbody key={'detailed-rules-row-' + i} isExpanded>
7342
<Tr isExpanded>
7443
<Td noPadding colSpan={4}>
75-
<HealthColorSquare alert={a} />
76-
<Link to={getAlertLink(a)} title={t('Navigate to alert details')}>
77-
{a.annotations['summary']}
78-
</Link>
79-
</Td>
80-
</Tr>
81-
<Tr>
82-
<Td noPadding>{a.state}</Td>
83-
<Td>{a.labels.severity}</Td>
84-
<Td>
85-
{labels.length === 0
86-
? t('None')
87-
: labels.map(kv => (
88-
<Label key={kv[0]}>
89-
{kv[0]}={kv[1]}
90-
</Label>
91-
))}
92-
</Td>
93-
<Td>
94-
{valueFormat(a.value as number, 2)}
95-
{md?.threshold && ' > ' + md.threshold + ' ' + md.unit}
44+
<AlertSummaryCell alert={a} showTooltip={true} />
9645
</Td>
9746
</Tr>
47+
<AlertRow alert={a} resourceName={info.name} wide={wide} />
9848
</Tbody>
9949
);
10050
})

0 commit comments

Comments
 (0)