@parca/profile 0.19.73 → 0.19.74

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 (71) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/MatchersInput/index.d.ts.map +1 -1
  3. package/dist/MatchersInput/index.js +4 -2
  4. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -12
  5. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
  6. package/dist/ProfileExplorer/ProfileExplorerCompare.js +52 -11
  7. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -7
  8. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
  9. package/dist/ProfileExplorer/ProfileExplorerSingle.js +4 -2
  10. package/dist/ProfileExplorer/index.d.ts +1 -4
  11. package/dist/ProfileExplorer/index.d.ts.map +1 -1
  12. package/dist/ProfileExplorer/index.js +11 -225
  13. package/dist/ProfileMetricsGraph/index.d.ts +1 -1
  14. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  15. package/dist/ProfileMetricsGraph/index.js +16 -20
  16. package/dist/ProfileSelector/MetricsGraphSection.d.ts +3 -3
  17. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  18. package/dist/ProfileSelector/MetricsGraphSection.js +10 -6
  19. package/dist/ProfileSelector/index.d.ts +2 -7
  20. package/dist/ProfileSelector/index.d.ts.map +1 -1
  21. package/dist/ProfileSelector/index.js +40 -46
  22. package/dist/ProfileSelector/useAutoQuerySelector.d.ts.map +1 -1
  23. package/dist/ProfileSelector/useAutoQuerySelector.js +19 -4
  24. package/dist/ProfileTypeSelector/index.d.ts.map +1 -1
  25. package/dist/ProfileTypeSelector/index.js +1 -1
  26. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  27. package/dist/ProfileView/components/ViewSelector/index.js +10 -4
  28. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  29. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +4 -2
  30. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  31. package/dist/ProfileView/hooks/useVisualizationState.js +20 -13
  32. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  33. package/dist/Table/MoreDropdown.js +7 -3
  34. package/dist/Table/TableContextMenu.d.ts.map +1 -1
  35. package/dist/Table/TableContextMenu.js +9 -5
  36. package/dist/hooks/useCompareModeMeta.d.ts +10 -0
  37. package/dist/hooks/useCompareModeMeta.d.ts.map +1 -0
  38. package/dist/hooks/useCompareModeMeta.js +113 -0
  39. package/dist/hooks/useQueryState.d.ts +32 -0
  40. package/dist/hooks/useQueryState.d.ts.map +1 -0
  41. package/dist/hooks/useQueryState.js +285 -0
  42. package/dist/hooks/useQueryState.test.d.ts +2 -0
  43. package/dist/hooks/useQueryState.test.d.ts.map +1 -0
  44. package/dist/hooks/useQueryState.test.js +910 -0
  45. package/dist/index.d.ts +4 -5
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +6 -3
  48. package/dist/useSumBy.d.ts +7 -0
  49. package/dist/useSumBy.d.ts.map +1 -1
  50. package/dist/useSumBy.js +31 -6
  51. package/package.json +6 -6
  52. package/src/MatchersInput/index.tsx +4 -2
  53. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +64 -46
  54. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +7 -19
  55. package/src/ProfileExplorer/index.tsx +11 -339
  56. package/src/ProfileMetricsGraph/index.tsx +16 -20
  57. package/src/ProfileSelector/MetricsGraphSection.tsx +14 -10
  58. package/src/ProfileSelector/index.tsx +65 -83
  59. package/src/ProfileSelector/useAutoQuerySelector.ts +23 -5
  60. package/src/ProfileTypeSelector/index.tsx +3 -1
  61. package/src/ProfileView/components/ViewSelector/index.tsx +9 -4
  62. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +4 -2
  63. package/src/ProfileView/hooks/useVisualizationState.ts +25 -12
  64. package/src/Table/MoreDropdown.tsx +7 -3
  65. package/src/Table/TableContextMenu.tsx +9 -5
  66. package/src/hooks/useCompareModeMeta.ts +131 -0
  67. package/src/hooks/useQueryState.test.tsx +1202 -0
  68. package/src/hooks/useQueryState.ts +414 -0
  69. package/src/index.tsx +9 -11
  70. package/src/useSumBy.ts +62 -7
  71. package/src/ProfileExplorer/index.test.ts +0 -97
@@ -11,28 +11,22 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useEffect, useMemo, useState} from 'react';
14
+ import {useEffect, useMemo} from 'react';
15
15
 
16
16
  import {Provider} from 'react-redux';
17
17
 
18
18
  import {QueryServiceClient} from '@parca/client';
19
- import {DateTimeRange, KeyDownProvider, useParcaContext} from '@parca/components';
20
- import {Query} from '@parca/parser';
19
+ import {KeyDownProvider, useParcaContext} from '@parca/components';
21
20
  import {createStore} from '@parca/store';
22
- import {capitalizeOnlyFirstLetter, safeDecode, type NavigateFunction} from '@parca/utilities';
21
+ import {capitalizeOnlyFirstLetter, type NavigateFunction} from '@parca/utilities';
23
22
 
24
- import {ProfileSelection, ProfileSelectionFromParams, SuffixParams} from '..';
25
- import {QuerySelection} from '../ProfileSelector';
26
- import {useResetFlameGraphState} from '../ProfileView/hooks/useResetFlameGraphState';
27
- import {useResetStateOnProfileTypeChange} from '../ProfileView/hooks/useResetStateOnProfileTypeChange';
23
+ import {useCompareModeMeta} from '../hooks/useCompareModeMeta';
28
24
  import {useHasProfileData} from '../useHasProfileData';
29
- import {sumByToParam, useSumByFromParams} from '../useSumBy';
30
25
  import ProfileExplorerCompare from './ProfileExplorerCompare';
31
26
  import ProfileExplorerSingle from './ProfileExplorerSingle';
32
27
 
33
28
  interface ProfileExplorerProps {
34
29
  queryClient: QueryServiceClient;
35
- queryParams: any;
36
30
  navigateTo: NavigateFunction;
37
31
  }
38
32
 
@@ -47,73 +41,7 @@ const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
47
41
  );
48
42
  };
49
43
 
50
- export const getExpressionAsAString = (expression: string | []): string => {
51
- const x = Array.isArray(expression) ? expression.join() : expression;
52
- return x;
53
- };
54
-
55
- /* eslint-disable @typescript-eslint/naming-convention */
56
- const sanitizeDateRange = (
57
- time_selection_a: string,
58
- from_a: number,
59
- to_a: number
60
- ): {time_selection_a: string; from_a: number; to_a: number} => {
61
- const range = DateTimeRange.fromRangeKey(time_selection_a, from_a, to_a);
62
- if (from_a == null && to_a == null) {
63
- from_a = range.getFromMs();
64
- to_a = range.getToMs();
65
- }
66
- return {time_selection_a: range.getRangeKey(), from_a, to_a};
67
- };
68
- /* eslint-enable @typescript-eslint/naming-convention */
69
-
70
- export const filterEmptyParams = (o: Record<string, any>): Record<string, any> => {
71
- return Object.fromEntries(
72
- Object.entries(o)
73
- .filter(
74
- ([_, value]) =>
75
- value !== '' && value !== undefined && (Array.isArray(value) ? value.length > 0 : true)
76
- )
77
- .map(([key, value]) => {
78
- if (typeof value === 'string') {
79
- return [key, value];
80
- }
81
- if (Array.isArray(value)) {
82
- return [key, value];
83
- }
84
- return [key, value];
85
- })
86
- );
87
- };
88
-
89
- const filterSuffix = (
90
- o: {[key: string]: string | string[] | undefined},
91
- suffix: string
92
- ): {[key: string]: string | string[] | undefined} =>
93
- Object.fromEntries(
94
- Object.entries(o)
95
- .filter(([key]) => !key.endsWith(suffix))
96
- .map(([key, value]) => {
97
- return [key, value];
98
- })
99
- );
100
-
101
- const swapQueryParameters = (o: {
102
- [key: string]: string | string[] | undefined;
103
- }): {[key: string]: string | string[] | undefined} => {
104
- Object.entries(o).forEach(([key, value]) => {
105
- if (key.endsWith('_b')) {
106
- o[key.slice(0, -2) + '_a'] = value;
107
- }
108
- });
109
- return o;
110
- };
111
-
112
- const ProfileExplorerApp = ({
113
- queryClient,
114
- queryParams,
115
- navigateTo,
116
- }: ProfileExplorerProps): JSX.Element => {
44
+ const ProfileExplorerApp = ({queryClient, navigateTo}: ProfileExplorerProps): JSX.Element => {
117
45
  const {
118
46
  loading: hasProfileDataLoading,
119
47
  data: hasProfileData,
@@ -121,6 +49,7 @@ const ProfileExplorerApp = ({
121
49
  } = useHasProfileData(queryClient);
122
50
 
123
51
  const {loader, noDataPrompt, onError, authenticationErrorMessage} = useParcaContext();
52
+ const {isCompareMode} = useCompareModeMeta();
124
53
 
125
54
  useEffect(() => {
126
55
  if (hasProfileDataError !== undefined && hasProfileDataError !== null) {
@@ -128,72 +57,6 @@ const ProfileExplorerApp = ({
128
57
  }
129
58
  }, [hasProfileDataError, onError]);
130
59
 
131
- /* eslint-disable @typescript-eslint/naming-convention */
132
- let {
133
- from_a,
134
- to_a,
135
- merge_from_a,
136
- merge_to_a,
137
- time_selection_a,
138
- compare_a,
139
- sum_by_a,
140
- from_b,
141
- to_b,
142
- merge_from_b,
143
- merge_to_b,
144
- time_selection_b,
145
- compare_b,
146
- sum_by_b,
147
- } = queryParams;
148
-
149
- // eslint-disable-next-line @typescript-eslint/naming-convention
150
- const expression_a = getExpressionAsAString(queryParams.expression_a);
151
-
152
- // eslint-disable-next-line @typescript-eslint/naming-convention
153
- const expression_b = getExpressionAsAString(queryParams.expression_b);
154
-
155
- // eslint-disable-next-line @typescript-eslint/naming-convention
156
- const selection_a = getExpressionAsAString(queryParams.selection_a);
157
-
158
- // eslint-disable-next-line @typescript-eslint/naming-convention
159
- const selection_b = getExpressionAsAString(queryParams.selection_b);
160
-
161
- /* eslint-enable @typescript-eslint/naming-convention */
162
- const [profileA, setProfileA] = useState<ProfileSelection | null>(null);
163
- const [profileB, setProfileB] = useState<ProfileSelection | null>(null);
164
-
165
- const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
166
- const resetFlameGraphState = useResetFlameGraphState();
167
-
168
- const sumByA = useSumByFromParams(sum_by_a);
169
- const sumByB = useSumByFromParams(sum_by_b);
170
-
171
- useEffect(() => {
172
- const mergeFrom = merge_from_a ?? undefined;
173
- const mergeTo = merge_to_a ?? undefined;
174
- const profileA = ProfileSelectionFromParams(
175
- mergeFrom as string,
176
- mergeTo as string,
177
- selection_a
178
- );
179
-
180
- setProfileA(profileA);
181
- // eslint-disable-next-line react-hooks/exhaustive-deps
182
- }, [merge_from_a, merge_to_a, selection_a]);
183
-
184
- useEffect(() => {
185
- const mergeFrom = merge_from_b ?? undefined;
186
- const mergeTo = merge_to_b ?? undefined;
187
- const profileB = ProfileSelectionFromParams(
188
- mergeFrom as string,
189
- mergeTo as string,
190
- selection_b
191
- );
192
-
193
- setProfileB(profileB);
194
- // eslint-disable-next-line react-hooks/exhaustive-deps
195
- }, [merge_from_b, merge_to_b, selection_b]);
196
-
197
60
  if (hasProfileDataLoading) {
198
61
  return <>{loader}</>;
199
62
  }
@@ -213,201 +76,14 @@ const ProfileExplorerApp = ({
213
76
  return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(hasProfileDataError.message)} />;
214
77
  }
215
78
 
216
- const sanitizedRange = sanitizeDateRange(time_selection_a, from_a, to_a);
217
- time_selection_a = sanitizedRange.time_selection_a;
218
- from_a = sanitizedRange.from_a;
219
- to_a = sanitizedRange.to_a;
220
-
221
- if ((queryParams?.expression_a ?? '') !== '') queryParams.expression_a = safeDecode(expression_a);
222
- if ((queryParams?.expression_b ?? '') !== '') queryParams.expression_b = safeDecode(expression_b);
223
-
224
- const selectProfile = (p: ProfileSelection, suffix: string): void => {
225
- return navigateTo('/', {
226
- ...queryParams,
227
- ...SuffixParams(p.HistoryParams(), suffix),
228
- });
229
- };
230
-
231
- const selectProfileA = (p: ProfileSelection): void => {
232
- return selectProfile(p, '_a');
233
- };
234
-
235
- const selectProfileB = (p: ProfileSelection): void => {
236
- return selectProfile(p, '_b');
237
- };
238
-
239
- const queryA = {
240
- expression: expression_a,
241
- from: parseInt(from_a as string),
242
- to: parseInt(to_a as string),
243
- timeSelection: time_selection_a as string,
244
- sumBy: sumByA,
245
- };
246
-
247
- // Show the SingleProfileExplorer when not comparing
248
- if (compare_a !== 'true' && compare_b !== 'true') {
249
- const selectQuery = (q: QuerySelection): void => {
250
- const profileNameAfter = Query.parse(q.expression).profileName();
251
- if (profileA != null) {
252
- if (profileA.ProfileName() !== profileNameAfter) {
253
- // Reset required state when the profile type changes.
254
- resetStateOnProfileTypeChange();
255
- } else {
256
- // Reset the state when a new search is performed.
257
- resetFlameGraphState();
258
- }
259
- }
260
-
261
- const mergeParams =
262
- q.mergeFrom !== undefined && q.mergeTo !== undefined
263
- ? {
264
- merge_from_a: q.mergeFrom,
265
- merge_to_a: q.mergeTo,
266
- selection_a: q.expression,
267
- }
268
- : {};
269
- return navigateTo(
270
- '/',
271
- // Filtering the _a suffix causes us to reset potential profile
272
- // selection when running a new query.
273
- filterEmptyParams({
274
- ...filterSuffix(queryParams, '_a'),
275
- ...{
276
- expression_a: q.expression,
277
- from_a: q.from.toString(),
278
- to_a: q.to.toString(),
279
- time_selection_a: q.timeSelection,
280
- sum_by_a: sumByToParam(q.sumBy),
281
- ...mergeParams,
282
- },
283
- })
284
- );
285
- };
286
-
287
- const selectProfile = (p: ProfileSelection): void => {
288
- return navigateTo('/', {
289
- ...queryParams,
290
- ...SuffixParams(p.HistoryParams(), '_a'),
291
- });
292
- };
293
-
294
- return (
295
- <ProfileExplorerSingle
296
- queryClient={queryClient}
297
- query={queryA}
298
- profile={profileA}
299
- selectQuery={selectQuery}
300
- selectProfile={selectProfile}
301
- navigateTo={navigateTo}
302
- />
303
- );
79
+ if (isCompareMode) {
80
+ return <ProfileExplorerCompare queryClient={queryClient} navigateTo={navigateTo} />;
304
81
  }
305
82
 
306
- const queryB = {
307
- expression: expression_b,
308
- from: parseInt(from_b as string),
309
- to: parseInt(to_b as string),
310
- timeSelection: time_selection_b as string,
311
- sumBy: sumByB,
312
- };
313
-
314
- const selectQueryA = (q: QuerySelection): void => {
315
- const mergeParams =
316
- q.mergeFrom !== undefined && q.mergeTo !== undefined
317
- ? {
318
- merge_from_a: q.mergeFrom,
319
- merge_to_a: q.mergeTo,
320
- selection_a: q.expression,
321
- }
322
- : {};
323
- return navigateTo(
324
- '/',
325
- // Filtering the _a suffix causes us to reset potential profile
326
- // selection when running a new query.
327
- filterEmptyParams({
328
- ...filterSuffix(queryParams, '_a'),
329
- ...{
330
- compare_a: 'true',
331
- expression_a: q.expression,
332
- expression_b,
333
- selection_b,
334
- from_a: q.from.toString(),
335
- to_a: q.to.toString(),
336
- time_selection_a: q.timeSelection,
337
- sum_by_a: sumByToParam(q.sumBy),
338
- ...mergeParams,
339
- },
340
- })
341
- );
342
- };
343
-
344
- const selectQueryB = (q: QuerySelection): void => {
345
- const mergeParams =
346
- q.mergeFrom !== undefined && q.mergeTo !== undefined
347
- ? {
348
- merge_from_b: q.mergeFrom,
349
- merge_to_b: q.mergeTo,
350
- selection_b: q.expression,
351
- }
352
- : {};
353
- return navigateTo(
354
- '/',
355
- // Filtering the _b suffix causes us to reset potential profile
356
- // selection when running a new query.
357
- filterEmptyParams({
358
- ...filterSuffix(queryParams, '_b'),
359
- ...{
360
- compare_b: 'true',
361
- expression_b: q.expression,
362
- expression_a,
363
- selection_a,
364
- from_b: q.from.toString(),
365
- to_b: q.to.toString(),
366
- time_selection_b: q.timeSelection,
367
- sum_by_b: sumByToParam(q.sumBy),
368
- ...mergeParams,
369
- },
370
- })
371
- );
372
- };
373
-
374
- const closeProfile = (card: string): void => {
375
- let newQueryParameters = queryParams;
376
- if (card === 'A') {
377
- newQueryParameters = swapQueryParameters(queryParams);
378
- }
379
-
380
- return navigateTo('/', {
381
- ...filterSuffix(newQueryParameters, '_b'),
382
- ...{
383
- compare_a: 'false',
384
- compare_b: 'false',
385
- },
386
- });
387
- };
388
-
389
- return (
390
- <ProfileExplorerCompare
391
- queryClient={queryClient}
392
- queryA={queryA}
393
- queryB={queryB}
394
- profileA={profileA}
395
- profileB={profileB}
396
- selectQueryA={selectQueryA}
397
- selectQueryB={selectQueryB}
398
- selectProfileA={selectProfileA}
399
- selectProfileB={selectProfileB}
400
- closeProfile={closeProfile}
401
- navigateTo={navigateTo}
402
- />
403
- );
83
+ return <ProfileExplorerSingle queryClient={queryClient} navigateTo={navigateTo} />;
404
84
  };
405
85
 
406
- const ProfileExplorer = ({
407
- queryClient,
408
- queryParams,
409
- navigateTo,
410
- }: ProfileExplorerProps): JSX.Element => {
86
+ const ProfileExplorer = ({queryClient, navigateTo}: ProfileExplorerProps): JSX.Element => {
411
87
  const {additionalFlamegraphColorProfiles} = useParcaContext();
412
88
 
413
89
  const {store: reduxStore} = useMemo(() => {
@@ -417,11 +93,7 @@ const ProfileExplorer = ({
417
93
  return (
418
94
  <Provider store={reduxStore}>
419
95
  <KeyDownProvider>
420
- <ProfileExplorerApp
421
- queryClient={queryClient}
422
- queryParams={queryParams}
423
- navigateTo={navigateTo}
424
- />
96
+ <ProfileExplorerApp queryClient={queryClient} navigateTo={navigateTo} />
425
97
  </KeyDownProvider>
426
98
  </Provider>
427
99
  );
@@ -77,35 +77,32 @@ const createProfileContextMenuItems = (
77
77
  label: 'Add to query',
78
78
  icon: 'material-symbols:add',
79
79
  createDynamicItems: (closestPoint, _series) => {
80
+ const noLabelsAvailable = [
81
+ {
82
+ id: 'no-labels-available',
83
+ label: 'No labels available',
84
+ icon: 'ph:warning',
85
+ disabled: () => true,
86
+ onClick: () => {}, // No-op for disabled item
87
+ },
88
+ ];
80
89
  if (closestPoint == null || data.length === 0 || data[closestPoint.seriesIndex] == null) {
81
- return [
82
- {
83
- id: 'no-labels-available',
84
- label: 'No labels available',
85
- icon: 'ph:warning',
86
- disabled: () => true,
87
- onClick: () => {}, // No-op for disabled item
88
- },
89
- ];
90
+ return noLabelsAvailable;
90
91
  }
91
92
 
92
93
  const originalSeriesData = data[closestPoint.seriesIndex];
93
94
  if (originalSeriesData.labelset?.labels == null) {
94
- return [
95
- {
96
- id: 'no-labels-available',
97
- label: 'No labels available',
98
- icon: 'ph:warning',
99
- disabled: () => true,
100
- onClick: () => {}, // No-op for disabled item
101
- },
102
- ];
95
+ return noLabelsAvailable;
103
96
  }
104
97
 
105
98
  const labels = originalSeriesData.labelset.labels.filter(
106
99
  (label: Label) => label.name !== '__name__'
107
100
  );
108
101
 
102
+ if (labels.length === 0) {
103
+ return noLabelsAvailable;
104
+ }
105
+
109
106
  return labels.map((label: Label) => ({
110
107
  id: `add-label-${label.name}`,
111
108
  label: (
@@ -207,13 +204,12 @@ const ProfileMetricsGraph = ({
207
204
  onPointClick,
208
205
  comparing = false,
209
206
  sumBy,
210
- sumByLoading,
211
207
  }: ProfileMetricsGraphProps): JSX.Element => {
212
208
  const {
213
209
  isLoading: metricsGraphLoading,
214
210
  response,
215
211
  error,
216
- } = useQueryRange(queryClient, queryExpression, from, to, sumBy, sumByLoading);
212
+ } = useQueryRange(queryClient, queryExpression, from, to, sumBy, queryExpression === '');
217
213
  const {onError, perf, authenticationErrorMessage, isDarkMode, timezone} = useParcaContext();
218
214
  const {width, height, margin, heightStyle} = useMetricsGraphDimensions(comparing);
219
215
  const [showAllSeriesForResponse, setShowAllSeriesForResponse] = useState<typeof response | null>(
@@ -14,10 +14,10 @@
14
14
  import cx from 'classnames';
15
15
 
16
16
  import {Label, QueryServiceClient} from '@parca/client';
17
- import {DateTimeRange} from '@parca/components';
17
+ import {DateTimeRange, useURLStateBatch} from '@parca/components';
18
18
  import {Query} from '@parca/parser';
19
19
 
20
- import {MergedProfileSelection, ProfileSelection} from '..';
20
+ import {ProfileSelection} from '..';
21
21
  import UtilizationMetricsGraph from '../MetricsGraph/UtilizationMetrics';
22
22
  import AreaChart from '../MetricsGraph/UtilizationMetrics/Throughput';
23
23
  import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
@@ -37,9 +37,9 @@ interface MetricsGraphSectionProps {
37
37
  queryExpressionString: string;
38
38
  setTimeRangeSelection: (range: DateTimeRange) => void;
39
39
  selectQuery: (query: QuerySelection) => void;
40
- selectProfile: (source: ProfileSelection) => void;
40
+ setProfileSelection: (mergeFrom: bigint, mergeTo: bigint, query: Query) => void;
41
41
  query: Query;
42
- setNewQueryExpression: (queryExpression: string) => void;
42
+ setNewQueryExpression: (queryExpression: string, commit?: boolean) => void;
43
43
  setQueryExpression: (updateTs?: boolean) => void;
44
44
  utilizationMetrics?: Array<{
45
45
  name: string;
@@ -63,7 +63,7 @@ export function MetricsGraphSection({
63
63
  queryExpressionString,
64
64
  setTimeRangeSelection,
65
65
  selectQuery,
66
- selectProfile,
66
+ setProfileSelection,
67
67
  query,
68
68
  setNewQueryExpression,
69
69
  utilizationMetrics,
@@ -71,6 +71,7 @@ export function MetricsGraphSection({
71
71
  onUtilizationSeriesSelect,
72
72
  }: MetricsGraphSectionProps): JSX.Element {
73
73
  const resetStateOnSeriesChange = useResetStateOnSeriesChange();
74
+ const batchUpdates = useURLStateBatch();
74
75
  const handleTimeRangeChange = (range: DateTimeRange): void => {
75
76
  const from = range.getFromMs();
76
77
  const to = range.getToMs();
@@ -118,7 +119,8 @@ export function MetricsGraphSection({
118
119
 
119
120
  if (hasChanged) {
120
121
  // TODO: Change this to store the query object
121
- setNewQueryExpression(newQuery.toString());
122
+ // Pass commit: true to immediately apply the filter when clicking on metrics graph labels
123
+ setNewQueryExpression(newQuery.toString(), true);
122
124
  }
123
125
  };
124
126
 
@@ -129,6 +131,7 @@ export function MetricsGraphSection({
129
131
  duration: number
130
132
  ): void => {
131
133
  let query = Query.parse(queryExpression);
134
+
132
135
  labels.forEach(l => {
133
136
  const [newQuery, updated] = query.setMatcher(l.name, l.value);
134
137
  if (updated) {
@@ -138,9 +141,10 @@ export function MetricsGraphSection({
138
141
 
139
142
  const mergeFrom = timestamp;
140
143
  const mergeTo = query.profileType().delta ? mergeFrom + BigInt(duration) : mergeFrom;
141
-
142
- resetStateOnSeriesChange(); // reset some state when a new series is selected
143
- selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
144
+ batchUpdates(() => {
145
+ resetStateOnSeriesChange(); // reset some state when a new series is selected
146
+ setProfileSelection(mergeFrom, mergeTo, query);
147
+ });
144
148
  };
145
149
 
146
150
  const UtilizationGraphToShow = ({
@@ -235,7 +239,7 @@ export function MetricsGraphSection({
235
239
  {showMetricsGraph && (
236
240
  <>
237
241
  <div style={{height: heightStyle}}>
238
- {querySelection.expression !== '' &&
242
+ {(querySelection.expression !== '' || defaultSumByLoading) &&
239
243
  querySelection.from !== undefined &&
240
244
  querySelection.to !== undefined ? (
241
245
  <>