From ee1e2b824f83a654362c5233fd97b6f1fb610f93 Mon Sep 17 00:00:00 2001 From: Shaheer Kochai Date: Tue, 10 Sep 2024 11:30:22 +0430 Subject: [PATCH 1/2] fix: make the trace table row act as an anchor tag (#5626) * fix: wrap the trace row cells inside a tag to make them clickable --- .../TracesExplorer/ListView/utils.tsx | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/frontend/src/container/TracesExplorer/ListView/utils.tsx b/frontend/src/container/TracesExplorer/ListView/utils.tsx index 254ff93296..a6201436d1 100644 --- a/frontend/src/container/TracesExplorer/ListView/utils.tsx +++ b/frontend/src/container/TracesExplorer/ListView/utils.tsx @@ -5,10 +5,26 @@ import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util'; import { formUrlParams } from 'container/TraceDetail/utils'; import dayjs from 'dayjs'; import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { Link } from 'react-router-dom'; import { ILog } from 'types/api/logs/log'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { QueryDataV3 } from 'types/api/widgets/getQuery'; +function BlockLink({ + children, + to, +}: { + children: React.ReactNode; + to: string; +}): any { + // Display block to make the whole cell clickable + return ( + + {children} + + ); +} + export const transformDataWithDate = ( data: QueryDataV3[], ): Omit[] => @@ -36,7 +52,11 @@ export const getListColumns = ( typeof item === 'string' ? dayjs(item).format('YYYY-MM-DD HH:mm:ss.SSS') : dayjs(item / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'); - return {date}; + return ( + + {date} + + ); }, }, ]; @@ -49,22 +69,36 @@ export const getListColumns = ( width: 145, render: (value): JSX.Element => { if (value === '') { - return N/A; + return ( + + N/A + + ); } if (key === 'httpMethod' || key === 'responseStatusCode') { return ( - - {value} - + + + {value} + + ); } if (key === 'durationNano') { - return {getMs(value)}ms; + return ( + + {getMs(value)}ms + + ); } - return {value}; + return ( + + {value} + + ); }, responsive: ['md'], })) || []; From 3c151e3adbb2f051f43959594a411658d4cb8c7f Mon Sep 17 00:00:00 2001 From: Shaheer Kochai Date: Tue, 10 Sep 2024 11:31:43 +0430 Subject: [PATCH 2/2] feat: preserve last used saved view in explorer pages (#5453) * feat: preserve last used saved view in explorer pages --- frontend/src/constants/localStorage.ts | 1 + .../ExplorerOptions/ExplorerOptions.tsx | 111 ++++++++++++++++-- .../container/ExplorerOptions/constants.ts | 4 + .../src/container/ExplorerOptions/types.ts | 10 ++ .../TimeSeriesView/TimeSeriesView.tsx | 1 + 5 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 frontend/src/container/ExplorerOptions/constants.ts diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index bab93a7ff1..4e6859a2dd 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -19,5 +19,6 @@ export enum LOCALSTORAGE { SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR', PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES', THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1', + LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS', SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS', } diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx index 44378e602b..5be22deb2e 100644 --- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx +++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx @@ -19,6 +19,7 @@ import axios from 'axios'; import cx from 'classnames'; import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils'; import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { LOCALSTORAGE } from 'constants/localStorage'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; @@ -48,6 +49,7 @@ import { Dispatch, SetStateAction, useCallback, + useEffect, useMemo, useRef, useState, @@ -61,7 +63,9 @@ import { DataSource, StringOperators } from 'types/common/queryBuilder'; import AppReducer from 'types/reducer/app'; import { USER_ROLES } from 'types/roles'; +import { PreservedViewsTypes } from './constants'; import ExplorerOptionsHideArea from './ExplorerOptionsHideArea'; +import { PreservedViewsInLocalStorage } from './types'; import { DATASOURCE_VS_ROUTES, generateRGBAFromHex, @@ -90,6 +94,12 @@ function ExplorerOptions({ const history = useHistory(); const ref = useRef(null); 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) => { setIsExport(value); @@ -107,7 +117,7 @@ function ExplorerOptions({ logEvent('Traces Explorer: Save view clicked', { panelType, }); - } else if (sourcepage === DataSource.LOGS) { + } else if (isLogsExplorer) { logEvent('Logs Explorer: Save view clicked', { panelType, }); @@ -141,7 +151,7 @@ function ExplorerOptions({ logEvent('Traces Explorer: Create alert', { panelType, }); - } else if (sourcepage === DataSource.LOGS) { + } else if (isLogsExplorer) { logEvent('Logs Explorer: Create alert', { panelType, }); @@ -166,7 +176,7 @@ function ExplorerOptions({ logEvent('Traces Explorer: Add to dashboard clicked', { panelType, }); - } else if (sourcepage === DataSource.LOGS) { + } else if (isLogsExplorer) { logEvent('Logs Explorer: Add to dashboard clicked', { panelType, }); @@ -265,6 +275,31 @@ function ExplorerOptions({ [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 = ( value: string, option: { key: string; value: string }, @@ -277,18 +312,42 @@ function ExplorerOptions({ panelType, viewName: option?.value, }); - } else if (sourcepage === DataSource.LOGS) { + } else if (isLogsExplorer) { logEvent('Logs Explorer: Select view', { panelType, viewName: option?.value, }); } + + updatePreservedViewInLocalStorage(option); + if (ref.current) { 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 => { + removeCurrentViewFromLocalStorage(); + history.replace(DATASOURCE_VS_ROUTES[sourcepage]); }; @@ -323,7 +382,7 @@ function ExplorerOptions({ panelType, viewName: newViewName, }); - } else if (sourcepage === DataSource.LOGS) { + } else if (isLogsExplorer) { logEvent('Logs Explorer: Save view successful', { panelType, viewName: newViewName, @@ -358,6 +417,44 @@ function ExplorerOptions({ 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 (
{isQueryUpdated && !isExplorerOptionHidden && ( @@ -476,12 +573,12 @@ function ExplorerOptions({ - {sourcepage === DataSource.LOGS + {isLogsExplorer ? 'Learn more about Logs explorer ' : 'Learn more about Traces explorer '} >; } + +export type PreservedViewType = + | PreservedViewsTypes.LOGS + | PreservedViewsTypes.TRACES; + +export type PreservedViewsInLocalStorage = Partial< + Record +>; diff --git a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx index fdb5405473..225d115bca 100644 --- a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx +++ b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx @@ -140,6 +140,7 @@ function TimeSeriesView({ className="graph-container" style={{ height: '100%', width: '100%' }} ref={graphRef} + data-testid="time-series-graph" > {isLoading && (dataSource === DataSource.LOGS ? : )}