mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-12 12:46:23 +01:00
vmui: fix URL params handling for navigation (#6284)
This PR fixes the handling of URL parameters to ensure correct browser
navigation using the back and forward buttons.
#6126
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5516#issuecomment-1867507232
(cherry picked from commit f14497f1cd
)
This commit is contained in:
parent
97c3c946a7
commit
33eaa18c14
@ -178,6 +178,10 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
||||
}
|
||||
}, [stateQuery, awaitStateQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
setStateQuery(query || []);
|
||||
}, [query]);
|
||||
|
||||
return <div
|
||||
className={classNames({
|
||||
"vm-query-configurator": true,
|
||||
|
@ -1,12 +1,18 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTimeState } from "../../../state/time/TimeStateContext";
|
||||
import { useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||
import { useAppState } from "../../../state/common/StateContext";
|
||||
import { useQueryState } from "../../../state/query/QueryStateContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
|
||||
import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
||||
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
||||
import { displayTypeTabs } from "../DisplayTypeSwitch";
|
||||
import { compactObject } from "../../../utils/object";
|
||||
import { useGraphState } from "../../../state/graph/GraphStateContext";
|
||||
import { useGraphDispatch, useGraphState } from "../../../state/graph/GraphStateContext";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useCallback } from "preact/compat";
|
||||
import { getInitialDisplayType } from "../../../state/customPanel/reducer";
|
||||
import { getInitialTimeState } from "../../../state/time/reducer";
|
||||
import useEventListener from "../../../hooks/useEventListener";
|
||||
import { getQueryArray } from "../../../utils/query-string";
|
||||
import { arrayEquals } from "../../../utils/array";
|
||||
import { isEqualURLSearchParams } from "../../../utils/url";
|
||||
|
||||
export const useSetQueryParams = () => {
|
||||
const { tenantId } = useAppState();
|
||||
@ -14,25 +20,108 @@ export const useSetQueryParams = () => {
|
||||
const { query } = useQueryState();
|
||||
const { duration, relativeTime, period: { date, step } } = useTimeState();
|
||||
const { customStep } = useGraphState();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const timeDispatch = useTimeDispatch();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const queryDispatch = useQueryDispatch();
|
||||
const customPanelDispatch = useCustomPanelDispatch();
|
||||
|
||||
const [isPopstate, setIsPopstate] = useState(false);
|
||||
|
||||
const setterSearchParams = useCallback(() => {
|
||||
if (isPopstate) {
|
||||
// After the popstate event, the states synchronizes with the searchParams,
|
||||
// so there's no need to refresh the searchParams again.
|
||||
setIsPopstate(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
|
||||
const setSearchParamsFromState = () => {
|
||||
const params: Record<string, unknown> = {};
|
||||
query.forEach((q, i) => {
|
||||
const group = `g${i}`;
|
||||
params[`${group}.expr`] = q;
|
||||
params[`${group}.range_input`] = duration;
|
||||
params[`${group}.end_input`] = date;
|
||||
params[`${group}.tab`] = displayTypeTabs.find(t => t.value === displayType)?.prometheusCode || 0;
|
||||
params[`${group}.relative_time`] = relativeTime;
|
||||
params[`${group}.tenantID`] = tenantId;
|
||||
if ((searchParams.get(`${group}.expr`) !== q) && q) {
|
||||
newSearchParams.set(`${group}.expr`, q);
|
||||
}
|
||||
|
||||
if ((step !== customStep) && customStep) params[`${group}.step_input`] = customStep;
|
||||
if (searchParams.get(`${group}.range_input`) !== duration) {
|
||||
newSearchParams.set(`${group}.range_input`, duration);
|
||||
}
|
||||
|
||||
if (searchParams.get(`${group}.end_input`) !== date) {
|
||||
newSearchParams.set(`${group}.end_input`, date);
|
||||
}
|
||||
|
||||
if (searchParams.get(`${group}.relative_time`) !== relativeTime) {
|
||||
newSearchParams.set(`${group}.relative_time`, relativeTime || "none");
|
||||
}
|
||||
|
||||
const stepFromUrl = searchParams.get(`${group}.step_input`) || step;
|
||||
if (stepFromUrl && (stepFromUrl !== customStep)) {
|
||||
newSearchParams.set(`${group}.step_input`, customStep);
|
||||
}
|
||||
|
||||
const displayTypeCode = `${displayTypeTabs.find(t => t.value === displayType)?.prometheusCode || 0}`;
|
||||
if (searchParams.get(`${group}.tab`) !== displayTypeCode) {
|
||||
newSearchParams.set(`${group}.tab`, `${displayTypeCode}`);
|
||||
}
|
||||
|
||||
if (searchParams.get(`${group}.tenantID`) !== tenantId && tenantId) {
|
||||
newSearchParams.set(`${group}.tenantID`, tenantId);
|
||||
}
|
||||
});
|
||||
if (isEqualURLSearchParams(newSearchParams, searchParams) || !newSearchParams.size) return;
|
||||
setSearchParams(newSearchParams);
|
||||
}, [tenantId, displayType, query, duration, relativeTime, date, step, customStep]);
|
||||
|
||||
setSearchParams(compactObject(params) as Record<string, string>);
|
||||
};
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(setterSearchParams, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [setterSearchParams]);
|
||||
|
||||
useEffect(setSearchParamsFromState, [tenantId, displayType, query, duration, relativeTime, date, step, customStep]);
|
||||
useEffect(setSearchParamsFromState, []);
|
||||
useEffect(() => {
|
||||
// Synchronize the states with searchParams only after the popstate event.
|
||||
if (!isPopstate) return;
|
||||
|
||||
const timeFromUrl = getInitialTimeState();
|
||||
const isDurationDifferent = (timeFromUrl.duration !== duration);
|
||||
const isRelativeTimeDifferent = timeFromUrl.relativeTime !== relativeTime;
|
||||
const isDateDifferent = timeFromUrl.relativeTime === "none" && timeFromUrl.period.date !== date;
|
||||
const someNotEqual = isDurationDifferent || isRelativeTimeDifferent || isDateDifferent;
|
||||
if (someNotEqual) {
|
||||
timeDispatch({ type: "SET_TIME_STATE", payload: timeFromUrl });
|
||||
}
|
||||
|
||||
const displayTypeFromUrl = getInitialDisplayType();
|
||||
if (displayTypeFromUrl !== displayType) {
|
||||
customPanelDispatch({ type: "SET_DISPLAY_TYPE", payload: displayTypeFromUrl });
|
||||
}
|
||||
|
||||
const tenantIdFromUrl = searchParams.get("g0.tenantID") || "";
|
||||
if (tenantIdFromUrl !== tenantId) {
|
||||
dispatch({ type: "SET_TENANT_ID", payload: tenantIdFromUrl });
|
||||
}
|
||||
|
||||
const queryFromUrl = getQueryArray();
|
||||
if (!arrayEquals(queryFromUrl, query)) {
|
||||
queryDispatch({ type: "SET_QUERY", payload: queryFromUrl });
|
||||
timeDispatch({ type: "RUN_QUERY" });
|
||||
}
|
||||
|
||||
// Timer prevents customStep reset on time range change.
|
||||
const timer = setTimeout(() => {
|
||||
const customStepFromUrl = searchParams.get("g0.step_input") || step;
|
||||
if (customStepFromUrl && customStepFromUrl !== customStep) {
|
||||
graphDispatch({ type: "SET_CUSTOM_STEP", payload: customStepFromUrl });
|
||||
}
|
||||
}, 50);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchParams, isPopstate]);
|
||||
|
||||
useEventListener("popstate", () => {
|
||||
setIsPopstate(true);
|
||||
});
|
||||
};
|
||||
|
@ -12,7 +12,6 @@ import Alert from "../../components/Main/Alert/Alert";
|
||||
import classNames from "classnames";
|
||||
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||
import InstantQueryTip from "./InstantQueryTip/InstantQueryTip";
|
||||
import useEventListener from "../../hooks/useEventListener";
|
||||
import { useRef } from "react";
|
||||
import CustomPanelTraces from "./CustomPanelTraces/CustomPanelTraces";
|
||||
import WarningLimitSeries from "./WarningLimitSeries/WarningLimitSeries";
|
||||
@ -65,9 +64,6 @@ const CustomPanel: FC = () => {
|
||||
setHideError(false);
|
||||
};
|
||||
|
||||
const handleChangePopstate = () => window.location.reload();
|
||||
useEventListener("popstate", handleChangePopstate);
|
||||
|
||||
useEffect(() => {
|
||||
graphDispatch({ type: "SET_IS_HISTOGRAM", payload: isHistogram });
|
||||
}, [graphData]);
|
||||
|
@ -19,12 +19,16 @@ export type CustomPanelAction =
|
||||
| { type: "TOGGLE_QUERY_TRACING" }
|
||||
| { type: "TOGGLE_TABLE_COMPACT" }
|
||||
|
||||
const queryTab = getQueryStringValue("g0.tab", 0) as string;
|
||||
const displayType = displayTypeTabs.find(t => t.prometheusCode === +queryTab || t.value === queryTab);
|
||||
export const getInitialDisplayType = () => {
|
||||
const queryTab = getQueryStringValue("g0.tab", 0) as string;
|
||||
const displayType = displayTypeTabs.find(t => t.prometheusCode === +queryTab || t.value === queryTab);
|
||||
return displayType?.value || DisplayType.chart;
|
||||
};
|
||||
|
||||
const limitsStorage = getFromStorage("SERIES_LIMITS") as string;
|
||||
|
||||
export const initialCustomPanelState: CustomPanelState = {
|
||||
displayType: (displayType?.value || DisplayType.chart),
|
||||
displayType: getInitialDisplayType(),
|
||||
nocache: false,
|
||||
isTracingEnabled: false,
|
||||
seriesLimits: limitsStorage ? JSON.parse(limitsStorage) : DEFAULT_MAX_SERIES,
|
||||
|
@ -21,6 +21,7 @@ export interface TimeState {
|
||||
}
|
||||
|
||||
export type TimeAction =
|
||||
| { type: "SET_TIME_STATE", payload: { duration: string, period: TimeParams, relativeTime?: string; } }
|
||||
| { type: "SET_DURATION", payload: string }
|
||||
| { type: "SET_RELATIVE_TIME", payload: {id: string, duration: string, until: Date} }
|
||||
| { type: "SET_PERIOD", payload: TimePeriod }
|
||||
@ -32,24 +33,35 @@ export type TimeAction =
|
||||
const timezone = getFromStorage("TIMEZONE") as string || getBrowserTimezone().region;
|
||||
setTimezone(timezone);
|
||||
|
||||
const defaultDuration = getQueryStringValue("g0.range_input") as string;
|
||||
export const getInitialTimeState = () => {
|
||||
const defaultDuration = getQueryStringValue("g0.range_input") as string;
|
||||
|
||||
const { duration, endInput, relativeTimeId } = getRelativeTime({
|
||||
defaultDuration: defaultDuration || "1h",
|
||||
defaultEndInput: formatDateToLocal(getQueryStringValue("g0.end_input", getDateNowUTC()) as string),
|
||||
relativeTimeId: defaultDuration ? getQueryStringValue("g0.relative_time", "none") as string : undefined
|
||||
});
|
||||
const { duration, endInput, relativeTimeId } = getRelativeTime({
|
||||
defaultDuration: defaultDuration || "1h",
|
||||
defaultEndInput: formatDateToLocal(getQueryStringValue("g0.end_input", getDateNowUTC()) as string),
|
||||
relativeTimeId: defaultDuration ? getQueryStringValue("g0.relative_time", "none") as string : undefined
|
||||
});
|
||||
|
||||
return {
|
||||
duration,
|
||||
period: getTimeperiodForDuration(duration, endInput),
|
||||
relativeTime: relativeTimeId,
|
||||
};
|
||||
};
|
||||
|
||||
export const initialTimeState: TimeState = {
|
||||
duration,
|
||||
period: getTimeperiodForDuration(duration, endInput),
|
||||
relativeTime: relativeTimeId,
|
||||
...getInitialTimeState(),
|
||||
timezone,
|
||||
};
|
||||
|
||||
|
||||
export function reducer(state: TimeState, action: TimeAction): TimeState {
|
||||
switch (action.type) {
|
||||
case "SET_TIME_STATE":
|
||||
return {
|
||||
...state,
|
||||
...action.payload
|
||||
};
|
||||
case "SET_DURATION":
|
||||
return {
|
||||
...state,
|
||||
|
@ -11,3 +11,17 @@ export const isValidHttpUrl = (str: string): boolean => {
|
||||
};
|
||||
|
||||
export const removeTrailingSlash = (url: string) => url.replace(/\/$/, "");
|
||||
|
||||
export const isEqualURLSearchParams = (params1: URLSearchParams, params2: URLSearchParams): boolean => {
|
||||
if (Array.from(params1.entries()).length !== Array.from(params2.entries()).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [key, value] of params1) {
|
||||
if (params2.get(key) !== value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -50,6 +50,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix bug that prevents the first query trace from expanding on click event. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6186). The issue was introduced in [v1.100.0](https://docs.victoriametrics.com/changelog/#v11000) release.
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix calendar display when `UTC+00:00` timezone is set. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6239).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): remove redundant requests on the `Explore Cardinality` page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6240).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix handling of URL params for browser history navigation (back and forward buttons). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6126) and [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5516#issuecomment-1867507232).
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/): prevent potential panic during [stream aggregation](https://docs.victoriametrics.com/stream-aggregation.html) if more than one `--remoteWrite.streamAggr.dedupInterval` is configured. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6205).
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/): skip empty data blocks before sending to the remote write destination. Thanks to @viperstars for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6241).
|
||||
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/stream-aggregation/): set correct suffix `<output>_prometheus` for aggregation outputs [increase_prometheus](https://docs.victoriametrics.com/stream-aggregation/#increase_prometheus) and [total_prometheus](https://docs.victoriametrics.com/stream-aggregation/#total_prometheus). Before, outputs `total` and `total_prometheus` or `increase` and `increase_prometheus` had the same suffix.
|
||||
|
Loading…
Reference in New Issue
Block a user