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,
|
||||
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}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&_hidden {
|
||||
min-height: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
&_panning {
|
||||
|
|
|
@ -9,4 +9,5 @@ export interface GraphOptions {
|
|||
graphStyle: GRAPH_STYLES;
|
||||
stacked: 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 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
|
||||
|
|
|
@ -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 } });
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: $padding-global;
|
||||
|
||||
&-keys {
|
||||
max-height: 300px;
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue