mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-10 15:14:09 +00:00
vmui: fix app routing issues (#4408)
The change focuses on rectifying inconsistencies in the navigation behavior of the application and eliminating issues encountered when manually altering the URL. The key updates include: - Refactoring of the routing mechanism to handle all possible routes and their states. - Enhancement of the React Router usage to ensure a smoother navigation experience. - Handling application state when the URL is manually changed.
This commit is contained in:
parent
7eeb2d553f
commit
8c190ec8fb
22 changed files with 1285 additions and 1168 deletions
2198
app/vmui/packages/vmui/package-lock.json
generated
2198
app/vmui/packages/vmui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -26,7 +26,7 @@
|
|||
"preact": "^10.7.1",
|
||||
"qs": "^6.10.3",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"sass": "^1.56.0",
|
||||
"typescript": "~4.6.2",
|
||||
"uplot": "^1.6.19",
|
||||
|
|
|
@ -8,21 +8,22 @@ import { DATE_FORMAT } from "../../../constants/date";
|
|||
import DatePicker from "../../Main/DatePicker/DatePicker";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
|
||||
const CardinalityDatePicker: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const appModeEnable = getAppModeEnable();
|
||||
const buttonRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
||||
const date = searchParams.get("date") || dayjs().tz().format(DATE_FORMAT);
|
||||
|
||||
const dateFormatted = useMemo(() => dayjs.tz(date).format(DATE_FORMAT), [date]);
|
||||
|
||||
const handleChangeDate = (val: string) => {
|
||||
searchParams.set("date", val);
|
||||
setSearchParams(searchParams);
|
||||
setSearchParamsFromKeys({ date: val });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
|||
import DateTimeInput from "../../../Main/DatePicker/DateTimeInput/DateTimeInput";
|
||||
import useBoolean from "../../../../hooks/useBoolean";
|
||||
import useWindowSize from "../../../../hooks/useWindowSize";
|
||||
import usePrevious from "../../../../hooks/usePrevious";
|
||||
|
||||
export const TimeSelector: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
@ -31,6 +32,7 @@ export const TimeSelector: FC = () => {
|
|||
const { period: { end, start }, relativeTime, timezone, duration } = useTimeState();
|
||||
const dispatch = useTimeDispatch();
|
||||
const appModeEnable = getAppModeEnable();
|
||||
const prevTimezone = usePrevious(timezone);
|
||||
|
||||
const {
|
||||
value: openOptions,
|
||||
|
@ -95,8 +97,10 @@ export const TimeSelector: FC = () => {
|
|||
defaultDuration: duration,
|
||||
defaultEndInput: dateFromSeconds(end),
|
||||
});
|
||||
setDuration({ id: value.relativeTimeId, duration: value.duration, until: value.endInput });
|
||||
}, [timezone]);
|
||||
if (prevTimezone && timezone !== prevTimezone) {
|
||||
setDuration({ id: value.relativeTimeId, duration: value.duration, until: value.endInput });
|
||||
}
|
||||
}, [timezone, prevTimezone]);
|
||||
|
||||
useClickOutside(wrapperRef, (e) => {
|
||||
if (isMobile) return;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { TimeStateProvider } from "../state/time/TimeStateContext";
|
|||
import { QueryStateProvider } from "../state/query/QueryStateContext";
|
||||
import { CustomPanelStateProvider } from "../state/customPanel/CustomPanelStateContext";
|
||||
import { GraphStateProvider } from "../state/graph/GraphStateContext";
|
||||
import { TopQueriesStateProvider } from "../state/topQueries/TopQueriesStateContext";
|
||||
import { SnackbarProvider } from "./Snackbar";
|
||||
|
||||
import { combineComponents } from "../utils/combine-components";
|
||||
|
@ -15,7 +14,6 @@ const providers = [
|
|||
QueryStateProvider,
|
||||
CustomPanelStateProvider,
|
||||
GraphStateProvider,
|
||||
TopQueriesStateProvider,
|
||||
SnackbarProvider,
|
||||
DashboardsStateProvider
|
||||
];
|
||||
|
|
|
@ -6,18 +6,21 @@ import "./style.scss";
|
|||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
import { useEffect, useState } from "preact/compat";
|
||||
import { useEffect } from "preact/compat";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import CardinalityTotals, { CardinalityTotalsProps } from "../CardinalityTotals/CardinalityTotals";
|
||||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
import useStateSearchParams from "../../../hooks/useStateSearchParams";
|
||||
|
||||
const CardinalityConfigurator: FC<CardinalityTotalsProps> = (props) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
||||
const showTips = searchParams.get("tips") || "";
|
||||
const [match, setMatch] = useState(searchParams.get("match") || "");
|
||||
const [focusLabel, setFocusLabel] = useState(searchParams.get("focusLabel") || "");
|
||||
const [topN, setTopN] = useState(+(searchParams.get("topN") || 10));
|
||||
const [match, setMatch] = useStateSearchParams("", "match");
|
||||
const [focusLabel, setFocusLabel] = useStateSearchParams("", "focusLabel");
|
||||
const [topN, setTopN] = useStateSearchParams(10, "topN");
|
||||
|
||||
const errorTopN = useMemo(() => topN < 0 ? "Number must be bigger than zero" : "", [topN]);
|
||||
|
||||
|
@ -27,23 +30,16 @@ const CardinalityConfigurator: FC<CardinalityTotalsProps> = (props) => {
|
|||
};
|
||||
|
||||
const handleRunQuery = () => {
|
||||
searchParams.set("match", match);
|
||||
searchParams.set("topN", topN.toString());
|
||||
searchParams.set("focusLabel", focusLabel);
|
||||
setSearchParams(searchParams);
|
||||
setSearchParamsFromKeys({ match, topN, focusLabel });
|
||||
};
|
||||
|
||||
const handleResetQuery = () => {
|
||||
searchParams.set("match", "");
|
||||
searchParams.set("focusLabel", "");
|
||||
setSearchParams(searchParams);
|
||||
setSearchParamsFromKeys({ match: "", focusLabel: "" });
|
||||
};
|
||||
|
||||
const handleToggleTips = () => {
|
||||
const showTips = searchParams.get("tips") || "";
|
||||
if (showTips) searchParams.delete("tips");
|
||||
else searchParams.set("tips", "true");
|
||||
setSearchParams(searchParams);
|
||||
setSearchParamsFromKeys({ tips: showTips ? "" : "true" });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
TipHighNumberOfSeries,
|
||||
TipHighNumberOfValues
|
||||
} from "./CardinalityTips";
|
||||
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
|
||||
|
||||
const spinnerMessage = `Please wait while cardinality stats is calculated.
|
||||
This may take some time if the db contains big number of time series.`;
|
||||
|
@ -24,7 +25,8 @@ const spinnerMessage = `Please wait while cardinality stats is calculated.
|
|||
const CardinalityPanel: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
const showTips = searchParams.get("tips") || "";
|
||||
const match = searchParams.get("match") || "";
|
||||
const focusLabel = searchParams.get("focusLabel") || "";
|
||||
|
@ -35,14 +37,14 @@ const CardinalityPanel: FC = () => {
|
|||
|
||||
const handleFilterClick = (key: string) => (query: string) => {
|
||||
const value = queryUpdater[key]({ query, focusLabel, match });
|
||||
searchParams.set("match", value);
|
||||
const params: Record<string, string> = { match: value };
|
||||
if (key === "labelValueCountByLabelName" || key == "seriesCountByLabelName") {
|
||||
searchParams.set("focusLabel", query);
|
||||
params.focusLabel = query;
|
||||
}
|
||||
if (key == "seriesCountByFocusLabelValue") {
|
||||
searchParams.set("focusLabel", "");
|
||||
params.focusLabel = "";
|
||||
}
|
||||
setSearchParams(searchParams);
|
||||
setSearchParamsFromKeys(params);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useQueryState } from "../../../state/query/QueryStateContext";
|
|||
import { displayTypeTabs } from "../DisplayTypeSwitch";
|
||||
import { compactObject } from "../../../utils/object";
|
||||
import { useGraphState } from "../../../state/graph/GraphStateContext";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
|
||||
export const useSetQueryParams = () => {
|
||||
const { tenantId } = useAppState();
|
||||
|
@ -14,7 +14,7 @@ export const useSetQueryParams = () => {
|
|||
const { query } = useQueryState();
|
||||
const { duration, relativeTime, period: { date, step } } = useTimeState();
|
||||
const { customStep } = useGraphState();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
||||
const setSearchParamsFromState = () => {
|
||||
const params: Record<string, unknown> = {};
|
||||
|
@ -30,7 +30,7 @@ export const useSetQueryParams = () => {
|
|||
if ((step !== customStep) && customStep) params[`${group}.step_input`] = customStep;
|
||||
});
|
||||
|
||||
setSearchParams(compactObject(params) as Record<string, string>);
|
||||
setSearchParamsFromKeys(compactObject(params) as Record<string, string>);
|
||||
};
|
||||
|
||||
useEffect(setSearchParamsFromState, [tenantId, displayType, query, duration, relativeTime, date, step, customStep]);
|
||||
|
|
|
@ -26,6 +26,7 @@ import GraphTips from "../../components/Chart/GraphTips/GraphTips";
|
|||
import InstantQueryTip from "./InstantQueryTip/InstantQueryTip";
|
||||
import useBoolean from "../../hooks/useBoolean";
|
||||
import { getColumns } from "../../hooks/useSortedCategories";
|
||||
import useEventListener from "../../hooks/useEventListener";
|
||||
|
||||
const CustomPanel: FC = () => {
|
||||
const { displayType, isTracingEnabled } = useCustomPanelState();
|
||||
|
@ -100,6 +101,9 @@ const CustomPanel: FC = () => {
|
|||
customPanelDispatch({ type: "TOGGLE_TABLE_COMPACT" });
|
||||
};
|
||||
|
||||
const handleChangePopstate = () => window.location.reload();
|
||||
useEventListener("popstate", handleChangePopstate);
|
||||
|
||||
useEffect(() => {
|
||||
if (traces) {
|
||||
setTracesState([...tracesState, ...traces]);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||
import { compactObject } from "../../../utils/object";
|
||||
import { useTimeState } from "../../../state/time/TimeStateContext";
|
||||
import { useGraphState } from "../../../state/graph/GraphStateContext";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
|
||||
interface queryProps {
|
||||
job: string
|
||||
|
@ -14,7 +14,7 @@ interface queryProps {
|
|||
export const useSetQueryParams = ({ job, instance, metrics, size }: queryProps) => {
|
||||
const { duration, relativeTime, period: { date } } = useTimeState();
|
||||
const { customStep } = useGraphState();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
||||
const setSearchParamsFromState = () => {
|
||||
const params = compactObject({
|
||||
|
@ -28,7 +28,7 @@ export const useSetQueryParams = ({ job, instance, metrics, size }: queryProps)
|
|||
metrics
|
||||
});
|
||||
|
||||
setSearchParams(params);
|
||||
setSearchParamsFromKeys(params);
|
||||
};
|
||||
|
||||
useEffect(setSearchParamsFromState, [duration, relativeTime, date, customStep, job, instance, metrics, size]);
|
||||
|
|
|
@ -2,12 +2,12 @@ import { useEffect } from "react";
|
|||
import { compactObject } from "../../../utils/object";
|
||||
import { useTimeState } from "../../../state/time/TimeStateContext";
|
||||
import { useGraphState } from "../../../state/graph/GraphStateContext";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
|
||||
export const useSetQueryParams = () => {
|
||||
const { duration, relativeTime, period: { date } } = useTimeState();
|
||||
const { customStep } = useGraphState();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
||||
const setSearchParamsFromState = () => {
|
||||
const params = compactObject({
|
||||
|
@ -17,7 +17,7 @@ export const useSetQueryParams = () => {
|
|||
["g0.relative_time"]: relativeTime
|
||||
});
|
||||
|
||||
setSearchParams(params);
|
||||
setSearchParamsFromKeys(params);
|
||||
};
|
||||
|
||||
useEffect(setSearchParamsFromState, [duration, relativeTime, date, customStep]);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { FC, useEffect } from "preact/compat";
|
||||
import "./style.scss";
|
||||
import TextField from "../../components/Main/TextField/TextField";
|
||||
import { useState } from "react";
|
||||
import Button from "../../components/Main/Button/Button";
|
||||
import { InfoIcon, PlayIcon, WikiIcon } from "../../components/Main/Icons";
|
||||
import "./style.scss";
|
||||
|
@ -9,6 +8,7 @@ import { useRelabelDebug } from "./hooks/useRelabelDebug";
|
|||
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||
import Alert from "../../components/Main/Alert/Alert";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
||||
|
||||
const example = {
|
||||
config: `- if: '{bar_label=~"b.*"}'
|
||||
|
@ -27,8 +27,8 @@ const Relabel: FC = () => {
|
|||
|
||||
const { data, loading, error, fetchData } = useRelabelDebug();
|
||||
|
||||
const [config, setConfig] = useState("");
|
||||
const [labels, setLabels] = useState("");
|
||||
const [config, setConfig] = useStateSearchParams("", "config");
|
||||
const [labels, setLabels] = useStateSearchParams("", "labels");
|
||||
|
||||
const handleChangeConfig = (val: string) => {
|
||||
setConfig(val);
|
||||
|
|
|
@ -6,12 +6,12 @@ import classNames from "classnames";
|
|||
import { ArrowDropDownIcon, CopyIcon, PlayCircleOutlineIcon } from "../../../components/Main/Icons";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
import { useSnack } from "../../../contexts/Snackbar";
|
||||
import { Link } from "react-router-dom";
|
||||
import router from "../../../router";
|
||||
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
|
||||
|
||||
const TopQueryTable:FC<TopQueryPanelProps> = ({ rows, columns, defaultOrderBy }) => {
|
||||
const { showInfoMessage } = useSnack();
|
||||
const copyToClipboard = useCopyToClipboard();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<keyof TopQuery>(defaultOrderBy || "count");
|
||||
const [orderDir, setOrderDir] = useState<"asc" | "desc">("desc");
|
||||
|
@ -28,10 +28,8 @@ const TopQueryTable:FC<TopQueryPanelProps> = ({ rows, columns, defaultOrderBy })
|
|||
onSortHandler(col);
|
||||
};
|
||||
|
||||
const createCopyHandler = ({ query }: TopQuery) => () => {
|
||||
// TODO add useCopyToClipboard after merge https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4145
|
||||
navigator.clipboard.writeText(query);
|
||||
showInfoMessage({ text: "Query has been copied", type: "success" });
|
||||
const createCopyHandler = ({ query }: TopQuery) => async () => {
|
||||
await copyToClipboard(query, "Query has been copied");
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { ErrorTypes } from "../../../types";
|
||||
import { useAppState } from "../../../state/common/StateContext";
|
||||
import { useMemo } from "preact/compat";
|
||||
import { useMemo, useState } from "preact/compat";
|
||||
import { getTopQueries } from "../../../api/top-queries";
|
||||
import { TopQueriesData } from "../../../types";
|
||||
import { useTopQueriesState } from "../../../state/topQueries/TopQueriesStateContext";
|
||||
import { getDurationFromMilliseconds } from "../../../utils/time";
|
||||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
|
||||
export const useFetchTopQueries = () => {
|
||||
interface useFetchTopQueriesProps {
|
||||
topN: number;
|
||||
maxLifetime: string;
|
||||
}
|
||||
|
||||
export const useFetchTopQueries = ({ topN, maxLifetime }: useFetchTopQueriesProps) => {
|
||||
const { serverUrl } = useAppState();
|
||||
const { topN, maxLifetime, runQuery } = useTopQueriesState();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
||||
const [data, setData] = useState<TopQueriesData | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
@ -19,6 +23,7 @@ export const useFetchTopQueries = () => {
|
|||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setSearchParamsFromKeys({ topN, maxLifetime });
|
||||
try {
|
||||
const response = await fetch(fetchUrl);
|
||||
const resp = await response.json();
|
||||
|
@ -42,13 +47,10 @@ export const useFetchTopQueries = () => {
|
|||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [runQuery]);
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading
|
||||
loading,
|
||||
fetch: fetchData
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { useTopQueriesState } from "../../../state/topQueries/TopQueriesStateContext";
|
||||
import { useEffect } from "react";
|
||||
import { compactObject } from "../../../utils/object";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export const useSetQueryParams = () => {
|
||||
const { topN, maxLifetime } = useTopQueriesState();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
|
||||
const setSearchParamsFromState = () => {
|
||||
const params = compactObject({
|
||||
topN: String(topN),
|
||||
maxLifetime: maxLifetime,
|
||||
});
|
||||
|
||||
setSearchParams(params);
|
||||
};
|
||||
|
||||
useEffect(setSearchParamsFromState, [topN, maxLifetime]);
|
||||
useEffect(setSearchParamsFromState, []);
|
||||
};
|
|
@ -2,12 +2,10 @@ import React, { FC, useEffect, useMemo, KeyboardEvent } from "react";
|
|||
import { useFetchTopQueries } from "./hooks/useFetchTopQueries";
|
||||
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||
import TopQueryPanel from "./TopQueryPanel/TopQueryPanel";
|
||||
import { useTopQueriesDispatch, useTopQueriesState } from "../../state/topQueries/TopQueriesStateContext";
|
||||
import { formatPrettyNumber } from "../../utils/uplot/helpers";
|
||||
import { isSupportedDuration } from "../../utils/time";
|
||||
import dayjs from "dayjs";
|
||||
import { TopQueryStats } from "../../types";
|
||||
import { useSetQueryParams } from "./hooks/useSetQueryParams";
|
||||
import Button from "../../components/Main/Button/Button";
|
||||
import { PlayIcon } from "../../components/Main/Icons";
|
||||
import TextField from "../../components/Main/TextField/TextField";
|
||||
|
@ -16,15 +14,17 @@ import Tooltip from "../../components/Main/Tooltip/Tooltip";
|
|||
import "./style.scss";
|
||||
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
||||
|
||||
const exampleDuration = "30ms, 15s, 3d4h, 1y2w";
|
||||
|
||||
const TopQueries: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const { data, error, loading } = useFetchTopQueries();
|
||||
const { topN, maxLifetime } = useTopQueriesState();
|
||||
const topQueriesDispatch = useTopQueriesDispatch();
|
||||
useSetQueryParams();
|
||||
|
||||
const [topN, setTopN] = useStateSearchParams(10, "topN");
|
||||
const [maxLifetime, setMaxLifetime] = useStateSearchParams("10m", "maxLifetime");
|
||||
|
||||
const { data, error, loading, fetch } = useFetchTopQueries({ topN, maxLifetime });
|
||||
|
||||
const maxLifetimeValid = useMemo(() => {
|
||||
const durItems = maxLifetime.trim().split(" ");
|
||||
|
@ -48,27 +48,32 @@ const TopQueries: FC = () => {
|
|||
};
|
||||
|
||||
const onTopNChange = (value: string) => {
|
||||
topQueriesDispatch({ type: "SET_TOP_N", payload: +value });
|
||||
setTopN(+value);
|
||||
};
|
||||
|
||||
const onMaxLifetimeChange = (value: string) => {
|
||||
topQueriesDispatch({ type: "SET_MAX_LIFE_TIME", payload: value });
|
||||
};
|
||||
|
||||
const onApplyQuery = () => {
|
||||
topQueriesDispatch({ type: "SET_RUN_QUERY" });
|
||||
setMaxLifetime(value);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") onApplyQuery();
|
||||
if (e.key === "Enter") fetch();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
if (!topN) topQueriesDispatch({ type: "SET_TOP_N", payload: +data.topN });
|
||||
if (!maxLifetime) topQueriesDispatch({ type: "SET_MAX_LIFE_TIME", payload: data.maxLifetime });
|
||||
if (!topN) setTopN(+data.topN);
|
||||
if (!maxLifetime) setMaxLifetime(data.maxLifetime);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch();
|
||||
window.addEventListener("popstate", fetch);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("popstate", fetch);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
|
@ -130,7 +135,7 @@ const TopQueries: FC = () => {
|
|||
<div className="vm-top-queries-controls-bottom__button">
|
||||
<Button
|
||||
startIcon={<PlayIcon/>}
|
||||
onClick={onApplyQuery}
|
||||
onClick={fetch}
|
||||
>
|
||||
Execute
|
||||
</Button>
|
||||
|
|
|
@ -9,7 +9,6 @@ import { CloseIcon } from "../../components/Main/Icons";
|
|||
import Modal from "../../components/Main/Modal/Modal";
|
||||
import JsonForm from "./JsonForm/JsonForm";
|
||||
import { ErrorTypes } from "../../types";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import useDropzone from "../../hooks/useDropzone";
|
||||
import TraceUploadButtons from "./TraceUploadButtons/TraceUploadButtons";
|
||||
import useBoolean from "../../hooks/useBoolean";
|
||||
|
@ -18,7 +17,6 @@ const TracePage: FC = () => {
|
|||
const [tracesState, setTracesState] = useState<Trace[]>([]);
|
||||
const [errors, setErrors] = useState<{filename: string, text: string}[]>([]);
|
||||
const hasTraces = useMemo(() => !!tracesState.length, [tracesState]);
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
|
||||
const {
|
||||
value: openModal,
|
||||
|
@ -77,11 +75,6 @@ const TracePage: FC = () => {
|
|||
handleCloseError(index);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({});
|
||||
}, []);
|
||||
|
||||
|
||||
const { files, dragging } = useDropzone();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import React, { createContext, FC, useContext, useMemo, useReducer } from "preact/compat";
|
||||
import { Action, TopQueriesState, initialState, reducer } from "./reducer";
|
||||
import { Dispatch } from "react";
|
||||
type TopQueriesStateContextType = { state: TopQueriesState, dispatch: Dispatch<Action> };
|
||||
|
||||
export const TopQueriesStateContext = createContext<TopQueriesStateContextType>({} as TopQueriesStateContextType);
|
||||
|
||||
export const useTopQueriesState = (): TopQueriesState => useContext(TopQueriesStateContext).state;
|
||||
export const useTopQueriesDispatch = (): Dispatch<Action> => useContext(TopQueriesStateContext).dispatch;
|
||||
|
||||
export const TopQueriesStateProvider: FC = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const contextValue = useMemo(() => {
|
||||
return { state, dispatch };
|
||||
}, [state, dispatch]);
|
||||
|
||||
return <TopQueriesStateContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</TopQueriesStateContext.Provider>;
|
||||
};
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import { getQueryStringValue } from "../../utils/query-string";
|
||||
|
||||
export interface TopQueriesState {
|
||||
maxLifetime: string,
|
||||
topN: number | null,
|
||||
runQuery: number
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| { type: "SET_TOP_N", payload: number | null }
|
||||
| { type: "SET_MAX_LIFE_TIME", payload: string }
|
||||
| { type: "SET_RUN_QUERY" }
|
||||
|
||||
|
||||
export const initialState: TopQueriesState = {
|
||||
topN: getQueryStringValue("topN", null) as number,
|
||||
maxLifetime: getQueryStringValue("maxLifetime", "") as string,
|
||||
runQuery: 0
|
||||
};
|
||||
|
||||
export function reducer(state: TopQueriesState, action: Action): TopQueriesState {
|
||||
switch (action.type) {
|
||||
case "SET_TOP_N":
|
||||
return {
|
||||
...state,
|
||||
topN: action.payload
|
||||
};
|
||||
case "SET_MAX_LIFE_TIME":
|
||||
return {
|
||||
...state,
|
||||
maxLifetime: action.payload
|
||||
};
|
||||
case "SET_RUN_QUERY":
|
||||
return {
|
||||
...state,
|
||||
runQuery: state.runQuery + 1
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { getAppModeParams } from "./app-mode";
|
||||
import { replaceTenantId } from "./tenants";
|
||||
const { REACT_APP_LOGS } = process.env;
|
||||
|
||||
export const getDefaultServer = (tenantId?: string): string => {
|
||||
|
@ -9,8 +10,3 @@ export const getDefaultServer = (tenantId?: string): string => {
|
|||
if (tenantId) return replaceTenantId(url, tenantId);
|
||||
return url;
|
||||
};
|
||||
|
||||
export const replaceTenantId = (serverUrl: string, tenantId: string) => {
|
||||
const regexp = /(\/select\/)(\d+|\d.+)(\/)(.+)/;
|
||||
return serverUrl.replace(regexp, `$1${tenantId}/$4`);
|
||||
};
|
||||
|
|
|
@ -12,5 +12,5 @@ export function filterObject<T extends object>(
|
|||
}
|
||||
|
||||
export function compactObject<T extends object>(obj: T) {
|
||||
return filterObject(obj, (entry) => !!entry[1]);
|
||||
return filterObject(obj, (entry) => !!entry[1] || typeof entry[1] === "number");
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
|||
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): properly return error from [/api/v1/query](https://docs.victoriametrics.com/keyConcepts.html#instant-query) and [/api/v1/query_range](https://docs.victoriametrics.com/keyConcepts.html#range-query) at `vmselect` when the `-search.maxSamplesPerQuery` or `-search.maxSamplesPerSeries` [limit](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#resource-usage-limits) is exceeded. Previously incomplete response could be returned without the error if `vmselect` runs with `-replicationFactor` greater than 1. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4472).
|
||||
* BUGFIX: [storage](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html): Properly creates `parts.json` after migration from versions below `v1.90.0. It must fix errors on start-up after unclean shutdown. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4336) for details.
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix a memory leak issue associated with chart updates. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4455).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix application routing issues and problems with manual URL changes. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4408).
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): retry all errors except 4XX status codes while pushing via remote-write to the remote storage. Previously, errors like broken connection could prevent vmalert from retrying the request.
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly interrupt retry attempts on vmalert shutdown. Before, vmalert could have waited for all retries to finish for shutdown.
|
||||
|
||||
|
|
Loading…
Reference in a new issue