From f5521ce4d469a8027fba362f998e4c8cae8bdb33 Mon Sep 17 00:00:00 2001 From: Yury Molodov <yurymolodov@gmail.com> Date: Mon, 2 Oct 2023 21:41:03 +0200 Subject: [PATCH] vmui: add storage for query history (#5022) * vmui: add storage for query history * docs/vmui: add storage for query history --- .../vmui/src/components/Main/Icons/index.tsx | 20 ++ app/vmui/packages/vmui/src/constants/graph.ts | 1 + .../packages/vmui/src/hooks/useFetchQuery.ts | 4 +- .../QueryConfigurator/QueryConfigurator.tsx | 11 +- .../CustomPanel/QueryHistory/QueryHistory.tsx | 188 ++++++++++++++++++ .../QueryHistory/QueryHistoryItem.tsx | 65 ++++++ .../QueryHistory/QueryHistoryList.tsx | 114 ----------- .../pages/CustomPanel/QueryHistory/style.scss | 72 +++++-- .../pages/CustomPanel/QueryHistory/utils.ts | 27 +++ .../vmui/src/state/customPanel/reducer.ts | 2 +- .../packages/vmui/src/state/query/reducer.ts | 10 +- app/vmui/packages/vmui/src/utils/storage.ts | 12 +- docs/CHANGELOG.md | 1 + 13 files changed, 369 insertions(+), 158 deletions(-) create mode 100644 app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistory.tsx create mode 100644 app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryItem.tsx delete mode 100644 app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryList.tsx create mode 100644 app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/utils.ts diff --git a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx index 6e96b3cee..4831ba7ec 100644 --- a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx @@ -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> +); diff --git a/app/vmui/packages/vmui/src/constants/graph.ts b/app/vmui/packages/vmui/src/constants/graph.ts index 48147c639..97d0fcd27 100644 --- a/app/vmui/packages/vmui/src/constants/graph.ts +++ b/app/vmui/packages/vmui/src/constants/graph.ts @@ -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, diff --git a/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts b/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts index 4ab13b24d..a4c39f56c 100644 --- a/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts +++ b/app/vmui/packages/vmui/src/hooks/useFetchQuery.ts @@ -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); diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx index badf930ee..7648ffc0b 100644 --- a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx @@ -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" diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistory.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistory.tsx new file mode 100644 index 000000000..cdec815c8 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistory.tsx @@ -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; diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryItem.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryItem.tsx new file mode 100644 index 000000000..23e54efc1 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryItem.tsx @@ -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; diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryList.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryList.tsx deleted file mode 100644 index 0ce8ddf35..000000000 --- a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/QueryHistoryList.tsx +++ /dev/null @@ -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; diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/style.scss b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/style.scss index 661c1d7f8..8b595e920 100644 --- a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/style.scss +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/style.scss @@ -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,29 +23,51 @@ display: grid; align-items: flex-start; - &-item { - display: grid; - grid-template-columns: 1fr auto; - gap: $padding-small; - align-items: center; + &__group-title { + font-weight: bold; margin: 0 (-$padding-medium) 0; - padding: $padding-small calc($padding-medium + $padding-small); - border-bottom: $border-divider; + padding: $padding-medium $padding-global $padding-small; - &:first-child { - border-top: $border-divider; - } - - &__value { - white-space: pre-wrap; - overflow-wrap: anywhere; - font-family: $font-family-monospace; - } - - &__buttons { - display: flex; - gap: $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 $padding-medium; + border-bottom: $border-divider; + + &__value { + white-space: pre-wrap; + overflow-wrap: anywhere; + font-family: $font-family-monospace; + } + + &__buttons { + display: flex; + } + } + + &-footer { + display: flex; + justify-content: flex-end; + padding-top: $padding-medium; } } diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/utils.ts b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/utils.ts new file mode 100644 index 000000000..e4c6a1230 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryHistory/utils.ts @@ -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)); +}; diff --git a/app/vmui/packages/vmui/src/state/customPanel/reducer.ts b/app/vmui/packages/vmui/src/state/customPanel/reducer.ts index 1b7a885ba..b9854d626 100644 --- a/app/vmui/packages/vmui/src/state/customPanel/reducer.ts +++ b/app/vmui/packages/vmui/src/state/customPanel/reducer.ts @@ -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, }; diff --git a/app/vmui/packages/vmui/src/state/query/reducer.ts b/app/vmui/packages/vmui/src/state/query/reducer.ts index 3971d6355..39e9c86f8 100644 --- a/app/vmui/packages/vmui/src/state/query/reducer.ts +++ b/app/vmui/packages/vmui/src/state/query/reducer.ts @@ -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 diff --git a/app/vmui/packages/vmui/src/utils/storage.ts b/app/vmui/packages/vmui/src/utils/storage.ts index 05734dfd2..844ac5b75 100644 --- a/app/vmui/packages/vmui/src/utils/storage.ts +++ b/app/vmui/packages/vmui/src/utils/storage.ts @@ -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) { @@ -22,7 +21,7 @@ export const saveToStorage = (key: StorageKeys, value: string | boolean | Record }; // TODO: make this aware of data type that is stored -export const getFromStorage = (key: StorageKeys): undefined | boolean | string | Record<string, unknown> => { +export const getFromStorage = (key: StorageKeys): undefined | boolean | string | Record<string, unknown> => { const valueObj = window.localStorage.getItem(key); if (valueObj === null) { return undefined; @@ -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"]; - diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 729af3939..2a55defe9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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).