diff --git a/app/vmui/packages/vmui/src/components/Configurators/QueryEditor/QueryEditorAutocomplete.tsx b/app/vmui/packages/vmui/src/components/Configurators/QueryEditor/QueryEditorAutocomplete.tsx index c19934895b..3e47507aa2 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/QueryEditor/QueryEditorAutocomplete.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/QueryEditor/QueryEditorAutocomplete.tsx @@ -34,14 +34,25 @@ const QueryEditorAutocomplete: FC = ({ }, [value, caretPosition]); const exprLastPart = useMemo(() => { - const parts = values.beforeCursor.split("}"); + const regexpSplit = /\s(or|and|unless|default|ifnot|if|group_left|group_right)\s|}|\+|\|-|\*|\/|\^/i; + const parts = values.beforeCursor.split(regexpSplit); return parts[parts.length - 1]; }, [values]); const metric = useMemo(() => { - const regexp = /\b[^{}(),\s]+(?={|$)/g; - const match = exprLastPart.match(regexp); - return match ? match[0] : ""; + const regex1 = /\w+\((?[^)]+)\)\s+(by|without|on|ignoring)\s*\(\w*/gi; + const matchAlt = [...exprLastPart.matchAll(regex1)]; + if (matchAlt.length > 0 && matchAlt[0].groups && matchAlt[0].groups.metricName) { + return matchAlt[0].groups.metricName; + } + + const regex2 = /^\s*\b(?[^{}(),\s]+)(?={|$)/g; + const match = [...exprLastPart.matchAll(regex2)]; + if (match.length > 0 && match[0].groups && match[0].groups.metricName) { + return match[0].groups.metricName; + } + + return ""; }, [exprLastPart]); const label = useMemo(() => { @@ -51,7 +62,7 @@ const QueryEditorAutocomplete: FC = ({ }, [exprLastPart]); const shouldSuppressAutoSuggestion = (value: string) => { - const pattern = /([{(),+\-*/^]|\b(?:or|and|unless|default|ifnot|if|group_left|group_right)\b)/; + const pattern = /([{(),+\-*/^]|\b(?:or|and|unless|default|ifnot|if|group_left|group_right|by|without|on|ignoring)\b)/i; const parts = value.split(/\s+/); const partsCount = parts.length; const lastPart = parts[partsCount - 1]; @@ -63,12 +74,16 @@ const QueryEditorAutocomplete: FC = ({ }; const context = useMemo(() => { - if (!values.beforeCursor || values.beforeCursor.endsWith("}") || shouldSuppressAutoSuggestion(values.beforeCursor)) { + const valueBeforeCursor = values.beforeCursor.trim(); + const endOfClosedBrackets = ["}", ")"].some(char => valueBeforeCursor.endsWith(char)); + const endOfClosedQuotes = !hasUnclosedQuotes(valueBeforeCursor) && ["`", "'", "\""].some(char => valueBeforeCursor.endsWith(char)); + if (!values.beforeCursor || endOfClosedBrackets || endOfClosedQuotes || shouldSuppressAutoSuggestion(values.beforeCursor)) { return QueryContextType.empty; } - const labelRegexp = /\{[^}]*$/; - const labelValueRegexp = new RegExp(`(${escapeRegexp(metric)})?{?.+${escapeRegexp(label)}(=|!=|=~|!~)"?([^"]*)$`, "g"); + const labelRegexp = /(?:by|without|on|ignoring)\s*\(\s*[^)]*$|\{[^}]*$/i; + const patternLabelValue = `(${escapeRegexp(metric)})?{?.+${escapeRegexp(label)}(=|!=|=~|!~)"?([^"]*)$`; + const labelValueRegexp = new RegExp(patternLabelValue, "g"); switch (true) { case labelValueRegexp.test(values.beforeCursor): @@ -81,7 +96,7 @@ const QueryEditorAutocomplete: FC = ({ }, [values, metric, label]); const valueByContext = useMemo(() => { - const wordMatch = values.beforeCursor.match(/([\w_\-.:/]+(?![},]))$/); + const wordMatch = values.beforeCursor.match(/([\w_.:]+(?![},]))$/); return wordMatch ? wordMatch[0] : ""; }, [values.beforeCursor]); @@ -119,9 +134,10 @@ const QueryEditorAutocomplete: FC = ({ // Add quotes around the value if the context is labelValue if (context === QueryContextType.labelValue) { const quote = "\""; - const needsQuote = /(?:=|!=|=~|!~)$/.test(beforeValueByContext); valueAfterCursor = valueAfterCursor.replace(/^[^\s"|},]*/, ""); - insert = `${needsQuote ? quote : ""}${insert}`; + const needsOpenQuote = /(?:=|!=|=~|!~)$/.test(beforeValueByContext); + const needsCloseQuote = valueAfterCursor.trim()[0] !== "\""; + insert = `${needsOpenQuote ? quote : ""}${insert}${needsCloseQuote ? quote : ""}`; } if (context === QueryContextType.label) { diff --git a/app/vmui/packages/vmui/src/hooks/useFetchQueryOptions.tsx b/app/vmui/packages/vmui/src/hooks/useFetchQueryOptions.tsx index 3fe2a614bf..d9a45d31fc 100644 --- a/app/vmui/packages/vmui/src/hooks/useFetchQueryOptions.tsx +++ b/app/vmui/packages/vmui/src/hooks/useFetchQueryOptions.tsx @@ -137,7 +137,7 @@ export const useFetchQueryOptions = ({ valueByContext, metric, label, context }: // fetch labels useEffect(() => { - if (!serverUrl || !metric || context !== QueryContextType.label) { + if (!serverUrl || context !== QueryContextType.label) { return; } setLabels([]); @@ -149,7 +149,7 @@ export const useFetchQueryOptions = ({ valueByContext, metric, label, context }: urlSuffix: "labels", setter: setLabels, type: TypeData.label, - params: getQueryParams({ "match[]": `{__name__="${metricEscaped}"}` }) + params: getQueryParams(metric ? { "match[]": `{__name__="${metricEscaped}"}` } : undefined) }); return () => abortControllerRef.current?.abort(); @@ -157,20 +157,23 @@ export const useFetchQueryOptions = ({ valueByContext, metric, label, context }: // fetch labelValues useEffect(() => { - if (!serverUrl || !metric || !label || context !== QueryContextType.labelValue) { + if (!serverUrl || !label || context !== QueryContextType.labelValue) { return; } setLabelValues([]); const metricEscaped = escapeDoubleQuotes(metric); const valueReEscaped = escapeDoubleQuotes(escapeRegexp(value)); + const matchMetric = metric ? `__name__="${metricEscaped}"` : ""; + const matchLabel = `${label}=~".*${valueReEscaped}.*"`; + const matchValue = [matchMetric, matchLabel].filter(Boolean).join(","); fetchData({ value, urlSuffix: `label/${label}/values`, setter: setLabelValues, type: TypeData.labelValue, - params: getQueryParams({ "match[]": `{__name__="${metricEscaped}", ${label}=~".*${valueReEscaped}.*"}` }) + params: getQueryParams({ "match[]": `{${matchValue}}` }) }); return () => abortControllerRef.current?.abort(); diff --git a/app/vmui/packages/vmui/src/utils/regexp.ts b/app/vmui/packages/vmui/src/utils/regexp.ts index 1322914abe..ed4f17cfa1 100644 --- a/app/vmui/packages/vmui/src/utils/regexp.ts +++ b/app/vmui/packages/vmui/src/utils/regexp.ts @@ -8,6 +8,6 @@ export const escapeDoubleQuotes = (s: string) => { }; export const hasUnclosedQuotes = (str: string) => { - const matches = str.match(/"/g); + const matches = str.match(/["`']/g); return matches ? matches.length % 2 !== 0 : false; }; diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6afaa68995..a6321e51cd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -40,6 +40,8 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/). * BUGFIX: all VictoriaMetrics components: validate files specified via `-tlsKeyFile` and `-tlsCertFile` cmd-line flags on the process start-up. Previously, validation happened on the first connection accepted by HTTP server. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6608) for the details. Thanks to @yincongcyincong for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6621). * BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): properly proxy requests to backend urls ending with `/` if the original request path equals to `/`. Previously the trailing `/` at the backend path was incorrectly removed. For example, if the request to `http://vmauth/` is configured to be proxied to `url_prefix=http://backend/foo/`, then it was proxied to `http://backend/foo`, while it should go to `http://backend/foo/`. * BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): fix `cannot read data after closing the reader` error when proxying HTTP requests without body (aka `GET` requests). The issue has been introduced in [v1.102.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.0) in [this commit](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/7ee57974935a662896f2de40fdf613156630617d). +* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix auto-completion triggers for metric and label names, and disable it after specific characters. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6153) and [these comments](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5866#issuecomment-2065273421). +* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix automatic addition of quotes when inserting label values from autocomplete suggestions. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6260). ## [v1.102.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.0)