mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
a8d8987825
commit
6c9772b101
16 changed files with 145 additions and 27 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
|
@ -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%;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-explore-logs-body {
|
||||
position: relative;
|
||||
|
||||
&-header {
|
||||
margin: -$padding-medium 0-$padding-medium 0;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -29,8 +29,18 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__execute {
|
||||
&-execute {
|
||||
position: relative;
|
||||
display: grid;
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
|
||||
&_hidden {
|
||||
position: relative;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-helpful {
|
||||
|
|
|
@ -118,5 +118,6 @@ export const useFetchLogHits = (server: string, query: string) => {
|
|||
isLoading: Object.values(isLoading).some(s => s),
|
||||
error,
|
||||
fetchLogHits,
|
||||
abortController: abortControllerRef.current
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in a new issue