mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
36a86c3aaf
commit
423df09d7d
10 changed files with 86 additions and 33 deletions
|
@ -33,12 +33,15 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
|
||||||
graphStyle: GRAPH_STYLES.LINE_STEPPED,
|
graphStyle: GRAPH_STYLES.LINE_STEPPED,
|
||||||
stacked: false,
|
stacked: false,
|
||||||
fill: false,
|
fill: false,
|
||||||
|
hideChart: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { xRange, setPlotScale } = usePlotScale({ period, setPeriod });
|
const { xRange, setPlotScale } = usePlotScale({ period, setPeriod });
|
||||||
const { onReadyChart, isPanning } = useReadyChart(setPlotScale);
|
const { onReadyChart, isPanning } = useReadyChart(setPlotScale);
|
||||||
useZoomChart({ uPlotInst, xRange, setPlotScale });
|
useZoomChart({ uPlotInst, xRange, setPlotScale });
|
||||||
|
|
||||||
|
const isEmptyData = useMemo(() => _data.every(d => d.length === 0), [_data]);
|
||||||
|
|
||||||
const { data, bands } = useMemo(() => {
|
const { data, bands } = useMemo(() => {
|
||||||
return graphOptions.stacked ? stack(_data, () => false) : { data: _data, bands: [] };
|
return graphOptions.stacked ? stack(_data, () => false) : { data: _data, bands: [] };
|
||||||
}, [graphOptions, _data]);
|
}, [graphOptions, _data]);
|
||||||
|
@ -88,26 +91,33 @@ const BarHitsChart: FC<Props> = ({ logHits, data: _data, period, setPeriod, onAp
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-bar-hits-chart__wrapper">
|
<div
|
||||||
<div
|
className={classNames({
|
||||||
className={classNames({
|
"vm-bar-hits-chart__wrapper": true,
|
||||||
"vm-bar-hits-chart": true,
|
"vm-bar-hits-chart__wrapper_hidden": graphOptions.hideChart
|
||||||
"vm-bar-hits-chart_panning": isPanning
|
})}
|
||||||
})}
|
>
|
||||||
ref={containerRef}
|
{!graphOptions.hideChart && (
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="vm-line-chart__u-plot"
|
className={classNames({
|
||||||
ref={uPlotRef}
|
"vm-bar-hits-chart": true,
|
||||||
/>
|
"vm-bar-hits-chart_panning": isPanning
|
||||||
<BarHitsTooltip
|
})}
|
||||||
uPlotInst={uPlotInst}
|
ref={containerRef}
|
||||||
data={_data}
|
>
|
||||||
focusDataIdx={focusDataIdx}
|
<div
|
||||||
/>
|
className="vm-line-chart__u-plot"
|
||||||
</div>
|
ref={uPlotRef}
|
||||||
|
/>
|
||||||
|
<BarHitsTooltip
|
||||||
|
uPlotInst={uPlotInst}
|
||||||
|
data={_data}
|
||||||
|
focusDataIdx={focusDataIdx}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<BarHitsOptions onChange={setGraphOptions}/>
|
<BarHitsOptions onChange={setGraphOptions}/>
|
||||||
{uPlotInst && (
|
{uPlotInst && !isEmptyData && !graphOptions.hideChart && (
|
||||||
<BarHitsLegend
|
<BarHitsLegend
|
||||||
uPlotInst={uPlotInst}
|
uPlotInst={uPlotInst}
|
||||||
onApplyFilter={onApplyFilter}
|
onApplyFilter={onApplyFilter}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import useStateSearchParams from "../../../../hooks/useStateSearchParams";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import Button from "../../../Main/Button/Button";
|
import Button from "../../../Main/Button/Button";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { SettingsIcon } from "../../../Main/Icons";
|
import { SettingsIcon, VisibilityIcon, VisibilityOffIcon } from "../../../Main/Icons";
|
||||||
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
||||||
import Popper from "../../../Main/Popper/Popper";
|
import Popper from "../../../Main/Popper/Popper";
|
||||||
import useBoolean from "../../../../hooks/useBoolean";
|
import useBoolean from "../../../../hooks/useBoolean";
|
||||||
|
@ -27,12 +27,14 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
|
||||||
const [graphStyle, setGraphStyle] = useStateSearchParams(GRAPH_STYLES.LINE_STEPPED, "graph");
|
const [graphStyle, setGraphStyle] = useStateSearchParams(GRAPH_STYLES.LINE_STEPPED, "graph");
|
||||||
const [stacked, setStacked] = useStateSearchParams(false, "stacked");
|
const [stacked, setStacked] = useStateSearchParams(false, "stacked");
|
||||||
const [fill, setFill] = useStateSearchParams(false, "fill");
|
const [fill, setFill] = useStateSearchParams(false, "fill");
|
||||||
|
const [hideChart, setHideChart] = useStateSearchParams(false, "hide_chart");
|
||||||
|
|
||||||
const options: GraphOptions = useMemo(() => ({
|
const options: GraphOptions = useMemo(() => ({
|
||||||
graphStyle,
|
graphStyle,
|
||||||
stacked,
|
stacked,
|
||||||
fill,
|
fill,
|
||||||
}), [graphStyle, stacked, fill]);
|
hideChart,
|
||||||
|
}), [graphStyle, stacked, fill, hideChart]);
|
||||||
|
|
||||||
const handleChangeGraphStyle = (val: string) => () => {
|
const handleChangeGraphStyle = (val: string) => () => {
|
||||||
setGraphStyle(val as GRAPH_STYLES);
|
setGraphStyle(val as GRAPH_STYLES);
|
||||||
|
@ -52,24 +54,41 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleHideChart = () => {
|
||||||
|
setHideChart(prev => {
|
||||||
|
const newVal = !prev;
|
||||||
|
newVal ? searchParams.set("hide_chart", "true") : searchParams.delete("hide_chart");
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
return newVal;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange(options);
|
onChange(options);
|
||||||
}, [options]);
|
}, [options]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="vm-bar-hits-options">
|
||||||
className="vm-bar-hits-options"
|
<Tooltip title={hideChart ? "Show chart and resume hits updates" : "Hide chart and pause hits updates"}>
|
||||||
ref={optionsButtonRef}
|
|
||||||
>
|
|
||||||
<Tooltip title="Graph settings">
|
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={<SettingsIcon/>}
|
startIcon={hideChart ? <VisibilityOffIcon/> : <VisibilityIcon/>}
|
||||||
onClick={toggleOpenOptions}
|
onClick={toggleHideChart}
|
||||||
ariaLabel="settings"
|
ariaLabel="settings"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<div ref={optionsButtonRef}>
|
||||||
|
<Tooltip title="Graph settings">
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<SettingsIcon/>}
|
||||||
|
onClick={toggleOpenOptions}
|
||||||
|
ariaLabel="settings"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
<Popper
|
<Popper
|
||||||
open={openOptions}
|
open={openOptions}
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
@use "src/styles/variables" as *;
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
.vm-bar-hits-options {
|
.vm-bar-hits-options {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $padding-small;
|
top: $padding-small;
|
||||||
right: $padding-small;
|
right: $padding-small;
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
&_hidden {
|
||||||
|
min-height: 90px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_panning {
|
&_panning {
|
||||||
|
|
|
@ -9,4 +9,5 @@ export interface GraphOptions {
|
||||||
graphStyle: GRAPH_STYLES;
|
graphStyle: GRAPH_STYLES;
|
||||||
stacked: boolean;
|
stacked: boolean;
|
||||||
fill: boolean;
|
fill: boolean;
|
||||||
|
hideChart: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ExploreLogsBody from "./ExploreLogsBody/ExploreLogsBody";
|
||||||
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
||||||
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
|
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
|
||||||
|
@ -14,6 +14,7 @@ import ExploreLogsBarChart from "./ExploreLogsBarChart/ExploreLogsBarChart";
|
||||||
import { useFetchLogHits } from "./hooks/useFetchLogHits";
|
import { useFetchLogHits } from "./hooks/useFetchLogHits";
|
||||||
import { LOGS_ENTRIES_LIMIT } from "../../constants/logs";
|
import { LOGS_ENTRIES_LIMIT } from "../../constants/logs";
|
||||||
import { getTimeperiodForDuration, relativeTimeOptions } from "../../utils/time";
|
import { getTimeperiodForDuration, relativeTimeOptions } from "../../utils/time";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
|
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
|
||||||
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
|
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
|
||||||
|
@ -22,6 +23,8 @@ const ExploreLogs: FC = () => {
|
||||||
const { serverUrl } = useAppState();
|
const { serverUrl } = useAppState();
|
||||||
const { duration, relativeTime, period: periodState } = useTimeState();
|
const { duration, relativeTime, period: periodState } = useTimeState();
|
||||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const hideChart = useMemo(() => searchParams.get("hide_chart"), [searchParams]);
|
||||||
|
|
||||||
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
|
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
|
||||||
const [query, setQuery] = useStateSearchParams("*", "query");
|
const [query, setQuery] = useStateSearchParams("*", "query");
|
||||||
|
@ -49,7 +52,7 @@ const ExploreLogs: FC = () => {
|
||||||
const newPeriod = getPeriod();
|
const newPeriod = getPeriod();
|
||||||
setPeriod(newPeriod);
|
setPeriod(newPeriod);
|
||||||
fetchLogs(newPeriod).then((isSuccess) => {
|
fetchLogs(newPeriod).then((isSuccess) => {
|
||||||
isSuccess && fetchLogHits(newPeriod);
|
isSuccess && !hideChart && fetchLogHits(newPeriod);
|
||||||
}).catch(e => e);
|
}).catch(e => e);
|
||||||
setSearchParamsFromKeys( {
|
setSearchParamsFromKeys( {
|
||||||
query,
|
query,
|
||||||
|
@ -88,6 +91,10 @@ const ExploreLogs: FC = () => {
|
||||||
setTmpQuery(query);
|
setTmpQuery(query);
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
!hideChart && fetchLogHits(period);
|
||||||
|
}, [hideChart]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-explore-logs">
|
<div className="vm-explore-logs">
|
||||||
<ExploreLogsHeader
|
<ExploreLogsHeader
|
||||||
|
|
|
@ -10,6 +10,7 @@ import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart";
|
||||||
import Alert from "../../../components/Main/Alert/Alert";
|
import Alert from "../../../components/Main/Alert/Alert";
|
||||||
import { TimeParams } from "../../../types";
|
import { TimeParams } from "../../../types";
|
||||||
import LineLoader from "../../../components/Main/LineLoader/LineLoader";
|
import LineLoader from "../../../components/Main/LineLoader/LineLoader";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { getHitsTimeParams } from "../../../utils/logs";
|
import { getHitsTimeParams } from "../../../utils/logs";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -24,6 +25,8 @@ interface Props {
|
||||||
const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onApplyFilter }) => {
|
const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onApplyFilter }) => {
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
const timeDispatch = useTimeDispatch();
|
const timeDispatch = useTimeDispatch();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const hideChart = useMemo(() => searchParams.get("hide_chart"), [searchParams]);
|
||||||
|
|
||||||
const getYAxes = (logHits: LogHits[], timestamps: number[]) => {
|
const getYAxes = (logHits: LogHits[], timestamps: number[]) => {
|
||||||
return logHits.map(hits => {
|
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 noData = data.every(d => d.length === 0);
|
||||||
const noTimestamps = data[0].length === 0;
|
const noTimestamps = data[0].length === 0;
|
||||||
const noValues = data[1].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.";
|
return "No logs volume available\nNo volume information available for the current queries and time range.";
|
||||||
} else if (noTimestamps) {
|
} else if (noTimestamps) {
|
||||||
return "No timestamp information available for the current queries and time range.";
|
return "No timestamp information available for the current queries and time range.";
|
||||||
} else if (noValues) {
|
} else if (noValues) {
|
||||||
return "No value information available for the current queries and time range.";
|
return "No value information available for the current queries and time range.";
|
||||||
} return "";
|
} return "";
|
||||||
}, [data]);
|
}, [data, hideChart]);
|
||||||
|
|
||||||
const setPeriod = ({ from, to }: {from: Date, to: Date}) => {
|
const setPeriod = ({ from, to }: {from: Date, to: Date}) => {
|
||||||
timeDispatch({ type: "SET_PERIOD", payload: { from, to } });
|
timeDispatch({ type: "SET_PERIOD", payload: { from, to } });
|
||||||
|
|
|
@ -13,8 +13,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateY(-25px);
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: $padding-global;
|
|
||||||
|
|
||||||
&-keys {
|
&-keys {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
|
|
|
@ -15,6 +15,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||||
|
|
||||||
## tip
|
## 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: 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 [`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.
|
* 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.
|
||||||
|
|
Loading…
Reference in a new issue