vmui: add gap display option for charts #5152 (#5862)

This commit is contained in:
Yury Molodov 2024-02-29 23:42:50 +01:00 committed by GitHub
parent 5aa3dfbd20
commit e130f29659
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 77 additions and 13 deletions

View file

@ -30,7 +30,7 @@
"sass": "^1.56.0", "sass": "^1.56.0",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3",
"typescript": "~4.6.2", "typescript": "~4.6.2",
"uplot": "^1.6.19", "uplot": "^1.6.30",
"web-vitals": "^3.3.2" "web-vitals": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -26,7 +26,7 @@
"sass": "^1.56.0", "sass": "^1.56.0",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3",
"typescript": "~4.6.2", "typescript": "~4.6.2",
"uplot": "^1.6.19", "uplot": "^1.6.30",
"web-vitals": "^3.3.2" "web-vitals": "^3.3.2"
}, },
"scripts": { "scripts": {

View file

@ -41,6 +41,7 @@ export interface LineChartProps {
layoutSize: ElementSize; layoutSize: ElementSize;
height?: number; height?: number;
anomalyView?: boolean; anomalyView?: boolean;
spanGaps?: boolean;
} }
const LineChart: FC<LineChartProps> = ({ const LineChart: FC<LineChartProps> = ({
@ -53,7 +54,8 @@ const LineChart: FC<LineChartProps> = ({
setPeriod, setPeriod,
layoutSize, layoutSize,
height, height,
anomalyView anomalyView,
spanGaps = false
}) => { }) => {
const { isDarkTheme } = useAppState(); const { isDarkTheme } = useAppState();
@ -106,10 +108,10 @@ const LineChart: FC<LineChartProps> = ({
useEffect(() => { useEffect(() => {
if (!uPlotInst) return; if (!uPlotInst) return;
delSeries(uPlotInst); delSeries(uPlotInst);
addSeries(uPlotInst, series); addSeries(uPlotInst, series, spanGaps);
setBand(uPlotInst, series); setBand(uPlotInst, series);
uPlotInst.redraw(); uPlotInst.redraw();
}, [series]); }, [series, spanGaps]);
useEffect(() => { useEffect(() => {
if (!uPlotInst) return; if (!uPlotInst) return;

View file

@ -7,16 +7,21 @@ import Popper from "../../Main/Popper/Popper";
import "./style.scss"; import "./style.scss";
import Tooltip from "../../Main/Tooltip/Tooltip"; import Tooltip from "../../Main/Tooltip/Tooltip";
import useBoolean from "../../../hooks/useBoolean"; import useBoolean from "../../../hooks/useBoolean";
import LinesConfigurator from "./LinesConfigurator/LinesConfigurator";
const title = "Axes settings"; const title = "Graph settings";
interface GraphSettingsProps { interface GraphSettingsProps {
yaxis: YaxisState, yaxis: YaxisState,
setYaxisLimits: (limits: AxisRange) => void, setYaxisLimits: (limits: AxisRange) => void,
toggleEnableLimits: () => void toggleEnableLimits: () => void,
spanGaps: {
value: boolean,
onChange: (value: boolean) => void,
},
} }
const GraphSettings: FC<GraphSettingsProps> = ({ yaxis, setYaxisLimits, toggleEnableLimits }) => { const GraphSettings: FC<GraphSettingsProps> = ({ yaxis, setYaxisLimits, toggleEnableLimits, spanGaps }) => {
const popperRef = useRef<HTMLDivElement>(null); const popperRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLDivElement>(null); const buttonRef = useRef<HTMLDivElement>(null);
@ -55,6 +60,10 @@ const GraphSettings: FC<GraphSettingsProps> = ({ yaxis, setYaxisLimits, toggleEn
setYaxisLimits={setYaxisLimits} setYaxisLimits={setYaxisLimits}
toggleEnableLimits={toggleEnableLimits} toggleEnableLimits={toggleEnableLimits}
/> />
<LinesConfigurator
spanGaps={spanGaps.value}
onChange={spanGaps.onChange}
/>
</div> </div>
</div> </div>
</Popper> </Popper>

View file

@ -0,0 +1,23 @@
import React, { FC } from "preact/compat";
import Switch from "../../../Main/Switch/Switch";
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
interface Props {
spanGaps: boolean,
onChange: (value: boolean) => void,
}
const LinesConfigurator: FC<Props> = ({ spanGaps, onChange }) => {
const { isMobile } = useDeviceDetect();
return <div>
<Switch
value={spanGaps}
onChange={onChange}
label="Connect null values"
fullWidth={isMobile}
/>
</div>;
};
export default LinesConfigurator;

View file

@ -8,7 +8,7 @@
&__body { &__body {
display: grid; display: grid;
gap: $padding-small; gap: $padding-large;
padding: 0 $padding-global; padding: 0 $padding-global;
} }
} }

View file

@ -41,6 +41,7 @@ export interface GraphViewProps {
height?: number; height?: number;
isHistogram?: boolean; isHistogram?: boolean;
anomalyView?: boolean; anomalyView?: boolean;
spanGaps?: boolean;
} }
const GraphView: FC<GraphViewProps> = ({ const GraphView: FC<GraphViewProps> = ({
@ -58,6 +59,7 @@ const GraphView: FC<GraphViewProps> = ({
height, height,
isHistogram, isHistogram,
anomalyView, anomalyView,
spanGaps
}) => { }) => {
const { isMobile } = useDeviceDetect(); const { isMobile } = useDeviceDetect();
const { timezone } = useTimeState(); const { timezone } = useTimeState();
@ -196,6 +198,7 @@ const GraphView: FC<GraphViewProps> = ({
layoutSize={containerSize} layoutSize={containerSize}
height={height} height={height}
anomalyView={anomalyView} anomalyView={anomalyView}
spanGaps={spanGaps}
/> />
)} )}
{isHistogram && ( {isHistogram && (

View file

@ -20,7 +20,7 @@ type Props = {
const GraphTab: FC<Props> = ({ isHistogram, graphData, controlsRef, anomalyView }) => { const GraphTab: FC<Props> = ({ isHistogram, graphData, controlsRef, anomalyView }) => {
const { isMobile } = useDeviceDetect(); const { isMobile } = useDeviceDetect();
const { customStep, yaxis } = useGraphState(); const { customStep, yaxis, spanGaps } = useGraphState();
const { period } = useTimeState(); const { period } = useTimeState();
const { query } = useQueryState(); const { query } = useQueryState();
@ -35,6 +35,10 @@ const GraphTab: FC<Props> = ({ isHistogram, graphData, controlsRef, anomalyView
graphDispatch({ type: "TOGGLE_ENABLE_YAXIS_LIMITS" }); graphDispatch({ type: "TOGGLE_ENABLE_YAXIS_LIMITS" });
}; };
const setSpanGaps = (value: boolean) => {
graphDispatch({ type: "SET_SPAN_GAPS", payload: value });
};
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 } });
}; };
@ -46,6 +50,7 @@ const GraphTab: FC<Props> = ({ isHistogram, graphData, controlsRef, anomalyView
yaxis={yaxis} yaxis={yaxis}
setYaxisLimits={setYaxisLimits} setYaxisLimits={setYaxisLimits}
toggleEnableLimits={toggleEnableLimits} toggleEnableLimits={toggleEnableLimits}
spanGaps={{ value: spanGaps, onChange: setSpanGaps }}
/> />
</div> </div>
); );
@ -64,6 +69,7 @@ const GraphTab: FC<Props> = ({ isHistogram, graphData, controlsRef, anomalyView
height={isMobile ? window.innerHeight * 0.5 : 500} height={isMobile ? window.innerHeight * 0.5 : 500}
isHistogram={isHistogram} isHistogram={isHistogram}
anomalyView={anomalyView} anomalyView={anomalyView}
spanGaps={spanGaps}
/> />
</> </>
); );

View file

@ -34,6 +34,7 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [spanGaps, setSpanGaps] = useState(false);
const [yaxis, setYaxis] = useState<YaxisState>({ const [yaxis, setYaxis] = useState<YaxisState>({
limits: { limits: {
enable: false, enable: false,
@ -121,6 +122,7 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
yaxis={yaxis} yaxis={yaxis}
setYaxisLimits={setYaxisLimits} setYaxisLimits={setYaxisLimits}
toggleEnableLimits={toggleEnableLimits} toggleEnableLimits={toggleEnableLimits}
spanGaps={{ value: spanGaps, onChange: setSpanGaps }}
/> />
</div> </div>
<div className="vm-predefined-panel-body"> <div className="vm-predefined-panel-body">
@ -140,6 +142,7 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
setPeriod={setPeriod} setPeriod={setPeriod}
fullWidth={false} fullWidth={false}
height={isMobile ? window.innerHeight * 0.5 : 500} height={isMobile ? window.innerHeight * 0.5 : 500}
spanGaps={spanGaps}
/> />
} }
</div> </div>

View file

@ -49,7 +49,7 @@ const QueryAnalyzerView: FC<Props> = ({ data, period }) => {
}, [data]); }, [data]);
const [displayType, setDisplayType] = useState(tabs[0].value); const [displayType, setDisplayType] = useState(tabs[0].value);
const { yaxis } = useGraphState(); const { yaxis, spanGaps } = useGraphState();
const graphDispatch = useGraphDispatch(); const graphDispatch = useGraphDispatch();
const setYaxisLimits = (limits: AxisRange) => { const setYaxisLimits = (limits: AxisRange) => {
@ -60,6 +60,10 @@ const QueryAnalyzerView: FC<Props> = ({ data, period }) => {
graphDispatch({ type: "TOGGLE_ENABLE_YAXIS_LIMITS" }); graphDispatch({ type: "TOGGLE_ENABLE_YAXIS_LIMITS" });
}; };
const setSpanGaps = (value: boolean) => {
graphDispatch({ type: "SET_SPAN_GAPS", payload: value });
};
const handleChangeDisplayType = (newValue: string) => { const handleChangeDisplayType = (newValue: string) => {
setDisplayType(newValue as DisplayType); setDisplayType(newValue as DisplayType);
}; };
@ -137,6 +141,7 @@ const QueryAnalyzerView: FC<Props> = ({ data, period }) => {
yaxis={yaxis} yaxis={yaxis}
setYaxisLimits={setYaxisLimits} setYaxisLimits={setYaxisLimits}
toggleEnableLimits={toggleEnableLimits} toggleEnableLimits={toggleEnableLimits}
spanGaps={{ value: spanGaps, onChange: setSpanGaps }}
/> />
)} )}
{displayType === "table" && ( {displayType === "table" && (
@ -161,6 +166,7 @@ const QueryAnalyzerView: FC<Props> = ({ data, period }) => {
setPeriod={() => null} setPeriod={() => null}
height={isMobile ? window.innerHeight * 0.5 : 500} height={isMobile ? window.innerHeight * 0.5 : 500}
isHistogram={isHistogram} isHistogram={isHistogram}
spanGaps={spanGaps}
/> />
)} )}
{liveData && (displayType === "code") && ( {liveData && (displayType === "code") && (

View file

@ -15,6 +15,8 @@ export interface GraphState {
customStep: string customStep: string
yaxis: YaxisState yaxis: YaxisState
isHistogram: boolean isHistogram: boolean
/** when true, null data values will not cause line breaks */
spanGaps: boolean
} }
export type GraphAction = export type GraphAction =
@ -22,13 +24,15 @@ export type GraphAction =
| { type: "SET_YAXIS_LIMITS", payload: AxisRange } | { type: "SET_YAXIS_LIMITS", payload: AxisRange }
| { type: "SET_CUSTOM_STEP", payload: string} | { type: "SET_CUSTOM_STEP", payload: string}
| { type: "SET_IS_HISTOGRAM", payload: boolean } | { type: "SET_IS_HISTOGRAM", payload: boolean }
| { type: "SET_SPAN_GAPS", payload: boolean }
export const initialGraphState: GraphState = { export const initialGraphState: GraphState = {
customStep: getQueryStringValue("g0.step_input", "") as string, customStep: getQueryStringValue("g0.step_input", "") as string,
yaxis: { yaxis: {
limits: { enable: false, range: { "1": [0, 0] } } limits: { enable: false, range: { "1": [0, 0] } }
}, },
isHistogram: false isHistogram: false,
spanGaps: false,
}; };
export function reducer(state: GraphState, action: GraphAction): GraphState { export function reducer(state: GraphState, action: GraphAction): GraphState {
@ -65,6 +69,11 @@ export function reducer(state: GraphState, action: GraphAction): GraphState {
...state, ...state,
isHistogram: action.payload isHistogram: action.payload
}; };
case "SET_SPAN_GAPS":
return {
...state,
spanGaps: action.payload
};
default: default:
throw new Error(); throw new Error();
} }

View file

@ -94,6 +94,7 @@ export const getSeriesItemContext = (data: MetricResult[], hideSeries: string[],
width, width,
stroke, stroke,
points, points,
spanGaps: false,
forecast: forecast.value, forecast: forecast.value,
forecastGroup: forecast.group, forecastGroup: forecast.group,
freeFormFields: d.metric, freeFormFields: d.metric,
@ -165,8 +166,9 @@ export const delSeries = (u: uPlot) => {
} }
}; };
export const addSeries = (u: uPlot, series: uPlotSeries[]) => { export const addSeries = (u: uPlot, series: uPlotSeries[], spanGaps = false) => {
series.forEach((s) => { series.forEach((s) => {
if (s.label) s.spanGaps = spanGaps;
u.addSeries(s); u.addSeries(s);
}); });
}; };

View file

@ -41,6 +41,7 @@ See also [LTS releases](https://docs.victoriametrics.com/LTS-releases.html).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for `enable_compression` option in [scrape_configs](https://docs.victoriametrics.com/sd_configs/#scrape_configs) in order to be compatible with Prometheus scrape configs. See [this pull request](https://github.com/prometheus/prometheus/pull/13166) and [this feature request](https://github.com/prometheus/prometheus/issues/12319). Note that `vmagent` was always supporting [`disable_compression` option](https://docs.victoriametrics.com/vmagent/#scrape_config-enhancements) before Prometheus added `enable_compression` option. * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for `enable_compression` option in [scrape_configs](https://docs.victoriametrics.com/sd_configs/#scrape_configs) in order to be compatible with Prometheus scrape configs. See [this pull request](https://github.com/prometheus/prometheus/pull/13166) and [this feature request](https://github.com/prometheus/prometheus/issues/12319). Note that `vmagent` was always supporting [`disable_compression` option](https://docs.victoriametrics.com/vmagent/#scrape_config-enhancements) before Prometheus added `enable_compression` option.
* FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): support client-side TLS configuration for [InfluxDB](https://docs.victoriametrics.com/vmctl/#migrating-data-from-influxdb-1x), [Remote Read protocol](https://docs.victoriametrics.com/vmctl/#migrating-data-by-remote-read-protocol) and [OpenTSDB](https://docs.victoriametrics.com/vmctl/#migrating-data-from-opentsdb). See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5748). Thanks to @khushijain21 for pull requests [1](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5783), [2](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5798), [3](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5797). * FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): support client-side TLS configuration for [InfluxDB](https://docs.victoriametrics.com/vmctl/#migrating-data-from-influxdb-1x), [Remote Read protocol](https://docs.victoriametrics.com/vmctl/#migrating-data-by-remote-read-protocol) and [OpenTSDB](https://docs.victoriametrics.com/vmctl/#migrating-data-from-opentsdb). See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5748). Thanks to @khushijain21 for pull requests [1](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5783), [2](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5798), [3](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5797).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): preserve [`WITH` templates](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/expand-with-exprs) when clicking the `prettify query` button at the right side of query input field. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5383). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): preserve [`WITH` templates](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/expand-with-exprs) when clicking the `prettify query` button at the right side of query input field. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5383).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add a feature that allows to choose how zero values, representing gaps in data, are displayed on a chart: time series data points with gaps are either connected or show breaks. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5152).
* FEATURE: [vmalert](https://docs.victoriametrics.com/#vmalert): support filtering by group, rule or labels in [vmalert's UI](https://docs.victoriametrics.com/vmalert/#web) for `/groups` and `/alerts` pages. See [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5791) by @victoramsantos. * FEATURE: [vmalert](https://docs.victoriametrics.com/#vmalert): support filtering by group, rule or labels in [vmalert's UI](https://docs.victoriametrics.com/vmalert/#web) for `/groups` and `/alerts` pages. See [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5791) by @victoramsantos.
* FEATURE: [docker-compose](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#docker-compose-environment-for-victoriametrics): create a separate [docker-compose environment](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/docker-compose-victorialogs.yml) for VictoriaLogs installation, including fluentbit and [VictoriaLogs Grafana datasource](https://github.com/VictoriaMetrics/victorialogs-datasource). * FEATURE: [docker-compose](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#docker-compose-environment-for-victoriametrics): create a separate [docker-compose environment](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/docker-compose-victorialogs.yml) for VictoriaLogs installation, including fluentbit and [VictoriaLogs Grafana datasource](https://github.com/VictoriaMetrics/victorialogs-datasource).
* FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): wait for up 30 seconds before making a [snapshot](https://docs.victoriametrics.com/#how-to-work-with-snapshots) for backup if `vmstorage` is temporarily unavailalbe. This should prevent from `vmbackupmanager` termination in this case. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5859). * FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): wait for up 30 seconds before making a [snapshot](https://docs.victoriametrics.com/#how-to-work-with-snapshots) for backup if `vmstorage` is temporarily unavailalbe. This should prevent from `vmbackupmanager` termination in this case. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5859).