mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
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:
parent
685493187e
commit
d8ca9aa67b
|
@ -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 { Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faSearch } from '@fortawesome/free-solid-svg-icons';
|
import { faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export interface SearchBarProps {
|
export interface SearchBarProps {
|
||||||
handleChange: (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
handleChange: (e: string) => void;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
defaultValue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchBar: FC<SearchBarProps> = ({ handleChange, placeholder }) => {
|
const SearchBar: FC<SearchBarProps> = ({ handleChange, placeholder, defaultValue }) => {
|
||||||
let filterTimeout: NodeJS.Timeout;
|
let filterTimeout: NodeJS.Timeout;
|
||||||
|
|
||||||
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||||
clearTimeout(filterTimeout);
|
clearTimeout(filterTimeout);
|
||||||
filterTimeout = setTimeout(() => {
|
filterTimeout = setTimeout(() => {
|
||||||
handleChange(e);
|
handleChange(e.target.value);
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleChange(defaultValue);
|
||||||
|
}, [defaultValue, handleChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupAddon addonType="prepend">
|
<InputGroupAddon addonType="prepend">
|
||||||
<InputGroupText>{<FontAwesomeIcon icon={faSearch} />}</InputGroupText>
|
<InputGroupText>{<FontAwesomeIcon icon={faSearch} />}</InputGroupText>
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
<Input autoFocus onChange={handleSearchChange} placeholder={placeholder} />
|
<Input autoFocus onChange={handleSearchChange} placeholder={placeholder} defaultValue={defaultValue} />
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 { Badge, Col, Row } from 'reactstrap';
|
||||||
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
||||||
import Checkbox from '../../components/Checkbox';
|
import Checkbox from '../../components/Checkbox';
|
||||||
import { isPresent } from '../../utils';
|
import { getQuerySearchFilter, isPresent, setQuerySearchFilter } from '../../utils';
|
||||||
import { Rule } from '../../types/types';
|
import { Rule } from '../../types/types';
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||||
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
|
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
|
||||||
|
@ -71,7 +71,6 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
inactive: true,
|
inactive: true,
|
||||||
});
|
});
|
||||||
const [showAnnotations, setShowAnnotations] = useLocalStorage('alerts-annotations-status', { checked: false });
|
const [showAnnotations, setShowAnnotations] = useLocalStorage('alerts-annotations-status', { checked: false });
|
||||||
|
|
||||||
const toggleFilter = (ruleState: RuleState) => () => {
|
const toggleFilter = (ruleState: RuleState) => () => {
|
||||||
setFilter({
|
setFilter({
|
||||||
...filter,
|
...filter,
|
||||||
|
@ -79,26 +78,32 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleSearchChange = useCallback(
|
||||||
if (e.target.value !== '') {
|
(value: string) => {
|
||||||
const pattern = e.target.value.trim();
|
setQuerySearchFilter(value);
|
||||||
const result: RuleGroup[] = [];
|
if (value !== '') {
|
||||||
for (const group of groups) {
|
const pattern = value.trim();
|
||||||
const ruleFilterList = kvSearchRule.filter(pattern, group.rules);
|
const result: RuleGroup[] = [];
|
||||||
if (ruleFilterList.length > 0) {
|
for (const group of groups) {
|
||||||
result.push({
|
const ruleFilterList = kvSearchRule.filter(pattern, group.rules);
|
||||||
file: group.file,
|
if (ruleFilterList.length > 0) {
|
||||||
name: group.name,
|
result.push({
|
||||||
interval: group.interval,
|
file: group.file,
|
||||||
rules: ruleFilterList.map((value) => value.original),
|
name: group.name,
|
||||||
});
|
interval: group.interval,
|
||||||
|
rules: ruleFilterList.map((value) => value.original),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
setGroupList(result);
|
||||||
|
} else {
|
||||||
|
setGroupList(groups);
|
||||||
}
|
}
|
||||||
setGroupList(result);
|
},
|
||||||
} else {
|
[groups]
|
||||||
setGroupList(groups);
|
);
|
||||||
}
|
|
||||||
};
|
const defaultValue = useMemo(getQuerySearchFilter, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const result: RuleGroup[] = [];
|
const result: RuleGroup[] = [];
|
||||||
|
@ -131,7 +136,7 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
})}
|
})}
|
||||||
</Col>
|
</Col>
|
||||||
<Col lg="5" md="4">
|
<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>
|
||||||
<Col className="d-flex flex-row-reverse" md="3">
|
<Col className="d-flex flex-row-reverse" md="3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
@ -100,6 +100,7 @@ exports[`AlertsContent matches a snapshot 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
|
defaultValue=""
|
||||||
handleChange={[Function]}
|
handleChange={[Function]}
|
||||||
placeholder="Filter by name or labels"
|
placeholder="Filter by name or labels"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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 { useFetch } from '../../hooks/useFetch';
|
||||||
import { LabelsTable } from './LabelsTable';
|
import { LabelsTable } from './LabelsTable';
|
||||||
import { DroppedTarget, Labels, Target } from '../targets/target';
|
import { DroppedTarget, Labels, Target } from '../targets/target';
|
||||||
|
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import { mapObjEntries } from '../../utils';
|
import { setQuerySearchFilter, mapObjEntries, getQuerySearchFilter } from '../../utils';
|
||||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
import { API_PATH } from '../../constants/constants';
|
import { API_PATH } from '../../constants/constants';
|
||||||
import { KVSearch } from '@nexucis/kvsearch';
|
import { KVSearch } from '@nexucis/kvsearch';
|
||||||
|
@ -94,14 +94,20 @@ export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, dropped
|
||||||
const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets));
|
const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets));
|
||||||
const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets));
|
const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets));
|
||||||
|
|
||||||
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleSearchChange = useCallback(
|
||||||
if (e.target.value !== '') {
|
(value: string) => {
|
||||||
const result = kvSearch.filter(e.target.value.trim(), activeTargets);
|
setQuerySearchFilter(value);
|
||||||
setActiveTargetList(result.map((value) => value.original));
|
if (value !== '') {
|
||||||
} else {
|
const result = kvSearch.filter(value.trim(), activeTargets);
|
||||||
setActiveTargetList(activeTargets);
|
setActiveTargetList(result.map((value) => value.original));
|
||||||
}
|
} else {
|
||||||
};
|
setActiveTargetList(activeTargets);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activeTargets]
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultValue = useMemo(getQuerySearchFilter, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTargetList(processSummary(activeTargetList, droppedTargets));
|
setTargetList(processSummary(activeTargetList, droppedTargets));
|
||||||
|
@ -112,7 +118,7 @@ export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, dropped
|
||||||
<>
|
<>
|
||||||
<h2>Service Discovery</h2>
|
<h2>Service Discovery</h2>
|
||||||
<Container>
|
<Container>
|
||||||
<SearchBar handleChange={handleSearchChange} placeholder="Filter by labels" />
|
<SearchBar defaultValue={defaultValue} handleChange={handleSearchChange} placeholder="Filter by labels" />
|
||||||
</Container>
|
</Container>
|
||||||
<ul>
|
<ul>
|
||||||
{mapObjEntries(targetList, ([k, v]) => (
|
{mapObjEntries(targetList, ([k, v]) => (
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useFetch } from '../../hooks/useFetch';
|
||||||
import { API_PATH } from '../../constants/constants';
|
import { API_PATH } from '../../constants/constants';
|
||||||
import { groupTargets, ScrapePool, ScrapePools, Target } from './target';
|
import { groupTargets, ScrapePool, ScrapePools, Target } from './target';
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
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 { Col, Collapse, Row } from 'reactstrap';
|
||||||
import { ScrapePoolContent } from './ScrapePoolContent';
|
import { ScrapePoolContent } from './ScrapePoolContent';
|
||||||
import Filter, { Expanded, FilterData } from './Filter';
|
import Filter, { Expanded, FilterData } from './Filter';
|
||||||
|
@ -12,6 +12,7 @@ import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||||
import styles from './ScrapePoolPanel.module.css';
|
import styles from './ScrapePoolPanel.module.css';
|
||||||
import { ToggleMoreLess } from '../../components/ToggleMoreLess';
|
import { ToggleMoreLess } from '../../components/ToggleMoreLess';
|
||||||
import SearchBar from '../../components/SearchBar';
|
import SearchBar from '../../components/SearchBar';
|
||||||
|
import { setQuerySearchFilter, getQuerySearchFilter } from '../../utils/index';
|
||||||
|
|
||||||
interface ScrapePoolListProps {
|
interface ScrapePoolListProps {
|
||||||
activeTargets: Target[];
|
activeTargets: Target[];
|
||||||
|
@ -72,14 +73,20 @@ const ScrapePoolListContent: FC<ScrapePoolListProps> = ({ activeTargets }) => {
|
||||||
const [expanded, setExpanded] = useLocalStorage('targets-page-expansion-state', initialExpanded);
|
const [expanded, setExpanded] = useLocalStorage('targets-page-expansion-state', initialExpanded);
|
||||||
const { showHealthy, showUnhealthy } = filter;
|
const { showHealthy, showUnhealthy } = filter;
|
||||||
|
|
||||||
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleSearchChange = useCallback(
|
||||||
if (e.target.value !== '') {
|
(value: string) => {
|
||||||
const result = kvSearch.filter(e.target.value.trim(), activeTargets);
|
setQuerySearchFilter(value);
|
||||||
setTargetList(result.map((value) => value.original));
|
if (value !== '') {
|
||||||
} else {
|
const result = kvSearch.filter(value.trim(), activeTargets);
|
||||||
setTargetList(activeTargets);
|
setTargetList(result.map((value) => value.original));
|
||||||
}
|
} else {
|
||||||
};
|
setTargetList(activeTargets);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activeTargets]
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultValue = useMemo(getQuerySearchFilter, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const list = targetList.filter((t) => showHealthy || t.health.toLowerCase() !== 'up');
|
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} />
|
<Filter filter={filter} setFilter={setFilter} expanded={expanded} setExpanded={setExpanded} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="6">
|
<Col xs="6">
|
||||||
<SearchBar handleChange={handleSearchChange} placeholder="Filter by endpoint or labels" />
|
<SearchBar
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
handleChange={handleSearchChange}
|
||||||
|
placeholder="Filter by endpoint or labels"
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{Object.keys(poolList)
|
{Object.keys(poolList)
|
||||||
|
|
|
@ -243,9 +243,21 @@ export const encodePanelOptionsToQueryString = (panels: PanelMeta[]): string =>
|
||||||
return `?${panels.map(toQueryString).join('&')}`;
|
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 => {
|
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.`;
|
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>(
|
export const mapObjEntries = <T, key extends keyof T, Z>(
|
||||||
o: T,
|
o: T,
|
||||||
cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z
|
cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z
|
||||||
|
|
Loading…
Reference in a new issue