mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 03:25:57 +08:00
feat: preserve last used saved view in explorer pages (#5453)
* feat: preserve last used saved view in explorer pages
This commit is contained in:
parent
ee1e2b824f
commit
3c151e3adb
@ -19,5 +19,6 @@ export enum LOCALSTORAGE {
|
|||||||
SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR',
|
SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR',
|
||||||
PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES',
|
PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES',
|
||||||
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
|
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
|
||||||
|
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
|
||||||
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
|
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import axios from 'axios';
|
|||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
|
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
@ -48,6 +49,7 @@ import {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@ -61,7 +63,9 @@ import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
|||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { USER_ROLES } from 'types/roles';
|
import { USER_ROLES } from 'types/roles';
|
||||||
|
|
||||||
|
import { PreservedViewsTypes } from './constants';
|
||||||
import ExplorerOptionsHideArea from './ExplorerOptionsHideArea';
|
import ExplorerOptionsHideArea from './ExplorerOptionsHideArea';
|
||||||
|
import { PreservedViewsInLocalStorage } from './types';
|
||||||
import {
|
import {
|
||||||
DATASOURCE_VS_ROUTES,
|
DATASOURCE_VS_ROUTES,
|
||||||
generateRGBAFromHex,
|
generateRGBAFromHex,
|
||||||
@ -90,6 +94,12 @@ function ExplorerOptions({
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const ref = useRef<RefSelectProps>(null);
|
const ref = useRef<RefSelectProps>(null);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
const isLogsExplorer = sourcepage === DataSource.LOGS;
|
||||||
|
|
||||||
|
const PRESERVED_VIEW_LOCAL_STORAGE_KEY = LOCALSTORAGE.LAST_USED_SAVED_VIEWS;
|
||||||
|
const PRESERVED_VIEW_TYPE = isLogsExplorer
|
||||||
|
? PreservedViewsTypes.LOGS
|
||||||
|
: PreservedViewsTypes.TRACES;
|
||||||
|
|
||||||
const onModalToggle = useCallback((value: boolean) => {
|
const onModalToggle = useCallback((value: boolean) => {
|
||||||
setIsExport(value);
|
setIsExport(value);
|
||||||
@ -107,7 +117,7 @@ function ExplorerOptions({
|
|||||||
logEvent('Traces Explorer: Save view clicked', {
|
logEvent('Traces Explorer: Save view clicked', {
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
} else if (sourcepage === DataSource.LOGS) {
|
} else if (isLogsExplorer) {
|
||||||
logEvent('Logs Explorer: Save view clicked', {
|
logEvent('Logs Explorer: Save view clicked', {
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
@ -141,7 +151,7 @@ function ExplorerOptions({
|
|||||||
logEvent('Traces Explorer: Create alert', {
|
logEvent('Traces Explorer: Create alert', {
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
} else if (sourcepage === DataSource.LOGS) {
|
} else if (isLogsExplorer) {
|
||||||
logEvent('Logs Explorer: Create alert', {
|
logEvent('Logs Explorer: Create alert', {
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
@ -166,7 +176,7 @@ function ExplorerOptions({
|
|||||||
logEvent('Traces Explorer: Add to dashboard clicked', {
|
logEvent('Traces Explorer: Add to dashboard clicked', {
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
} else if (sourcepage === DataSource.LOGS) {
|
} else if (isLogsExplorer) {
|
||||||
logEvent('Logs Explorer: Add to dashboard clicked', {
|
logEvent('Logs Explorer: Add to dashboard clicked', {
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
@ -265,6 +275,31 @@ function ExplorerOptions({
|
|||||||
[viewsData, handleExplorerTabChange],
|
[viewsData, handleExplorerTabChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updatePreservedViewInLocalStorage = (option: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}): void => {
|
||||||
|
// Retrieve stored views from local storage
|
||||||
|
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
|
||||||
|
|
||||||
|
// Initialize or parse the stored views
|
||||||
|
const updatedViews: PreservedViewsInLocalStorage = storedViews
|
||||||
|
? JSON.parse(storedViews)
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Update the views with the new selection
|
||||||
|
updatedViews[PRESERVED_VIEW_TYPE] = {
|
||||||
|
key: option.key,
|
||||||
|
value: option.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the updated views back to local storage
|
||||||
|
localStorage.setItem(
|
||||||
|
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
|
||||||
|
JSON.stringify(updatedViews),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelect = (
|
const handleSelect = (
|
||||||
value: string,
|
value: string,
|
||||||
option: { key: string; value: string },
|
option: { key: string; value: string },
|
||||||
@ -277,18 +312,42 @@ function ExplorerOptions({
|
|||||||
panelType,
|
panelType,
|
||||||
viewName: option?.value,
|
viewName: option?.value,
|
||||||
});
|
});
|
||||||
} else if (sourcepage === DataSource.LOGS) {
|
} else if (isLogsExplorer) {
|
||||||
logEvent('Logs Explorer: Select view', {
|
logEvent('Logs Explorer: Select view', {
|
||||||
panelType,
|
panelType,
|
||||||
viewName: option?.value,
|
viewName: option?.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePreservedViewInLocalStorage(option);
|
||||||
|
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.blur();
|
ref.current.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeCurrentViewFromLocalStorage = (): void => {
|
||||||
|
// Retrieve stored views from local storage
|
||||||
|
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
|
||||||
|
|
||||||
|
if (storedViews) {
|
||||||
|
// Parse the stored views
|
||||||
|
const parsedViews = JSON.parse(storedViews);
|
||||||
|
|
||||||
|
// Remove the current view type from the parsed views
|
||||||
|
delete parsedViews[PRESERVED_VIEW_TYPE];
|
||||||
|
|
||||||
|
// Update local storage with the modified views
|
||||||
|
localStorage.setItem(
|
||||||
|
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
|
||||||
|
JSON.stringify(parsedViews),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleClearSelect = (): void => {
|
const handleClearSelect = (): void => {
|
||||||
|
removeCurrentViewFromLocalStorage();
|
||||||
|
|
||||||
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
|
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -323,7 +382,7 @@ function ExplorerOptions({
|
|||||||
panelType,
|
panelType,
|
||||||
viewName: newViewName,
|
viewName: newViewName,
|
||||||
});
|
});
|
||||||
} else if (sourcepage === DataSource.LOGS) {
|
} else if (isLogsExplorer) {
|
||||||
logEvent('Logs Explorer: Save view successful', {
|
logEvent('Logs Explorer: Save view successful', {
|
||||||
panelType,
|
panelType,
|
||||||
viewName: newViewName,
|
viewName: newViewName,
|
||||||
@ -358,6 +417,44 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
const isEditDeleteSupported = allowedRoles.includes(role as string);
|
const isEditDeleteSupported = allowedRoles.includes(role as string);
|
||||||
|
|
||||||
|
const [
|
||||||
|
isRecentlyUsedSavedViewSelected,
|
||||||
|
setIsRecentlyUsedSavedViewSelected,
|
||||||
|
] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const parsedPreservedView = JSON.parse(
|
||||||
|
localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
|
||||||
|
);
|
||||||
|
|
||||||
|
const preservedView = parsedPreservedView[PRESERVED_VIEW_TYPE] || {};
|
||||||
|
|
||||||
|
let timeoutId: string | number | NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!!preservedView?.key &&
|
||||||
|
viewsData?.data?.data &&
|
||||||
|
!(!!viewName || !!viewKey) &&
|
||||||
|
!isRecentlyUsedSavedViewSelected
|
||||||
|
) {
|
||||||
|
// prevent the race condition with useShareBuilderUrl
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
onMenuItemSelectHandler({ key: preservedView.key });
|
||||||
|
}, 0);
|
||||||
|
setIsRecentlyUsedSavedViewSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (): void => clearTimeout(timeoutId);
|
||||||
|
}, [
|
||||||
|
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
|
||||||
|
PRESERVED_VIEW_TYPE,
|
||||||
|
isRecentlyUsedSavedViewSelected,
|
||||||
|
onMenuItemSelectHandler,
|
||||||
|
viewKey,
|
||||||
|
viewName,
|
||||||
|
viewsData?.data?.data,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="explorer-options-container">
|
<div className="explorer-options-container">
|
||||||
{isQueryUpdated && !isExplorerOptionHidden && (
|
{isQueryUpdated && !isExplorerOptionHidden && (
|
||||||
@ -476,12 +573,12 @@ function ExplorerOptions({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
{sourcepage === DataSource.LOGS
|
{isLogsExplorer
|
||||||
? 'Learn more about Logs explorer '
|
? 'Learn more about Logs explorer '
|
||||||
: 'Learn more about Traces explorer '}
|
: 'Learn more about Traces explorer '}
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
href={
|
href={
|
||||||
sourcepage === DataSource.LOGS
|
isLogsExplorer
|
||||||
? 'https://signoz.io/docs/product-features/logs-explorer/?utm_source=product&utm_medium=logs-explorer-toolbar'
|
? 'https://signoz.io/docs/product-features/logs-explorer/?utm_source=product&utm_medium=logs-explorer-toolbar'
|
||||||
: 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar'
|
: 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar'
|
||||||
}
|
}
|
||||||
|
4
frontend/src/container/ExplorerOptions/constants.ts
Normal file
4
frontend/src/container/ExplorerOptions/constants.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum PreservedViewsTypes {
|
||||||
|
LOGS = 'logs',
|
||||||
|
TRACES = 'traces',
|
||||||
|
}
|
@ -8,6 +8,8 @@ import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
|||||||
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
||||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { PreservedViewsTypes } from './constants';
|
||||||
|
|
||||||
export interface SaveNewViewHandlerProps {
|
export interface SaveNewViewHandlerProps {
|
||||||
viewName: string;
|
viewName: string;
|
||||||
compositeQuery: ICompositeMetricQuery;
|
compositeQuery: ICompositeMetricQuery;
|
||||||
@ -26,3 +28,11 @@ export interface SaveNewViewHandlerProps {
|
|||||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||||
setNewViewName: Dispatch<SetStateAction<string>>;
|
setNewViewName: Dispatch<SetStateAction<string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PreservedViewType =
|
||||||
|
| PreservedViewsTypes.LOGS
|
||||||
|
| PreservedViewsTypes.TRACES;
|
||||||
|
|
||||||
|
export type PreservedViewsInLocalStorage = Partial<
|
||||||
|
Record<PreservedViewType, { key: string; value: string }>
|
||||||
|
>;
|
||||||
|
@ -140,6 +140,7 @@ function TimeSeriesView({
|
|||||||
className="graph-container"
|
className="graph-container"
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={{ height: '100%', width: '100%' }}
|
||||||
ref={graphRef}
|
ref={graphRef}
|
||||||
|
data-testid="time-series-graph"
|
||||||
>
|
>
|
||||||
{isLoading &&
|
{isLoading &&
|
||||||
(dataSource === DataSource.LOGS ? <LogsLoading /> : <TracesLoading />)}
|
(dataSource === DataSource.LOGS ? <LogsLoading /> : <TracesLoading />)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user