mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
introduce codemirror editor for local query to filter targets, service discovery
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
This commit is contained in:
parent
070e409dba
commit
58402a741e
61
web/ui/package-lock.json
generated
61
web/ui/package-lock.json
generated
|
@ -2963,17 +2963,32 @@
|
|||
"@lezer/common": "^0.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nexucis/codemirror-kvsearch": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/codemirror-kvsearch/-/codemirror-kvsearch-0.2.0.tgz",
|
||||
"integrity": "sha512-6amnDba+DhQnYAjz87yj18c2co5rmEDd2XbSsIsMxuV+QRfcVOPeKtsr/pWFrVH5/7lp6cGSaWqPgqYNmjAP9g==",
|
||||
"dependencies": {
|
||||
"@nexucis/kvsearch": "^0.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/autocomplete": "^0.19.8",
|
||||
"@codemirror/language": "^0.19.5",
|
||||
"@codemirror/lint": "^0.19.3",
|
||||
"@codemirror/state": "^0.19.6",
|
||||
"@lezer/common": "^0.15.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nexucis/fuzzy": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.4.0.tgz",
|
||||
"integrity": "sha512-VZVzETBXJjFzTd5RmKR+X8V5uM9PKM2yyb7v/aygMF+1iAnZwxzQtxbFx358SNP23eid4s85TIREHw50p8Zugg=="
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.4.1.tgz",
|
||||
"integrity": "sha512-oe+IW6ELwVGYL3340M+nKIT1exZizOjxdUFlTs36BqzxTENBbynG+cCWr4RNaUQF3bV78NspKwTBpTlnYADrTA=="
|
||||
},
|
||||
"node_modules/@nexucis/kvsearch": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/kvsearch/-/kvsearch-0.7.0.tgz",
|
||||
"integrity": "sha512-Zl1u0wUpgpfY1JmHIKyLuqDdN5iTm/wuLXbBbm//Qck/un9ivGYtePpT1/BjG/2XisBsvHb7EldMAuRQaUahtg==",
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/kvsearch/-/kvsearch-0.8.0.tgz",
|
||||
"integrity": "sha512-83m6AgFNva4+5Zoc3Z5vnk+KZBMqA6cotNIIm9Zejwp5VlhO/Pfy6RTZCB8KKKA3MRSP7EDYBMBwnOfMQKpHyg==",
|
||||
"dependencies": {
|
||||
"@nexucis/fuzzy": "^0.4.0"
|
||||
"@nexucis/fuzzy": "^0.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
|
@ -17331,8 +17346,9 @@
|
|||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.17",
|
||||
"@nexucis/fuzzy": "^0.4.0",
|
||||
"@nexucis/kvsearch": "^0.7.0",
|
||||
"@nexucis/codemirror-kvsearch": "^0.2.0",
|
||||
"@nexucis/fuzzy": "^0.4.1",
|
||||
"@nexucis/kvsearch": "^0.8.0",
|
||||
"bootstrap": "^4.6.1",
|
||||
"codemirror-promql": "0.19.0",
|
||||
"css.escape": "^1.5.1",
|
||||
|
@ -19495,17 +19511,25 @@
|
|||
"@lezer/common": "^0.15.0"
|
||||
}
|
||||
},
|
||||
"@nexucis/codemirror-kvsearch": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/codemirror-kvsearch/-/codemirror-kvsearch-0.2.0.tgz",
|
||||
"integrity": "sha512-6amnDba+DhQnYAjz87yj18c2co5rmEDd2XbSsIsMxuV+QRfcVOPeKtsr/pWFrVH5/7lp6cGSaWqPgqYNmjAP9g==",
|
||||
"requires": {
|
||||
"@nexucis/kvsearch": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"@nexucis/fuzzy": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.4.0.tgz",
|
||||
"integrity": "sha512-VZVzETBXJjFzTd5RmKR+X8V5uM9PKM2yyb7v/aygMF+1iAnZwxzQtxbFx358SNP23eid4s85TIREHw50p8Zugg=="
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.4.1.tgz",
|
||||
"integrity": "sha512-oe+IW6ELwVGYL3340M+nKIT1exZizOjxdUFlTs36BqzxTENBbynG+cCWr4RNaUQF3bV78NspKwTBpTlnYADrTA=="
|
||||
},
|
||||
"@nexucis/kvsearch": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/kvsearch/-/kvsearch-0.7.0.tgz",
|
||||
"integrity": "sha512-Zl1u0wUpgpfY1JmHIKyLuqDdN5iTm/wuLXbBbm//Qck/un9ivGYtePpT1/BjG/2XisBsvHb7EldMAuRQaUahtg==",
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@nexucis/kvsearch/-/kvsearch-0.8.0.tgz",
|
||||
"integrity": "sha512-83m6AgFNva4+5Zoc3Z5vnk+KZBMqA6cotNIIm9Zejwp5VlhO/Pfy6RTZCB8KKKA3MRSP7EDYBMBwnOfMQKpHyg==",
|
||||
"requires": {
|
||||
"@nexucis/fuzzy": "^0.4.0"
|
||||
"@nexucis/fuzzy": "^0.4.1"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
|
@ -23951,8 +23975,9 @@
|
|||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.17",
|
||||
"@nexucis/fuzzy": "^0.4.0",
|
||||
"@nexucis/kvsearch": "^0.7.0",
|
||||
"@nexucis/codemirror-kvsearch": "^0.2.0",
|
||||
"@nexucis/fuzzy": "^0.4.1",
|
||||
"@nexucis/kvsearch": "^0.8.0",
|
||||
"@testing-library/react-hooks": "^7.0.1",
|
||||
"@types/enzyme": "^3.10.10",
|
||||
"@types/flot": "0.0.32",
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.17",
|
||||
"@nexucis/fuzzy": "^0.4.0",
|
||||
"@nexucis/kvsearch": "^0.7.0",
|
||||
"@nexucis/fuzzy": "^0.4.1",
|
||||
"@nexucis/kvsearch": "^0.8.0",
|
||||
"@nexucis/codemirror-kvsearch": "^0.2.0",
|
||||
"bootstrap": "^4.6.1",
|
||||
"codemirror-promql": "0.19.0",
|
||||
"css.escape": "^1.5.1",
|
||||
|
|
|
@ -1,34 +1,110 @@
|
|||
import React, { ChangeEvent, FC, useEffect } from 'react';
|
||||
import { Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||
import React, { FC, useEffect, useRef } from 'react';
|
||||
import { InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { baseTheme, lightTheme, promqlHighlighter } from '../pages/graph/CMTheme';
|
||||
import { EditorView, highlightSpecialChars, keymap, placeholder as placeholderFunc, ViewUpdate } from '@codemirror/view';
|
||||
import { history, historyKeymap } from '@codemirror/history';
|
||||
import { indentOnInput } from '@codemirror/language';
|
||||
import { bracketMatching } from '@codemirror/matchbrackets';
|
||||
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
|
||||
import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
|
||||
import { highlightSelectionMatches } from '@codemirror/search';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { commentKeymap } from '@codemirror/comment';
|
||||
import { lintKeymap } from '@codemirror/lint';
|
||||
import { KVSearchExtension } from '@nexucis/codemirror-kvsearch';
|
||||
|
||||
export interface SearchBarProps {
|
||||
handleChange: (e: string) => void;
|
||||
handleChange: (state: EditorState) => void;
|
||||
placeholder: string;
|
||||
defaultValue: string;
|
||||
objects?: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
const SearchBar: FC<SearchBarProps> = ({ handleChange, placeholder, defaultValue }) => {
|
||||
let filterTimeout: NodeJS.Timeout;
|
||||
|
||||
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||
clearTimeout(filterTimeout);
|
||||
filterTimeout = setTimeout(() => {
|
||||
handleChange(e.target.value);
|
||||
}, 300);
|
||||
};
|
||||
const SearchBar: FC<SearchBarProps> = ({ handleChange, placeholder, defaultValue, objects }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const viewRef = useRef<EditorView | null>(null);
|
||||
const filterTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
handleChange(defaultValue);
|
||||
}, [defaultValue, handleChange]);
|
||||
const kvsearchExtension = new KVSearchExtension(objects);
|
||||
const handleSearchChange = (state: EditorState) => {
|
||||
let filterTimeout = filterTimeoutRef.current;
|
||||
if (filterTimeout !== null) {
|
||||
clearTimeout(filterTimeout);
|
||||
}
|
||||
|
||||
filterTimeout = setTimeout(() => {
|
||||
handleChange(state);
|
||||
}, 300);
|
||||
filterTimeoutRef.current = filterTimeout;
|
||||
};
|
||||
// Create or reconfigure the editor.
|
||||
let view = viewRef.current;
|
||||
if (view === null) {
|
||||
// If the editor does not exist yet, create it.
|
||||
if (!containerRef.current) {
|
||||
throw new Error('expected CodeMirror container element to exist');
|
||||
}
|
||||
const startState = EditorState.create({
|
||||
doc: defaultValue,
|
||||
extensions: [
|
||||
baseTheme,
|
||||
promqlHighlighter,
|
||||
lightTheme,
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
autocompletion(),
|
||||
highlightSelectionMatches(),
|
||||
EditorView.lineWrapping,
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
...commentKeymap,
|
||||
...completionKeymap,
|
||||
...lintKeymap,
|
||||
]),
|
||||
placeholderFunc(placeholder),
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Escape',
|
||||
run: (v: EditorView): boolean => {
|
||||
v.contentDOM.blur();
|
||||
return false;
|
||||
},
|
||||
},
|
||||
]),
|
||||
EditorView.updateListener.of((update: ViewUpdate): void => {
|
||||
handleSearchChange(update.state);
|
||||
}),
|
||||
kvsearchExtension.asExtension(),
|
||||
],
|
||||
});
|
||||
|
||||
view = new EditorView({
|
||||
state: startState,
|
||||
parent: containerRef.current,
|
||||
});
|
||||
viewRef.current = view;
|
||||
|
||||
view.focus();
|
||||
}
|
||||
handleChange(view.state);
|
||||
}, [defaultValue, handleChange, objects, placeholder]);
|
||||
|
||||
return (
|
||||
<InputGroup>
|
||||
<InputGroupAddon addonType="prepend">
|
||||
<InputGroupText>{<FontAwesomeIcon icon={faSearch} />}</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
<Input autoFocus onChange={handleSearchChange} placeholder={placeholder} defaultValue={defaultValue} />
|
||||
<div ref={containerRef} className="cm-expression-input" />
|
||||
</InputGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,8 @@ import { useLocalStorage } from '../../hooks/useLocalStorage';
|
|||
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
|
||||
import { KVSearch } from '@nexucis/kvsearch';
|
||||
import SearchBar from '../../components/SearchBar';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { translate } from '@nexucis/codemirror-kvsearch';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type RuleState = keyof RuleStatus<any>;
|
||||
|
@ -79,13 +81,15 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
|||
};
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(value: string) => {
|
||||
setQuerySearchFilter(value);
|
||||
if (value !== '') {
|
||||
const pattern = value.trim();
|
||||
(state: EditorState) => {
|
||||
const pattern = state.doc.toString().trim();
|
||||
setQuerySearchFilter(pattern);
|
||||
if (pattern !== '') {
|
||||
const query = translate(state);
|
||||
const result: RuleGroup[] = [];
|
||||
for (const group of groups) {
|
||||
const ruleFilterList = kvSearchRule.filter(pattern, group.rules);
|
||||
const ruleFilterList =
|
||||
query !== null ? kvSearchRule.filterWithQuery(query, group.rules) : kvSearchRule.filter(pattern, group.rules);
|
||||
if (ruleFilterList.length > 0) {
|
||||
result.push({
|
||||
file: group.file,
|
||||
|
|
|
@ -10,6 +10,8 @@ import { API_PATH } from '../../constants/constants';
|
|||
import { KVSearch } from '@nexucis/kvsearch';
|
||||
import { Container } from 'reactstrap';
|
||||
import SearchBar from '../../components/SearchBar';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { translate } from '@nexucis/codemirror-kvsearch';
|
||||
|
||||
interface ServiceMap {
|
||||
activeTargets: Target[];
|
||||
|
@ -101,11 +103,19 @@ export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, dropped
|
|||
const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets));
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(value: string) => {
|
||||
setQuerySearchFilter(value);
|
||||
if (value !== '') {
|
||||
const activeTargetResult = activeTargetKVSearch.filter(value.trim(), activeTargets);
|
||||
const droppedTargetResult = droppedTargetKVSearch.filter(value.trim(), droppedTargets);
|
||||
(state: EditorState) => {
|
||||
const pattern = state.doc.toString().trim();
|
||||
setQuerySearchFilter(pattern);
|
||||
if (pattern !== '') {
|
||||
const query = translate(state);
|
||||
const activeTargetResult =
|
||||
query !== null
|
||||
? activeTargetKVSearch.filterWithQuery(query, activeTargets)
|
||||
: activeTargetKVSearch.filter(pattern, activeTargets);
|
||||
const droppedTargetResult =
|
||||
query !== null
|
||||
? droppedTargetKVSearch.filterWithQuery(query, droppedTargets)
|
||||
: droppedTargetKVSearch.filter(pattern, droppedTargets);
|
||||
setActiveTargetList(activeTargetResult.map((value) => value.original));
|
||||
setDroppedTargetList(droppedTargetResult.map((value) => value.original));
|
||||
} else {
|
||||
|
@ -126,7 +136,12 @@ export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, dropped
|
|||
<>
|
||||
<h2>Service Discovery</h2>
|
||||
<Container>
|
||||
<SearchBar defaultValue={defaultValue} handleChange={handleSearchChange} placeholder="Filter by labels" />
|
||||
<SearchBar
|
||||
defaultValue={defaultValue}
|
||||
handleChange={handleSearchChange}
|
||||
placeholder="Filter by labels"
|
||||
objects={activeTargets}
|
||||
/>
|
||||
</Container>
|
||||
<ul>
|
||||
{mapObjEntries(targetList, ([k, v]) => (
|
||||
|
|
|
@ -13,6 +13,8 @@ import styles from './ScrapePoolPanel.module.css';
|
|||
import { ToggleMoreLess } from '../../components/ToggleMoreLess';
|
||||
import SearchBar from '../../components/SearchBar';
|
||||
import { setQuerySearchFilter, getQuerySearchFilter } from '../../utils/index';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { translate } from '@nexucis/codemirror-kvsearch';
|
||||
|
||||
interface ScrapePoolListProps {
|
||||
activeTargets: Target[];
|
||||
|
@ -74,10 +76,13 @@ const ScrapePoolListContent: FC<ScrapePoolListProps> = ({ activeTargets }) => {
|
|||
const { showHealthy, showUnhealthy } = filter;
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(value: string) => {
|
||||
setQuerySearchFilter(value);
|
||||
if (value !== '') {
|
||||
const result = kvSearch.filter(value.trim(), activeTargets);
|
||||
(state: EditorState) => {
|
||||
const pattern = state.doc.toString().trim();
|
||||
setQuerySearchFilter(pattern);
|
||||
if (pattern !== '') {
|
||||
const query = translate(state);
|
||||
const result =
|
||||
query !== null ? kvSearch.filterWithQuery(query, activeTargets) : kvSearch.filter(pattern, activeTargets);
|
||||
setTargetList(result.map((value) => value.original));
|
||||
} else {
|
||||
setTargetList(activeTargets);
|
||||
|
@ -104,6 +109,7 @@ const ScrapePoolListContent: FC<ScrapePoolListProps> = ({ activeTargets }) => {
|
|||
defaultValue={defaultValue}
|
||||
handleChange={handleSearchChange}
|
||||
placeholder="Filter by endpoint or labels"
|
||||
objects={activeTargets}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
Loading…
Reference in a new issue