diff --git a/deploy/docker-swarm/docker-compose.ha.yaml b/deploy/docker-swarm/docker-compose.ha.yaml index eec6c81eaab2..bae29a745cb5 100644 --- a/deploy/docker-swarm/docker-compose.ha.yaml +++ b/deploy/docker-swarm/docker-compose.ha.yaml @@ -174,7 +174,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:v0.86.2 + image: signoz/signoz:v0.87.0 command: - --config=/root/config/prometheus.yml ports: diff --git a/deploy/docker-swarm/docker-compose.yaml b/deploy/docker-swarm/docker-compose.yaml index 6c023d71379b..2a6e3214a00f 100644 --- a/deploy/docker-swarm/docker-compose.yaml +++ b/deploy/docker-swarm/docker-compose.yaml @@ -110,7 +110,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:v0.86.2 + image: signoz/signoz:v0.87.0 command: - --config=/root/config/prometheus.yml ports: diff --git a/deploy/docker/docker-compose.ha.yaml b/deploy/docker/docker-compose.ha.yaml index af3e83dc82a7..031fe243c9c5 100644 --- a/deploy/docker/docker-compose.ha.yaml +++ b/deploy/docker/docker-compose.ha.yaml @@ -177,7 +177,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:${VERSION:-v0.86.2} + image: signoz/signoz:${VERSION:-v0.87.0} container_name: signoz command: - --config=/root/config/prometheus.yml diff --git a/deploy/docker/docker-compose.yaml b/deploy/docker/docker-compose.yaml index 27b8bc5dce36..3aff6df135e2 100644 --- a/deploy/docker/docker-compose.yaml +++ b/deploy/docker/docker-compose.yaml @@ -110,7 +110,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:${VERSION:-v0.86.2} + image: signoz/signoz:${VERSION:-v0.87.0} container_name: signoz command: - --config=/root/config/prometheus.yml diff --git a/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx b/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx index dd41a0498b94..fa980cce9d34 100644 --- a/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx +++ b/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; import ROUTES from 'constants/routes'; +import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider'; import MockQueryClientProvider from 'providers/test/MockQueryClientProvider'; import { DataSource } from 'types/common/queryBuilder'; @@ -52,11 +53,32 @@ jest.mock('hooks/saveViews/useDeleteView', () => ({ })), })); +// Mock usePreferenceSync +jest.mock('providers/preferences/sync/usePreferenceSync', () => ({ + usePreferenceSync: (): any => ({ + preferences: { + columns: [], + formatting: { + maxLines: 2, + format: 'table', + fontSize: 'small', + version: 1, + }, + }, + loading: false, + error: null, + updateColumns: jest.fn(), + updateFormatting: jest.fn(), + }), +})); + describe('ExplorerCard', () => { it('renders a card with a title and a description', () => { render( - child + + child + , ); expect(screen.queryByText('Query Builder')).not.toBeInTheDocument(); @@ -65,7 +87,9 @@ describe('ExplorerCard', () => { it('renders a save view button', () => { render( - child + + child + , ); expect(screen.queryByText('Save view')).not.toBeInTheDocument(); diff --git a/frontend/src/components/ExplorerCard/utils.ts b/frontend/src/components/ExplorerCard/utils.ts index 1e681e42bd5e..0f90435f6fb8 100644 --- a/frontend/src/components/ExplorerCard/utils.ts +++ b/frontend/src/components/ExplorerCard/utils.ts @@ -6,6 +6,7 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi'; import isEqual from 'lodash-es/isEqual'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; import { DeleteViewHandlerProps, @@ -106,7 +107,11 @@ export const isQueryUpdatedInView = ({ !isEqual( options?.selectColumns, extraData && JSON.parse(extraData)?.selectColumns, - ) + ) || + (stagedQuery?.builder?.queryData?.[0]?.dataSource === DataSource.LOGS && + (!isEqual(options?.format, extraData && JSON.parse(extraData)?.format) || + !isEqual(options?.maxLines, extraData && JSON.parse(extraData)?.maxLines) || + !isEqual(options?.fontSize, extraData && JSON.parse(extraData)?.fontSize))) ); }; diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index 38999ef6cf0c..fcf1481e615f 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -74,6 +74,7 @@ const formatMap = { 'MM/dd HH:mm': DATE_TIME_FORMATS.SLASH_SHORT, 'MM/DD': DATE_TIME_FORMATS.DATE_SHORT, 'YY-MM': DATE_TIME_FORMATS.YEAR_MONTH, + 'MMM d, yyyy, h:mm:ss aaaa': DATE_TIME_FORMATS.DASH_DATETIME, YY: DATE_TIME_FORMATS.YEAR_SHORT, }; diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx index 98b94df9937f..cb74b1bb5dbd 100644 --- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx +++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx @@ -54,6 +54,7 @@ import { X, } from 'lucide-react'; import { useAppContext } from 'providers/App/App'; +import { FormattingOptions } from 'providers/preferences/types'; import { CSSProperties, Dispatch, @@ -270,17 +271,26 @@ function ExplorerOptions({ const getUpdatedExtraData = ( extraData: string | undefined, newSelectedColumns: BaseAutocompleteData[], + formattingOptions?: FormattingOptions, ): string => { let updatedExtraData; if (extraData) { const parsedExtraData = JSON.parse(extraData); parsedExtraData.selectColumns = newSelectedColumns; + if (formattingOptions) { + parsedExtraData.format = formattingOptions.format; + parsedExtraData.maxLines = formattingOptions.maxLines; + parsedExtraData.fontSize = formattingOptions.fontSize; + } updatedExtraData = JSON.stringify(parsedExtraData); } else { updatedExtraData = JSON.stringify({ color: Color.BG_SIENNA_500, selectColumns: newSelectedColumns, + format: formattingOptions?.format, + maxLines: formattingOptions?.maxLines, + fontSize: formattingOptions?.fontSize, }); } return updatedExtraData; @@ -289,6 +299,14 @@ function ExplorerOptions({ const updatedExtraData = getUpdatedExtraData( extraData, options?.selectColumns, + // pass this only for logs + sourcepage === DataSource.LOGS + ? { + format: options?.format, + maxLines: options?.maxLines, + fontSize: options?.fontSize, + } + : undefined, ); const { @@ -517,6 +535,14 @@ function ExplorerOptions({ color, selectColumns: options.selectColumns, version: 1, + ...// pass this only for logs + (sourcepage === DataSource.LOGS + ? { + format: options?.format, + maxLines: options?.maxLines, + fontSize: options?.fontSize, + } + : {}), }), notifications, panelType: panelType || PANEL_TYPES.LIST, diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index a6d7c5a1a13e..5b5afdb7d6ef 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -114,7 +114,6 @@ function LogsExplorerViews({ // Context const { - initialDataSource, currentQuery, stagedQuery, panelType, @@ -144,7 +143,7 @@ function LogsExplorerViews({ const { options, config } = useOptionsMenu({ storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS, - dataSource: initialDataSource || DataSource.LOGS, + dataSource: DataSource.LOGS, aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP, }); diff --git a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx index 2ddaa028b581..e29f598a6a36 100644 --- a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx +++ b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx @@ -5,6 +5,7 @@ import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_qu import { server } from 'mocks-server/server'; import { rest } from 'msw'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; +import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider'; import { QueryBuilderContext } from 'providers/QueryBuilder'; import { VirtuosoMockContext } from 'react-virtuoso'; import { fireEvent, render, RenderResult } from 'tests/test-utils'; @@ -87,6 +88,25 @@ jest.mock('hooks/useSafeNavigate', () => ({ }), })); +// Mock usePreferenceSync +jest.mock('providers/preferences/sync/usePreferenceSync', () => ({ + usePreferenceSync: (): any => ({ + preferences: { + columns: [], + formatting: { + maxLines: 2, + format: 'table', + fontSize: 'small', + version: 1, + }, + }, + loading: false, + error: null, + updateColumns: jest.fn(), + updateFormatting: jest.fn(), + }), +})); + jest.mock('hooks/logs/useCopyLogLink', () => ({ useCopyLogLink: jest.fn().mockReturnValue({ activeLogId: ACTIVE_LOG_ID, @@ -105,13 +125,15 @@ const renderer = (): RenderResult => - {}} - listQueryKeyRef={{ current: {} }} - chartQueryKeyRef={{ current: {} }} - /> + + {}} + listQueryKeyRef={{ current: {} }} + chartQueryKeyRef={{ current: {} }} + /> + , ); @@ -184,13 +206,15 @@ describe('LogsExplorerViews -', () => { lodsQueryServerRequest(); render( - {}} - listQueryKeyRef={{ current: {} }} - chartQueryKeyRef={{ current: {} }} - /> + + {}} + listQueryKeyRef={{ current: {} }} + chartQueryKeyRef={{ current: {} }} + /> + , ); diff --git a/frontend/src/container/LogsPanelTable/__tests__/LogsPanelComponent.test.tsx b/frontend/src/container/LogsPanelTable/__tests__/LogsPanelComponent.test.tsx index d77f05f1bd31..443721243f6b 100644 --- a/frontend/src/container/LogsPanelTable/__tests__/LogsPanelComponent.test.tsx +++ b/frontend/src/container/LogsPanelTable/__tests__/LogsPanelComponent.test.tsx @@ -5,6 +5,7 @@ import { logsPaginationQueryRangeSuccessResponse } from 'mocks-server/__mockdata import { server } from 'mocks-server/server'; import { rest } from 'msw'; import { DashboardProvider } from 'providers/Dashboard/Dashboard'; +import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider'; import { I18nextProvider } from 'react-i18next'; import i18n from 'ReactI18'; import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils'; @@ -108,11 +109,13 @@ describe('LogsPanelComponent', () => { render( - + + + , ); diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index 93f99348fbd9..3ae5abe9872b 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -1,7 +1,4 @@ -import getFromLocalstorage from 'api/browser/localstorage/get'; -import setToLocalstorage from 'api/browser/localstorage/set'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; -import { LOCALSTORAGE } from 'constants/localStorage'; import { LogViewMode } from 'container/LogsTable'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import useDebounce from 'hooks/useDebounce'; @@ -11,6 +8,7 @@ import { AllTraceFilterKeys, AllTraceFilterKeyValue, } from 'pages/TracesExplorer/Filter/filterUtils'; +import { usePreferenceContext } from 'providers/preferences/context/PreferenceContextProvider'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useQueries } from 'react-query'; import { ErrorResponse, SuccessResponse } from 'types/api'; @@ -35,10 +33,10 @@ import { import { getOptionsFromKeys } from './utils'; interface UseOptionsMenuProps { + storageKey?: string; dataSource: DataSource; aggregateOperator: string; initialOptions?: InitialOptions; - storageKey: LOCALSTORAGE; } interface UseOptionsMenu { @@ -48,22 +46,21 @@ interface UseOptionsMenu { } const useOptionsMenu = ({ - storageKey, dataSource, aggregateOperator, initialOptions = {}, }: UseOptionsMenuProps): UseOptionsMenu => { const { notifications } = useNotifications(); + const { + preferences, + updateColumns, + updateFormatting, + } = usePreferenceContext(); const [searchText, setSearchText] = useState(''); const [isFocused, setIsFocused] = useState(false); const debouncedSearchText = useDebounce(searchText, 300); - const localStorageOptionsQuery = useMemo( - () => getFromLocalstorage(storageKey), - [storageKey], - ); - const initialQueryParams = useMemo( () => ({ searchText: '', @@ -77,7 +74,6 @@ const useOptionsMenu = ({ const { query: optionsQuery, - queryData: optionsQueryData, redirectWithQuery: redirectWithOptionsData, } = useUrlQueryData(URL_OPTIONS, defaultOptionsQuery); @@ -105,7 +101,9 @@ const useOptionsMenu = ({ ); const initialSelectedColumns = useMemo(() => { - if (!isFetchedInitialAttributes) return []; + if (!isFetchedInitialAttributes) { + return []; + } const attributesData = initialAttributesResult?.reduce( (acc, attributeResponse) => { @@ -142,14 +140,12 @@ const useOptionsMenu = ({ }) .filter(Boolean) as BaseAutocompleteData[]; - // this is the last point where we can set the default columns and if uptil now also we have an empty array then we will set the default columns if (!initialSelected || !initialSelected?.length) { initialSelected = defaultTraceSelectedColumns; } } return initialSelected || []; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ isFetchedInitialAttributes, initialOptions?.selectColumns, @@ -171,7 +167,6 @@ const useOptionsMenu = ({ const searchedAttributeKeys = useMemo(() => { if (searchedAttributesData?.payload?.attributeKeys?.length) { if (dataSource === DataSource.LOGS) { - // add timestamp and body to the list of attributes return [ ...defaultLogsSelectedColumns, ...searchedAttributesData.payload.attributeKeys.filter( @@ -188,32 +183,35 @@ const useOptionsMenu = ({ return []; }, [dataSource, searchedAttributesData?.payload?.attributeKeys]); - const initialOptionsQuery: OptionsQuery = useMemo( - () => ({ + const initialOptionsQuery: OptionsQuery = useMemo(() => { + let defaultColumns = defaultOptionsQuery.selectColumns; + if (dataSource === DataSource.TRACES) { + defaultColumns = defaultTraceSelectedColumns; + } else if (dataSource === DataSource.LOGS) { + defaultColumns = defaultLogsSelectedColumns; + } + + const finalSelectColumns = initialOptions?.selectColumns + ? initialSelectedColumns + : defaultColumns; + + return { ...defaultOptionsQuery, ...initialOptions, - // eslint-disable-next-line no-nested-ternary - selectColumns: initialOptions?.selectColumns - ? initialSelectedColumns - : dataSource === DataSource.TRACES - ? defaultTraceSelectedColumns - : defaultOptionsQuery.selectColumns, - }), - [dataSource, initialOptions, initialSelectedColumns], - ); + selectColumns: finalSelectColumns, + }; + }, [dataSource, initialOptions, initialSelectedColumns]); const selectedColumnKeys = useMemo( - () => optionsQueryData?.selectColumns?.map(({ id }) => id) || [], - [optionsQueryData], + () => preferences?.columns?.map(({ id }) => id) || [], + [preferences?.columns], ); const optionsFromAttributeKeys = useMemo(() => { const filteredAttributeKeys = searchedAttributeKeys.filter((item) => { - // For other data sources, only filter out 'body' if it exists if (dataSource !== DataSource.LOGS) { return item.key !== 'body'; } - // For LOGS, keep all keys return true; }); @@ -223,10 +221,8 @@ const useOptionsMenu = ({ const handleRedirectWithOptionsData = useCallback( (newQueryData: OptionsQuery) => { redirectWithOptionsData(newQueryData); - - setToLocalstorage(storageKey, JSON.stringify(newQueryData)); }, - [storageKey, redirectWithOptionsData], + [redirectWithOptionsData], ); const handleSelectColumns = useCallback( @@ -235,7 +231,7 @@ const useOptionsMenu = ({ const newSelectedColumns = newSelectedColumnKeys.reduce((acc, key) => { const column = [ ...searchedAttributeKeys, - ...optionsQueryData.selectColumns, + ...(preferences?.columns || []), ].find(({ id }) => id === key); if (!column) return acc; @@ -243,75 +239,116 @@ const useOptionsMenu = ({ }, [] as BaseAutocompleteData[]); const optionsData: OptionsQuery = { - ...optionsQueryData, + ...defaultOptionsQuery, selectColumns: newSelectedColumns, + format: preferences?.formatting?.format || defaultOptionsQuery.format, + maxLines: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, + fontSize: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, }; + updateColumns(newSelectedColumns); handleRedirectWithOptionsData(optionsData); }, [ searchedAttributeKeys, selectedColumnKeys, - optionsQueryData, + preferences, handleRedirectWithOptionsData, + updateColumns, ], ); const handleRemoveSelectedColumn = useCallback( (columnKey: string) => { - const newSelectedColumns = optionsQueryData?.selectColumns?.filter( + const newSelectedColumns = preferences?.columns?.filter( ({ id }) => id !== columnKey, ); - if (!newSelectedColumns.length && dataSource !== DataSource.LOGS) { + if (!newSelectedColumns?.length && dataSource !== DataSource.LOGS) { notifications.error({ message: 'There must be at least one selected column', }); } else { const optionsData: OptionsQuery = { - ...optionsQueryData, - selectColumns: newSelectedColumns, + ...defaultOptionsQuery, + selectColumns: newSelectedColumns || [], + format: preferences?.formatting?.format || defaultOptionsQuery.format, + maxLines: + preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, + fontSize: + preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, }; - + updateColumns(newSelectedColumns || []); handleRedirectWithOptionsData(optionsData); } }, - [dataSource, notifications, optionsQueryData, handleRedirectWithOptionsData], + [ + dataSource, + notifications, + preferences, + handleRedirectWithOptionsData, + updateColumns, + ], ); const handleFormatChange = useCallback( (value: LogViewMode) => { const optionsData: OptionsQuery = { - ...optionsQueryData, + ...defaultOptionsQuery, + selectColumns: preferences?.columns || [], format: value, + maxLines: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, + fontSize: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, }; + updateFormatting({ + maxLines: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, + format: value, + fontSize: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, + }); handleRedirectWithOptionsData(optionsData); }, - [handleRedirectWithOptionsData, optionsQueryData], + [handleRedirectWithOptionsData, preferences, updateFormatting], ); const handleMaxLinesChange = useCallback( (value: string | number | null) => { const optionsData: OptionsQuery = { - ...optionsQueryData, + ...defaultOptionsQuery, + selectColumns: preferences?.columns || [], + format: preferences?.formatting?.format || defaultOptionsQuery.format, maxLines: value as number, + fontSize: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, }; + updateFormatting({ + maxLines: value as number, + format: preferences?.formatting?.format || defaultOptionsQuery.format, + fontSize: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, + }); handleRedirectWithOptionsData(optionsData); }, - [handleRedirectWithOptionsData, optionsQueryData], + [handleRedirectWithOptionsData, preferences, updateFormatting], ); + const handleFontSizeChange = useCallback( (value: FontSize) => { const optionsData: OptionsQuery = { - ...optionsQueryData, + ...defaultOptionsQuery, + selectColumns: preferences?.columns || [], + format: preferences?.formatting?.format || defaultOptionsQuery.format, + maxLines: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, fontSize: value, }; + updateFormatting({ + maxLines: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, + format: preferences?.formatting?.format || defaultOptionsQuery.format, + fontSize: value, + }); handleRedirectWithOptionsData(optionsData); }, - [handleRedirectWithOptionsData, optionsQueryData], + [handleRedirectWithOptionsData, preferences, updateFormatting], ); const handleSearchAttribute = useCallback((value: string) => { @@ -331,7 +368,7 @@ const useOptionsMenu = ({ () => ({ addColumn: { isFetching: isSearchedAttributesFetching, - value: optionsQueryData?.selectColumns || defaultOptionsQuery.selectColumns, + value: preferences?.columns || defaultOptionsQuery.selectColumns, options: optionsFromAttributeKeys || [], onFocus: handleFocus, onBlur: handleBlur, @@ -340,24 +377,21 @@ const useOptionsMenu = ({ onSearch: handleSearchAttribute, }, format: { - value: optionsQueryData.format || defaultOptionsQuery.format, + value: preferences?.formatting?.format || defaultOptionsQuery.format, onChange: handleFormatChange, }, maxLines: { - value: optionsQueryData.maxLines || defaultOptionsQuery.maxLines, + value: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, onChange: handleMaxLinesChange, }, fontSize: { - value: optionsQueryData?.fontSize || defaultOptionsQuery.fontSize, + value: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, onChange: handleFontSizeChange, }, }), [ isSearchedAttributesFetching, - optionsQueryData?.selectColumns, - optionsQueryData.format, - optionsQueryData.maxLines, - optionsQueryData?.fontSize, + preferences, optionsFromAttributeKeys, handleSelectColumns, handleRemoveSelectedColumn, @@ -369,23 +403,25 @@ const useOptionsMenu = ({ ); useEffect(() => { - if (optionsQuery || !isFetchedInitialAttributes) return; + if (optionsQuery || !isFetchedInitialAttributes) { + return; + } - const nextOptionsQuery = localStorageOptionsQuery - ? JSON.parse(localStorageOptionsQuery) - : initialOptionsQuery; - - redirectWithOptionsData(nextOptionsQuery); + redirectWithOptionsData(initialOptionsQuery); }, [ isFetchedInitialAttributes, optionsQuery, initialOptionsQuery, - localStorageOptionsQuery, redirectWithOptionsData, ]); return { - options: optionsQueryData, + options: { + selectColumns: preferences?.columns || [], + format: preferences?.formatting?.format || defaultOptionsQuery.format, + maxLines: preferences?.formatting?.maxLines || defaultOptionsQuery.maxLines, + fontSize: preferences?.formatting?.fontSize || defaultOptionsQuery.fontSize, + }, config: optionsMenuConfig, handleOptionsChange: handleRedirectWithOptionsData, }; diff --git a/frontend/src/container/PipelinePage/tests/PipelineListsView.test.tsx b/frontend/src/container/PipelinePage/tests/PipelineListsView.test.tsx index 2145de152fbf..003e0ac63e69 100644 --- a/frontend/src/container/PipelinePage/tests/PipelineListsView.test.tsx +++ b/frontend/src/container/PipelinePage/tests/PipelineListsView.test.tsx @@ -1,5 +1,6 @@ /* eslint-disable sonarjs/no-duplicate-string */ import { screen } from '@testing-library/react'; +import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider'; import { findByText, fireEvent, render, waitFor } from 'tests/test-utils'; import { pipelineApiResponseMockData } from '../mocks/pipeline'; @@ -19,6 +20,18 @@ jest.mock('uplot', () => { }; }); +// Mock useUrlQuery hook +const mockUrlQuery = { + get: jest.fn(), + set: jest.fn(), + toString: jest.fn(() => ''), +}; + +jest.mock('hooks/useUrlQuery', () => ({ + __esModule: true, + default: jest.fn(() => mockUrlQuery), +})); + const samplePipelinePreviewResponse = { isLoading: false, logs: [ @@ -57,17 +70,38 @@ jest.mock( }), ); +// Mock usePreferenceSync +jest.mock('providers/preferences/sync/usePreferenceSync', () => ({ + usePreferenceSync: (): any => ({ + preferences: { + columns: [], + formatting: { + maxLines: 2, + format: 'table', + fontSize: 'small', + version: 1, + }, + }, + loading: false, + error: null, + updateColumns: jest.fn(), + updateFormatting: jest.fn(), + }), +})); + describe('PipelinePage container test', () => { it('should render PipelineListsView section', () => { const { getByText, container } = render( - , + + + , ); // table headers assertions @@ -91,14 +125,16 @@ describe('PipelinePage container test', () => { it('should render expanded content and edit mode correctly', async () => { const { getByText } = render( - , + + + , ); // content assertion @@ -122,14 +158,16 @@ describe('PipelinePage container test', () => { it('should be able to perform actions and edit on expanded view content', async () => { render( - , + + + , ); // content assertion @@ -180,14 +218,16 @@ describe('PipelinePage container test', () => { it('should be able to toggle and delete pipeline', async () => { const { getByText } = render( - , + + + , ); const addNewPipelineBtn = getByText('add_new_pipeline'); @@ -247,14 +287,16 @@ describe('PipelinePage container test', () => { it('should have populated form fields when edit pipeline is clicked', async () => { render( - , + + + , ); // content assertion diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index 71ea070e670c..7192561a2723 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -324,7 +324,7 @@ export const Query = memo(function Query({ ]); const disableOperatorSelector = - !query?.aggregateAttribute.key || query?.aggregateAttribute.key === ''; + !query?.aggregateAttribute?.key || query?.aggregateAttribute?.key === ''; const isVersionV4 = version && version === ENTITY_VERSION_V4; diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx index 4489355525ca..f82012223f3e 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx @@ -1037,7 +1037,9 @@ function QueryBuilderSearchV2( ); })} - {!hideSpanScopeSelector && } + {!hideSpanScopeSelector && ( + + )} ); } diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector.tsx index bbcdff7625f1..b8855eeea0a3 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector.tsx @@ -2,7 +2,11 @@ import { Select } from 'antd'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { cloneDeep } from 'lodash-es'; import { useEffect, useState } from 'react'; -import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQuery, + TagFilter, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; import { v4 as uuid } from 'uuid'; enum SpanScope { @@ -17,7 +21,8 @@ interface SpanFilterConfig { } interface SpanScopeSelectorProps { - queryName: string; + onChange?: (value: TagFilter) => void; + query?: IBuilderQuery; } const SPAN_FILTER_CONFIG: Record = { @@ -50,7 +55,10 @@ const SELECT_OPTIONS = [ { value: SpanScope.ENTRYPOINT_SPANS, label: 'Entrypoint Spans' }, ]; -function SpanScopeSelector({ queryName }: SpanScopeSelectorProps): JSX.Element { +function SpanScopeSelector({ + onChange, + query, +}: SpanScopeSelectorProps): JSX.Element { const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder(); const [selectedScope, setSelectedScope] = useState( SpanScope.ALL_SPANS, @@ -60,7 +68,7 @@ function SpanScopeSelector({ queryName }: SpanScopeSelectorProps): JSX.Element { filters: TagFilterItem[] = [], ): SpanScope => { const hasFilter = (key: string): boolean => - filters.some( + filters?.some( (filter) => filter.key?.type === 'spanSearchScope' && filter.key.key === key && @@ -71,15 +79,19 @@ function SpanScopeSelector({ queryName }: SpanScopeSelectorProps): JSX.Element { if (hasFilter('isEntryPoint')) return SpanScope.ENTRYPOINT_SPANS; return SpanScope.ALL_SPANS; }; - useEffect(() => { - const queryData = (currentQuery?.builder?.queryData || [])?.find( - (item) => item.queryName === queryName, + let queryData = (currentQuery?.builder?.queryData || [])?.find( + (item) => item.queryName === query?.queryName, ); + + if (onChange && query) { + queryData = query; + } + const filters = queryData?.filters?.items; const currentScope = getCurrentScopeFromFilters(filters); setSelectedScope(currentScope); - }, [currentQuery, queryName]); + }, [currentQuery, onChange, query]); const handleScopeChange = (newScope: SpanScope): void => { const newQuery = cloneDeep(currentQuery); @@ -108,14 +120,28 @@ function SpanScopeSelector({ queryName }: SpanScopeSelectorProps): JSX.Element { ...item, filters: { ...item.filters, - items: getUpdatedFilters(item.filters?.items, item.queryName === queryName), + items: getUpdatedFilters( + item.filters?.items, + item.queryName === query?.queryName, + ), }, })); - redirectWithQueryBuilderData(newQuery); + if (onChange && query) { + onChange({ + ...query.filters, + items: getUpdatedFilters( + [...query.filters.items, ...newQuery.builder.queryData[0].filters.items], + true, + ), + }); + + setSelectedScope(newScope); + } else { + redirectWithQueryBuilderData(newQuery); + } }; - // return (