Skip to content

Commit ec976e5

Browse files
committed
feat: add isComplete to streamed chart query results
1 parent 5c015ce commit ec976e5

File tree

5 files changed

+53
-28
lines changed

5 files changed

+53
-28
lines changed

packages/app/src/components/PatternTable.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ export default function PatternTable({
2929

3030
const [selectedPattern, setSelectedPattern] = useState<Pattern | null>(null);
3131

32-
const { totalCount, isLoading: isTotalCountLoading } = useSearchTotalCount(
33-
totalCountConfig,
34-
totalCountQueryKeyPrefix,
35-
true, // disable chunked queries for total count to avoid estimated counts flickering
36-
);
32+
const {
33+
totalCount,
34+
isLoading: isTotalCountLoading,
35+
isTotalCountComplete,
36+
} = useSearchTotalCount(totalCountConfig, totalCountQueryKeyPrefix);
3737

3838
const {
3939
data: groupedResults,
@@ -47,7 +47,8 @@ export default function PatternTable({
4747
totalCount,
4848
});
4949

50-
const isLoading = isTotalCountLoading || isGroupedPatternsLoading;
50+
const isLoading =
51+
isTotalCountLoading || !isTotalCountComplete || isGroupedPatternsLoading;
5152

5253
const sortedGroupedResults = useMemo(() => {
5354
return Object.values(groupedResults).sort(

packages/app/src/components/SearchTotalCountChart.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { useQueriedChartConfig } from '@/hooks/useChartConfig';
99
export function useSearchTotalCount(
1010
config: ChartConfigWithDateRange,
1111
queryKeyPrefix: string,
12-
disableQueryChunking: boolean = false,
1312
) {
1413
// copied from DBTimeChart
1514
const { granularity } = useTimeChartSettings(config);
@@ -23,13 +22,14 @@ export function useSearchTotalCount(
2322
isLoading,
2423
isError,
2524
} = useQueriedChartConfig(queriedConfig, {
26-
queryKey: [queryKeyPrefix, queriedConfig, disableQueryChunking],
25+
queryKey: [queryKeyPrefix, queriedConfig],
2726
staleTime: 1000 * 60 * 5,
2827
refetchOnWindowFocus: false,
2928
placeholderData: keepPreviousData, // no need to flash loading state when in live tail
30-
disableQueryChunking,
3129
});
3230

31+
const isTotalCountComplete = !!totalCountData?.isComplete;
32+
3333
const totalCount = useMemo(() => {
3434
return totalCountData?.data?.reduce(
3535
(p: number, v: any) => p + Number.parseInt(v['count()']),
@@ -41,6 +41,7 @@ export function useSearchTotalCount(
4141
totalCount,
4242
isLoading,
4343
isError,
44+
isTotalCountComplete,
4445
};
4546
}
4647

packages/app/src/hooks/__tests__/useChartConfig.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ describe('useChartConfig', () => {
357357
data: mockResponse.data,
358358
meta: mockResponse.meta,
359359
rows: mockResponse.rows,
360+
isComplete: true,
360361
});
361362
expect(result.current.isLoading).toBe(false);
362363
expect(result.current.isPending).toBe(false);
@@ -402,6 +403,7 @@ describe('useChartConfig', () => {
402403
data: mockResponse.data,
403404
meta: mockResponse.meta,
404405
rows: mockResponse.rows,
406+
isComplete: true,
405407
});
406408
expect(result.current.isLoading).toBe(false);
407409
expect(result.current.isPending).toBe(false);
@@ -452,6 +454,7 @@ describe('useChartConfig', () => {
452454
data: mockResponse.data,
453455
meta: mockResponse.meta,
454456
rows: mockResponse.rows,
457+
isComplete: true,
455458
});
456459
});
457460

@@ -527,6 +530,7 @@ describe('useChartConfig', () => {
527530
data: mockResponse.data,
528531
meta: mockResponse.meta,
529532
rows: mockResponse.rows,
533+
isComplete: true,
530534
});
531535
});
532536

@@ -577,6 +581,7 @@ describe('useChartConfig', () => {
577581
data: mockResponse.data,
578582
meta: mockResponse.meta,
579583
rows: mockResponse.rows,
584+
isComplete: true,
580585
});
581586
});
582587

@@ -667,6 +672,7 @@ describe('useChartConfig', () => {
667672
],
668673
meta: mockResponse1.meta,
669674
rows: 5,
675+
isComplete: true,
670676
});
671677
expect(result.current.isLoading).toBe(false);
672678
expect(result.current.isPending).toBe(false);
@@ -726,6 +732,7 @@ describe('useChartConfig', () => {
726732
data: [...mockResponse2.data, ...mockResponse1.data],
727733
meta: mockResponse1.meta,
728734
rows: 4,
735+
isComplete: false,
729736
});
730737
expect(result.current.isFetching).toBe(true);
731738
expect(result.current.isLoading).toBe(false); // isLoading is false because we have partial data
@@ -750,6 +757,7 @@ describe('useChartConfig', () => {
750757
],
751758
meta: mockResponse1.meta,
752759
rows: 5,
760+
isComplete: true,
753761
});
754762
});
755763

@@ -798,6 +806,7 @@ describe('useChartConfig', () => {
798806
data: mockResponse1.data,
799807
meta: mockResponse1.meta,
800808
rows: 1,
809+
isComplete: false,
801810
});
802811
});
803812

packages/app/src/hooks/useChartConfig.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ type TimeWindow = {
4848
dateRangeEndInclusive?: boolean;
4949
};
5050

51-
type TQueryFnData = Pick<ResponseJSON<any>, 'data' | 'meta' | 'rows'> & {};
51+
type TQueryFnData = Pick<ResponseJSON<any>, 'data' | 'meta' | 'rows'> & {
52+
isComplete: boolean;
53+
};
5254

5355
const shouldUseChunking = (
5456
config: ChartConfigWithOptDateRange,
@@ -125,7 +127,6 @@ async function* fetchDataInChunks(
125127
? getGranularityAlignedTimeWindows(config)
126128
: ([undefined] as const);
127129

128-
let query = null;
129130
if (IS_MTVIEWS_ENABLED) {
130131
const { dataTableDDL, mtViewDDL, renderMTViewConfig } =
131132
await buildMTViewSelectQuery(config);
@@ -134,10 +135,12 @@ async function* fetchDataInChunks(
134135
console.log('dataTableDDL:', dataTableDDL);
135136
// eslint-disable-next-line no-console
136137
console.log('mtViewDDL:', mtViewDDL);
137-
query = await renderMTViewConfig();
138+
await renderMTViewConfig();
138139
}
139140

140-
for (const window of windows) {
141+
for (let i = 0; i < windows.length; i++) {
142+
const window = windows[i];
143+
141144
const windowedConfig = {
142145
...config,
143146
...(window ?? {}),
@@ -151,7 +154,7 @@ async function* fetchDataInChunks(
151154
},
152155
});
153156

154-
yield result;
157+
yield { chunk: result, isComplete: i === windows.length - 1 };
155158
}
156159
}
157160

@@ -164,13 +167,17 @@ async function* fetchDataInChunks(
164167
*
165168
* For chunked queries, note the following:
166169
* - `config.limit`, if provided, is applied to each chunk, so the total number
167-
* of rows returned may be up to `limit * number_of_chunks`.
170+
* of rows returned may be up to `limit * number_of_chunks`.
168171
* - The returned data will be ordered within each chunk, and chunks will
169-
* be ordered oldest-first, by the `timestampValueExpression`.
172+
* be ordered oldest-first, by the `timestampValueExpression`.
173+
* - `isPending` is true until the first chunk is fetched. Once the first chunk
174+
* is available, `isPending` will be false and `isSuccess` will be true.
175+
* `isFetching` will be true until all chunks have been fetched.
176+
* - `data.isComplete` indicates whether all chunks have been fetched.
170177
*/
171178
export function useQueriedChartConfig(
172179
config: ChartConfigWithOptDateRange,
173-
options?: Partial<UseQueryOptions<ResponseJSON<any>>> &
180+
options?: Partial<UseQueryOptions<TQueryFnData>> &
174181
AdditionalUseQueriedChartConfigOptions,
175182
) {
176183
const clickhouseClient = useClickhouseClient();
@@ -194,12 +201,18 @@ export function useQueriedChartConfig(
194201
* in flickering or render loops.
195202
*/
196203
refetchMode: 'replace',
197-
initialValue: { data: [], meta: [], rows: 0 } as TQueryFnData,
198-
reducer: (acc, chunk) => {
204+
initialValue: {
205+
data: [],
206+
meta: [],
207+
rows: 0,
208+
isComplete: false,
209+
} as TQueryFnData,
210+
reducer: (acc, { chunk, isComplete }) => {
199211
return {
200212
data: [...(chunk.data || []), ...(acc?.data || [])],
201213
meta: chunk.meta,
202214
rows: (acc?.rows || 0) + (chunk.rows || 0),
215+
isComplete,
203216
};
204217
},
205218
}),

packages/app/src/hooks/usePatterns.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,15 @@ function usePatterns({
141141
limit: { limit: samples },
142142
});
143143

144-
const { data: sampleRows } = useQueriedChartConfig(
145-
configWithPrimaryAndPartitionKey ?? config, // `config` satisfying type, never used due to `enabled` check
146-
{
147-
enabled: configWithPrimaryAndPartitionKey != null && enabled,
148-
// Disable chunking to ensure we get the desired sample size
149-
disableQueryChunking: true,
150-
},
151-
);
144+
const { data: sampleRows, isLoading: isSampleLoading } =
145+
useQueriedChartConfig(
146+
configWithPrimaryAndPartitionKey ?? config, // `config` satisfying type, never used due to `enabled` check
147+
{
148+
enabled: configWithPrimaryAndPartitionKey != null && enabled,
149+
// Disable chunking to ensure we get the desired sample size
150+
disableQueryChunking: true,
151+
},
152+
);
152153

153154
const { data: pyodide, isLoading: isLoadingPyodide } = usePyodide({
154155
enabled,
@@ -195,7 +196,7 @@ function usePatterns({
195196

196197
return {
197198
...query,
198-
isLoading: query.isLoading || isLoadingPyodide,
199+
isLoading: query.isLoading || isSampleLoading || isLoadingPyodide,
199200
patternQueryConfig: configWithPrimaryAndPartitionKey,
200201
};
201202
}

0 commit comments

Comments
 (0)