vmui/logs: add ability to hide hits chart (#7206)

### Describe Your Changes

**Added ability to hide the hits chart**

- Users can now hide or show the hits chart by clicking the "eye" icon
located in the upper-right corner of the chart.
- When the chart is hidden, it will stop sending requests to
`/select/logsql/hits`.
- Upon displaying the chart again, it will automatically refresh. If a
relative time range is set, the chart will update according to the time
period of the logs currently being displayed.

**Hits chart visible:**

![image](https://github.com/user-attachments/assets/577e877b-6417-4b83-8d84-c55e3d39864a)

**Hits chart hidden:**

![image](https://github.com/user-attachments/assets/068b1143-d140-4d72-8d65-663900124f32)

Related issue: #7117

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2024-10-18 02:30:56 +02:00 committed by GitHub
parent 36a86c3aaf
commit 423df09d7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 86 additions and 33 deletions

View file

@ -33,12 +33,15 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
graphStyle: GRAPH_STYLES.LINE_STEPPED,
stacked: false,
fill: false,
hideChart: false,
});
const { xRange, setPlotScale } = usePlotScale({ period, setPeriod });
const { onReadyChart, isPanning } = useReadyChart(setPlotScale);
useZoomChart({ uPlotInst, xRange, setPlotScale });
const isEmptyData = useMemo(() => _data.every(d => d.length === 0), [_data]);
const { data, bands } = useMemo(() => {
return graphOptions.stacked ? stack(_data, () => false) : { data: _data, bands: [] };
}, [graphOptions, _data]);
@ -88,7 +91,13 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
}, [data]);
return (
<div className="vm-bar-hits-chart__wrapper">
<div
className={classNames({
"vm-bar-hits-chart__wrapper": true,
"vm-bar-hits-chart__wrapper_hidden": graphOptions.hideChart
})}
>
{!graphOptions.hideChart && (
<div
className={classNames({
"vm-bar-hits-chart": true,
@ -106,8 +115,9 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
focusDataIdx={focusDataIdx}
/>
</div>
)}
<BarHitsOptions onChange={setGraphOptions}/>
{uPlotInst && (
{uPlotInst && !isEmptyData && !graphOptions.hideChart && (
<BarHitsLegend
uPlotInst={uPlotInst}
onApplyFilter={onApplyFilter}

View file

@ -6,7 +6,7 @@ import useStateSearchParams from "../../../../hooks/useStateSearchParams";
import { useSearchParams } from "react-router-dom";
import Button from "../../../Main/Button/Button";
import classNames from "classnames";
import { SettingsIcon } from "../../../Main/Icons";
import { SettingsIcon, VisibilityIcon, VisibilityOffIcon } from "../../../Main/Icons";
import Tooltip from "../../../Main/Tooltip/Tooltip";
import Popper from "../../../Main/Popper/Popper";
import useBoolean from "../../../../hooks/useBoolean";
@ -27,12 +27,14 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
const [graphStyle, setGraphStyle] = useStateSearchParams(GRAPH_STYLES.LINE_STEPPED, "graph");
const [stacked, setStacked] = useStateSearchParams(false, "stacked");
const [fill, setFill] = useStateSearchParams(false, "fill");
const [hideChart, setHideChart] = useStateSearchParams(false, "hide_chart");
const options: GraphOptions = useMemo(() => ({
graphStyle,
stacked,
fill,
}), [graphStyle, stacked, fill]);
hideChart,
}), [graphStyle, stacked, fill, hideChart]);
const handleChangeGraphStyle = (val: string) => () => {
setGraphStyle(val as GRAPH_STYLES);
@ -52,15 +54,31 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
setSearchParams(searchParams);
};
const toggleHideChart = () => {
setHideChart(prev => {
const newVal = !prev;
newVal ? searchParams.set("hide_chart", "true") : searchParams.delete("hide_chart");
setSearchParams(searchParams);
return newVal;
});
};
useEffect(() => {
onChange(options);
}, [options]);
return (
<div
className="vm-bar-hits-options"
ref={optionsButtonRef}
>
<div className="vm-bar-hits-options">
<Tooltip title={hideChart ? "Show chart and resume hits updates" : "Hide chart and pause hits updates"}>
<Button
variant="text"
color="primary"
startIcon={hideChart ? <VisibilityOffIcon/> : <VisibilityIcon/>}
onClick={toggleHideChart}
ariaLabel="settings"
/>
</Tooltip>
<div ref={optionsButtonRef}>
<Tooltip title="Graph settings">
<Button
variant="text"
@ -70,6 +88,7 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
ariaLabel="settings"
/>
</Tooltip>
</div>
<Popper
open={openOptions}
placement="bottom-right"

View file

@ -1,6 +1,8 @@
@use "src/styles/variables" as *;
.vm-bar-hits-options {
display: flex;
align-items: center;
position: absolute;
top: $padding-small;
right: $padding-small;

View file

@ -10,6 +10,10 @@
flex-direction: column;
width: 100%;
height: 100%;
&_hidden {
min-height: 90px;
}
}
&_panning {

View file

@ -9,4 +9,5 @@ export interface GraphOptions {
graphStyle: GRAPH_STYLES;
stacked: boolean;
fill: boolean;
hideChart: boolean;
}

View file

@ -1,4 +1,4 @@
import React, { FC, useCallback, useEffect, useState } from "preact/compat";
import React, { FC, useCallback, useEffect, useMemo, useState } from "preact/compat";
import ExploreLogsBody from "./ExploreLogsBody/ExploreLogsBody";
import useStateSearchParams from "../../hooks/useStateSearchParams";
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
@ -14,6 +14,7 @@ import ExploreLogsBarChart from "./ExploreLogsBarChart/ExploreLogsBarChart";
import { useFetchLogHits } from "./hooks/useFetchLogHits";
import { LOGS_ENTRIES_LIMIT } from "../../constants/logs";
import { getTimeperiodForDuration, relativeTimeOptions } from "../../utils/time";
import { useSearchParams } from "react-router-dom";
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
@ -22,6 +23,8 @@ const ExploreLogs: FC = () => {
const { serverUrl } = useAppState();
const { duration, relativeTime, period: periodState } = useTimeState();
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
const [searchParams] = useSearchParams();
const hideChart = useMemo(() => searchParams.get("hide_chart"), [searchParams]);
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
const [query, setQuery] = useStateSearchParams("*", "query");
@ -49,7 +52,7 @@ const ExploreLogs: FC = () => {
const newPeriod = getPeriod();
setPeriod(newPeriod);
fetchLogs(newPeriod).then((isSuccess) => {
isSuccess && fetchLogHits(newPeriod);
isSuccess && !hideChart && fetchLogHits(newPeriod);
}).catch(e => e);
setSearchParamsFromKeys( {
query,
@ -88,6 +91,10 @@ const ExploreLogs: FC = () => {
setTmpQuery(query);
}, [query]);
useEffect(() => {
!hideChart && fetchLogHits(period);
}, [hideChart]);
return (
<div className="vm-explore-logs">
<ExploreLogsHeader

View file

@ -10,6 +10,7 @@ import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart";
import Alert from "../../../components/Main/Alert/Alert";
import { TimeParams } from "../../../types";
import LineLoader from "../../../components/Main/LineLoader/LineLoader";
import { useSearchParams } from "react-router-dom";
import { getHitsTimeParams } from "../../../utils/logs";
interface Props {
@ -24,6 +25,8 @@ interface Props {
const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onApplyFilter }) => {
const { isMobile } = useDeviceDetect();
const timeDispatch = useTimeDispatch();
const [searchParams] = useSearchParams();
const hideChart = useMemo(() => searchParams.get("hide_chart"), [searchParams]);
const getYAxes = (logHits: LogHits[], timestamps: number[]) => {
return logHits.map(hits => {
@ -69,14 +72,16 @@ const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onA
const noData = data.every(d => d.length === 0);
const noTimestamps = data[0].length === 0;
const noValues = data[1].length === 0;
if (noData) {
if (hideChart) {
return "Chart hidden. Hits updates paused.";
} else if (noData) {
return "No logs volume available\nNo volume information available for the current queries and time range.";
} else if (noTimestamps) {
return "No timestamp information available for the current queries and time range.";
} else if (noValues) {
return "No value information available for the current queries and time range.";
} return "";
}, [data]);
}, [data, hideChart]);
const setPeriod = ({ from, to }: {from: Date, to: Date}) => {
timeDispatch({ type: "SET_PERIOD", payload: { from, to } });

View file

@ -13,8 +13,12 @@
}
&__empty {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
transform: translateY(-25px);
top: 0;
bottom: 0;
z-index: 2;
}
}

View file

@ -7,7 +7,6 @@
display: flex;
align-items: center;
justify-content: flex-end;
gap: $padding-global;
&-keys {
max-height: 300px;

View file

@ -15,6 +15,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
* FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): add ability to hide hits chart. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7117).
* FEATURE: add basic [alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-vlogs.yml) for VictoriaLogs process. See details at [monitoring docs](https://docs.victoriametrics.com/victorialogs/#monitoring).
* FEATURE: improve [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe) performance on systems with many CPU cores when `by(...)` fields contain big number of unique values. For example, `_time:1d | stats by (user_id) count() x` should be executed much faster when `user_id` field contains millions of unique values.
* FEATURE: improve performance for [`top`](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe), [`uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#uniq-pipe) and [`field_values`](https://docs.victoriametrics.com/victorialogs/logsql/#field_values-pipe) pipes on systems with many CPU cores when it is applied to [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) with big number of unique values. For example, `_time:1d | top 5 (user_id)` should be executed much faster when `user_id` field contains millions of unique values.