diff --git a/frontend/src/components/ExplorerCard/ExplorerCard.tsx b/frontend/src/components/ExplorerCard/ExplorerCard.tsx index 32ab5b9297d..e04af8fedaf 100644 --- a/frontend/src/components/ExplorerCard/ExplorerCard.tsx +++ b/frontend/src/components/ExplorerCard/ExplorerCard.tsx @@ -7,9 +7,8 @@ import axios from 'axios'; import TextToolTip from 'components/TextToolTip'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import { LOCALSTORAGE } from 'constants/localStorage'; -import { QueryParams } from 'constants/query'; import { useOptionsMenu } from 'container/OptionsMenu'; -import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam'; +import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useDeleteView } from 'hooks/saveViews/useDeleteView'; import { useGetAllViews } from 'hooks/saveViews/useGetAllViews'; @@ -69,9 +68,7 @@ function ExplorerCard({ setIsOpen(newOpen); }; - const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; - - const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || ''; + const { viewName, viewKey } = useGetSavedViewParams(); const { options } = useOptionsMenu({ storageKey: diff --git a/frontend/src/components/ExplorerCard/constants.ts b/frontend/src/components/ExplorerCard/constants.ts index 3a08ad5edb7..8cc5887b41b 100644 --- a/frontend/src/components/ExplorerCard/constants.ts +++ b/frontend/src/components/ExplorerCard/constants.ts @@ -1,5 +1,3 @@ -import { QueryParams } from 'constants/query'; - export const ExploreHeaderToolTip = { url: 'https://signoz.io/docs/querying/overview/?utm_source=product&utm_medium=new-query-builder', text: 'More details on how to use query builder', @@ -9,5 +7,3 @@ export const SaveButtonText = { SAVE_AS_NEW_VIEW: 'Save as new view', SAVE_VIEW: 'Save view', }; - -export type QuerySearchParamNames = QueryParams.viewName | QueryParams.viewKey; diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx index 4d548a8209d..712ff7c8e3b 100644 --- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx +++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx @@ -54,7 +54,7 @@ import { } from 'container/OptionsMenu/constants'; import { OptionsQuery } from 'container/OptionsMenu/types'; import { ExportDashboard } from 'hooks/dashboard/useExportDashboards'; -import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam'; +import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useGetAllViews } from 'hooks/saveViews/useGetAllViews'; import { useSaveView } from 'hooks/saveViews/useSaveView'; @@ -287,8 +287,7 @@ function ExplorerOptions({ const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType); - const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; - const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || ''; + const { viewName, viewKey } = useGetSavedViewParams(); const extraData = viewsData?.data?.data?.find( (view) => view.id === viewKey, diff --git a/frontend/src/container/LogDetailedView/BodyTitleRenderer.tsx b/frontend/src/container/LogDetailedView/BodyTitleRenderer.tsx index 39be93760d8..b34279abe37 100644 --- a/frontend/src/container/LogDetailedView/BodyTitleRenderer.tsx +++ b/frontend/src/container/LogDetailedView/BodyTitleRenderer.tsx @@ -15,9 +15,8 @@ import { QUERY_BUILDER_FUNCTIONS, } from 'constants/antlrQueryConstants'; import { FeatureKeys } from 'constants/features'; -import { QueryParams } from 'constants/query'; import { useActiveLog } from 'hooks/logs/useActiveLog'; -import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam'; +import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange'; import { useNotifications } from 'hooks/useNotifications'; @@ -50,7 +49,7 @@ function BodyTitleRenderer({ const { featureFlags } = useAppContext(); const [, setCopy] = useCopyToClipboard(); const { notifications } = useNotifications(); - const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; + const { viewName } = useGetSavedViewParams(); const cleanedNodeKey = removeObjectFromString(nodeKey); const isBodyJsonQueryEnabled = diff --git a/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx b/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx index 138f7134fb0..42234a7440a 100644 --- a/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx +++ b/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx @@ -7,13 +7,12 @@ import GroupByIcon from 'assets/CustomIcons/GroupByIcon'; import cx from 'classnames'; import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC'; import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; -import { QueryParams } from 'constants/query'; import { OPERATORS } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import { ChangeViewFunctionType } from 'container/ExplorerOptions/types'; import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config'; import { MetricsType } from 'container/MetricsApplication/constant'; -import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam'; +import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange'; import { @@ -141,7 +140,7 @@ export default function TableViewActions( const { pathname } = useLocation(); const { stagedQuery, updateQueriesData } = useQueryBuilder(); - const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; + const { viewName } = useGetSavedViewParams(); const { dataType, logType: fieldType } = getFieldAttributes(record.field); // there is no option for where clause in old logs explorer and live logs page or infra monitoring diff --git a/frontend/src/container/LogDetailedView/TableView/__test__/TableViewActions.test.tsx b/frontend/src/container/LogDetailedView/TableView/__test__/TableViewActions.test.tsx index 1602b47e4cc..536613b94bd 100644 --- a/frontend/src/container/LogDetailedView/TableView/__test__/TableViewActions.test.tsx +++ b/frontend/src/container/LogDetailedView/TableView/__test__/TableViewActions.test.tsx @@ -1,6 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config'; -import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam'; +import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { ExplorerViews } from 'pages/LogsExplorer/utils'; @@ -88,7 +88,7 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('hooks/queryBuilder/useQueryBuilder'); -jest.mock('hooks/queryBuilder/useGetSearchQueryParam'); +jest.mock('hooks/saveViews/useGetSavedViewParams'); describe('TableViewActions', () => { const TEST_VALUE = 'test value'; @@ -140,8 +140,10 @@ describe('TableViewActions', () => { }), } as any); - // Default mock for useGetSearchQueryParam - jest.mocked(useGetSearchQueryParam).mockReturnValue(null); + // Default mock for useGetSavedViewParams + jest + .mocked(useGetSavedViewParams) + .mockReturnValue({ viewName: '', viewKey: '' }); }); it('should render without crashing', () => { @@ -249,7 +251,9 @@ describe('TableViewActions', () => { updateQueriesData: mockUpdateQueriesData, } as any); - jest.mocked(useGetSearchQueryParam).mockReturnValue(null); + jest + .mocked(useGetSavedViewParams) + .mockReturnValue({ viewName: '', viewKey: '' }); render( flag.name === FeatureKeys.USE_JSON_BODY) diff --git a/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts deleted file mode 100644 index 254f24c4d79..00000000000 --- a/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useMemo } from 'react'; -import { QuerySearchParamNames } from 'components/ExplorerCard/constants'; -import useUrlQuery from 'hooks/useUrlQuery'; - -export const useGetSearchQueryParam = ( - searchParams: QuerySearchParamNames, -): string | null => { - const urlQuery = useUrlQuery(); - - return useMemo(() => { - const searchQuery = urlQuery.get(searchParams); - - return searchQuery ? JSON.parse(searchQuery) : null; - }, [urlQuery, searchParams]); -}; diff --git a/frontend/src/hooks/saveViews/__tests__/useGetSavedViewParams.test.ts b/frontend/src/hooks/saveViews/__tests__/useGetSavedViewParams.test.ts new file mode 100644 index 00000000000..1cb251bf3e5 --- /dev/null +++ b/frontend/src/hooks/saveViews/__tests__/useGetSavedViewParams.test.ts @@ -0,0 +1,60 @@ +import { renderHook } from '@testing-library/react'; +import useUrlQuery from 'hooks/useUrlQuery'; + +import { useGetSavedViewParams } from '../useGetSavedViewParams'; + +jest.mock('hooks/useUrlQuery'); + +const mockedUseUrlQuery = useUrlQuery as jest.Mock; + +const setSearch = (search: string): void => { + mockedUseUrlQuery.mockReturnValue(new URLSearchParams(search)); +}; + +describe('useGetSavedViewParams', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns empty strings when no params are present', () => { + setSearch(''); + + const { result } = renderHook(() => useGetSavedViewParams()); + + expect(result.current).toStrictEqual({ viewName: '', viewKey: '' }); + }); + + it('parses JSON-stringified values', () => { + setSearch( + `viewName=${encodeURIComponent( + JSON.stringify('Hindsight'), + )}&viewKey=${encodeURIComponent(JSON.stringify('abc-123'))}`, + ); + + const { result } = renderHook(() => useGetSavedViewParams()); + + expect(result.current).toStrictEqual({ + viewName: 'Hindsight', + viewKey: 'abc-123', + }); + }); + + it('falls back to the raw string when a value is not valid JSON', () => { + setSearch('viewName=Hindsight&viewKey=some-uuid-value'); + + const { result } = renderHook(() => useGetSavedViewParams()); + + expect(result.current).toStrictEqual({ + viewName: 'Hindsight', + viewKey: 'some-uuid-value', + }); + }); + + it('does not throw and keeps the raw string for non-string JSON', () => { + setSearch('viewName=123'); + + const { result } = renderHook(() => useGetSavedViewParams()); + + expect(result.current).toStrictEqual({ viewName: '123', viewKey: '' }); + }); +}); diff --git a/frontend/src/hooks/saveViews/useGetSavedViewParams.ts b/frontend/src/hooks/saveViews/useGetSavedViewParams.ts new file mode 100644 index 00000000000..dcba66991c0 --- /dev/null +++ b/frontend/src/hooks/saveViews/useGetSavedViewParams.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; +import { QueryParams } from 'constants/query'; +import useUrlQuery from 'hooks/useUrlQuery'; + +interface SavedViewParams { + viewName: string; + viewKey: string; +} + +const parseViewParam = (value: string | null): string => { + if (!value) { + return ''; + } + + try { + const parsed = JSON.parse(value); + return typeof parsed === 'string' ? parsed : value; + } catch { + return value; + } +}; + +export const useGetSavedViewParams = (): SavedViewParams => { + const urlQuery = useUrlQuery(); + + return useMemo( + () => ({ + viewName: parseViewParam(urlQuery.get(QueryParams.viewName)), + viewKey: parseViewParam(urlQuery.get(QueryParams.viewKey)), + }), + [urlQuery], + ); +}; diff --git a/frontend/src/hooks/useHandleExplorerTabChange.ts b/frontend/src/hooks/useHandleExplorerTabChange.ts index 0ae0ded66f2..88940358d61 100644 --- a/frontend/src/hooks/useHandleExplorerTabChange.ts +++ b/frontend/src/hooks/useHandleExplorerTabChange.ts @@ -6,7 +6,7 @@ import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/const import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import { useGetSearchQueryParam } from './queryBuilder/useGetSearchQueryParam'; +import { useGetSavedViewParams } from './saveViews/useGetSavedViewParams'; import { useQueryBuilder } from './queryBuilder/useQueryBuilder'; export interface ICurrentQueryData { @@ -31,9 +31,7 @@ export const useHandleExplorerTabChange = (): { updateQueriesData, } = useQueryBuilder(); - const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; - - const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || ''; + const { viewName, viewKey } = useGetSavedViewParams(); const getUpdateQuery = useCallback( (newPanelType: PANEL_TYPES): Query => {