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:
Augustin Husson 2022-01-22 10:39:15 +01:00 committed by GitHub
parent a4d3206618
commit 027b082793
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 228 additions and 132 deletions

View File

@ -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,

View File

@ -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} &gt; {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} &gt; {group.name}
</GroupInfo>
<CustomInfiniteScroll allItems={group.rules} child={GroupContent(showAnnotations.checked)} />
</Fragment>
))}
</>
);
};

View File

@ -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>
`;

View File

@ -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;