vmui/logs: fix the update of the relative time range (#6517)

### Describe Your Changes

- Fixed the update of the relative time range when `Execute Query` is
clicked
- Optimized server requests: now, if an error occurs in the `/query`
request, the `/hits` request will not be executed.

#6345 (duplicates: #6440, #6312)

(cherry picked from commit 43342745ac)
This commit is contained in:
Yury Molodov 2024-06-26 11:23:22 +02:00 committed by hagen1778
parent 8249de986e
commit 6bde0196d8
No known key found for this signature in database
GPG Key ID: 3BF75F3741CA9640
6 changed files with 67 additions and 51 deletions

View File

@ -1,4 +1,4 @@
import React, { FC, useEffect } from "preact/compat";
import React, { FC, useCallback, useEffect } from "preact/compat";
import ExploreLogsBody from "./ExploreLogsBody/ExploreLogsBody";
import useStateSearchParams from "../../hooks/useStateSearchParams";
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
@ -8,45 +8,54 @@ import Spinner from "../../components/Main/Spinner/Spinner";
import Alert from "../../components/Main/Alert/Alert";
import ExploreLogsHeader from "./ExploreLogsHeader/ExploreLogsHeader";
import "./style.scss";
import { ErrorTypes } from "../../types";
import { ErrorTypes, TimeParams } from "../../types";
import { useState } from "react";
import { useTimeState } from "../../state/time/TimeStateContext";
import { getFromStorage, saveToStorage } from "../../utils/storage";
import ExploreLogsBarChart from "./ExploreLogsBarChart/ExploreLogsBarChart";
import { useFetchLogHits } from "./hooks/useFetchLogHits";
import { LOGS_ENTRIES_LIMIT } from "../../constants/logs";
import { getTimeperiodForDuration, relativeTimeOptions } from "../../utils/time";
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
const ExploreLogs: FC = () => {
const { serverUrl } = useAppState();
const { duration, relativeTime, period } = useTimeState();
const { duration, relativeTime, period: periodState } = useTimeState();
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
const [query, setQuery] = useStateSearchParams("*", "query");
const [period, setPeriod] = useState<TimeParams>(periodState);
const { logs, isLoading, error, fetchLogs } = useFetchLogs(serverUrl, query, limit);
const { fetchLogHits, ...dataLogHits } = useFetchLogHits(serverUrl, query);
const [queryError, setQueryError] = useState<ErrorTypes | string>("");
const [loaded, isLoaded] = useState(false);
const [markdownParsing, setMarkdownParsing] = useState(getFromStorage("LOGS_MARKDOWN") === "true");
const getPeriod = useCallback(() => {
const relativeTimeOpts = relativeTimeOptions.find(d => d.id === relativeTime);
if (!relativeTimeOpts) return periodState;
const { duration, until } = relativeTimeOpts;
return getTimeperiodForDuration(duration, until());
}, [periodState, relativeTime]);
const handleRunQuery = () => {
if (!query) {
setQueryError(ErrorTypes.validQuery);
return;
}
fetchLogs().then(() => {
isLoaded(true);
});
fetchLogHits();
const newPeriod = getPeriod();
setPeriod(newPeriod);
fetchLogs(newPeriod).then(() => {
fetchLogHits(newPeriod);
}).catch(e => e);
setSearchParamsFromKeys( {
query,
"g0.range_input": duration,
"g0.end_input": period.date,
"g0.end_input": newPeriod.date,
"g0.relative_time": relativeTime || "none",
});
};
@ -64,7 +73,7 @@ const ExploreLogs: FC = () => {
useEffect(() => {
if (query) handleRunQuery();
}, [period]);
}, [periodState]);
useEffect(() => {
setQueryError("");
@ -84,14 +93,15 @@ const ExploreLogs: FC = () => {
/>
{isLoading && <Spinner />}
{error && <Alert variant="error">{error}</Alert>}
<ExploreLogsBarChart
query={query}
loaded={loaded}
{...dataLogHits}
/>
{!error && (
<ExploreLogsBarChart
query={query}
period={period}
{...dataLogHits}
/>
)}
<ExploreLogsBody
data={logs}
loaded={loaded}
markdownParsing={markdownParsing}
/>
</div>

View File

@ -4,22 +4,22 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
import classNames from "classnames";
import { LogHits } from "../../../api/types";
import dayjs from "dayjs";
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
import { useTimeDispatch } from "../../../state/time/TimeStateContext";
import { AlignedData } from "uplot";
import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart";
import Alert from "../../../components/Main/Alert/Alert";
import { TimeParams } from "../../../types";
interface Props {
query: string;
logHits: LogHits[];
period: TimeParams;
error?: string;
isLoading: boolean;
loaded: boolean;
}
const ExploreLogsBarChart: FC<Props> = ({ logHits, error, loaded }) => {
const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error }) => {
const { isMobile } = useDeviceDetect();
const { period } = useTimeState();
const timeDispatch = useTimeDispatch();
const data = useMemo(() => {
@ -56,13 +56,13 @@ const ExploreLogsBarChart: FC<Props> = ({ logHits, error, loaded }) => {
"vm-block_mobile": isMobile,
})}
>
{!error && loaded && noDataMessage && (
{!error && noDataMessage && (
<div className="vm-explore-logs-chart__empty">
<Alert variant="info">{noDataMessage}</Alert>
</div>
)}
{error && loaded && noDataMessage && (
{error && noDataMessage && (
<div className="vm-explore-logs-chart__empty">
<Alert variant="error">{error}</Alert>
</div>

View File

@ -19,7 +19,6 @@ import { marked } from "marked";
export interface ExploreLogBodyProps {
data: Logs[];
loaded?: boolean;
markdownParsing: boolean;
}
@ -35,7 +34,7 @@ const tabs = [
{ label: "JSON", value: DisplayType.json, icon: <CodeIcon/> },
];
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, loaded, markdownParsing }) => {
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, markdownParsing }) => {
const { isMobile } = useDeviceDetect();
const { timezone } = useTimeState();
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
@ -109,11 +108,7 @@ const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, loaded, markdownParsin
"vm-explore-logs-body__table_mobile": isMobile,
})}
>
{!data.length && (
<div className="vm-explore-logs-body__empty">
{loaded ? "No logs found" : "Run query to see logs"}
</div>
)}
{!data.length && <div className="vm-explore-logs-body__empty">No logs found</div>}
{!!data.length && (
<>
{activeTab === DisplayType.table && (

View File

@ -1,13 +1,11 @@
import { useCallback, useMemo, useRef, useState } from "preact/compat";
import { getLogHitsUrl } from "../../../api/logs";
import { ErrorTypes } from "../../../types";
import { ErrorTypes, TimeParams } from "../../../types";
import { LogHits } from "../../../api/types";
import { useTimeState } from "../../../state/time/TimeStateContext";
import dayjs from "dayjs";
import { LOGS_BARS_VIEW } from "../../../constants/logs";
export const useFetchLogHits = (server: string, query: string) => {
const { period } = useTimeState();
const [logHits, setLogHits] = useState<LogHits[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<ErrorTypes | string>();
@ -15,13 +13,14 @@ export const useFetchLogHits = (server: string, query: string) => {
const url = useMemo(() => getLogHitsUrl(server), [server]);
const options = useMemo(() => {
const getOptions = (query: string, period: TimeParams, signal: AbortSignal) => {
const start = dayjs(period.start * 1000);
const end = dayjs(period.end * 1000);
const totalSeconds = end.diff(start, "milliseconds");
const step = Math.ceil(totalSeconds / LOGS_BARS_VIEW) || 1;
return {
signal,
method: "POST",
body: new URLSearchParams({
query: query.trim(),
@ -30,30 +29,34 @@ export const useFetchLogHits = (server: string, query: string) => {
end: end.toISOString(),
})
};
}, [query, period]);
};
const fetchLogHits = useCallback(async () => {
const fetchLogHits = useCallback(async (period: TimeParams) => {
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
const { signal } = abortControllerRef.current;
setIsLoading(true);
setError(undefined);
try {
const response = await fetch(url, { ...options, signal });
const options = getOptions(query, period, signal);
const response = await fetch(url, options);
if (!response.ok || !response.body) {
const text = await response.text();
setError(text);
setLogHits([]);
setIsLoading(false);
return;
return Promise.reject(new Error(text));
}
const data = await response.json();
const hits = data?.hits as LogHits[];
if (!hits) {
setError("Error: No 'hits' field in response");
return;
const error = "Error: No 'hits' field in response";
setError(error);
return Promise.reject(new Error(error));
}
setLogHits(hits);
@ -63,9 +66,11 @@ export const useFetchLogHits = (server: string, query: string) => {
console.error(e);
setLogHits([]);
}
return Promise.reject(e);
} finally {
setIsLoading(false);
}
// setIsLoading(false);
}, [url, options]);
}, [url, query]);
return {
logHits,

View File

@ -1,12 +1,10 @@
import { useCallback, useMemo, useRef, useState } from "preact/compat";
import { getLogsUrl } from "../../../api/logs";
import { ErrorTypes } from "../../../types";
import { ErrorTypes, TimeParams } from "../../../types";
import { Logs } from "../../../api/types";
import { useTimeState } from "../../../state/time/TimeStateContext";
import dayjs from "dayjs";
export const useFetchLogs = (server: string, query: string, limit: number) => {
const { period } = useTimeState();
const [logs, setLogs] = useState<Logs[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<ErrorTypes | string>();
@ -14,7 +12,8 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
const url = useMemo(() => getLogsUrl(server), [server]);
const options = useMemo(() => ({
const getOptions = (query: string, period: TimeParams, limit: number, signal: AbortSignal) => ({
signal,
method: "POST",
headers: {
"Accept": "application/stream+json",
@ -25,7 +24,7 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
start: dayjs(period.start * 1000).tz().toISOString(),
end: dayjs(period.end * 1000).tz().toISOString()
})
}), [query, limit, period]);
});
const parseLineToJSON = (line: string): Logs | null => {
try {
@ -35,22 +34,24 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
}
};
const fetchLogs = useCallback(async () => {
const fetchLogs = useCallback(async (period: TimeParams) => {
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
const { signal } = abortControllerRef.current;
const limit = Number(options.body.get("limit"));
setIsLoading(true);
setError(undefined);
try {
const response = await fetch(url, { ...options, signal });
const options = getOptions(query, period, limit, signal);
const response = await fetch(url, options);
const text = await response.text();
if (!response.ok || !response.body) {
setError(text);
setLogs([]);
setIsLoading(false);
return;
return Promise.reject(new Error(text));
}
const lines = text.split("\n").filter(line => line).slice(0, limit);
@ -62,9 +63,12 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
console.error(e);
setLogs([]);
}
return Promise.reject(e);
} finally {
setIsLoading(false);
}
setIsLoading(false);
}, [url, options]);
}, [url, query, limit]);
return {
logs,

View File

@ -21,6 +21,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
* FEATURE: add `-retention.maxDiskSpaceUsageBytes` command-line flag, which allows limiting disk space usage for [VictoriaLogs data](https://docs.victoriametrics.com/victorialogs/#storage) by automatic dropping the oldest per-day partitions if the storage disk space usage becomes bigger than the `-retention.maxDiskSpaceUsageBytes`. See [these docs](https://docs.victoriametrics.com/victorialogs/#retention-by-disk-space-usage).
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): fix the update of the relative time range when `Execute Query` is clicked. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6345).
## [v0.23.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.23.0-victorialogs)
Released at 2024-06-25