diff --git a/CLAUDE.md b/CLAUDE.md index 45ec90f4..ab1e7773 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,7 +42,7 @@ The project uses **Yarn 4.5.1** workspaces. Docker Compose manages ClickHouse, M 2. **Type safety**: Use TypeScript strictly; Zod schemas for validation 3. **Existing patterns**: Follow established patterns in the codebase - explore similar files before implementing 4. **Component size**: Keep files under 300 lines; break down large components -5. **Testing**: Tests live in `__tests__/` directories; use Jest for unit/integration tests +5. **Testing**: Tests live in `__tests__/` directories; use Jest for unit/integration tests, `cd packages/app && yarn ci:unit` for unit tests, and `cd packages/app && yarn ci:int` for integration tests ## Important Context diff --git a/packages/app/src/__tests__/DBSearchPageQueryKey.test.tsx b/packages/app/src/__tests__/DBSearchPageQueryKey.test.tsx new file mode 100644 index 00000000..02515839 --- /dev/null +++ b/packages/app/src/__tests__/DBSearchPageQueryKey.test.tsx @@ -0,0 +1,178 @@ +import React from 'react'; +import objectHash from 'object-hash'; +import { + ChartConfigWithDateRange, + DisplayType, +} from '@hyperdx/common-utils/dist/types'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import { DBTimeChart } from '@/components/DBTimeChart'; +import SearchTotalCountChart from '@/components/SearchTotalCountChart'; + +// Mock the API and hooks +jest.mock('@/api', () => ({ + __esModule: true, + default: { + useMe: () => ({ + data: { team: { parallelizeWhenPossible: false } }, + isLoading: false, + }), + }, +})); + +jest.mock('@/hooks/useChartConfig', () => ({ + useQueriedChartConfig: jest.fn(() => ({ + data: { data: [], isComplete: true }, + isLoading: false, + isError: false, + isPlaceholderData: false, + isSuccess: true, + })), +})); + +jest.mock('@/source', () => ({ + useSource: () => ({ data: null, isLoading: false }), +})); + +jest.mock('@/ChartUtils', () => ({ + useTimeChartSettings: () => ({ + displayType: DisplayType.StackedBar, + dateRange: [new Date('2024-01-01'), new Date('2024-01-02')], + granularity: 'auto', + fillNulls: true, + }), + formatResponseForTimeChart: () => ({ + graphResults: [], + timestampColumn: undefined, + lineData: [], + groupColumns: [], + valueColumns: [], + isSingleValueColumn: true, + }), + getPreviousDateRange: (dateRange: [Date, Date]) => [ + new Date('2023-12-31'), + new Date('2024-01-01'), + ], + getPreviousPeriodOffsetSeconds: () => 86400, +})); + +describe('DBSearchPage QueryKey Consistency', () => { + let queryClient: QueryClient; + let mockUseQueriedChartConfig: jest.Mock; + + beforeEach(async () => { + queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + + mockUseQueriedChartConfig = (await import('@/hooks/useChartConfig')) + .useQueriedChartConfig as any; + mockUseQueriedChartConfig.mockClear(); + }); + + it('should use matching queryKeys between SearchTotalCountChart and DBTimeChart', () => { + const config: ChartConfigWithDateRange = { + select: 'count()', + from: { databaseName: 'test', tableName: 'logs' }, + where: '', + timestampValueExpression: 'timestamp', + connection: 'test-connection', + displayType: DisplayType.StackedBar, + dateRange: [new Date('2024-01-01'), new Date('2024-01-02')], + }; + + const queryKeyPrefix = 'search'; + + // Render SearchTotalCountChart + renderWithMantine( + , + ); + + // Render DBTimeChart + renderWithMantine( + , + ); + + // Get all calls to useQueriedChartConfig + const calls = mockUseQueriedChartConfig.mock.calls; + + // Should have at least 2 calls (one for each component) + expect(calls.length).toBeGreaterThanOrEqual(2); + + // Extract queryKey from each call + const searchTotalCountQueryKey = calls[0][1]?.queryKey; + const dbTimeChartQueryKey = calls[1][1]?.queryKey; + + // Both should exist + expect(searchTotalCountQueryKey).toBeDefined(); + expect(dbTimeChartQueryKey).toBeDefined(); + + // The key structure should be identical for both components + // This ensures React Query can properly dedupe the queries + expect(searchTotalCountQueryKey).toEqual(dbTimeChartQueryKey); + + // Additional object hash check for deep equality verification + const searchQueryKeyHash = objectHash(searchTotalCountQueryKey); + const chartQueryKeyHash = objectHash(dbTimeChartQueryKey); + expect(searchQueryKeyHash).toBe(chartQueryKeyHash); + }); + + it('should use consistent queryKeys when disableQueryChunking is set', () => { + const config: ChartConfigWithDateRange = { + select: 'count()', + from: { databaseName: 'test', tableName: 'logs' }, + where: '', + timestampValueExpression: 'timestamp', + connection: 'test-connection', + displayType: DisplayType.StackedBar, + dateRange: [new Date('2024-01-01'), new Date('2024-01-02')], + }; + + const queryKeyPrefix = 'search'; + + // Render both components with disableQueryChunking + renderWithMantine( + , + ); + + renderWithMantine( + , + ); + + const calls = mockUseQueriedChartConfig.mock.calls; + const searchQueryKey = calls[0][1]?.queryKey; + const chartQueryKey = calls[1][1]?.queryKey; + + // Verify the options include disableQueryChunking + expect(searchQueryKey[3]).toHaveProperty('disableQueryChunking', true); + expect(chartQueryKey[3]).toHaveProperty('disableQueryChunking', true); + + // Keys should still match + expect(searchQueryKey).toEqual(chartQueryKey); + + // Additional object hash check for deep equality verification + const searchQueryKeyHash = objectHash(searchQueryKey); + const chartQueryKeyHash = objectHash(chartQueryKey); + expect(searchQueryKeyHash).toBe(chartQueryKeyHash); + }); +});