mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-10 15:14:09 +00:00
vmui: add storage for query history (#5022)
* vmui: add storage for query history * docs/vmui: add storage for query history
This commit is contained in:
parent
a4bd73ec7e
commit
f39045eca6
13 changed files with 369 additions and 158 deletions
|
@ -430,3 +430,23 @@ export const ListIcon = () => (
|
|||
<path d="M3 14h4v-4H3v4zm0 5h4v-4H3v4zM3 9h4V5H3v4zm5 5h13v-4H8v4zm0 5h13v-4H8v4zM8 5v4h13V5H8z"></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const StarBorderIcon = () => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="m22 9.24-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const StarIcon = () => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path>
|
||||
</svg>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { GraphSize, SeriesItemStats } from "../types";
|
||||
|
||||
export const MAX_QUERY_FIELDS = 4;
|
||||
export const MAX_QUERIES_HISTORY = 25;
|
||||
export const DEFAULT_MAX_SERIES = {
|
||||
table: 100,
|
||||
chart: 20,
|
||||
|
|
|
@ -125,7 +125,7 @@ export const useFetchQuery = ({
|
|||
}
|
||||
|
||||
isHistogramResult = isDisplayChart && isHistogramData(resp.data.result);
|
||||
seriesLimit = isHistogramResult ? Infinity : Math.max(totalLength, defaultLimit);
|
||||
seriesLimit = isHistogramResult ? Infinity : defaultLimit;
|
||||
const freeTempSize = seriesLimit - tempData.length;
|
||||
resp.data.result.slice(0, freeTempSize).forEach((d: MetricBase) => {
|
||||
d.group = counter;
|
||||
|
@ -140,7 +140,7 @@ export const useFetchQuery = ({
|
|||
counter++;
|
||||
}
|
||||
|
||||
const limitText = `Showing ${seriesLimit} series out of ${totalLength} series due to performance reasons. Please narrow down the query, so it returns less series`;
|
||||
const limitText = `Showing ${tempData.length} series out of ${totalLength} series due to performance reasons. Please narrow down the query, so it returns less series`;
|
||||
setWarning(totalLength > seriesLimit ? limitText : "");
|
||||
isDisplayChart ? setGraphData(tempData as MetricResult[]) : setLiveData(tempData as InstantMetricResult[]);
|
||||
setTraces(tempTraces);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC, StateUpdater, useEffect, useState } from "preact/compat";
|
|||
import QueryEditor from "../../../components/Configurators/QueryEditor/QueryEditor";
|
||||
import AdditionalSettings from "../../../components/Configurators/AdditionalSettings/AdditionalSettings";
|
||||
import usePrevious from "../../../hooks/usePrevious";
|
||||
import { MAX_QUERY_FIELDS } from "../../../constants/graph";
|
||||
import { MAX_QUERIES_HISTORY, MAX_QUERY_FIELDS } from "../../../constants/graph";
|
||||
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
||||
import { useTimeDispatch } from "../../../state/time/TimeStateContext";
|
||||
import {
|
||||
|
@ -22,7 +22,7 @@ import { arrayEquals } from "../../../utils/array";
|
|||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import { QueryStats } from "../../../api/types";
|
||||
import { usePrettifyQuery } from "./hooks/usePrettifyQuery";
|
||||
import QueryHistoryList from "../QueryHistory/QueryHistoryList";
|
||||
import QueryHistory from "../QueryHistory/QueryHistory";
|
||||
|
||||
export interface QueryConfiguratorProps {
|
||||
queryErrors: string[];
|
||||
|
@ -66,7 +66,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
|||
const newValues = !queryEqual && q ? [...h.values, q] : h.values;
|
||||
|
||||
// limit the history
|
||||
if (newValues.length > 25) newValues.shift();
|
||||
if (newValues.length > MAX_QUERIES_HISTORY) newValues.shift();
|
||||
|
||||
return {
|
||||
index: h.values.length - Number(queryEqual),
|
||||
|
@ -243,10 +243,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
|||
<div className="vm-query-configurator-settings">
|
||||
<AdditionalSettings/>
|
||||
<div className="vm-query-configurator-settings__buttons">
|
||||
<QueryHistoryList
|
||||
history={queryHistory}
|
||||
handleSelectQuery={handleSelectHistory}
|
||||
/>
|
||||
<QueryHistory handleSelectQuery={handleSelectHistory}/>
|
||||
{stateQuery.length < MAX_QUERY_FIELDS && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
import React, { FC, useEffect, useMemo, useState } from "preact/compat";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import { ClockIcon, DeleteIcon } from "../../../components/Main/Icons";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import Modal from "../../../components/Main/Modal/Modal";
|
||||
import Tabs from "../../../components/Main/Tabs/Tabs";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import useEventListener from "../../../hooks/useEventListener";
|
||||
import { useQueryState } from "../../../state/query/QueryStateContext";
|
||||
import { getQueriesFromStorage } from "./utils";
|
||||
import QueryHistoryItem from "./QueryHistoryItem";
|
||||
import classNames from "classnames";
|
||||
import "./style.scss";
|
||||
import { saveToStorage } from "../../../utils/storage";
|
||||
import { arrayEquals } from "../../../utils/array";
|
||||
|
||||
interface Props {
|
||||
handleSelectQuery: (query: string, index: number) => void
|
||||
}
|
||||
|
||||
export const HistoryTabTypes = {
|
||||
session: "session",
|
||||
storage: "saved",
|
||||
favorite: "favorite",
|
||||
};
|
||||
|
||||
export const historyTabs = [
|
||||
{ label: "Session history", value: HistoryTabTypes.session },
|
||||
{ label: "Saved history", value: HistoryTabTypes.storage },
|
||||
{ label: "Favorite queries", value: HistoryTabTypes.favorite },
|
||||
];
|
||||
|
||||
const QueryHistory: FC<Props> = ({ handleSelectQuery }) => {
|
||||
const { queryHistory: historyState } = useQueryState();
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const {
|
||||
value: openModal,
|
||||
setTrue: handleOpenModal,
|
||||
setFalse: handleCloseModal,
|
||||
} = useBoolean(false);
|
||||
|
||||
const [activeTab, setActiveTab] = useState(historyTabs[0].value);
|
||||
const [historyStorage, setHistoryStorage] = useState(getQueriesFromStorage("QUERY_HISTORY"));
|
||||
const [historyFavorites, setHistoryFavorites] = useState(getQueriesFromStorage("QUERY_FAVORITES"));
|
||||
|
||||
const historySession = useMemo(() => {
|
||||
return historyState.map((h) => h.values.filter(q => q).reverse());
|
||||
}, [historyState]);
|
||||
|
||||
const list = useMemo(() => {
|
||||
switch (activeTab) {
|
||||
case HistoryTabTypes.favorite:
|
||||
return historyFavorites;
|
||||
case HistoryTabTypes.storage:
|
||||
return historyStorage;
|
||||
default:
|
||||
return historySession;
|
||||
}
|
||||
}, [activeTab, historyFavorites, historyStorage, historySession]);
|
||||
|
||||
const isNoData = list?.every(s => !s.length);
|
||||
|
||||
const noDataText = useMemo(() => {
|
||||
switch (activeTab) {
|
||||
case HistoryTabTypes.favorite:
|
||||
return "Favorites queries are empty.\nTo see your favorites, mark a query as a favorite.";
|
||||
default:
|
||||
return "Query history is empty.\nTo see the history, please make a query.";
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
const handleRunQuery = (group: number) => (value: string) => {
|
||||
handleSelectQuery(value, group);
|
||||
handleCloseModal();
|
||||
};
|
||||
|
||||
const handleToggleFavorite = (value: string, isFavorite: boolean) => {
|
||||
setHistoryFavorites((prev) => {
|
||||
const values = prev[0] || [];
|
||||
if (isFavorite) return [values.filter(v => v !== value)];
|
||||
if (!isFavorite && !values.includes(value)) return [[...values, value]];
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const updateStageHistory = () => {
|
||||
setHistoryStorage(getQueriesFromStorage("QUERY_HISTORY"));
|
||||
setHistoryFavorites(getQueriesFromStorage("QUERY_FAVORITES"));
|
||||
};
|
||||
|
||||
const handleClearStorage = () => {
|
||||
saveToStorage("QUERY_HISTORY", "");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const nextValue = historyFavorites[0] || [];
|
||||
const prevValue = getQueriesFromStorage("QUERY_FAVORITES")[0] || [];
|
||||
const isEqual = arrayEquals(nextValue, prevValue);
|
||||
if (isEqual) return;
|
||||
saveToStorage("QUERY_FAVORITES", JSON.stringify(historyFavorites));
|
||||
}, [historyFavorites]);
|
||||
|
||||
useEventListener("storage", updateStageHistory);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={"Show history"}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="text"
|
||||
onClick={handleOpenModal}
|
||||
startIcon={<ClockIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
{openModal && (
|
||||
<Modal
|
||||
title={"Query history"}
|
||||
onClose={handleCloseModal}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-query-history": true,
|
||||
"vm-query-history_mobile": isMobile,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-query-history__tabs": true,
|
||||
"vm-section-header__tabs": true,
|
||||
"vm-query-history__tabs_mobile": isMobile,
|
||||
})}
|
||||
>
|
||||
<Tabs
|
||||
activeItem={activeTab}
|
||||
items={historyTabs}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-query-history-list">
|
||||
{isNoData && <div className="vm-query-history-list__no-data">{noDataText}</div>}
|
||||
{list.map((queries, group) => (
|
||||
<div key={group}>
|
||||
{list.length > 1 && (
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-query-history-list__group-title": true,
|
||||
"vm-query-history-list__group-title_first": group === 0,
|
||||
})}
|
||||
>
|
||||
Query {group + 1}
|
||||
</div>
|
||||
)}
|
||||
{queries.map((query, index) => (
|
||||
<QueryHistoryItem
|
||||
key={index}
|
||||
query={query}
|
||||
favorites={historyFavorites.flat()}
|
||||
onRun={handleRunQuery(group)}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
{(activeTab === HistoryTabTypes.storage) && !isNoData && (
|
||||
<div className="vm-query-history-footer">
|
||||
<Button
|
||||
color="error"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<DeleteIcon/>}
|
||||
onClick={handleClearStorage}
|
||||
>
|
||||
clear history
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryHistory;
|
|
@ -0,0 +1,65 @@
|
|||
import React, { FC, useMemo } from "preact/compat";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import { CopyIcon, PlayCircleOutlineIcon, StarBorderIcon, StarIcon } from "../../../components/Main/Icons";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
|
||||
import "./style.scss";
|
||||
|
||||
interface Props {
|
||||
query: string;
|
||||
favorites: string[];
|
||||
onRun: (query: string) => void;
|
||||
onToggleFavorite: (query: string, isFavorite: boolean) => void;
|
||||
}
|
||||
|
||||
const QueryHistoryItem: FC<Props> = ({ query, favorites, onRun, onToggleFavorite }) => {
|
||||
const copyToClipboard = useCopyToClipboard();
|
||||
const isFavorite = useMemo(() => favorites.includes(query), [query, favorites]);
|
||||
|
||||
const handleCopyQuery = async () => {
|
||||
await copyToClipboard(query, "Query has been copied");
|
||||
};
|
||||
|
||||
const handleRunQuery = () => {
|
||||
onRun(query);
|
||||
};
|
||||
|
||||
const handleToggleFavorite = () => {
|
||||
onToggleFavorite(query, isFavorite);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vm-query-history-item">
|
||||
<span className="vm-query-history-item__value">{query}</span>
|
||||
<div className="vm-query-history-item__buttons">
|
||||
<Tooltip title={"Execute query"}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
onClick={handleRunQuery}
|
||||
startIcon={<PlayCircleOutlineIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={"Copy query"}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
onClick={handleCopyQuery}
|
||||
startIcon={<CopyIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={isFavorite ? "Remove Favorite" : "Add to Favorites"}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
color={isFavorite ? "warning" : "primary"}
|
||||
onClick={handleToggleFavorite}
|
||||
startIcon={isFavorite ? <StarIcon/> : <StarBorderIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryHistoryItem;
|
|
@ -1,114 +0,0 @@
|
|||
import React, { FC, useMemo } from "preact/compat";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import { ClockIcon, CopyIcon, PlayCircleOutlineIcon } from "../../../components/Main/Icons";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
import { QueryHistory } from "../../../state/query/reducer";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import Modal from "../../../components/Main/Modal/Modal";
|
||||
import "./style.scss";
|
||||
import Tabs from "../../../components/Main/Tabs/Tabs";
|
||||
import { useState } from "react";
|
||||
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface QueryHistoryProps {
|
||||
history: QueryHistory[];
|
||||
handleSelectQuery: (query: string, index: number) => void
|
||||
}
|
||||
|
||||
const QueryHistoryList: FC<QueryHistoryProps> = ({ history, handleSelectQuery }) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const copyToClipboard = useCopyToClipboard();
|
||||
const {
|
||||
value: openModal,
|
||||
setTrue: handleOpenModal,
|
||||
setFalse: handleCloseModal,
|
||||
} = useBoolean(false);
|
||||
|
||||
const [activeTab, setActiveTab] = useState("0");
|
||||
const tabs = useMemo(() => history.map((item, i) => ({
|
||||
value: `${i}`,
|
||||
label: `Query ${i+1}`,
|
||||
})), [history]);
|
||||
|
||||
const queries = useMemo(() => {
|
||||
const historyItem = history[+activeTab];
|
||||
return historyItem ? historyItem.values.filter(q => q).reverse() : [];
|
||||
}, [activeTab, history]);
|
||||
|
||||
const handleCopyQuery = (value: string) => async () => {
|
||||
await copyToClipboard(value, "Query has been copied");
|
||||
};
|
||||
|
||||
const handleRunQuery = (value: string, index: number) => () => {
|
||||
handleSelectQuery(value, index);
|
||||
handleCloseModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={"Show history"}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="text"
|
||||
onClick={handleOpenModal}
|
||||
startIcon={<ClockIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
{openModal && (
|
||||
<Modal
|
||||
title={"Query history"}
|
||||
onClose={handleCloseModal}
|
||||
>
|
||||
<div className="vm-query-history">
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-query-history__tabs": true,
|
||||
"vm-section-header__tabs": true,
|
||||
"vm-query-history__tabs_mobile": isMobile,
|
||||
})}
|
||||
>
|
||||
<Tabs
|
||||
activeItem={activeTab}
|
||||
items={tabs}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-query-history-list">
|
||||
{queries.map((query, index) => (
|
||||
<div
|
||||
className="vm-query-history-list-item"
|
||||
key={index}
|
||||
>
|
||||
<span className="vm-query-history-list-item__value">{query}</span>
|
||||
<div className="vm-query-history-list-item__buttons">
|
||||
<Tooltip title={"Execute query"}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
onClick={handleRunQuery(query, +activeTab)}
|
||||
startIcon={<PlayCircleOutlineIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={"Copy query"}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
onClick={handleCopyQuery(query)}
|
||||
startIcon={<CopyIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryHistoryList;
|
|
@ -2,11 +2,17 @@
|
|||
|
||||
.vm-query-history {
|
||||
max-width: 80vw;
|
||||
min-width: 40vw;
|
||||
min-width: 500px;
|
||||
|
||||
&_mobile {
|
||||
max-width: 100vw;
|
||||
min-width: 100vw;
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
margin: (-$padding-medium) (-$padding-medium) 0;
|
||||
padding: 0 $padding-medium;
|
||||
padding: 0 $padding-small;
|
||||
border-bottom: $border-divider;
|
||||
|
||||
&_mobile {
|
||||
margin: (-$padding-global) (-$padding-medium) 0;
|
||||
|
@ -17,19 +23,37 @@
|
|||
display: grid;
|
||||
align-items: flex-start;
|
||||
|
||||
&__group-title {
|
||||
font-weight: bold;
|
||||
margin: 0 (-$padding-medium) 0;
|
||||
padding: $padding-medium $padding-global $padding-small;
|
||||
|
||||
&_first {
|
||||
padding-top: $padding-global;
|
||||
}
|
||||
}
|
||||
|
||||
&__no-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $padding-large $padding-global;
|
||||
color: $color-text-secondary;
|
||||
text-align: center;
|
||||
line-height: $font-size-large;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: $padding-small;
|
||||
align-items: center;
|
||||
margin: 0 (-$padding-medium) 0;
|
||||
padding: $padding-small calc($padding-medium + $padding-small);
|
||||
padding: $padding-small $padding-medium;
|
||||
border-bottom: $border-divider;
|
||||
|
||||
&:first-child {
|
||||
border-top: $border-divider;
|
||||
}
|
||||
|
||||
&__value {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
|
@ -38,8 +62,12 @@
|
|||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
gap: $padding-small;
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: $padding-medium;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { getFromStorage, saveToStorage, StorageKeys } from "../../../utils/storage";
|
||||
import { QueryHistoryType } from "../../../state/query/reducer";
|
||||
import { MAX_QUERIES_HISTORY, MAX_QUERY_FIELDS } from "../../../constants/graph";
|
||||
|
||||
export const getQueriesFromStorage = (key: StorageKeys) => {
|
||||
const list = getFromStorage(key) as string;
|
||||
return list ? JSON.parse(list) as string[][] : [];
|
||||
};
|
||||
|
||||
export const setQueriesToStorage = (history: QueryHistoryType[]) => {
|
||||
// For localStorage, avoid splitting into query fields because when working from multiple tabs can cause confusion.
|
||||
// For convenience, we maintain the original structure of `string[][]`
|
||||
const lastValues = history.map(h => h.values[h.index]);
|
||||
const storageValues = getQueriesFromStorage("QUERY_HISTORY");
|
||||
if (!storageValues[0]) storageValues[0] = [];
|
||||
|
||||
const values = storageValues[0];
|
||||
const TOTAL_LIMIT = MAX_QUERIES_HISTORY * MAX_QUERY_FIELDS;
|
||||
|
||||
lastValues.forEach((v) => {
|
||||
const already = values.includes(v);
|
||||
if (!already && v) values.unshift(v);
|
||||
if (values.length > TOTAL_LIMIT) values.shift();
|
||||
});
|
||||
|
||||
saveToStorage("QUERY_HISTORY", JSON.stringify(storageValues));
|
||||
};
|
|
@ -27,7 +27,7 @@ export const initialCustomPanelState: CustomPanelState = {
|
|||
displayType: (displayType?.value || "chart") as DisplayType,
|
||||
nocache: false,
|
||||
isTracingEnabled: false,
|
||||
seriesLimits: limitsStorage ? JSON.parse(getFromStorage("SERIES_LIMITS") as string) : DEFAULT_MAX_SERIES,
|
||||
seriesLimits: limitsStorage ? JSON.parse(limitsStorage) : DEFAULT_MAX_SERIES,
|
||||
tableCompact: getFromStorage("TABLE_COMPACT") as boolean || false,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import { getFromStorage, saveToStorage } from "../../utils/storage";
|
||||
import { getQueryArray } from "../../utils/query-string";
|
||||
import { setQueriesToStorage } from "../../pages/CustomPanel/QueryHistory/utils";
|
||||
|
||||
export interface QueryHistory {
|
||||
export interface QueryHistoryType {
|
||||
index: number;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface QueryState {
|
||||
query: string[];
|
||||
queryHistory: QueryHistory[];
|
||||
queryHistory: QueryHistoryType[];
|
||||
autocomplete: boolean;
|
||||
|
||||
}
|
||||
|
||||
export type QueryAction =
|
||||
| { type: "SET_QUERY", payload: string[] }
|
||||
| { type: "SET_QUERY_HISTORY_BY_INDEX", payload: {value: QueryHistory, queryNumber: number} }
|
||||
| { type: "SET_QUERY_HISTORY", payload: QueryHistory[] }
|
||||
| { type: "SET_QUERY_HISTORY_BY_INDEX", payload: {value: QueryHistoryType, queryNumber: number} }
|
||||
| { type: "SET_QUERY_HISTORY", payload: QueryHistoryType[] }
|
||||
| { type: "TOGGLE_AUTOCOMPLETE"}
|
||||
|
||||
const query = getQueryArray();
|
||||
|
@ -34,6 +35,7 @@ export function reducer(state: QueryState, action: QueryAction): QueryState {
|
|||
query: action.payload.map(q => q)
|
||||
};
|
||||
case "SET_QUERY_HISTORY":
|
||||
setQueriesToStorage(action.payload);
|
||||
return {
|
||||
...state,
|
||||
queryHistory: action.payload
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
export type StorageKeys = "BASIC_AUTH_DATA"
|
||||
| "BEARER_AUTH_DATA"
|
||||
| "AUTH_TYPE"
|
||||
| "AUTOCOMPLETE"
|
||||
export type StorageKeys = "AUTOCOMPLETE"
|
||||
| "NO_CACHE"
|
||||
| "QUERY_TRACING"
|
||||
| "SERIES_LIMITS"
|
||||
|
@ -10,6 +7,8 @@ export type StorageKeys = "BASIC_AUTH_DATA"
|
|||
| "THEME"
|
||||
| "LOGS_LIMIT"
|
||||
| "EXPLORE_METRICS_TIPS"
|
||||
| "QUERY_HISTORY"
|
||||
| "QUERY_FAVORITES"
|
||||
|
||||
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record<string, unknown>): void => {
|
||||
if (value) {
|
||||
|
@ -36,6 +35,3 @@ export const getFromStorage = (key: StorageKeys): undefined | boolean | string |
|
|||
};
|
||||
|
||||
export const removeFromStorage = (keys: StorageKeys[]): void => keys.forEach(k => window.localStorage.removeItem(k));
|
||||
|
||||
export const authKeys: StorageKeys[] = ["BASIC_AUTH_DATA", "BEARER_AUTH_DATA"];
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ The sandbox cluster installation is running under the constant load generated by
|
|||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): improve accessibility score to 100 according to [Google's Lighthouse](https://developer.chrome.com/docs/lighthouse/accessibility/) tests.
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): organize `min`, `max`, `median` values on the chart legend and tooltips for better visibility.
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add explanation about [cardinality explorer](https://docs.victoriametrics.com/#cardinality-explorer) statistic inaccuracy in VictoriaMetrics cluster. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3070).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add storage of query history in `localStorage`. See [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5022).
|
||||
* FEATURE: dashboards: provide copies of Grafana dashboards alternated with VictoriaMetrics datasource at [dashboards/vm](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/dashboards/vm).
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth.html): added ability to set, override and clear request and response headers on a per-user and per-path basis. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4825) and [these docs](https://docs.victoriametrics.com/vmauth.html#auth-config) for details.
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth.html): add ability to retry requests to the [remaining backends](https://docs.victoriametrics.com/vmauth.html#load-balancing) if they return response status codes specified in the `retry_status_codes` list. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4893).
|
||||
|
|
Loading…
Reference in a new issue