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 <elsenorcito@gmail.com>

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* update prettier corrections

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* update prettier corrections

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* correction suggested fixes PR

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* Update Snap AlertContents.test.tsx and update recommendations in the code

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* Update web/ui/react-app/src/components/SearchBar.tsx

exactly, thanks :)

Co-authored-by: Julius Volz <julius.volz@gmail.com>
Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* Revert "Update web/ui/react-app/src/components/SearchBar.tsx
"

This reverts commit 679c763a02f65297e3f761db372a0928550f288c.

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* update SearchBar component

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* eslint-disable update SearchBar testing failed

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* correction part in eslint-disable

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* fully corrected suggestion with search expression in url

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

* implementation in handleSearchChange with useCallBack

Signed-off-by: AndreSPy1 <elsenorcito@gmail.com>

Co-authored-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
AndreSPy1 2022-04-26 14:20:48 -05:00 committed by GitHub
parent 685493187e
commit d8ca9aa67b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 48 deletions

View file

@ -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<HTMLTextAreaElement | HTMLInputElement>) => void;
handleChange: (e: string) => void;
placeholder: string;
defaultValue: string;
}
const SearchBar: FC<SearchBarProps> = ({ handleChange, placeholder }) => {
const SearchBar: FC<SearchBarProps> = ({ handleChange, placeholder, defaultValue }) => {
let filterTimeout: NodeJS.Timeout;
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
clearTimeout(filterTimeout);
filterTimeout = setTimeout(() => {
handleChange(e);
handleChange(e.target.value);
}, 300);
};
useEffect(() => {
handleChange(defaultValue);
}, [defaultValue, handleChange]);
return (
<InputGroup>
<InputGroupAddon addonType="prepend">
<InputGroupText>{<FontAwesomeIcon icon={faSearch} />}</InputGroupText>
</InputGroupAddon>
<Input autoFocus onChange={handleSearchChange} placeholder={placeholder} />
<Input autoFocus onChange={handleSearchChange} placeholder={placeholder} defaultValue={defaultValue} />
</InputGroup>
);
};

View file

@ -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<AlertsProps> = ({ 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<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),
});
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<AlertsProps> = ({ groups = [], statsCount }) => {
})}
</Col>
<Col lg="5" md="4">
<SearchBar handleChange={handleSearchChange} placeholder="Filter by name or labels" />
<SearchBar defaultValue={defaultValue} handleChange={handleSearchChange} placeholder="Filter by name or labels" />
</Col>
<Col className="d-flex flex-row-reverse" md="3">
<Checkbox

View file

@ -100,6 +100,7 @@ exports[`AlertsContent matches a snapshot 1`] = `
}
>
<SearchBar
defaultValue=""
handleChange={[Function]}
placeholder="Filter by name or labels"
/>

View file

@ -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<ServiceMap> = ({ activeTargets, dropped
const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets));
const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets));
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
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<ServiceMap> = ({ activeTargets, dropped
<>
<h2>Service Discovery</h2>
<Container>
<SearchBar handleChange={handleSearchChange} placeholder="Filter by labels" />
<SearchBar defaultValue={defaultValue} handleChange={handleSearchChange} placeholder="Filter by labels" />
</Container>
<ul>
{mapObjEntries(targetList, ([k, v]) => (

View file

@ -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<ScrapePoolListProps> = ({ activeTargets }) => {
const [expanded, setExpanded] = useLocalStorage('targets-page-expansion-state', initialExpanded);
const { showHealthy, showUnhealthy } = filter;
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
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<ScrapePoolListProps> = ({ activeTargets }) => {
<Filter filter={filter} setFilter={setFilter} expanded={expanded} setExpanded={setExpanded} />
</Col>
<Col xs="6">
<SearchBar handleChange={handleSearchChange} placeholder="Filter by endpoint or labels" />
<SearchBar
defaultValue={defaultValue}
handleChange={handleSearchChange}
placeholder="Filter by endpoint or labels"
/>
</Col>
</Row>
{Object.keys(poolList)

View file

@ -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 = <T, key extends keyof T, Z>(
o: T,
cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z