vmui: correct url encoding (#2067)

* fix: correct encode multi-line queries

* fix: change autocomplete for correct arrows work

* app/vmselect/vmui: `make vmui-update`

* docs/CHANGELOG.md: document the bugfix for https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2039

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2022-01-18 22:31:46 +03:00 committed by GitHub
parent dcadec65b6
commit 70737ea4ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 42 deletions

View File

@ -1,12 +1,12 @@
{ {
"files": { "files": {
"main.css": "./static/css/main.79ff1ad2.css", "main.css": "./static/css/main.79ff1ad2.css",
"main.js": "./static/js/main.b46d35b9.js", "main.js": "./static/js/main.7d03dd65.js",
"static/js/27.cc1b69f7.chunk.js": "./static/js/27.cc1b69f7.chunk.js", "static/js/27.cc1b69f7.chunk.js": "./static/js/27.cc1b69f7.chunk.js",
"index.html": "./index.html" "index.html": "./index.html"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.79ff1ad2.css", "static/css/main.79ff1ad2.css",
"static/js/main.b46d35b9.js" "static/js/main.7d03dd65.js"
] ]
} }

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.b46d35b9.js"></script><link href="./static/css/main.79ff1ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.7d03dd65.js"></script><link href="./static/css/main.79ff1ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,12 @@
import React, {FC, useEffect, useState} from "preact/compat"; import React, {FC, useEffect, useMemo, useRef, useState} from "preact/compat";
import {KeyboardEvent} from "react"; import {KeyboardEvent} from "react";
import {ErrorTypes} from "../../../../types"; import {ErrorTypes} from "../../../../types";
import Autocomplete from "@mui/material/Autocomplete"; import Popper from "@mui/material/Popper";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import {queryToBreakLine} from "../../../../utils/query-string"; import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import MenuItem from "@mui/material/MenuItem";
import MenuList from "@mui/material/MenuList";
export interface QueryEditorProps { export interface QueryEditorProps {
setHistoryIndex: (step: number, index: number) => void; setHistoryIndex: (step: number, index: number) => void;
@ -28,18 +31,43 @@ const QueryEditor: FC<QueryEditorProps> = ({
queryOptions queryOptions
}) => { }) => {
const [value, setValue] = useState(query);
const [downMetaKeys, setDownMetaKeys] = useState<string[]>([]); const [downMetaKeys, setDownMetaKeys] = useState<string[]>([]);
const [focusField, setFocusField] = useState(false);
const [focusOption, setFocusOption] = useState(-1);
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
const wrapperEl = useRef<HTMLUListElement>(null);
useEffect(() => { const openAutocomplete = useMemo(() => {
setValue(queryToBreakLine(query)); return !(!autocomplete || downMetaKeys.length || query.length < 2 || !focusField);
}, [query]); }, [query, downMetaKeys, autocomplete, focusField]);
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => { const actualOptions = useMemo(() => {
if (e.ctrlKey || e.metaKey) setDownMetaKeys([...downMetaKeys, e.key]); if (!openAutocomplete) return [];
try {
const regexp = new RegExp(String(query), "i");
return queryOptions.filter((item) => regexp.test(item) && item !== query);
} catch (e) {
return [];
}
}, [autocomplete, query, queryOptions]);
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
const {key, ctrlKey, metaKey, shiftKey} = e;
if (ctrlKey || metaKey) setDownMetaKeys([...downMetaKeys, e.key]);
if (key === "ArrowUp" && openAutocomplete && actualOptions.length) {
e.preventDefault();
setFocusOption((prev) => prev === 0 ? 0 : prev - 1);
} else if (key === "ArrowDown" && openAutocomplete && actualOptions.length) {
e.preventDefault();
setFocusOption((prev) => prev >= actualOptions.length - 1 ? actualOptions.length - 1 : prev + 1);
} else if (key === "Enter" && openAutocomplete && actualOptions.length && !shiftKey) {
e.preventDefault();
setQuery(actualOptions[focusOption], index);
}
return true;
}; };
const handleKeyUp = (e: KeyboardEvent<HTMLDivElement>): void => { const handleKeyUp = (e: KeyboardEvent<HTMLDivElement>) => {
const {key, ctrlKey, metaKey} = e; const {key, ctrlKey, metaKey} = e;
if (downMetaKeys.includes(key)) setDownMetaKeys(downMetaKeys.filter(k => k !== key)); if (downMetaKeys.includes(key)) setDownMetaKeys(downMetaKeys.filter(k => k !== key));
const ctrlMetaKey = ctrlKey || metaKey; const ctrlMetaKey = ctrlKey || metaKey;
@ -52,25 +80,36 @@ const QueryEditor: FC<QueryEditorProps> = ({
} }
}; };
return <Autocomplete useEffect(() => {
freeSolo if (!wrapperEl.current) return;
fullWidth const target = wrapperEl.current.childNodes[focusOption] as HTMLElement;
disableClearable if (target?.scrollIntoView) target.scrollIntoView({block: "center"});
options={autocomplete && !downMetaKeys.length ? queryOptions : []} }, [focusOption]);
onChange={(event, value) => setQuery(value, index)}
onKeyDown={handleKeyDown} return <Box ref={autocompleteAnchorEl}>
onKeyUp={handleKeyUp} <TextField
value={value} defaultValue={query}
renderInput={(params) => fullWidth
<TextField label={`Query ${index + 1}`}
{...params} multiline
label={`Query ${index + 1}`} error={!!error}
multiline onFocus={() => setFocusField(true)}
error={!!error} onBlur={() => setFocusField(false)}
onChange={(e) => setQuery(e.target.value, index)} onKeyUp={handleKeyUp}
/> onKeyDown={handleKeyDown}
} onChange={(e) => setQuery(e.target.value, index)}
/>; />
<Popper open={openAutocomplete} anchorEl={autocompleteAnchorEl.current} placement="bottom-start">
<Paper elevation={3} sx={{ maxHeight: 300, overflow: "auto" }}>
<MenuList ref={wrapperEl} dense>
{actualOptions.map((item, i) =>
<MenuItem key={item} sx={{bgcolor: `rgba(0, 0, 0, ${i === focusOption ? 0.12 : 0})`}}>
{item}
</MenuItem>)}
</MenuList>
</Paper>
</Popper>
</Box>;
}; };
export default QueryEditor; export default QueryEditor;

View File

@ -11,7 +11,7 @@ import {
} from "../../utils/time"; } from "../../utils/time";
import {getFromStorage} from "../../utils/storage"; import {getFromStorage} from "../../utils/storage";
import {getDefaultServer} from "../../utils/default-server-url"; import {getDefaultServer} from "../../utils/default-server-url";
import {breakLineToQuery, getQueryArray, getQueryStringValue} from "../../utils/query-string"; import {getQueryArray, getQueryStringValue} from "../../utils/query-string";
import dayjs from "dayjs"; import dayjs from "dayjs";
export interface TimeState { export interface TimeState {
@ -88,7 +88,7 @@ export function reducer(state: AppState, action: Action): AppState {
case "SET_QUERY": case "SET_QUERY":
return { return {
...state, ...state,
query: action.payload.map(q => breakLineToQuery(q)) query: action.payload.map(q => q)
}; };
case "SET_QUERY_HISTORY": case "SET_QUERY_HISTORY":
return { return {

View File

@ -48,7 +48,7 @@ export const setQueryStringValue = (newValue: Record<string, unknown>): void =>
newQsValue.push(`g${i}.${queryKey}=${valueEncoded}`); newQsValue.push(`g${i}.${queryKey}=${valueEncoded}`);
} }
}); });
newQsValue.push(`g${i}.expr=${breakLineToQuery(q)}`); newQsValue.push(`g${i}.expr=${encodeURIComponent(q)}`);
}); });
setQueryStringWithoutPageReload(newQsValue.join("&")); setQueryStringWithoutPageReload(newQsValue.join("&"));
@ -69,7 +69,3 @@ export const getQueryArray = (): string[] => {
return getQueryStringValue(`g${i}.expr`, "") as string; return getQueryStringValue(`g${i}.expr`, "") as string;
}); });
}; };
export const breakLineToQuery = (q: string): string => q.replace(/\n/g, "%20");
export const queryToBreakLine = (q: string): string => q.replace(/%20/g, "\n");

View File

@ -39,7 +39,8 @@ sort: 15
* BUGFIX: fix possible data race when searching for time series matching `{key=~"value|"}` filter over time range covering multipe days. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2032). Thanks to @waldoweng for the provided fix. * BUGFIX: fix possible data race when searching for time series matching `{key=~"value|"}` filter over time range covering multipe days. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2032). Thanks to @waldoweng for the provided fix.
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): do not send staleness markers on graceful shutdown. This follows Prometheus behavior. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2013#issuecomment-1006994079). * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): do not send staleness markers on graceful shutdown. This follows Prometheus behavior. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2013#issuecomment-1006994079).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly set `__address__` label in `dockerswarm_sd_config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2038). Thanks to @ashtuchkin for the fix. * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly set `__address__` label in `dockerswarm_sd_config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2038). Thanks to @ashtuchkin for the fix.
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix incorrect calculations of graph limits on y axis. This could result in incorrect graph rendering in some cases. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2037). * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix incorrect calculations for graph limits on y axis. This could result in incorrect graph rendering in some cases. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2037).
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix handling for multi-line queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2039).
## [v1.71.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.71.0) ## [v1.71.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.71.0)