diff --git a/web/ui/react-app/package.json b/web/ui/react-app/package.json index f9ca0139b..153a5cf4b 100644 --- a/web/ui/react-app/package.json +++ b/web/ui/react-app/package.json @@ -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, diff --git a/web/ui/react-app/src/pages/alerts/AlertContents.tsx b/web/ui/react-app/src/pages/alerts/AlertContents.tsx index f96ae7523..64d0c8f67 100644 --- a/web/ui/react-app/src/pages/alerts/AlertContents.tsx +++ b/web/ui/react-app/src/pages/alerts/AlertContents.tsx @@ -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; @@ -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> = ({ items }) => { + return ( + <> + {items.map((rule, j) => ( + + ))} + + ); + }; + return Content; +} + const AlertsContent: FC = ({ 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 = ({ groups = [], statsCount }) => { }); }; + const handleSearchChange = (e: ChangeEvent) => { + 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 ( <> -
- {stateColorTuples.map(([state, color]) => { - return ( - - - {state} ({statsCount[state]}) - - - ); - })} - setShowAnnotations({ checked: target.checked })} - > - Show annotations - -
- {groups.map((group, i) => { - const hasFilterOn = group.rules.some((rule) => filter[rule.state]); - return hasFilterOn ? ( - - - {group.file} > {group.name} - - {group.rules.map((rule, j) => { - return ( - filter[rule.state] && ( - - ) - ); - })} - - ) : null; - })} + + + {stateColorTuples.map(([state, color]) => { + return ( + + + {state} ({statsCount[state]}) + + + ); + })} + + + + + + setShowAnnotations({ checked: target.checked })} + > + + Show annotations + + + + + {filteredList.map((group, i) => ( + + + {group.file} > {group.name} + + + + ))} ); }; diff --git a/web/ui/react-app/src/pages/alerts/__snapshots__/AlertContents.test.tsx.snap b/web/ui/react-app/src/pages/alerts/__snapshots__/AlertContents.test.tsx.snap index ff0b72cf8..856a8acab 100644 --- a/web/ui/react-app/src/pages/alerts/__snapshots__/AlertContents.test.tsx.snap +++ b/web/ui/react-app/src/pages/alerts/__snapshots__/AlertContents.test.tsx.snap @@ -2,99 +2,141 @@ exports[`AlertsContent matches a snapshot 1`] = ` -
- - - inactive - ( - 0 - ) - - - - + inactive + ( + 0 + ) + + + - pending - ( - 0 - ) - - - - + pending + ( + 0 + ) + + + - firing - ( - 0 - ) - - - + firing + ( + 0 + ) + + + + - + + + + - Show annotations - - -
+ > + Show annotations + + + +
`; diff --git a/web/ui/react-app/src/types/types.ts b/web/ui/react-app/src/types/types.ts index 8f42f0836..69da46ce4 100644 --- a/web/ui/react-app/src/types/types.ts +++ b/web/ui/react-app/src/types/types.ts @@ -16,7 +16,7 @@ export interface QueryParams { resolution: number; } -export interface Rule { +export type Rule = { alerts: Alert[]; annotations: Record; duration: number; @@ -29,7 +29,7 @@ export interface Rule { query: string; state: RuleState; type: string; -} +}; export interface WALReplayData { min: number;