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",
|
||||
"eject": "react-scripts eject",
|
||||
"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": {
|
||||
"singleQuote": true,
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React, { FC, Fragment } from 'react';
|
||||
import { Badge } from 'reactstrap';
|
||||
import React, { ChangeEvent, FC, Fragment, useEffect, useState } from 'react';
|
||||
import { Badge, Col, Row } from 'reactstrap';
|
||||
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
||||
import Checkbox from '../../components/Checkbox';
|
||||
import { isPresent } from '../../utils';
|
||||
import { Rule } from '../../types/types';
|
||||
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
|
||||
export type RuleState = keyof RuleStatus<any>;
|
||||
|
@ -35,13 +38,33 @@ interface RuleGroup {
|
|||
interval: number;
|
||||
}
|
||||
|
||||
const kvSearchRule = new KVSearch({
|
||||
shouldSort: true,
|
||||
indexedKeys: ['name', 'labels', ['labels', /.*/]],
|
||||
});
|
||||
|
||||
const stateColorTuples: Array<[RuleState, 'success' | 'warning' | 'danger']> = [
|
||||
['inactive', 'success'],
|
||||
['pending', 'warning'],
|
||||
['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 [groupList, setGroupList] = useState(groups);
|
||||
const [filteredList, setFilteredList] = useState(groups);
|
||||
const [filter, setFilter] = useLocalStorage('alerts-status-filter', {
|
||||
firing: 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 (
|
||||
<>
|
||||
<div className="d-flex togglers-wrapper">
|
||||
{stateColorTuples.map(([state, color]) => {
|
||||
return (
|
||||
<Checkbox
|
||||
key={state}
|
||||
wrapperStyles={{ marginRight: 20 }}
|
||||
checked={filter[state]}
|
||||
id={`${state}-toggler`}
|
||||
onChange={toggleFilter(state)}
|
||||
>
|
||||
<Badge color={color} className="text-capitalize">
|
||||
{state} ({statsCount[state]})
|
||||
</Badge>
|
||||
</Checkbox>
|
||||
);
|
||||
})}
|
||||
<Checkbox
|
||||
wrapperStyles={{ marginLeft: 'auto' }}
|
||||
checked={showAnnotations.checked}
|
||||
id="show-annotations-toggler"
|
||||
onChange={({ target }) => setShowAnnotations({ checked: target.checked })}
|
||||
>
|
||||
<span style={{ fontSize: '0.9rem', lineHeight: 1.9 }}>Show annotations</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
{groups.map((group, i) => {
|
||||
const hasFilterOn = group.rules.some((rule) => filter[rule.state]);
|
||||
return hasFilterOn ? (
|
||||
<Fragment key={i}>
|
||||
<GroupInfo rules={group.rules}>
|
||||
{group.file} > {group.name}
|
||||
</GroupInfo>
|
||||
{group.rules.map((rule, j) => {
|
||||
return (
|
||||
filter[rule.state] && (
|
||||
<CollapsibleAlertPanel key={rule.name + j} showAnnotations={showAnnotations.checked} rule={rule} />
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
) : null;
|
||||
})}
|
||||
<Row className="align-items-center">
|
||||
<Col className="d-flex" lg="4" md="5">
|
||||
{stateColorTuples.map(([state, color]) => {
|
||||
return (
|
||||
<Checkbox key={state} checked={filter[state]} id={`${state}-toggler`} onChange={toggleFilter(state)}>
|
||||
<Badge color={color} className="text-capitalize">
|
||||
{state} ({statsCount[state]})
|
||||
</Badge>
|
||||
</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
|
||||
checked={showAnnotations.checked}
|
||||
id="show-annotations-toggler"
|
||||
onChange={({ target }) => setShowAnnotations({ checked: target.checked })}
|
||||
>
|
||||
<span style={{ fontSize: '0.9rem', lineHeight: 1.9, display: 'inline-block', whiteSpace: 'nowrap' }}>
|
||||
Show annotations
|
||||
</span>
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
{filteredList.map((group, i) => (
|
||||
<Fragment key={i}>
|
||||
<GroupInfo rules={group.rules}>
|
||||
{group.file} > {group.name}
|
||||
</GroupInfo>
|
||||
<CustomInfiniteScroll allItems={group.rules} child={GroupContent(showAnnotations.checked)} />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,99 +2,141 @@
|
|||
|
||||
exports[`AlertsContent matches a snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="d-flex togglers-wrapper"
|
||||
<Row
|
||||
className="align-items-center"
|
||||
tag="div"
|
||||
widths={
|
||||
Array [
|
||||
"xs",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
]
|
||||
}
|
||||
>
|
||||
<Memo(Checkbox)
|
||||
checked={true}
|
||||
id="inactive-toggler"
|
||||
key="inactive"
|
||||
onChange={[Function]}
|
||||
wrapperStyles={
|
||||
Object {
|
||||
"marginRight": 20,
|
||||
}
|
||||
<Col
|
||||
className="d-flex"
|
||||
lg="4"
|
||||
md="5"
|
||||
tag="div"
|
||||
widths={
|
||||
Array [
|
||||
"xs",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
]
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
className="text-capitalize"
|
||||
color="success"
|
||||
pill={false}
|
||||
tag="span"
|
||||
<Memo(Checkbox)
|
||||
checked={true}
|
||||
id="inactive-toggler"
|
||||
key="inactive"
|
||||
onChange={[Function]}
|
||||
>
|
||||
inactive
|
||||
(
|
||||
0
|
||||
)
|
||||
</Badge>
|
||||
</Memo(Checkbox)>
|
||||
<Memo(Checkbox)
|
||||
checked={true}
|
||||
id="pending-toggler"
|
||||
key="pending"
|
||||
onChange={[Function]}
|
||||
wrapperStyles={
|
||||
Object {
|
||||
"marginRight": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
className="text-capitalize"
|
||||
color="warning"
|
||||
pill={false}
|
||||
tag="span"
|
||||
<Badge
|
||||
className="text-capitalize"
|
||||
color="success"
|
||||
pill={false}
|
||||
tag="span"
|
||||
>
|
||||
inactive
|
||||
(
|
||||
0
|
||||
)
|
||||
</Badge>
|
||||
</Memo(Checkbox)>
|
||||
<Memo(Checkbox)
|
||||
checked={true}
|
||||
id="pending-toggler"
|
||||
key="pending"
|
||||
onChange={[Function]}
|
||||
>
|
||||
pending
|
||||
(
|
||||
0
|
||||
)
|
||||
</Badge>
|
||||
</Memo(Checkbox)>
|
||||
<Memo(Checkbox)
|
||||
checked={true}
|
||||
id="firing-toggler"
|
||||
key="firing"
|
||||
onChange={[Function]}
|
||||
wrapperStyles={
|
||||
Object {
|
||||
"marginRight": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
className="text-capitalize"
|
||||
color="danger"
|
||||
pill={false}
|
||||
tag="span"
|
||||
<Badge
|
||||
className="text-capitalize"
|
||||
color="warning"
|
||||
pill={false}
|
||||
tag="span"
|
||||
>
|
||||
pending
|
||||
(
|
||||
0
|
||||
)
|
||||
</Badge>
|
||||
</Memo(Checkbox)>
|
||||
<Memo(Checkbox)
|
||||
checked={true}
|
||||
id="firing-toggler"
|
||||
key="firing"
|
||||
onChange={[Function]}
|
||||
>
|
||||
firing
|
||||
(
|
||||
0
|
||||
)
|
||||
</Badge>
|
||||
</Memo(Checkbox)>
|
||||
<Memo(Checkbox)
|
||||
checked={false}
|
||||
id="show-annotations-toggler"
|
||||
onChange={[Function]}
|
||||
wrapperStyles={
|
||||
Object {
|
||||
"marginLeft": "auto",
|
||||
}
|
||||
<Badge
|
||||
className="text-capitalize"
|
||||
color="danger"
|
||||
pill={false}
|
||||
tag="span"
|
||||
>
|
||||
firing
|
||||
(
|
||||
0
|
||||
)
|
||||
</Badge>
|
||||
</Memo(Checkbox)>
|
||||
</Col>
|
||||
<Col
|
||||
lg="5"
|
||||
md="4"
|
||||
tag="div"
|
||||
widths={
|
||||
Array [
|
||||
"xs",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
]
|
||||
}
|
||||
>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "0.9rem",
|
||||
"lineHeight": 1.9,
|
||||
<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)
|
||||
checked={false}
|
||||
id="show-annotations-toggler"
|
||||
onChange={[Function]}
|
||||
>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"display": "inline-block",
|
||||
"fontSize": "0.9rem",
|
||||
"lineHeight": 1.9,
|
||||
"whiteSpace": "nowrap",
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
Show annotations
|
||||
</span>
|
||||
</Memo(Checkbox)>
|
||||
</div>
|
||||
>
|
||||
Show annotations
|
||||
</span>
|
||||
</Memo(Checkbox)>
|
||||
</Col>
|
||||
</Row>
|
||||
</Fragment>
|
||||
`;
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface QueryParams {
|
|||
resolution: number;
|
||||
}
|
||||
|
||||
export interface Rule {
|
||||
export type Rule = {
|
||||
alerts: Alert[];
|
||||
annotations: Record<string, string>;
|
||||
duration: number;
|
||||
|
@ -29,7 +29,7 @@ export interface Rule {
|
|||
query: string;
|
||||
state: RuleState;
|
||||
type: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface WALReplayData {
|
||||
min: number;
|
||||
|
|
Loading…
Reference in New Issue