From d8ca9aa67be4c84ea5818f50548aca26889fbef3 Mon Sep 17 00:00:00 2001 From: AndreSPy1 <48663717+AndreSPy1@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:20:48 -0500 Subject: [PATCH] Allow setting the search term on alerts, targets, and discovery pages via the URL (#10613) * changes in files of the expr in alerts, service and targets, Signed-off-by: AndreSPy1 Signed-off-by: AndreSPy1 * update prettier corrections Signed-off-by: AndreSPy1 * update prettier corrections Signed-off-by: AndreSPy1 * correction suggested fixes PR Signed-off-by: AndreSPy1 * Update Snap AlertContents.test.tsx and update recommendations in the code Signed-off-by: AndreSPy1 * Update web/ui/react-app/src/components/SearchBar.tsx exactly, thanks :) Co-authored-by: Julius Volz Signed-off-by: AndreSPy1 * Revert "Update web/ui/react-app/src/components/SearchBar.tsx " This reverts commit 679c763a02f65297e3f761db372a0928550f288c. Signed-off-by: AndreSPy1 * update SearchBar component Signed-off-by: AndreSPy1 * eslint-disable update SearchBar testing failed Signed-off-by: AndreSPy1 * correction part in eslint-disable Signed-off-by: AndreSPy1 * fully corrected suggestion with search expression in url Signed-off-by: AndreSPy1 * implementation in handleSearchChange with useCallBack Signed-off-by: AndreSPy1 Co-authored-by: Julius Volz --- web/ui/react-app/src/components/SearchBar.tsx | 15 ++++-- .../src/pages/alerts/AlertContents.tsx | 49 ++++++++++--------- .../__snapshots__/AlertContents.test.tsx.snap | 1 + .../src/pages/serviceDiscovery/Services.tsx | 28 ++++++----- .../src/pages/targets/ScrapePoolList.tsx | 31 ++++++++---- web/ui/react-app/src/utils/index.ts | 12 +++++ 6 files changed, 88 insertions(+), 48 deletions(-) diff --git a/web/ui/react-app/src/components/SearchBar.tsx b/web/ui/react-app/src/components/SearchBar.tsx index 761b29068..23aa515ad 100644 --- a/web/ui/react-app/src/components/SearchBar.tsx +++ b/web/ui/react-app/src/components/SearchBar.tsx @@ -1,29 +1,34 @@ -import React, { ChangeEvent, FC } from 'react'; +import React, { ChangeEvent, FC, useEffect } from 'react'; import { Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch } from '@fortawesome/free-solid-svg-icons'; export interface SearchBarProps { - handleChange: (e: ChangeEvent) => void; + handleChange: (e: string) => void; placeholder: string; + defaultValue: string; } -const SearchBar: FC = ({ handleChange, placeholder }) => { +const SearchBar: FC = ({ handleChange, placeholder, defaultValue }) => { let filterTimeout: NodeJS.Timeout; const handleSearchChange = (e: ChangeEvent) => { clearTimeout(filterTimeout); filterTimeout = setTimeout(() => { - handleChange(e); + handleChange(e.target.value); }, 300); }; + useEffect(() => { + handleChange(defaultValue); + }, [defaultValue, handleChange]); + return ( {} - + ); }; diff --git a/web/ui/react-app/src/pages/alerts/AlertContents.tsx b/web/ui/react-app/src/pages/alerts/AlertContents.tsx index affc39df9..2837399f7 100644 --- a/web/ui/react-app/src/pages/alerts/AlertContents.tsx +++ b/web/ui/react-app/src/pages/alerts/AlertContents.tsx @@ -1,8 +1,8 @@ -import React, { ChangeEvent, FC, Fragment, useEffect, useState } from 'react'; +import React, { FC, Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import { Badge, Col, Row } from 'reactstrap'; import CollapsibleAlertPanel from './CollapsibleAlertPanel'; import Checkbox from '../../components/Checkbox'; -import { isPresent } from '../../utils'; +import { getQuerySearchFilter, isPresent, setQuerySearchFilter } from '../../utils'; import { Rule } from '../../types/types'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll'; @@ -71,7 +71,6 @@ const AlertsContent: FC = ({ groups = [], statsCount }) => { inactive: true, }); const [showAnnotations, setShowAnnotations] = useLocalStorage('alerts-annotations-status', { checked: false }); - const toggleFilter = (ruleState: RuleState) => () => { setFilter({ ...filter, @@ -79,26 +78,32 @@ 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), - }); + const handleSearchChange = useCallback( + (value: string) => { + setQuerySearchFilter(value); + if (value !== '') { + const pattern = 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), + }); + } } + setGroupList(result); + } else { + setGroupList(groups); } - setGroupList(result); - } else { - setGroupList(groups); - } - }; + }, + [groups] + ); + + const defaultValue = useMemo(getQuerySearchFilter, []); useEffect(() => { const result: RuleGroup[] = []; @@ -131,7 +136,7 @@ const AlertsContent: FC = ({ groups = [], statsCount }) => { })} - + diff --git a/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx b/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx index ea7fd9462..7664be1f5 100644 --- a/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx +++ b/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx @@ -1,10 +1,10 @@ -import React, { ChangeEvent, FC, useEffect, useState } from 'react'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useFetch } from '../../hooks/useFetch'; import { LabelsTable } from './LabelsTable'; import { DroppedTarget, Labels, Target } from '../targets/target'; import { withStatusIndicator } from '../../components/withStatusIndicator'; -import { mapObjEntries } from '../../utils'; +import { setQuerySearchFilter, mapObjEntries, getQuerySearchFilter } from '../../utils'; import { usePathPrefix } from '../../contexts/PathPrefixContext'; import { API_PATH } from '../../constants/constants'; import { KVSearch } from '@nexucis/kvsearch'; @@ -94,14 +94,20 @@ export const ServiceDiscoveryContent: FC = ({ activeTargets, dropped const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets)); const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets)); - const handleSearchChange = (e: ChangeEvent) => { - if (e.target.value !== '') { - const result = kvSearch.filter(e.target.value.trim(), activeTargets); - setActiveTargetList(result.map((value) => value.original)); - } else { - setActiveTargetList(activeTargets); - } - }; + const handleSearchChange = useCallback( + (value: string) => { + setQuerySearchFilter(value); + if (value !== '') { + const result = kvSearch.filter(value.trim(), activeTargets); + setActiveTargetList(result.map((value) => value.original)); + } else { + setActiveTargetList(activeTargets); + } + }, + [activeTargets] + ); + + const defaultValue = useMemo(getQuerySearchFilter, []); useEffect(() => { setTargetList(processSummary(activeTargetList, droppedTargets)); @@ -112,7 +118,7 @@ export const ServiceDiscoveryContent: FC = ({ activeTargets, dropped <>

Service Discovery

- +
    {mapObjEntries(targetList, ([k, v]) => ( diff --git a/web/ui/react-app/src/pages/targets/ScrapePoolList.tsx b/web/ui/react-app/src/pages/targets/ScrapePoolList.tsx index d401f772c..d7078128e 100644 --- a/web/ui/react-app/src/pages/targets/ScrapePoolList.tsx +++ b/web/ui/react-app/src/pages/targets/ScrapePoolList.tsx @@ -4,7 +4,7 @@ import { useFetch } from '../../hooks/useFetch'; import { API_PATH } from '../../constants/constants'; import { groupTargets, ScrapePool, ScrapePools, Target } from './target'; import { withStatusIndicator } from '../../components/withStatusIndicator'; -import { ChangeEvent, FC, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { Col, Collapse, Row } from 'reactstrap'; import { ScrapePoolContent } from './ScrapePoolContent'; import Filter, { Expanded, FilterData } from './Filter'; @@ -12,6 +12,7 @@ import { useLocalStorage } from '../../hooks/useLocalStorage'; import styles from './ScrapePoolPanel.module.css'; import { ToggleMoreLess } from '../../components/ToggleMoreLess'; import SearchBar from '../../components/SearchBar'; +import { setQuerySearchFilter, getQuerySearchFilter } from '../../utils/index'; interface ScrapePoolListProps { activeTargets: Target[]; @@ -72,14 +73,20 @@ const ScrapePoolListContent: FC = ({ activeTargets }) => { const [expanded, setExpanded] = useLocalStorage('targets-page-expansion-state', initialExpanded); const { showHealthy, showUnhealthy } = filter; - const handleSearchChange = (e: ChangeEvent) => { - if (e.target.value !== '') { - const result = kvSearch.filter(e.target.value.trim(), activeTargets); - setTargetList(result.map((value) => value.original)); - } else { - setTargetList(activeTargets); - } - }; + const handleSearchChange = useCallback( + (value: string) => { + setQuerySearchFilter(value); + if (value !== '') { + const result = kvSearch.filter(value.trim(), activeTargets); + setTargetList(result.map((value) => value.original)); + } else { + setTargetList(activeTargets); + } + }, + [activeTargets] + ); + + const defaultValue = useMemo(getQuerySearchFilter, []); useEffect(() => { const list = targetList.filter((t) => showHealthy || t.health.toLowerCase() !== 'up'); @@ -93,7 +100,11 @@ const ScrapePoolListContent: FC = ({ activeTargets }) => { - + {Object.keys(poolList) diff --git a/web/ui/react-app/src/utils/index.ts b/web/ui/react-app/src/utils/index.ts index ca714970f..5e05c25e1 100644 --- a/web/ui/react-app/src/utils/index.ts +++ b/web/ui/react-app/src/utils/index.ts @@ -243,9 +243,21 @@ export const encodePanelOptionsToQueryString = (panels: PanelMeta[]): string => return `?${panels.map(toQueryString).join('&')}`; }; +export const setQuerySearchFilter = (search: string) => { + window.history.pushState({}, '', `?search=${search}`); +}; + +export const getQuerySearchFilter = (): string => { + const locationSearch = window.location.search; + const params = new URLSearchParams(locationSearch); + const search = params.get('search') || ''; + return search; +}; + export const createExpressionLink = (expr: string): string => { return `../graph?g0.expr=${encodeURIComponent(expr)}&g0.tab=1&g0.stacked=0&g0.show_exemplars=0.g0.range_input=1h.`; }; + export const mapObjEntries = ( o: T, cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z