diff --git a/frontend/src/container/LogsExplorerChart/index.tsx b/frontend/src/container/LogsExplorerChart/index.tsx index d0acc3f2fd..ca15909123 100644 --- a/frontend/src/container/LogsExplorerChart/index.tsx +++ b/frontend/src/container/LogsExplorerChart/index.tsx @@ -62,6 +62,8 @@ function LogsExplorerChart({ urlQuery.set(QueryParams.startTime, minTime.toString()); urlQuery.set(QueryParams.endTime, maxTime.toString()); urlQuery.delete(QueryParams.relativeTime); + // Remove Hidden Filters from URL query parameters on time change + urlQuery.delete(QueryParams.activeLogId); const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; safeNavigate(generatedUrl); }, diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index 22c321bbce..a6d7c5a1a1 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -188,6 +188,26 @@ function LogsExplorerViews({ }, ], legend: '{{severity_text}}', + ...(activeLogId && { + filters: { + ...listQuery?.filters, + items: [ + ...(listQuery?.filters?.items || []), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['<='], + value: activeLogId, + }, + ], + op: 'AND', + }, + }), }; const modifiedQuery: Query = { @@ -202,7 +222,7 @@ function LogsExplorerViews({ }; return modifiedQuery; - }, [stagedQuery, listQuery]); + }, [stagedQuery, listQuery, activeLogId]); const exportDefaultQuery = useMemo( () => @@ -287,12 +307,12 @@ function LogsExplorerViews({ }); // Add filter for activeLogId if present - let updatedFilters = paginateData.filters; + let updatedFilters = params.filters; if (activeLogId) { updatedFilters = { - ...paginateData.filters, + ...params.filters, items: [ - ...(paginateData.filters?.items || []), + ...(params.filters?.items || []), { id: v4(), key: { diff --git a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx index da70cd6746..2ddaa028b5 100644 --- a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx +++ b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx @@ -1,17 +1,23 @@ import ROUTES from 'constants/routes'; +import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange'; import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_query_range'; import { server } from 'mocks-server/server'; import { rest } from 'msw'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; +import { QueryBuilderContext } from 'providers/QueryBuilder'; import { VirtuosoMockContext } from 'react-virtuoso'; import { fireEvent, render, RenderResult } from 'tests/test-utils'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import LogsExplorerViews from '..'; -import { logsQueryRangeSuccessNewFormatResponse } from './mock'; +import { + logsQueryRangeSuccessNewFormatResponse, + mockQueryBuilderContextValue, +} from './mock'; const queryRangeURL = 'http://localhost/api/v3/query_range'; - +const ACTIVE_LOG_ID = 'test-log-id'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useLocation: (): { pathname: string } => ({ @@ -81,6 +87,12 @@ jest.mock('hooks/useSafeNavigate', () => ({ }), })); +jest.mock('hooks/logs/useCopyLogLink', () => ({ + useCopyLogLink: jest.fn().mockReturnValue({ + activeLogId: ACTIVE_LOG_ID, + }), +})); + // Set up the specific behavior for useGetExplorerQueryRange in individual test cases beforeEach(() => { (useGetExplorerQueryRange as jest.Mock).mockReturnValue({ @@ -162,4 +174,47 @@ describe('LogsExplorerViews -', () => { queryByText('Something went wrong. Please try again or contact support.'), ).toBeInTheDocument(); }); + + it('should add activeLogId filter when present in URL', () => { + // Mock useCopyLogLink to return an activeLogId + (useCopyLogLink as jest.Mock).mockReturnValue({ + activeLogId: ACTIVE_LOG_ID, + }); + + lodsQueryServerRequest(); + render( + + {}} + listQueryKeyRef={{ current: {} }} + chartQueryKeyRef={{ current: {} }} + /> + , + ); + + // Get the query data from the first call to useGetExplorerQueryRange + const { + queryData, + } = (useGetExplorerQueryRange as jest.Mock).mock.calls[0][0].builder; + const firstQuery = queryData[0]; + + // Get the original number of filters from mock data + const originalFiltersLength = + mockQueryBuilderContextValue.currentQuery.builder.queryData[0].filters?.items + .length || 0; + const expectedFiltersLength = originalFiltersLength + 1; // +1 for activeLogId filter + + // Verify that the activeLogId filter is present + expect( + firstQuery.filters?.items.some( + (item: TagFilterItem) => + item.key?.key === 'id' && item.op === '<=' && item.value === ACTIVE_LOG_ID, + ), + ).toBe(true); + + // Verify the total number of filters (original + 1 new activeLogId filter) + expect(firstQuery.filters?.items.length).toBe(expectedFiltersLength); + }); }); diff --git a/frontend/src/container/LogsExplorerViews/tests/mock.ts b/frontend/src/container/LogsExplorerViews/tests/mock.ts index 6c07004eea..fa612054c8 100644 --- a/frontend/src/container/LogsExplorerViews/tests/mock.ts +++ b/frontend/src/container/LogsExplorerViews/tests/mock.ts @@ -1,3 +1,13 @@ +import { + initialQueriesMap, + initialQueryBuilderFormValues, + OPERATORS, + PANEL_TYPES, +} from 'constants/queryBuilder'; +import { noop } from 'lodash-es'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { Query } from 'types/api/queryBuilder/queryBuilderData'; + export const logsQueryRangeSuccessNewFormatResponse = { data: { result: [], @@ -49,3 +59,148 @@ export const logsQueryRangeSuccessNewFormatResponse = { }, }, }; + +export const mockQueryBuilderContextValue = { + isDefaultQuery: (): boolean => false, + currentQuery: { + ...initialQueriesMap.logs, + builder: { + ...initialQueriesMap.logs.builder, + queryData: [ + { + ...initialQueryBuilderFormValues, + filters: { + items: [ + { + id: '1', + key: { + key: 'service', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['='], + value: 'frontend', + }, + { + id: '2', + key: { + key: 'log_level', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['='], + value: 'INFO', + }, + ], + op: 'AND', + }, + }, + initialQueryBuilderFormValues, + ], + }, + }, + setSupersetQuery: jest.fn(), + supersetQuery: { + ...initialQueriesMap.logs, + builder: { + ...initialQueriesMap.logs.builder, + queryData: [ + { + ...initialQueryBuilderFormValues, + filters: { + items: [ + { + id: '1', + key: { + key: 'service', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['='], + value: 'frontend', + }, + { + id: '2', + key: { + key: 'log_level', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['='], + value: 'INFO', + }, + ], + op: 'AND', + }, + }, + initialQueryBuilderFormValues, + ], + }, + }, + stagedQuery: { + ...initialQueriesMap.logs, + builder: { + ...initialQueriesMap.logs.builder, + queryData: [ + { + ...initialQueryBuilderFormValues, + filters: { + items: [ + { + id: '1', + key: { + key: 'service', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['='], + value: 'frontend', + }, + { + id: '2', + key: { + key: 'log_level', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: OPERATORS['='], + value: 'INFO', + }, + ], + op: 'AND', + }, + }, + initialQueryBuilderFormValues, + ], + }, + }, + initialDataSource: null, + panelType: PANEL_TYPES.TIME_SERIES, + isEnabledQuery: false, + lastUsedQuery: 0, + setLastUsedQuery: noop, + handleSetQueryData: noop, + handleSetFormulaData: noop, + handleSetQueryItemData: noop, + handleSetConfig: noop, + removeQueryBuilderEntityByIndex: noop, + removeQueryTypeItemByIndex: noop, + addNewBuilderQuery: noop, + cloneQuery: noop, + addNewFormula: noop, + addNewQueryItem: noop, + redirectWithQueryBuilderData: noop, + handleRunQuery: noop, + resetQuery: noop, + updateAllQueriesOperators: (): Query => initialQueriesMap.logs, + updateQueriesData: (): Query => initialQueriesMap.logs, + initQueryBuilderData: noop, + handleOnUnitsChange: noop, + isStagedQueryUpdated: (): boolean => false, +}; diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx index b390798bab..3ca773569f 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx @@ -377,6 +377,8 @@ function DateTimeSelection({ urlQuery.delete('endTime'); urlQuery.set(QueryParams.relativeTime, value); + // Remove Hidden Filters from URL query parameters on time change + urlQuery.delete(QueryParams.activeLogId); const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; safeNavigate(generatedUrl); @@ -669,9 +671,7 @@ function DateTimeSelection({ urlQuery.set(QueryParams.endTime, endTime); urlQuery.delete(QueryParams.relativeTime); } - const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; - safeNavigate(generatedUrl); // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.pathname, updateTimeInterval, globalTimeLoading]); diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 3ce37600c0..f95f04a8c2 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -832,6 +832,8 @@ export function QueryBuilderProvider({ ), ); } + // Remove Hidden Filters from URL query parameters on query change + urlQuery.delete(QueryParams.activeLogId); const generatedUrl = redirectingUrl ? `${redirectingUrl}?${urlQuery}`