@parca/profile 0.19.95 → 0.19.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.d.ts.map +1 -1
  3. package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +65 -45
  4. package/dist/ProfileFlameGraph/FlameGraphArrow/index.d.ts.map +1 -1
  5. package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +16 -4
  6. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts +11 -0
  7. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts.map +1 -0
  8. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.js +65 -0
  9. package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.d.ts.map +1 -1
  10. package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +35 -5
  11. package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.d.ts.map +1 -1
  12. package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +29 -3
  13. package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -1
  14. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  15. package/dist/ProfileSelector/MetricsGraphSection.js +1 -1
  16. package/dist/ProfileSelector/index.d.ts.map +1 -1
  17. package/dist/ProfileSelector/index.js +1 -2
  18. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  19. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +8 -0
  20. package/dist/SimpleMatchers/Select.d.ts.map +1 -1
  21. package/dist/SimpleMatchers/Select.js +3 -3
  22. package/dist/hooks/useLabels.d.ts.map +1 -1
  23. package/dist/hooks/useLabels.js +7 -2
  24. package/dist/hooks/useQueryState.d.ts.map +1 -1
  25. package/dist/hooks/useQueryState.js +53 -23
  26. package/dist/hooks/useQueryState.test.js +32 -22
  27. package/dist/styles.css +1 -1
  28. package/dist/useSumBy.d.ts +10 -2
  29. package/dist/useSumBy.d.ts.map +1 -1
  30. package/dist/useSumBy.js +30 -7
  31. package/package.json +15 -10
  32. package/src/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.tsx +89 -57
  33. package/src/ProfileFlameGraph/FlameGraphArrow/index.tsx +27 -2
  34. package/src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts +84 -0
  35. package/src/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.ts +40 -5
  36. package/src/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.ts +41 -5
  37. package/src/ProfileSelector/MetricsGraphSection.tsx +2 -2
  38. package/src/ProfileSelector/index.tsx +1 -5
  39. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +8 -0
  40. package/src/SimpleMatchers/Select.tsx +3 -3
  41. package/src/hooks/useLabels.ts +8 -2
  42. package/src/hooks/useQueryState.test.tsx +41 -22
  43. package/src/hooks/useQueryState.ts +72 -31
  44. package/src/useSumBy.ts +58 -4
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ import {useEffect} from 'react';
15
+
14
16
  import {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
15
17
  import {useGrpcMetadata} from '@parca/components';
16
18
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
@@ -68,7 +70,9 @@ export const useLabelNames = (
68
70
  },
69
71
  });
70
72
 
71
- console.log('Label names query result:', {data, error, isLoading});
73
+ useEffect(() => {
74
+ console.log('Label names query result:', {data, error, isLoading});
75
+ }, [data, error, isLoading]);
72
76
 
73
77
  return {
74
78
  result: {response: data, error: error as Error},
@@ -109,7 +113,9 @@ export const useLabelValues = (
109
113
  },
110
114
  });
111
115
 
112
- console.log('Label values query result:', {data, error, isLoading, labelName});
116
+ useEffect(() => {
117
+ console.log('Label values query result:', {data, error, isLoading, labelName});
118
+ }, [data, error, isLoading, labelName]);
113
119
 
114
120
  return {
115
121
  result: {response: data ?? [], error: error as Error},
@@ -69,16 +69,33 @@ vi.mock('@parca/components/src/hooks/URLState/utils', async () => {
69
69
  };
70
70
  });
71
71
 
72
- // Mock useSumBy to return the sumBy from URL params or undefined
72
+ // Mock useSumBy with stateful behavior using React's useState
73
73
  vi.mock('../useSumBy', async () => {
74
74
  const actual = await vi.importActual('../useSumBy');
75
+ const react = await import('react');
76
+
75
77
  return {
76
78
  ...actual,
77
- useSumBy: (_queryClient: any, _profileType: any, _timeRange: any, defaultValue: any) => ({
78
- sumBy: defaultValue,
79
- setSumBy: vi.fn(),
80
- isLoading: false,
81
- }),
79
+ useSumBy: (
80
+ _queryClient: any,
81
+ _profileType: any,
82
+ _timeRange: any,
83
+ _draftProfileType: any,
84
+ _draftTimeRange: any,
85
+ defaultValue: any
86
+ ) => {
87
+ const [draftSumBy, setDraftSumBy] = react.useState<string[] | undefined>(defaultValue);
88
+ const [sumBy, setSumBy] = react.useState<string[] | undefined>(defaultValue);
89
+
90
+ return {
91
+ sumBy,
92
+ setSumBy,
93
+ isLoading: false,
94
+ draftSumBy,
95
+ setDraftSumBy,
96
+ isDraftSumByLoading: false,
97
+ };
98
+ },
82
99
  };
83
100
  });
84
101
 
@@ -207,7 +224,9 @@ describe('useQueryState', () => {
207
224
  it('should update sumBy', async () => {
208
225
  const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
209
226
 
227
+ // sumBy only applies to delta profiles, so we need to set one first
210
228
  act(() => {
229
+ result.current.setDraftExpression('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}');
211
230
  result.current.setDraftSumBy(['namespace', 'container']);
212
231
  });
213
232
 
@@ -256,15 +275,15 @@ describe('useQueryState', () => {
256
275
  const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
257
276
 
258
277
  act(() => {
259
- // Update multiple draft values
260
- result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
278
+ // Update multiple draft values (using delta profile since sumBy only applies to delta)
279
+ result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
261
280
  result.current.setDraftTimeRange(7000, 8000, 'relative:minute|30');
262
281
  result.current.setDraftSumBy(['pod', 'node']);
263
282
  });
264
283
 
265
284
  // All drafts should be updated
266
285
  expect(result.current.draftSelection.expression).toBe(
267
- 'memory:inuse_space:bytes:space:bytes{}'
286
+ 'memory:alloc_space:bytes:space:bytes:delta{}'
268
287
  );
269
288
  expect(result.current.draftSelection.from).toBe(7000);
270
289
  expect(result.current.draftSelection.to).toBe(8000);
@@ -278,7 +297,7 @@ describe('useQueryState', () => {
278
297
  // Should only navigate once for all updates
279
298
  expect(mockNavigateTo).toHaveBeenCalledTimes(1);
280
299
  const [, params] = mockNavigateTo.mock.calls[0];
281
- expect(params.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
300
+ expect(params.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
282
301
  expect(params.from).toBe('7000');
283
302
  expect(params.to).toBe('8000');
284
303
  expect(params.time_selection).toBe('relative:minute|30');
@@ -430,9 +449,9 @@ describe('useQueryState', () => {
430
449
  it('should handle _b suffix correctly', async () => {
431
450
  const {result} = renderHook(() => useQueryState({suffix: '_b'}), {wrapper: createWrapper()});
432
451
 
433
- // Update draft state
452
+ // Update draft state (using delta profile since sumBy only applies to delta)
434
453
  act(() => {
435
- result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
454
+ result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
436
455
  result.current.setDraftTimeRange(3333, 4444, 'relative:hour|2');
437
456
  result.current.setDraftSumBy(['label_b']);
438
457
  });
@@ -445,7 +464,7 @@ describe('useQueryState', () => {
445
464
  await waitFor(() => {
446
465
  expect(mockNavigateTo).toHaveBeenCalled();
447
466
  const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
448
- expect(params.expression_b).toBe('memory:inuse_space:bytes:space:bytes{}');
467
+ expect(params.expression_b).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
449
468
  expect(params.from_b).toBe('3333');
450
469
  expect(params.to_b).toBe('4444');
451
470
  expect(params.sum_by_b).toBe('label_b');
@@ -457,9 +476,9 @@ describe('useQueryState', () => {
457
476
  it('should not update URL until commit', async () => {
458
477
  const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
459
478
 
460
- // Make multiple draft changes
479
+ // Make multiple draft changes (using delta profile since sumBy only applies to delta)
461
480
  act(() => {
462
- result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
481
+ result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
463
482
  result.current.setDraftTimeRange(5000, 6000, 'relative:hour|3');
464
483
  result.current.setDraftSumBy(['namespace', 'pod']);
465
484
  });
@@ -476,7 +495,7 @@ describe('useQueryState', () => {
476
495
  await waitFor(() => {
477
496
  expect(mockNavigateTo).toHaveBeenCalledTimes(1);
478
497
  const [, params] = mockNavigateTo.mock.calls[0];
479
- expect(params.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
498
+ expect(params.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
480
499
  expect(params.from).toBe('5000');
481
500
  expect(params.to).toBe('6000');
482
501
  expect(params.sum_by).toBe('namespace,pod');
@@ -737,17 +756,17 @@ describe('useQueryState', () => {
737
756
 
738
757
  describe('State persistence after page reload', () => {
739
758
  it('should retain committed values after page reload simulation', async () => {
740
- // Initial state
759
+ // Initial state (using delta profile since sumBy only applies to delta)
741
760
  mockLocation.search =
742
- '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000';
761
+ '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
743
762
 
744
763
  const {result: result1, unmount} = renderHook(() => useQueryState(), {
745
764
  wrapper: createWrapper(),
746
765
  });
747
766
 
748
- // User makes changes to draft
767
+ // User makes changes to draft (using delta profile since sumBy only applies to delta)
749
768
  act(() => {
750
- result1.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
769
+ result1.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
751
770
  result1.current.setDraftTimeRange(5000, 6000, 'relative:minute|15');
752
771
  result1.current.setDraftSumBy(['namespace', 'pod']);
753
772
  });
@@ -786,7 +805,7 @@ describe('useQueryState', () => {
786
805
 
787
806
  // Verify state is loaded from URL after "reload"
788
807
  expect(result2.current.querySelection.expression).toBe(
789
- 'memory:inuse_space:bytes:space:bytes{}'
808
+ 'memory:alloc_space:bytes:space:bytes:delta{}'
790
809
  );
791
810
  expect(result2.current.querySelection.from).toBe(5000);
792
811
  expect(result2.current.querySelection.to).toBe(6000);
@@ -795,7 +814,7 @@ describe('useQueryState', () => {
795
814
 
796
815
  // Draft should be synced with URL state on page load
797
816
  expect(result2.current.draftSelection.expression).toBe(
798
- 'memory:inuse_space:bytes:space:bytes{}'
817
+ 'memory:alloc_space:bytes:space:bytes:delta{}'
799
818
  );
800
819
  expect(result2.current.draftSelection.from).toBe(5000);
801
820
  expect(result2.current.draftSelection.to).toBe(6000);
@@ -19,7 +19,8 @@ import {Query} from '@parca/parser';
19
19
  import {QuerySelection} from '../ProfileSelector';
20
20
  import {ProfileSelection, ProfileSelectionFromParams, ProfileSource} from '../ProfileSource';
21
21
  import {useResetFlameGraphState} from '../ProfileView/hooks/useResetFlameGraphState';
22
- import {sumByToParam, useSumBy, useSumByFromParams} from '../useSumBy';
22
+ import {useResetStateOnProfileTypeChange} from '../ProfileView/hooks/useResetStateOnProfileTypeChange';
23
+ import {DEFAULT_EMPTY_SUM_BY, sumByToParam, useSumBy, useSumByFromParams} from '../useSumBy';
23
24
 
24
25
  interface UseQueryStateOptions {
25
26
  suffix?: '_a' | '_b'; // For comparison mode
@@ -79,6 +80,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
79
80
 
80
81
  const batchUpdates = useURLStateBatch();
81
82
  const resetFlameGraphState = useResetFlameGraphState();
83
+ const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
82
84
 
83
85
  // URL state hooks with appropriate suffixes
84
86
  const [expression, setExpressionState] = useURLState<string>(`expression${suffix}`, {
@@ -115,29 +117,6 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
115
117
  const [draftTimeSelection, setDraftTimeSelection] = useState<string>(
116
118
  timeSelection ?? defaultTimeSelection
117
119
  );
118
- const [draftSumBy, setDraftSumBy] = useState<string[] | undefined>(sumBy);
119
-
120
- // Sync draft state with URL state when URL changes externally
121
- useEffect(() => {
122
- setDraftExpression(expression ?? defaultExpression);
123
- }, [expression, defaultExpression]);
124
-
125
- useEffect(() => {
126
- setDraftFrom(from ?? defaultFrom?.toString() ?? '');
127
- }, [from, defaultFrom]);
128
-
129
- useEffect(() => {
130
- setDraftTo(to ?? defaultTo?.toString() ?? '');
131
- }, [to, defaultTo]);
132
-
133
- useEffect(() => {
134
- setDraftTimeSelection(timeSelection ?? defaultTimeSelection);
135
- }, [timeSelection, defaultTimeSelection]);
136
-
137
- useEffect(() => {
138
- setDraftSumBy(sumBy);
139
- }, [sumBy]);
140
-
141
120
  // Parse the draft query to extract profile information
142
121
  const draftQuery = useMemo(() => {
143
122
  try {
@@ -147,8 +126,16 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
147
126
  }
148
127
  }, [draftExpression]);
149
128
 
129
+ const query = useMemo(() => {
130
+ try {
131
+ return Query.parse(expression ?? '');
132
+ } catch {
133
+ return Query.parse('');
134
+ }
135
+ }, [expression]);
150
136
  const draftProfileType = useMemo(() => draftQuery.profileType(), [draftQuery]);
151
137
  const draftProfileName = useMemo(() => draftQuery.profileName(), [draftQuery]);
138
+ const profileType = useMemo(() => query.profileType(), [query]);
152
139
 
153
140
  // Compute draft time range for label fetching
154
141
  const draftTimeRange = useMemo(() => {
@@ -159,13 +146,50 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
159
146
  );
160
147
  }, [draftTimeSelection, draftFrom, draftTo, defaultTimeSelection, defaultFrom, defaultTo]);
161
148
  // Use combined sumBy hook for fetching labels and computing defaults (based on committed state)
162
- const {sumBy: computedSumByFromURL, isLoading: sumBySelectionLoading} = useSumBy(
149
+ const {
150
+ sumBy: computedSumByFromURL,
151
+ isLoading: sumBySelectionLoading,
152
+ draftSumBy,
153
+ setDraftSumBy,
154
+ isDraftSumByLoading,
155
+ } = useSumBy(
163
156
  queryClient,
157
+ profileType?.profileName !== '' ? profileType : draftProfileType,
158
+ draftTimeRange,
164
159
  draftProfileType,
165
160
  draftTimeRange,
166
161
  sumBy
167
162
  );
168
163
 
164
+ // Sync draft state with URL state when URL changes externally
165
+ useEffect(() => {
166
+ setDraftExpression(expression ?? defaultExpression);
167
+ }, [expression, defaultExpression]);
168
+
169
+ useEffect(() => {
170
+ setDraftFrom(from ?? defaultFrom?.toString() ?? '');
171
+ }, [from, defaultFrom]);
172
+
173
+ useEffect(() => {
174
+ setDraftTo(to ?? defaultTo?.toString() ?? '');
175
+ }, [to, defaultTo]);
176
+
177
+ useEffect(() => {
178
+ setDraftTimeSelection(timeSelection ?? defaultTimeSelection);
179
+ }, [timeSelection, defaultTimeSelection]);
180
+
181
+ useEffect(() => {
182
+ setDraftSumBy(sumBy);
183
+ }, [sumBy, setDraftSumBy]);
184
+
185
+ // Sync computed sumBy to URL if URL doesn't already have a value
186
+ // to ensure the shared URL can always pick it up.
187
+ useEffect(() => {
188
+ if (sumByParam === undefined && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
189
+ setSumByParam(sumByToParam(computedSumByFromURL));
190
+ }
191
+ }, [sumByParam, computedSumByFromURL, sumBySelectionLoading, setSumByParam]);
192
+
169
193
  // Construct the QuerySelection object (committed state from URL)
170
194
  const querySelection: QuerySelection = useMemo(() => {
171
195
  const range = DateTimeRange.fromRangeKey(
@@ -285,12 +309,16 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
285
309
  setFromState(finalFrom);
286
310
  setToState(finalTo);
287
311
  setTimeSelectionState(finalTimeSelection);
288
- setSumByParam(sumByToParam(draftSumBy));
289
312
 
290
313
  // Auto-calculate merge parameters for delta profiles
291
314
  // Parse the final expression to check if it's a delta profile
292
315
  const finalQuery = Query.parse(finalExpression);
293
316
  const isDelta = finalQuery.profileType().delta;
317
+ if (isDelta) {
318
+ setSumByParam(sumByToParam(draftSumBy));
319
+ } else {
320
+ setSumByParam(DEFAULT_EMPTY_SUM_BY);
321
+ }
294
322
 
295
323
  if (isDelta && finalFrom !== '' && finalTo !== '') {
296
324
  const fromMs = parseInt(finalFrom);
@@ -313,6 +341,12 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
313
341
  setSelectionParam(undefined);
314
342
  }
315
343
  resetFlameGraphState();
344
+ if (
345
+ draftProfileType.toString() !==
346
+ Query.parse(querySelection.expression).profileType().toString()
347
+ ) {
348
+ resetStateOnProfileTypeChange();
349
+ }
316
350
  });
317
351
  },
318
352
  [
@@ -334,6 +368,9 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
334
368
  setMergeToState,
335
369
  setSelectionParam,
336
370
  resetFlameGraphState,
371
+ resetStateOnProfileTypeChange,
372
+ draftProfileType,
373
+ querySelection.expression,
337
374
  ]
338
375
  );
339
376
 
@@ -346,9 +383,12 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
346
383
  []
347
384
  );
348
385
 
349
- const setDraftSumByCallback = useCallback((newSumBy: string[] | undefined) => {
350
- setDraftSumBy(newSumBy);
351
- }, []);
386
+ const setDraftSumByCallback = useCallback(
387
+ (newSumBy: string[] | undefined) => {
388
+ setDraftSumBy(newSumBy);
389
+ },
390
+ [setDraftSumBy]
391
+ );
352
392
 
353
393
  const setDraftProfileName = useCallback(
354
394
  (newProfileName: string) => {
@@ -357,9 +397,10 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
357
397
  const [newQuery, changed] = draftQuery.setProfileName(newProfileName);
358
398
  if (changed) {
359
399
  setDraftExpression(newQuery.toString());
400
+ setDraftSumBy(undefined);
360
401
  }
361
402
  },
362
- [draftQuery]
403
+ [draftQuery, setDraftSumBy]
363
404
  );
364
405
 
365
406
  const setDraftMatchers = useCallback(
@@ -421,7 +462,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
421
462
  setProfileSelection,
422
463
 
423
464
  // Loading state
424
- sumByLoading: sumBySelectionLoading,
465
+ sumByLoading: isDraftSumByLoading || sumBySelectionLoading,
425
466
 
426
467
  draftParsedQuery,
427
468
  parsedQuery,
package/src/useSumBy.ts CHANGED
@@ -52,6 +52,7 @@ export const useSumBySelection = (
52
52
  profileType: ProfileType | undefined,
53
53
  labelNamesLoading: boolean,
54
54
  labels: string[] | undefined,
55
+ draftSumBy: string[] | undefined,
55
56
  {
56
57
  defaultValue,
57
58
  }: {
@@ -100,9 +101,15 @@ export const useSumBySelection = (
100
101
  const lastValidSumByRef = useRef<string[]>(DEFAULT_EMPTY_SUM_BY);
101
102
 
102
103
  const sumBy = useMemo(() => {
103
- // If loading, return the last valid value to prevent input from blanking
104
- if (labelNamesLoading && lastValidSumByRef.current !== DEFAULT_EMPTY_SUM_BY) {
105
- return lastValidSumByRef.current;
104
+ if (labelNamesLoading) {
105
+ // For smoother UX, return draftSumBy first if available during loading
106
+ // as this must be recently computed with the draft time range labels.
107
+ if (draftSumBy !== undefined) {
108
+ return draftSumBy;
109
+ }
110
+ if (lastValidSumByRef.current == null) {
111
+ return lastValidSumByRef.current;
112
+ }
106
113
  }
107
114
 
108
115
  let result =
@@ -116,7 +123,7 @@ export const useSumBySelection = (
116
123
  lastValidSumByRef.current = result;
117
124
 
118
125
  return result;
119
- }, [userSelectedSumBy, profileType, defaultSumBy, labelNamesLoading]);
126
+ }, [userSelectedSumBy, profileType, defaultSumBy, labelNamesLoading, draftSumBy]);
120
127
 
121
128
  return [
122
129
  sumBy,
@@ -187,11 +194,16 @@ export const useSumBy = (
187
194
  queryClient: QueryServiceClient,
188
195
  profileType: ProfileType | undefined,
189
196
  timeRange: DateTimeRange,
197
+ draftProfileType: ProfileType | undefined,
198
+ draftTimeRange: DateTimeRange,
190
199
  defaultValue?: string[]
191
200
  ): {
192
201
  sumBy: string[] | undefined;
193
202
  setSumBy: (sumBy: string[]) => void;
194
203
  isLoading: boolean;
204
+ draftSumBy: string[] | undefined;
205
+ setDraftSumBy: (sumBy: string[] | undefined) => void;
206
+ isDraftSumByLoading: boolean;
195
207
  } => {
196
208
  const {loading: labelNamesLoading, result} = useLabelNames(
197
209
  queryClient,
@@ -200,6 +212,13 @@ export const useSumBy = (
200
212
  timeRange.getToMs()
201
213
  );
202
214
 
215
+ const {draftSumBy, setDraftSumBy, isDraftSumByLoading} = useDraftSumBy(
216
+ queryClient,
217
+ draftProfileType,
218
+ draftTimeRange,
219
+ defaultValue
220
+ );
221
+
203
222
  const labels = useMemo(() => {
204
223
  return result.response?.labelNames === undefined ? [] : result.response.labelNames;
205
224
  }, [result]);
@@ -208,6 +227,7 @@ export const useSumBy = (
208
227
  profileType,
209
228
  labelNamesLoading,
210
229
  labels,
230
+ draftSumBy,
211
231
  {defaultValue}
212
232
  );
213
233
 
@@ -215,5 +235,39 @@ export const useSumBy = (
215
235
  sumBy: sumBySelection,
216
236
  setSumBy: setSumByInternal,
217
237
  isLoading,
238
+ draftSumBy,
239
+ setDraftSumBy,
240
+ isDraftSumByLoading,
241
+ };
242
+ };
243
+
244
+ export const useDraftSumBy = (
245
+ queryClient: QueryServiceClient,
246
+ profileType: ProfileType | undefined,
247
+ timeRange: DateTimeRange,
248
+ defaultValue?: string[]
249
+ ): {
250
+ draftSumBy: string[] | undefined;
251
+ setDraftSumBy: (sumBy: string[] | undefined) => void;
252
+ isDraftSumByLoading: boolean;
253
+ } => {
254
+ const [draftSumBy, setDraftSumBy] = useState<string[] | undefined>(defaultValue);
255
+ const {loading: labelNamesLoading, result} = useLabelNames(
256
+ queryClient,
257
+ profileType?.toString() ?? '',
258
+ timeRange.getFromMs(),
259
+ timeRange.getToMs()
260
+ );
261
+
262
+ const labels = useMemo(() => {
263
+ return result.response?.labelNames === undefined ? [] : result.response.labelNames;
264
+ }, [result]);
265
+
266
+ const {defaultSumBy, isLoading} = useDefaultSumBy(profileType, labelNamesLoading, labels);
267
+
268
+ return {
269
+ draftSumBy: draftSumBy ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY,
270
+ setDraftSumBy: setDraftSumBy,
271
+ isDraftSumByLoading: isLoading,
218
272
  };
219
273
  };