Skip to content

Commit

Permalink
vmui: add storage for query history (#5022)
Browse files Browse the repository at this point in the history
* vmui: add storage for query history

* docs/vmui: add storage for query history
  • Loading branch information
Loori-R authored and valyala committed Oct 2, 2023
1 parent 1f2cb59 commit f5521ce
Show file tree
Hide file tree
Showing 13 changed files with 367 additions and 156 deletions.
20 changes: 20 additions & 0 deletions app/vmui/packages/vmui/src/components/Main/Icons/index.tsx
Expand Up @@ -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 change: 1 addition & 0 deletions 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,
Expand Down
4 changes: 2 additions & 2 deletions app/vmui/packages/vmui/src/hooks/useFetchQuery.ts
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
Expand Up @@ -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 {
Expand All @@ -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[];
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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"
Expand Down
@@ -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;

0 comments on commit f5521ce

Please sign in to comment.