mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-20 15:16:42 +00:00
vmui: Add button to prettify query (#4694)
* Add button to prettify query Just capitalizes query text for now * Add /prettify-query API handler * Replace UI pretiffier using prettifier API * Add showing server errors Had to pass setQueryErrors from useFetchQuery.ts * Use serverUrl from global AppState * Change icon to AutoAwsome icon + added style change color when button is active * Add sync/await to prettifyQuery function * Doc public function for lint * Minor async fix * Removed extra blank lines * Extract usePrettifyQuery hook * Made more generic style for :active button * Refactor usePrettifyQuery However, prettify errors don't clean up query errors, but should * Add prettyQuery functionality to CHANGELOG.md * Reuse queryErrors * Unhide errors on start --------- Co-authored-by: Tamara <toma.vashchuk@gmail.com>
This commit is contained in:
parent
e9d246f367
commit
7349f18c55
10 changed files with 145 additions and 12 deletions
|
@ -480,6 +480,10 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
|||
expandWithExprsRequests.Inc()
|
||||
prometheus.ExpandWithExprs(w, r)
|
||||
return true
|
||||
case "/prettify-query":
|
||||
prettifyQueryRequests.Inc()
|
||||
prometheus.PrettifyQuery(w, r)
|
||||
return true
|
||||
case "/api/v1/rules", "/rules":
|
||||
rulesRequests.Inc()
|
||||
if len(*vmalertProxyURL) > 0 {
|
||||
|
@ -655,6 +659,7 @@ var (
|
|||
graphiteFunctionDetailsErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/functions/<func_name>"}`)
|
||||
|
||||
expandWithExprsRequests = metrics.NewCounter(`vm_http_requests_total{path="/expand-with-exprs"}`)
|
||||
prettifyQueryRequests = metrics.NewCounter(`vm_http_requests_total{path="/prettify-query"}`)
|
||||
|
||||
vmalertRequests = metrics.NewCounter(`vm_http_requests_total{path="/vmalert"}`)
|
||||
rulesRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/rules"}`)
|
||||
|
|
|
@ -3,6 +3,7 @@ package prometheus
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
"math"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
@ -75,6 +76,24 @@ func ExpandWithExprs(w http.ResponseWriter, r *http.Request) {
|
|||
_ = bw.Flush()
|
||||
}
|
||||
|
||||
// PrettifyQuery implements /prettify-query. Takes a MetricsQL query and returns it formatted.
|
||||
func PrettifyQuery(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.FormValue("query")
|
||||
bw := bufferedwriter.Get(w)
|
||||
defer bufferedwriter.Put(bw)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
httpserver.EnableCORS(w, r)
|
||||
|
||||
prettyQuery, err := metricsql.Prettify(query)
|
||||
if err != nil {
|
||||
fmt.Fprintf(bw, `{"status": "error", "msg": %q}`, err)
|
||||
} else {
|
||||
fmt.Fprintf(bw, `{"status": "success", "query": %q}`, prettyQuery)
|
||||
}
|
||||
|
||||
_ = bw.Flush()
|
||||
}
|
||||
|
||||
// FederateHandler implements /federate . See https://prometheus.io/docs/prometheus/latest/federation/
|
||||
func FederateHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
|
||||
defer federateDuration.UpdateDuration(startTime)
|
||||
|
|
|
@ -45,6 +45,10 @@ $button-radius: 6px;
|
|||
transform: translateZ(-1px);
|
||||
}
|
||||
|
||||
&:active:after {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
span {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
|
|
@ -291,6 +291,17 @@ export const VisibilityOffIcon = () => (
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const Prettify = () => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CopyIcon = () => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from "preact/compat";
|
||||
import { StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/compat";
|
||||
import { getQueryRangeUrl, getQueryUrl } from "../api/query-range";
|
||||
import { useAppState } from "../state/common/StateContext";
|
||||
import { InstantMetricResult, MetricBase, MetricResult, QueryStats } from "../api/types";
|
||||
|
@ -28,6 +28,7 @@ interface FetchQueryReturn {
|
|||
liveData?: InstantMetricResult[],
|
||||
error?: ErrorTypes | string,
|
||||
queryErrors: (ErrorTypes | string)[],
|
||||
setQueryErrors: StateUpdater<string[]>,
|
||||
queryStats: QueryStats[],
|
||||
warning?: string,
|
||||
traces?: Trace[],
|
||||
|
@ -62,12 +63,13 @@ export const useFetchQuery = ({
|
|||
const [liveData, setLiveData] = useState<InstantMetricResult[]>();
|
||||
const [traces, setTraces] = useState<Trace[]>();
|
||||
const [error, setError] = useState<ErrorTypes | string>();
|
||||
const [queryErrors, setQueryErrors] = useState<(ErrorTypes | string)[]>([]);
|
||||
const [queryErrors, setQueryErrors] = useState<string[]>([]);
|
||||
const [queryStats, setQueryStats] = useState<QueryStats[]>([]);
|
||||
const [warning, setWarning] = useState<string>();
|
||||
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
|
||||
const [isHistogram, setIsHistogram] = useState(false);
|
||||
|
||||
|
||||
const fetchData = async ({
|
||||
fetchUrl,
|
||||
fetchQueue,
|
||||
|
@ -193,5 +195,5 @@ export const useFetchQuery = ({
|
|||
setFetchQueue(fetchQueue.filter(f => !f.signal.aborted));
|
||||
}, [fetchQueue]);
|
||||
|
||||
return { fetchUrl, isLoading, graphData, liveData, error, queryErrors, queryStats, warning, traces, isHistogram };
|
||||
return { fetchUrl, isLoading, graphData, liveData, error, queryErrors, setQueryErrors, queryStats, warning, traces, isHistogram };
|
||||
};
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import React, { FC, useState, useEffect } from "preact/compat";
|
||||
import React, { FC, StateUpdater, useEffect, useState } from "preact/compat";
|
||||
import QueryEditor from "../../../components/Configurators/QueryEditor/QueryEditor";
|
||||
import AdditionalSettings from "../../../components/Configurators/AdditionalSettings/AdditionalSettings";
|
||||
import { ErrorTypes } from "../../../types";
|
||||
import usePrevious from "../../../hooks/usePrevious";
|
||||
import { MAX_QUERY_FIELDS } from "../../../constants/graph";
|
||||
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
||||
import { useTimeDispatch } from "../../../state/time/TimeStateContext";
|
||||
import { DeleteIcon, PlayIcon, PlusIcon, VisibilityIcon, VisibilityOffIcon } from "../../../components/Main/Icons";
|
||||
import {
|
||||
DeleteIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
Prettify,
|
||||
VisibilityIcon,
|
||||
VisibilityOffIcon
|
||||
} from "../../../components/Main/Icons";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import "./style.scss";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
|
@ -15,9 +21,12 @@ import { MouseEvent as ReactMouseEvent } from "react";
|
|||
import { arrayEquals } from "../../../utils/array";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import { QueryStats } from "../../../api/types";
|
||||
import { usePrettifyQuery } from "./hooks/usePrettifyQuery";
|
||||
|
||||
export interface QueryConfiguratorProps {
|
||||
errors: (ErrorTypes | string)[];
|
||||
queryErrors: string[];
|
||||
setQueryErrors: StateUpdater<string[]>;
|
||||
setHideError: StateUpdater<boolean>;
|
||||
stats: QueryStats[];
|
||||
queryOptions: string[]
|
||||
onHideQuery: (queries: number[]) => void
|
||||
|
@ -25,12 +34,15 @@ export interface QueryConfiguratorProps {
|
|||
}
|
||||
|
||||
const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
||||
errors,
|
||||
queryErrors,
|
||||
setQueryErrors,
|
||||
setHideError,
|
||||
stats,
|
||||
queryOptions,
|
||||
onHideQuery,
|
||||
onRunQuery
|
||||
}) => {
|
||||
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const { query, queryHistory, autocomplete } = useQueryState();
|
||||
|
@ -41,6 +53,8 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
|||
const [hideQuery, setHideQuery] = useState<number[]>([]);
|
||||
const prevStateQuery = usePrevious(stateQuery) as (undefined | string[]);
|
||||
|
||||
const getPrettifiedQuery = usePrettifyQuery();
|
||||
|
||||
const updateHistory = () => {
|
||||
queryDispatch({
|
||||
type: "SET_QUERY_HISTORY", payload: stateQuery.map((q, i) => {
|
||||
|
@ -106,13 +120,25 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
|||
|
||||
const createHandlerRemoveQuery = (i: number) => () => {
|
||||
handleRemoveQuery(i);
|
||||
setHideQuery(prev => prev.includes(i) ? prev.filter(n => n !== i) : prev.map(n => n > i ? n - 1: n));
|
||||
setHideQuery(prev => prev.includes(i) ? prev.filter(n => n !== i) : prev.map(n => n > i ? n - 1 : n));
|
||||
};
|
||||
|
||||
const createHandlerHideQuery = (i: number) => (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
handleToggleHideQuery(e, i);
|
||||
};
|
||||
|
||||
const handlePrettifyQuery = async (i:number) => {
|
||||
const prettyQuery = await getPrettifiedQuery(stateQuery[i]);
|
||||
setHideError(false);
|
||||
|
||||
handleChangeQuery(prettyQuery.query, i);
|
||||
|
||||
setQueryErrors((qe) => {
|
||||
qe[i] = prettyQuery.error;
|
||||
return [...qe];
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (prevStateQuery && (stateQuery.length < prevStateQuery.length)) {
|
||||
handleRunQuery();
|
||||
|
@ -144,7 +170,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
|||
value={stateQuery[i]}
|
||||
autocomplete={autocomplete}
|
||||
options={queryOptions}
|
||||
error={errors[i]}
|
||||
error={queryErrors[i]}
|
||||
stats={stats[i]}
|
||||
onArrowUp={createHandlerArrow(-1, i)}
|
||||
onArrowDown={createHandlerArrow(1, i)}
|
||||
|
@ -163,6 +189,19 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
|||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={"Prettify query"}>
|
||||
<div className="vm-query-configurator-list-row__button">
|
||||
<Button
|
||||
variant={"text"}
|
||||
color={"gray"}
|
||||
startIcon={<Prettify/>}
|
||||
onClick={async () => await handlePrettifyQuery(i)}
|
||||
className="prettify"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{stateQuery.length > 1 && (
|
||||
<Tooltip title="Remove Query">
|
||||
<div className="vm-query-configurator-list-row__button">
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { useAppState } from "../../../../state/common/StateContext";
|
||||
|
||||
export interface PrettyQuery {
|
||||
query: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export const usePrettifyQuery = () => {
|
||||
const { serverUrl } = useAppState();
|
||||
|
||||
const getPrettifiedQuery = async (query: string): Promise<PrettyQuery> => {
|
||||
try {
|
||||
const oldQuery = encodeURIComponent(query);
|
||||
const fetchUrl = `${serverUrl}/prettify-query?query=${oldQuery}`;
|
||||
|
||||
// {"status": "success", "query": "metrics"}
|
||||
// {"status": "error", "msg": "labelFilterExpr: unexpected token ..."}
|
||||
const response = await fetch(fetchUrl);
|
||||
|
||||
if (response.status != 200) {
|
||||
return {
|
||||
query: query,
|
||||
error: "Error requesting /prettify-query, status: " + response.status,
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data["status"] != "success") {
|
||||
return {
|
||||
query: query,
|
||||
error: String(data.msg) };
|
||||
}
|
||||
|
||||
return {
|
||||
query: String(data.query),
|
||||
error: "" };
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e instanceof Error && e.name !== "AbortError") {
|
||||
return { query: query, error: `${e.name}: ${e.message}` };
|
||||
}
|
||||
return { query: query, error: String(e) };
|
||||
}
|
||||
};
|
||||
|
||||
return getPrettifiedQuery;
|
||||
};
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
&-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
grid-template-columns: 1fr auto auto auto;
|
||||
align-items: center;
|
||||
gap: $padding-small;
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ const CustomPanel: FC = () => {
|
|||
graphData,
|
||||
error,
|
||||
queryErrors,
|
||||
setQueryErrors,
|
||||
queryStats,
|
||||
warning,
|
||||
traces,
|
||||
|
@ -128,7 +129,9 @@ const CustomPanel: FC = () => {
|
|||
})}
|
||||
>
|
||||
<QueryConfigurator
|
||||
errors={!hideError ? queryErrors : []}
|
||||
queryErrors={!hideError ? queryErrors : []}
|
||||
setQueryErrors={setQueryErrors}
|
||||
setHideError={setHideError}
|
||||
stats={queryStats}
|
||||
queryOptions={queryOptions}
|
||||
onHideQuery={handleHideQuery}
|
||||
|
|
|
@ -60,6 +60,7 @@ The v1.93.x line will be supported for at least 12 months since [v1.93.0](https:
|
|||
* FEATURE: [Official Grafana dashboards for VictoriaMetrics](https://grafana.com/orgs/victoriametrics): add panels for absolute Mem and CPU usage by vmalert. See related issue [here](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4627).
|
||||
* FEATURE: [Official Grafana dashboards for VictoriaMetrics](https://grafana.com/orgs/victoriametrics): correctly calculate `Bytes per point` value for single-server and cluster VM dashboards. Before, the calculation mistakenly accounted for the number of entries in indexdb in denominator, which could have shown lower values than expected.
|
||||
* FEATURE: [Alerting rules for VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#alerts): `ConcurrentFlushesHitTheLimit` alerting rule was moved from [single-server](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts.yml) and [cluster](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-cluster.yml) alerts to the [list of "health" alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-health.yml) as it could be related to many VictoriaMetrics components.
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): added Prettify query functionality. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4681)
|
||||
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): return human readable error if opentelemetry has json encoding. Follow-up after [PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2570).
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly validate scheme for `proxy_url` field at the scrape config. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4811) for details.
|
||||
|
|
Loading…
Reference in a new issue