mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-11 22:07:27 -08:00
Rework Alert page (#10142)
* rework the target page Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * put back the URL of the endpoint Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * replace old code by the new one and change function style Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * align filter and search bar on the same row Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * remove unnecessary return Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * upgrade kvsearch to v0.3.0 Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * fix unit test Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * add missing style on column Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * add placeholder and autofocus Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * put back the previous table design Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * fix issue relative to the position of the tooltip Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * fix health filter Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * fix test on label tooltip Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * simplify filter condition Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * rework service discovery page Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * introduced generic custom infinite scroll component Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * adjust the placeholder in discovery page Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * ignore returning type missing Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * rework alert page Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * update snapshot to match the new rendering Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * fix infinite scroll component usage in alert Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * align checkbox like it was before Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * propose a more responsive line to display the buttons and the search bar Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * add a script to update the snapshot and update it Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * text in span won't be wrapped Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
This commit is contained in:
parent
a4d3206618
commit
027b082793
|
@ -51,7 +51,8 @@
|
||||||
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"lint:ci": "eslint --quiet \"src/**/*.{ts,tsx}\"",
|
"lint:ci": "eslint --quiet \"src/**/*.{ts,tsx}\"",
|
||||||
"lint": "eslint --fix \"src/**/*.{ts,tsx}\""
|
"lint": "eslint --fix \"src/**/*.{ts,tsx}\"",
|
||||||
|
"snapshot": "react-scripts test --updateSnapshot"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import React, { FC, Fragment } from 'react';
|
import React, { ChangeEvent, FC, Fragment, useEffect, useState } from 'react';
|
||||||
import { Badge } from 'reactstrap';
|
import { Badge, Col, Row } from 'reactstrap';
|
||||||
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
||||||
import Checkbox from '../../components/Checkbox';
|
import Checkbox from '../../components/Checkbox';
|
||||||
import { isPresent } from '../../utils';
|
import { isPresent } from '../../utils';
|
||||||
import { Rule } from '../../types/types';
|
import { Rule } from '../../types/types';
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||||
|
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
|
||||||
|
import { KVSearch } from '@nexucis/kvsearch';
|
||||||
|
import SearchBar from '../../components/SearchBar';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type RuleState = keyof RuleStatus<any>;
|
export type RuleState = keyof RuleStatus<any>;
|
||||||
|
@ -35,13 +38,33 @@ interface RuleGroup {
|
||||||
interval: number;
|
interval: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kvSearchRule = new KVSearch({
|
||||||
|
shouldSort: true,
|
||||||
|
indexedKeys: ['name', 'labels', ['labels', /.*/]],
|
||||||
|
});
|
||||||
|
|
||||||
const stateColorTuples: Array<[RuleState, 'success' | 'warning' | 'danger']> = [
|
const stateColorTuples: Array<[RuleState, 'success' | 'warning' | 'danger']> = [
|
||||||
['inactive', 'success'],
|
['inactive', 'success'],
|
||||||
['pending', 'warning'],
|
['pending', 'warning'],
|
||||||
['firing', 'danger'],
|
['firing', 'danger'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function GroupContent(showAnnotations: boolean) {
|
||||||
|
const Content: FC<InfiniteScrollItemsProps<Rule>> = ({ items }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{items.map((rule, j) => (
|
||||||
|
<CollapsibleAlertPanel key={rule.name + j} showAnnotations={showAnnotations} rule={rule} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return Content;
|
||||||
|
}
|
||||||
|
|
||||||
const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
|
const [groupList, setGroupList] = useState(groups);
|
||||||
|
const [filteredList, setFilteredList] = useState(groups);
|
||||||
const [filter, setFilter] = useLocalStorage('alerts-status-filter', {
|
const [filter, setFilter] = useLocalStorage('alerts-status-filter', {
|
||||||
firing: true,
|
firing: true,
|
||||||
pending: true,
|
pending: true,
|
||||||
|
@ -56,50 +79,80 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||||
|
if (e.target.value !== '') {
|
||||||
|
const pattern = e.target.value.trim();
|
||||||
|
const result: RuleGroup[] = [];
|
||||||
|
for (const group of groups) {
|
||||||
|
const ruleFilterList = kvSearchRule.filter(pattern, group.rules);
|
||||||
|
if (ruleFilterList.length > 0) {
|
||||||
|
result.push({
|
||||||
|
file: group.file,
|
||||||
|
name: group.name,
|
||||||
|
interval: group.interval,
|
||||||
|
rules: ruleFilterList.map((value) => value.original as unknown as Rule),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setGroupList(result);
|
||||||
|
} else {
|
||||||
|
setGroupList(groups);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const result: RuleGroup[] = [];
|
||||||
|
for (const group of groupList) {
|
||||||
|
const newGroup = {
|
||||||
|
file: group.file,
|
||||||
|
name: group.name,
|
||||||
|
interval: group.interval,
|
||||||
|
rules: group.rules.filter((value) => filter[value.state]),
|
||||||
|
};
|
||||||
|
if (newGroup.rules.length > 0) {
|
||||||
|
result.push(newGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFilteredList(result);
|
||||||
|
}, [groupList, filter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="d-flex togglers-wrapper">
|
<Row className="align-items-center">
|
||||||
|
<Col className="d-flex" lg="4" md="5">
|
||||||
{stateColorTuples.map(([state, color]) => {
|
{stateColorTuples.map(([state, color]) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox key={state} checked={filter[state]} id={`${state}-toggler`} onChange={toggleFilter(state)}>
|
||||||
key={state}
|
|
||||||
wrapperStyles={{ marginRight: 20 }}
|
|
||||||
checked={filter[state]}
|
|
||||||
id={`${state}-toggler`}
|
|
||||||
onChange={toggleFilter(state)}
|
|
||||||
>
|
|
||||||
<Badge color={color} className="text-capitalize">
|
<Badge color={color} className="text-capitalize">
|
||||||
{state} ({statsCount[state]})
|
{state} ({statsCount[state]})
|
||||||
</Badge>
|
</Badge>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</Col>
|
||||||
|
<Col lg="5" md="4">
|
||||||
|
<SearchBar handleChange={handleSearchChange} placeholder="Filter by name or labels" />
|
||||||
|
</Col>
|
||||||
|
<Col className="d-flex flex-row-reverse" md="3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
wrapperStyles={{ marginLeft: 'auto' }}
|
|
||||||
checked={showAnnotations.checked}
|
checked={showAnnotations.checked}
|
||||||
id="show-annotations-toggler"
|
id="show-annotations-toggler"
|
||||||
onChange={({ target }) => setShowAnnotations({ checked: target.checked })}
|
onChange={({ target }) => setShowAnnotations({ checked: target.checked })}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: '0.9rem', lineHeight: 1.9 }}>Show annotations</span>
|
<span style={{ fontSize: '0.9rem', lineHeight: 1.9, display: 'inline-block', whiteSpace: 'nowrap' }}>
|
||||||
|
Show annotations
|
||||||
|
</span>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</Col>
|
||||||
{groups.map((group, i) => {
|
</Row>
|
||||||
const hasFilterOn = group.rules.some((rule) => filter[rule.state]);
|
{filteredList.map((group, i) => (
|
||||||
return hasFilterOn ? (
|
|
||||||
<Fragment key={i}>
|
<Fragment key={i}>
|
||||||
<GroupInfo rules={group.rules}>
|
<GroupInfo rules={group.rules}>
|
||||||
{group.file} > {group.name}
|
{group.file} > {group.name}
|
||||||
</GroupInfo>
|
</GroupInfo>
|
||||||
{group.rules.map((rule, j) => {
|
<CustomInfiniteScroll allItems={group.rules} child={GroupContent(showAnnotations.checked)} />
|
||||||
return (
|
|
||||||
filter[rule.state] && (
|
|
||||||
<CollapsibleAlertPanel key={rule.name + j} showAnnotations={showAnnotations.checked} rule={rule} />
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null;
|
))}
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,19 +2,39 @@
|
||||||
|
|
||||||
exports[`AlertsContent matches a snapshot 1`] = `
|
exports[`AlertsContent matches a snapshot 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div
|
<Row
|
||||||
className="d-flex togglers-wrapper"
|
className="align-items-center"
|
||||||
|
tag="div"
|
||||||
|
widths={
|
||||||
|
Array [
|
||||||
|
"xs",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Col
|
||||||
|
className="d-flex"
|
||||||
|
lg="4"
|
||||||
|
md="5"
|
||||||
|
tag="div"
|
||||||
|
widths={
|
||||||
|
Array [
|
||||||
|
"xs",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
]
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Memo(Checkbox)
|
<Memo(Checkbox)
|
||||||
checked={true}
|
checked={true}
|
||||||
id="inactive-toggler"
|
id="inactive-toggler"
|
||||||
key="inactive"
|
key="inactive"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
wrapperStyles={
|
|
||||||
Object {
|
|
||||||
"marginRight": 20,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
className="text-capitalize"
|
className="text-capitalize"
|
||||||
|
@ -33,11 +53,6 @@ exports[`AlertsContent matches a snapshot 1`] = `
|
||||||
id="pending-toggler"
|
id="pending-toggler"
|
||||||
key="pending"
|
key="pending"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
wrapperStyles={
|
|
||||||
Object {
|
|
||||||
"marginRight": 20,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
className="text-capitalize"
|
className="text-capitalize"
|
||||||
|
@ -56,11 +71,6 @@ exports[`AlertsContent matches a snapshot 1`] = `
|
||||||
id="firing-toggler"
|
id="firing-toggler"
|
||||||
key="firing"
|
key="firing"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
wrapperStyles={
|
|
||||||
Object {
|
|
||||||
"marginRight": 20,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
className="text-capitalize"
|
className="text-capitalize"
|
||||||
|
@ -74,27 +84,59 @@ exports[`AlertsContent matches a snapshot 1`] = `
|
||||||
)
|
)
|
||||||
</Badge>
|
</Badge>
|
||||||
</Memo(Checkbox)>
|
</Memo(Checkbox)>
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
lg="5"
|
||||||
|
md="4"
|
||||||
|
tag="div"
|
||||||
|
widths={
|
||||||
|
Array [
|
||||||
|
"xs",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SearchBar
|
||||||
|
handleChange={[Function]}
|
||||||
|
placeholder="Filter by name or labels"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
className="d-flex flex-row-reverse"
|
||||||
|
md="3"
|
||||||
|
tag="div"
|
||||||
|
widths={
|
||||||
|
Array [
|
||||||
|
"xs",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
<Memo(Checkbox)
|
<Memo(Checkbox)
|
||||||
checked={false}
|
checked={false}
|
||||||
id="show-annotations-toggler"
|
id="show-annotations-toggler"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
wrapperStyles={
|
|
||||||
Object {
|
|
||||||
"marginLeft": "auto",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"display": "inline-block",
|
||||||
"fontSize": "0.9rem",
|
"fontSize": "0.9rem",
|
||||||
"lineHeight": 1.9,
|
"lineHeight": 1.9,
|
||||||
|
"whiteSpace": "nowrap",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Show annotations
|
Show annotations
|
||||||
</span>
|
</span>
|
||||||
</Memo(Checkbox)>
|
</Memo(Checkbox)>
|
||||||
</div>
|
</Col>
|
||||||
|
</Row>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export interface QueryParams {
|
||||||
resolution: number;
|
resolution: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Rule {
|
export type Rule = {
|
||||||
alerts: Alert[];
|
alerts: Alert[];
|
||||||
annotations: Record<string, string>;
|
annotations: Record<string, string>;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
@ -29,7 +29,7 @@ export interface Rule {
|
||||||
query: string;
|
query: string;
|
||||||
state: RuleState;
|
state: RuleState;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface WALReplayData {
|
export interface WALReplayData {
|
||||||
min: number;
|
min: number;
|
||||||
|
|
Loading…
Reference in a new issue