vmui: add the ability to cancel running queries (#7204)

### Describe Your Changes

- Added functionality to cancel running queries on the Explore Logs and
Query pages.
- The loader was changed from a spinner to a top bar within the block.
This still indicates loading, but solves the issue of the spinner
"flickering," especially during graph dragging.

Related issue: #7097


https://github.com/user-attachments/assets/98e59aeb-905b-4b9d-bbb2-688223b22a82

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
This commit is contained in:
Yury Molodov 2024-10-15 14:48:40 +02:00 committed by GitHub
parent a8d8987825
commit 6c9772b101
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 145 additions and 27 deletions

View file

@ -553,3 +553,20 @@ export const SearchIcon = () => (
></path>
</svg>
);
export const SpinnerIcon = () => (
<svg viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"
>
<animateTransform
attributeName="transform"
dur="0.75s"
repeatCount="indefinite"
type="rotate"
values="0 12 12;360 12 12"
/>
</path>
</svg>
);

View file

@ -0,0 +1,13 @@
import React, { FC } from "preact/compat";
import "./style.scss";
const LineLoader: FC = () => {
return (
<div className="vm-line-loader">
<div className="vm-line-loader__background"></div>
<div className="vm-line-loader__line"></div>
</div>
);
};
export default LineLoader;

View file

@ -0,0 +1,39 @@
@use "src/styles/variables" as *;
.vm-line-loader {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
z-index: 2;
overflow: hidden;
&__background {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: $color-text;
opacity: 0.1;
}
&__line {
position: absolute;
width: 10%;
height: 100%;
background-color: $color-primary;
animation: slide 2s infinite linear;
opacity: 0.8;
}
}
@keyframes slide {
0% {
left: 0;
}
100% {
left: 100%;
}
}

View file

@ -34,7 +34,8 @@ interface FetchQueryReturn {
queryStats: QueryStats[],
warning?: string,
traces?: Trace[],
isHistogram: boolean
isHistogram: boolean,
abortFetch: () => void
}
interface FetchDataParams {
@ -160,6 +161,7 @@ export const useFetchQuery = ({
const error = e as Error;
if (error.name === "AbortError") {
// Aborts are expected, don't show an error for them.
setIsLoading(false);
return;
}
const helperText = "Please check your serverURL settings and confirm server availability.";
@ -197,6 +199,13 @@ export const useFetchQuery = ({
},
[serverUrl, period, displayType, customStep, hideQuery]);
const abortFetch = useCallback(() => {
fetchQueue.map(f => f.abort());
setFetchQueue([]);
setGraphData([]);
setLiveData([]);
}, [fetchQueue]);
const [prevUrl, setPrevUrl] = useState<string[]>([]);
useEffect(() => {
@ -238,6 +247,7 @@ export const useFetchQuery = ({
queryStats,
warning,
traces,
isHistogram
isHistogram,
abortFetch,
};
};

View file

@ -10,6 +10,7 @@ import {
PlayIcon,
PlusIcon,
Prettify,
SpinnerIcon,
VisibilityIcon,
VisibilityOffIcon
} from "../../../components/Main/Icons";
@ -30,8 +31,10 @@ export interface QueryConfiguratorProps {
setQueryErrors: Dispatch<SetStateAction<string[]>>;
setHideError: Dispatch<SetStateAction<boolean>>;
stats: QueryStats[];
isLoading?: boolean;
onHideQuery?: (queries: number[]) => void
onRunQuery: () => void;
abortFetch?: () => void;
hideButtons?: {
addQuery?: boolean;
prettify?: boolean;
@ -46,8 +49,10 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
setQueryErrors,
setHideError,
stats,
isLoading,
onHideQuery,
onRunQuery,
abortFetch,
hideButtons
}) => {
@ -84,6 +89,10 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
};
const handleRunQuery = () => {
if (isLoading) {
abortFetch && abortFetch();
return;
}
updateHistory();
queryDispatch({ type: "SET_QUERY", payload: stateQuery });
timeDispatch({ type: "RUN_QUERY" });
@ -271,9 +280,9 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
<Button
variant="contained"
onClick={handleRunQuery}
startIcon={<PlayIcon/>}
startIcon={isLoading ? <SpinnerIcon/> : <PlayIcon/>}
>
{isMobile ? "Execute" : "Execute Query"}
{`${isLoading ? "Cancel" : "Execute"} ${isMobile ? "" : "Query"}`}
</Button>
</div>
</div>

View file

@ -3,7 +3,7 @@ import QueryConfigurator from "./QueryConfigurator/QueryConfigurator";
import { useFetchQuery } from "../../hooks/useFetchQuery";
import { DisplayTypeSwitch } from "./DisplayTypeSwitch";
import { useGraphDispatch, useGraphState } from "../../state/graph/GraphStateContext";
import Spinner from "../../components/Main/Spinner/Spinner";
import LineLoader from "../../components/Main/LineLoader/LineLoader";
import { useCustomPanelState } from "../../state/customPanel/CustomPanelStateContext";
import { useQueryState } from "../../state/query/QueryStateContext";
import { useSetQueryParams } from "./hooks/useSetQueryParams";
@ -45,7 +45,8 @@ const CustomPanel: FC = () => {
queryStats,
warning,
traces,
isHistogram
isHistogram,
abortFetch,
} = useFetchQuery({
visible: true,
customStep,
@ -80,14 +81,15 @@ const CustomPanel: FC = () => {
setQueryErrors={setQueryErrors}
setHideError={setHideError}
stats={queryStats}
isLoading={isLoading}
onHideQuery={handleHideQuery}
onRunQuery={handleRunQuery}
abortFetch={abortFetch}
/>
<CustomPanelTraces
traces={traces}
displayType={displayType}
/>
{isLoading && <Spinner />}
{showError && <Alert variant="error">{error}</Alert>}
{showInstantQueryTip && <Alert variant="info"><InstantQueryTip/></Alert>}
{warning && (
@ -105,6 +107,7 @@ const CustomPanel: FC = () => {
"vm-block_mobile": isMobile,
})}
>
{isLoading && <LineLoader />}
<div
className="vm-custom-panel-body-header"
ref={controlsRef}

View file

@ -4,7 +4,6 @@ import useStateSearchParams from "../../hooks/useStateSearchParams";
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
import { useFetchLogs } from "./hooks/useFetchLogs";
import { useAppState } from "../../state/common/StateContext";
import Spinner from "../../components/Main/Spinner/Spinner";
import Alert from "../../components/Main/Alert/Alert";
import ExploreLogsHeader from "./ExploreLogsHeader/ExploreLogsHeader";
import "./style.scss";
@ -30,7 +29,7 @@ const ExploreLogs: FC = () => {
const [period, setPeriod] = useState<TimeParams>(periodState);
const [queryError, setQueryError] = useState<ErrorTypes | string>("");
const { logs, isLoading, error, fetchLogs } = useFetchLogs(serverUrl, query, limit);
const { logs, isLoading, error, fetchLogs, abortController } = useFetchLogs(serverUrl, query, limit);
const { fetchLogHits, ...dataLogHits } = useFetchLogHits(serverUrl, query);
const getPeriod = useCallback(() => {
@ -70,10 +69,15 @@ const ExploreLogs: FC = () => {
setQuery(prev => `_stream: ${val === "other" ? "{}" : val} AND (${prev})`);
};
const handleUpdateQuery = () => {
const handleUpdateQuery = useCallback(() => {
if (isLoading || dataLogHits.isLoading) {
abortController.abort && abortController.abort();
dataLogHits.abortController.abort && dataLogHits.abortController.abort();
} else {
setQuery(tmpQuery);
handleRunQuery();
};
}
}, [isLoading, dataLogHits.isLoading]);
useEffect(() => {
if (query) handleRunQuery();
@ -93,8 +97,8 @@ const ExploreLogs: FC = () => {
onChange={setTmpQuery}
onChangeLimit={handleChangeLimit}
onRun={handleUpdateQuery}
isLoading={isLoading || dataLogHits.isLoading}
/>
{isLoading && <Spinner message={"Loading logs..."}/>}
{error && <Alert variant="error">{error}</Alert>}
{!error && (
<ExploreLogsBarChart
@ -102,10 +106,12 @@ const ExploreLogs: FC = () => {
query={query}
period={period}
onApplyFilter={handleApplyFilter}
isLoading={isLoading ? false : dataLogHits.isLoading}
/>
)}
<ExploreLogsBody data={logs}/>
<ExploreLogsBody
data={logs}
isLoading={isLoading}
/>
</div>
);
};

View file

@ -9,7 +9,7 @@ import { AlignedData } from "uplot";
import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart";
import Alert from "../../../components/Main/Alert/Alert";
import { TimeParams } from "../../../types";
import Spinner from "../../../components/Main/Spinner/Spinner";
import LineLoader from "../../../components/Main/LineLoader/LineLoader";
interface Props {
query: string;
@ -72,10 +72,7 @@ const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onA
"vm-block_mobile": isMobile,
})}
>
{isLoading && <Spinner
message={"Loading hits stats..."}
containerStyles={{ position: "absolute" }}
/>}
{isLoading && <LineLoader/>}
{!error && noDataMessage && (
<div className="vm-explore-logs-chart__empty">
<Alert variant="info">{noDataMessage}</Alert>

View file

@ -16,9 +16,11 @@ import TableLogs from "./TableLogs";
import GroupLogs from "../GroupLogs/GroupLogs";
import { DATE_TIME_FORMAT } from "../../../constants/date";
import { marked } from "marked";
import LineLoader from "../../../components/Main/LineLoader/LineLoader";
export interface ExploreLogBodyProps {
data: Logs[];
isLoading: boolean;
}
enum DisplayType {
@ -33,7 +35,7 @@ const tabs = [
{ label: "JSON", value: DisplayType.json, icon: <CodeIcon/> },
];
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data }) => {
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, isLoading }) => {
const { isMobile } = useDeviceDetect();
const { timezone } = useTimeState();
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
@ -75,6 +77,7 @@ const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data }) => {
"vm-block_mobile": isMobile,
})}
>
{isLoading && <LineLoader/>}
<div
className={classNames({
"vm-explore-logs-body-header": true,

View file

@ -1,6 +1,8 @@
@use "src/styles/variables" as *;
.vm-explore-logs-body {
position: relative;
&-header {
margin: -$padding-medium 0-$padding-medium 0;

View file

@ -1,5 +1,5 @@
import React, { FC, useEffect, useState } from "preact/compat";
import { InfoIcon, PlayIcon, WikiIcon } from "../../../components/Main/Icons";
import { InfoIcon, PlayIcon, SpinnerIcon, WikiIcon } from "../../../components/Main/Icons";
import "./style.scss";
import classNames from "classnames";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
@ -11,6 +11,7 @@ export interface ExploreLogHeaderProps {
query: string;
limit: number;
error?: string;
isLoading: boolean;
onChange: (val: string) => void;
onChangeLimit: (val: number) => void;
onRun: () => void;
@ -20,6 +21,7 @@ const ExploreLogsHeader: FC<ExploreLogHeaderProps> = ({
query,
limit,
error,
isLoading,
onChange,
onChangeLimit,
onRun,
@ -94,13 +96,16 @@ const ExploreLogsHeader: FC<ExploreLogHeaderProps> = ({
Documentation
</a>
</div>
<div className="vm-explore-logs-header-bottom__execute">
<div className="vm-explore-logs-header-bottom-execute">
<Button
startIcon={<PlayIcon/>}
startIcon={isLoading ? <SpinnerIcon/> : <PlayIcon/>}
onClick={onRun}
fullWidth
>
Execute Query
<span className="vm-explore-logs-header-bottom-execute__text">
{isLoading ? "Cancel Query" : "Execute Query"}
</span>
<span className="vm-explore-logs-header-bottom-execute__text_hidden">Execute Query</span>
</Button>
</div>
</div>

View file

@ -29,8 +29,18 @@
flex-grow: 1;
}
&__execute {
&-execute {
position: relative;
display: grid;
&__text {
position: absolute;
&_hidden {
position: relative;
visibility: hidden;
}
}
}
&-helpful {

View file

@ -118,5 +118,6 @@ export const useFetchLogHits = (server: string, query: string) => {
isLoading: Object.values(isLoading).some(s => s),
error,
fetchLogHits,
abortController: abortControllerRef.current
};
};

View file

@ -81,5 +81,6 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
isLoading: Object.values(isLoading).some(s => s),
error,
fetchLogs,
abortController: abortControllerRef.current
};
};

View file

@ -17,6 +17,7 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
* FEATURE: add support for forced merge. See [these docs](https://docs.victoriametrics.com/victorialogs/#forced-merge).
* FEATURE: skip empty log fields in query results, since they are treated as non-existing fields in [VictoriaLogs data model](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
* FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): add the ability to cancel running queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7097).
* BUGFIX: avoid possible panic when logs for a new day are ingested during execution of concurrent queries.
* BUGFIX: avoid panic at `lib/logstorage.(*blockResultColumn).forEachDictValue()` when [stats with additional filters](https://docs.victoriametrics.com/victorialogs/logsql/#stats-with-additional-filters). The panic has been introduced in [v0.33.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.33.0-victorialogs) in [this commit](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/a350be48b68330ee1a487e1fb09b002d3be45163).

View file

@ -24,6 +24,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): add support of [exponential histograms](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram) ingested via [OpenTelemetry protocol for metrics](https://docs.victoriametrics.com/#sending-data-via-opentelemetry). Such histograms will be automatically converted to [VictoriaMetrics histogram format](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350). See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6354).
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) and [vmagent](https://docs.victoriametrics.com/vmagent/): disable stream processing mode for data [ingested via InfluxDB](https://docs.victoriametrics.com/#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf) HTTP endpoints by default. With this change, the data is processed in batches (see `-influx.maxRequestSize`) and user will get parsing errors immediately as they happen. This also improves users' experience and resiliency against thundering herd problems caused by clients without backoff policies like telegraf. To enable stream mode back, pass HTTP header `Stream-Mode: "1"` with each request. For data sent via TCP and UDP (see `-influxListenAddr`) protocols streaming processing remains enabled.
* FEATURE: [vmgateway](https://docs.victoriametrics.com/vmgateway/): allow parsing `scope` claim parsing in array format. This is useful for cases when identity provider does encode claims in array format.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to cancel running queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7097).
* BUGFIX: [vmgateway](https://docs.victoriametrics.com/vmgateway/): fix possible panic during parsing of a token without `vm_access` claim. This issue was introduced in v1.104.0.