@parca/profile 0.19.72 → 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 +8 -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 +7 -7
  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
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import type { ParamPreferences } from '@parca/components';
1
2
  import { useLabelNames } from './MatchersInput';
2
- import ProfileExplorer, { getExpressionAsAString } from './ProfileExplorer';
3
+ import ProfileExplorer from './ProfileExplorer';
3
4
  import ProfileTypeSelector from './ProfileTypeSelector';
4
5
  import SelectWithRefresh from './SelectWithRefresh';
5
6
  import CustomSelect from './SimpleMatchers/Select';
@@ -15,8 +16,6 @@ export * from './ProfileMetricsGraph';
15
16
  export * from './useSumBy';
16
17
  export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
17
18
  export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
18
- export declare const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: {
19
- dashboard_items: string;
20
- };
21
- export { ProfileExplorer, ProfileTypeSelector, getExpressionAsAString, CustomSelect, SelectWithRefresh, useLabelNames, };
19
+ export declare const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: ParamPreferences;
20
+ export { ProfileExplorer, ProfileTypeSelector, CustomSelect, SelectWithRefresh, useLabelNames };
22
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,eAAe,EAAE,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAC1E,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAEnD,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,2DAA2D,CAAC;AACnE,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAC,yBAAyB,EAAC,MAAM,mEAAmE,CAAC;AAE5G,eAAO,MAAM,qCAAqC;;CAEjD,CAAC;AAEF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,aAAa,GACd,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAEnD,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,2DAA2D,CAAC;AACnE,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAC,yBAAyB,EAAC,MAAM,mEAAmE,CAAC;AAE5G,eAAO,MAAM,qCAAqC,EAAE,gBAKnD,CAAC;AAEF,OAAO,EAAC,eAAe,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAC,CAAC"}
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { useLabelNames } from './MatchersInput';
14
- import ProfileExplorer, { getExpressionAsAString } from './ProfileExplorer';
14
+ import ProfileExplorer from './ProfileExplorer';
15
15
  import ProfileTypeSelector from './ProfileTypeSelector';
16
16
  import SelectWithRefresh from './SelectWithRefresh';
17
17
  import CustomSelect from './SimpleMatchers/Select';
@@ -28,6 +28,9 @@ export * from './useSumBy';
28
28
  export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
29
29
  export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
30
30
  export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
31
- dashboard_items: 'flamegraph',
31
+ dashboard_items: {
32
+ defaultValue: 'flamegraph',
33
+ splitOnCommas: true, // This param should split on commas for array values
34
+ },
32
35
  };
33
- export { ProfileExplorer, ProfileTypeSelector, getExpressionAsAString, CustomSelect, SelectWithRefresh, useLabelNames, };
36
+ export { ProfileExplorer, ProfileTypeSelector, CustomSelect, SelectWithRefresh, useLabelNames };
@@ -1,3 +1,5 @@
1
+ import { QueryServiceClient } from '@parca/client';
2
+ import { DateTimeRange } from '@parca/components';
1
3
  import { ProfileType } from '@parca/parser';
2
4
  export declare const DEFAULT_EMPTY_SUM_BY: string[];
3
5
  export declare const useSumBySelection: (profileType: ProfileType | undefined, labelNamesLoading: boolean, labels: string[] | undefined, { defaultValue, }?: {
@@ -11,4 +13,9 @@ export declare const useDefaultSumBy: (profileType: ProfileType | undefined, lab
11
13
  };
12
14
  export declare const useSumByFromParams: (param: string | string[] | undefined) => string[] | undefined;
13
15
  export declare const sumByToParam: (sumBy: string[] | undefined) => string | string[] | undefined;
16
+ export declare const useSumBy: (queryClient: QueryServiceClient, profileType: ProfileType | undefined, timeRange: DateTimeRange, defaultValue?: string[]) => {
17
+ sumBy: string[] | undefined;
18
+ setSumBy: (sumBy: string[]) => void;
19
+ isLoading: boolean;
20
+ };
14
21
  //# sourceMappingURL=useSumBy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSumBy.d.ts","sourceRoot":"","sources":["../src/useSumBy.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,oBAAoB,EAAE,MAAM,EAAO,CAAC;AA6BjD,eAAO,MAAM,iBAAiB,GAC5B,aAAa,WAAW,GAAG,SAAS,EACpC,mBAAmB,OAAO,EAC1B,QAAQ,MAAM,EAAE,GAAG,SAAS,EAC5B,oBAEG;IACD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,KACL,CACD,MAAM,EAAE,GAAG,SAAS,EACpB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,EAC1B;IACE,SAAS,EAAE,OAAO,CAAC;CACpB,CAgDF,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,aAAa,WAAW,GAAG,SAAS,EACpC,mBAAmB,OAAO,EAC1B,QAAQ,MAAM,EAAE,GAAG,SAAS,KAC3B;IAAC,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAMzD,CAAC;AAyBF,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,KAAG,MAAM,EAAE,GAAG,SAMpF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,GAAG,SAAS,KAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAU9E,CAAC"}
1
+ {"version":3,"file":"useSumBy.d.ts","sourceRoot":"","sources":["../src/useSumBy.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACjD,OAAO,EAAC,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAI1C,eAAO,MAAM,oBAAoB,EAAE,MAAM,EAAO,CAAC;AA6BjD,eAAO,MAAM,iBAAiB,GAC5B,aAAa,WAAW,GAAG,SAAS,EACpC,mBAAmB,OAAO,EAC1B,QAAQ,MAAM,EAAE,GAAG,SAAS,EAC5B,oBAEG;IACD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,KACL,CACD,MAAM,EAAE,GAAG,SAAS,EACpB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,EAC1B;IACE,SAAS,EAAE,OAAO,CAAC;CACpB,CA+DF,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,aAAa,WAAW,GAAG,SAAS,EACpC,mBAAmB,OAAO,EAC1B,QAAQ,MAAM,EAAE,GAAG,SAAS,KAC3B;IAAC,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAMzD,CAAC;AAyBF,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,KAAG,MAAM,EAAE,GAAG,SAMpF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,GAAG,SAAS,KAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAU9E,CAAC;AAGF,eAAO,MAAM,QAAQ,GACnB,aAAa,kBAAkB,EAC/B,aAAa,WAAW,GAAG,SAAS,EACpC,WAAW,aAAa,EACxB,eAAe,MAAM,EAAE,KACtB;IACD,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpC,SAAS,EAAE,OAAO,CAAC;CAyBpB,CAAC"}
package/dist/useSumBy.js CHANGED
@@ -10,7 +10,8 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { useCallback, useEffect, useMemo, useState } from 'react';
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
+ import { useLabelNames } from './MatchersInput/index';
14
15
  export const DEFAULT_EMPTY_SUM_BY = [];
15
16
  const getDefaultSumBy = (profile, labels) => {
16
17
  if (profile === undefined || labels === undefined) {
@@ -53,12 +54,23 @@ export const useSumBySelection = (profileType, labelNamesLoading, labels, { defa
53
54
  });
54
55
  }, [setUserSelectedSumBy, profileType]);
55
56
  const { defaultSumBy } = useDefaultSumBy(profileType, labelNamesLoading, labels);
56
- let sumBy = userSelectedSumBy[profileType?.toString() ?? ''] ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY;
57
- if (profileType?.delta !== true) {
58
- sumBy = DEFAULT_EMPTY_SUM_BY;
59
- }
57
+ // Store the last valid sumBy value to return during loading
58
+ const lastValidSumByRef = useRef(DEFAULT_EMPTY_SUM_BY);
59
+ const sumBy = useMemo(() => {
60
+ // If loading, return the last valid value to prevent input from blanking
61
+ if (labelNamesLoading && lastValidSumByRef.current !== DEFAULT_EMPTY_SUM_BY) {
62
+ return lastValidSumByRef.current;
63
+ }
64
+ let result = userSelectedSumBy[profileType?.toString() ?? ''] ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY;
65
+ if (profileType?.delta !== true) {
66
+ result = DEFAULT_EMPTY_SUM_BY;
67
+ }
68
+ // Store the computed value for next loading state
69
+ lastValidSumByRef.current = result;
70
+ return result;
71
+ }, [userSelectedSumBy, profileType, defaultSumBy, labelNamesLoading]);
60
72
  return [
61
- labelNamesLoading ? undefined : sumBy,
73
+ sumBy,
62
74
  setSumBy,
63
75
  {
64
76
  isLoading: labelNamesLoading,
@@ -105,3 +117,16 @@ export const sumByToParam = (sumBy) => {
105
117
  }
106
118
  return sumBy;
107
119
  };
120
+ // Combined hook that handles all sumBy logic: fetching labels, computing defaults, and managing selection
121
+ export const useSumBy = (queryClient, profileType, timeRange, defaultValue) => {
122
+ const { loading: labelNamesLoading, result } = useLabelNames(queryClient, profileType?.toString() ?? '', timeRange.getFromMs(), timeRange.getToMs());
123
+ const labels = useMemo(() => {
124
+ return result.response?.labelNames === undefined ? [] : result.response.labelNames;
125
+ }, [result]);
126
+ const [sumBySelection, setSumByInternal, { isLoading }] = useSumBySelection(profileType, labelNamesLoading, labels, { defaultValue });
127
+ return {
128
+ sumBy: sumBySelection,
129
+ setSumBy: setSumByInternal,
130
+ isLoading,
131
+ };
132
+ };
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.19.72",
3
+ "version": "0.19.74",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@floating-ui/react": "^0.27.12",
7
7
  "@headlessui/react": "^1.7.19",
8
8
  "@iconify/react": "^4.0.0",
9
- "@parca/client": "0.17.7",
10
- "@parca/components": "0.16.379",
9
+ "@parca/client": "0.17.8",
10
+ "@parca/components": "0.16.381",
11
11
  "@parca/dynamicsize": "0.16.67",
12
- "@parca/hooks": "0.0.106",
12
+ "@parca/hooks": "0.0.108",
13
13
  "@parca/icons": "0.16.74",
14
14
  "@parca/parser": "0.16.81",
15
- "@parca/store": "0.16.189",
15
+ "@parca/store": "0.16.191",
16
16
  "@parca/test-utils": "0.0.17",
17
- "@parca/utilities": "0.0.112",
17
+ "@parca/utilities": "0.0.114",
18
18
  "@popperjs/core": "^2.11.8",
19
19
  "@protobuf-ts/runtime-rpc": "^2.5.0",
20
20
  "@storybook/preview-api": "^8.4.3",
@@ -79,5 +79,5 @@
79
79
  "access": "public",
80
80
  "registry": "https://registry.npmjs.org/"
81
81
  },
82
- "gitHead": "629879e6320a89aa1f7bc7de27fb090eb525793c"
82
+ "gitHead": "6ca69bb6e4542edbc73ca635915b3ebae7a8e8c3"
83
83
  }
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useMemo, useRef, useState} from 'react';
14
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {useQuery} from '@tanstack/react-query';
17
17
  import cx from 'classnames';
@@ -78,7 +78,9 @@ export const useLabelNames = (
78
78
  },
79
79
  });
80
80
 
81
- console.log('Label names query result:', {data, error, isLoading});
81
+ useEffect(() => {
82
+ console.log('Label names query result:', {data, error, isLoading});
83
+ }, [data, error, isLoading]);
82
84
 
83
85
  return {
84
86
  result: {response: data, error: error as Error},
@@ -11,57 +11,87 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useState} from 'react';
14
+ import {useCallback, useEffect, useMemo, useState} from 'react';
15
15
 
16
16
  import {QueryServiceClient} from '@parca/client';
17
- import {useURLState} from '@parca/components';
17
+ import {useURLStateBatch} from '@parca/components';
18
18
  import {Query} from '@parca/parser';
19
19
  import {TEST_IDS, testId} from '@parca/test-utils';
20
20
  import type {NavigateFunction} from '@parca/utilities';
21
21
 
22
- import {ProfileDiffSource, ProfileSelection, ProfileViewWithData} from '..';
23
- import ProfileSelector, {QuerySelection} from '../ProfileSelector';
22
+ import {ProfileDiffSource, ProfileViewWithData} from '..';
23
+ import ProfileSelector from '../ProfileSelector';
24
+ import {useCompareModeMeta} from '../hooks/useCompareModeMeta';
25
+ import {useQueryState} from '../hooks/useQueryState';
24
26
 
25
27
  interface ProfileExplorerCompareProps {
26
28
  queryClient: QueryServiceClient;
27
-
28
- queryA: QuerySelection;
29
- queryB: QuerySelection;
30
- profileA: ProfileSelection | null;
31
- profileB: ProfileSelection | null;
32
- selectQueryA: (query: QuerySelection) => void;
33
- selectQueryB: (query: QuerySelection) => void;
34
- selectProfileA: (source: ProfileSelection) => void;
35
- selectProfileB: (source: ProfileSelection) => void;
36
- closeProfile: (card: string) => void;
37
-
38
29
  navigateTo: NavigateFunction;
39
30
  }
40
31
 
41
32
  const ProfileExplorerCompare = ({
42
33
  queryClient,
43
- queryA,
44
- queryB,
45
- profileA,
46
- profileB,
47
- selectQueryA,
48
- selectQueryB,
49
- selectProfileA,
50
- selectProfileB,
51
- closeProfile,
52
34
  navigateTo,
53
35
  }: ProfileExplorerCompareProps): JSX.Element => {
54
36
  const [showMetricsGraph, setShowMetricsGraph] = useState(true);
37
+ const batchUpdates = useURLStateBatch();
38
+ const {closeCompareMode, isCompareMode, isCompareAbsolute} = useCompareModeMeta();
39
+
40
+ // Read ProfileSource states from URL for both sides
41
+ const {profileSource: profileSourceA, querySelection: querySelectionA} = useQueryState({
42
+ suffix: '_a',
43
+ comparing: true,
44
+ });
45
+ const {
46
+ profileSource: profileSourceB,
47
+ querySelection: querySelectionB,
48
+ commitDraft: commitDraftB,
49
+ setDraftExpression: setDraftExpressionB,
50
+ setDraftTimeRange: setDraftTimeRangeB,
51
+ } = useQueryState({suffix: '_b', comparing: true});
52
+
53
+ // Derive enforced profile name from side A's expression
54
+ const enforcedProfileNameA = useMemo(() => {
55
+ return querySelectionA.expression !== ''
56
+ ? Query.parse(querySelectionA.expression).profileName()
57
+ : '';
58
+ }, [querySelectionA.expression]);
59
+
60
+ // Initialize side B with side A's values if side B is empty
61
+ useEffect(() => {
62
+ // If not in compare mode, don't initialize
63
+ if (!isCompareMode) {
64
+ return;
65
+ }
55
66
 
56
- const closeProfileA = (): void => {
57
- closeProfile('A');
58
- };
67
+ if (querySelectionB.expression === '' && querySelectionA.expression !== '') {
68
+ batchUpdates(() => {
69
+ setDraftExpressionB(querySelectionA.expression);
70
+ setDraftTimeRangeB(querySelectionA.from, querySelectionA.to, querySelectionA.timeSelection);
71
+ // Commit to update the URL and trigger metrics graph load
72
+ commitDraftB();
73
+ });
74
+ }
75
+ }, [
76
+ isCompareMode,
77
+ querySelectionA.expression,
78
+ querySelectionA.from,
79
+ querySelectionA.to,
80
+ querySelectionA.timeSelection,
81
+ querySelectionB.expression,
82
+ setDraftExpressionB,
83
+ setDraftTimeRangeB,
84
+ commitDraftB,
85
+ batchUpdates,
86
+ ]);
59
87
 
60
- const closeProfileB = (): void => {
61
- closeProfile('B');
62
- };
88
+ const closeProfileA = useCallback((): void => {
89
+ closeCompareMode('A');
90
+ }, [closeCompareMode]);
63
91
 
64
- const [compareAbsolute] = useURLState('compare_absolute');
92
+ const closeProfileB = useCallback((): void => {
93
+ closeCompareMode('B');
94
+ }, [closeCompareMode]);
65
95
 
66
96
  return (
67
97
  <div {...testId(TEST_IDS.COMPARE_CONTAINER)}>
@@ -72,10 +102,6 @@ const ProfileExplorerCompare = ({
72
102
  >
73
103
  <ProfileSelector
74
104
  queryClient={queryClient}
75
- querySelection={queryA}
76
- profileSelection={profileA}
77
- selectProfile={selectProfileA}
78
- selectQuery={selectQueryA}
79
105
  closeProfile={closeProfileA}
80
106
  enforcedProfileName={''}
81
107
  comparing={true}
@@ -91,12 +117,8 @@ const ProfileExplorerCompare = ({
91
117
  >
92
118
  <ProfileSelector
93
119
  queryClient={queryClient}
94
- querySelection={queryB}
95
- profileSelection={profileB}
96
- selectProfile={selectProfileB}
97
- selectQuery={selectQueryB}
98
120
  closeProfile={closeProfileB}
99
- enforcedProfileName={Query.parse(queryA.expression).profileName()}
121
+ enforcedProfileName={enforcedProfileNameA}
100
122
  comparing={true}
101
123
  navigateTo={navigateTo}
102
124
  suffix="_b"
@@ -106,16 +128,12 @@ const ProfileExplorerCompare = ({
106
128
  </div>
107
129
  </div>
108
130
  <div className="grid grid-cols-1">
109
- {profileA != null && profileB != null ? (
131
+ {profileSourceA != null && profileSourceB != null ? (
110
132
  <div {...testId(TEST_IDS.COMPARE_PROFILE_VIEW)}>
111
133
  <ProfileViewWithData
112
134
  queryClient={queryClient}
113
135
  profileSource={
114
- new ProfileDiffSource(
115
- profileA.ProfileSource(),
116
- profileB.ProfileSource(),
117
- compareAbsolute === 'true'
118
- )
136
+ new ProfileDiffSource(profileSourceA, profileSourceB, isCompareAbsolute)
119
137
  }
120
138
  />
121
139
  </div>
@@ -16,40 +16,30 @@ import {useState} from 'react';
16
16
  import {QueryServiceClient} from '@parca/client';
17
17
  import type {NavigateFunction} from '@parca/utilities';
18
18
 
19
- import {ProfileSelection, ProfileViewWithData} from '..';
20
- import ProfileSelector, {QuerySelection} from '../ProfileSelector';
19
+ import {ProfileViewWithData} from '..';
20
+ import ProfileSelector from '../ProfileSelector';
21
+ import {useQueryState} from '../hooks/useQueryState';
21
22
 
22
23
  interface ProfileExplorerSingleProps {
23
24
  queryClient: QueryServiceClient;
24
- query: QuerySelection;
25
- selectQuery: (query: QuerySelection) => void;
26
- selectProfile: (source: ProfileSelection) => void;
27
- profile: ProfileSelection | null;
28
25
  navigateTo: NavigateFunction;
29
26
  }
30
27
 
31
28
  const ProfileExplorerSingle = ({
32
29
  queryClient,
33
- query,
34
- selectQuery,
35
- selectProfile,
36
- profile,
37
30
  navigateTo,
38
31
  }: ProfileExplorerSingleProps): JSX.Element => {
39
32
  const [showMetricsGraph, setShowMetricsGraph] = useState(true);
33
+ const {profileSource} = useQueryState({suffix: '_a'});
40
34
 
41
35
  return (
42
36
  <>
43
37
  <div className="relative">
44
38
  <ProfileSelector
45
39
  queryClient={queryClient}
46
- querySelection={query}
47
- selectQuery={selectQuery}
48
- selectProfile={selectProfile}
49
40
  closeProfile={() => {}} // eslint-disable-line @typescript-eslint/no-empty-function
50
- profileSelection={profile}
51
41
  comparing={false}
52
- enforcedProfileName={''} // TODO
42
+ enforcedProfileName={''}
53
43
  navigateTo={navigateTo}
54
44
  suffix="_a"
55
45
  showMetricsGraph={showMetricsGraph}
@@ -57,10 +47,8 @@ const ProfileExplorerSingle = ({
57
47
  />
58
48
  </div>
59
49
 
60
- {profile != null ? (
61
- <ProfileViewWithData queryClient={queryClient} profileSource={profile.ProfileSource()} />
62
- ) : (
63
- <></>
50
+ {profileSource != null && (
51
+ <ProfileViewWithData queryClient={queryClient} profileSource={profileSource} />
64
52
  )}
65
53
  </>
66
54
  );