@parca/profile 0.16.390 → 0.16.392

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.16.392](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.391...@parca/profile@0.16.392) (2024-06-27)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## 0.16.391 (2024-06-24)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.390](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.389...@parca/profile@0.16.390) (2024-06-20)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -10,6 +10,6 @@ const ProfileExplorerCompare = ({ queryClient, queryA, queryB, profileA, profile
10
10
  const closeProfileB = () => {
11
11
  closeProfile('B');
12
12
  };
13
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, onCompareProfile: () => { } }) }), _jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, onCompareProfile: () => { } }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx("div", { children: _jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource()) }) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
13
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, navigateTo: navigateTo }) }), _jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, navigateTo: navigateTo }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx("div", { children: _jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource()) }) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
14
14
  };
15
15
  export default ProfileExplorerCompare;
@@ -9,8 +9,7 @@ interface ProfileExplorerSingleProps {
9
9
  selectQuery: (query: QuerySelection) => void;
10
10
  selectProfile: (source: ProfileSelection) => void;
11
11
  profile: ProfileSelection | null;
12
- compareProfile: () => void;
13
12
  navigateTo: NavigateFunction;
14
13
  }
15
- declare const ProfileExplorerSingle: ({ queryClient, query, selectQuery, selectProfile, profile, compareProfile, navigateTo, }: ProfileExplorerSingleProps) => JSX.Element;
14
+ declare const ProfileExplorerSingle: ({ queryClient, query, selectQuery, selectProfile, profile, navigateTo, }: ProfileExplorerSingleProps) => JSX.Element;
16
15
  export default ProfileExplorerSingle;
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Card } from '@parca/components';
3
3
  import { ProfileViewWithData } from '..';
4
4
  import ProfileSelector from '../ProfileSelector';
5
- const ProfileExplorerSingle = ({ queryClient, query, selectQuery, selectProfile, profile, compareProfile, navigateTo, }) => {
6
- return (_jsxs(_Fragment, { children: [_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: query, selectQuery: selectQuery, selectProfile: selectProfile, closeProfile: () => { }, profileSelection: profile, comparing: false, onCompareProfile: compareProfile, enforcedProfileName: '' }) }), profile != null ? (_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { queryClient: queryClient, profileSource: profile.ProfileSource(), navigateTo: navigateTo }) })) : (_jsx(_Fragment, {}))] }));
5
+ const ProfileExplorerSingle = ({ queryClient, query, selectQuery, selectProfile, profile, navigateTo, }) => {
6
+ return (_jsxs(_Fragment, { children: [_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: query, selectQuery: selectQuery, selectProfile: selectProfile, closeProfile: () => { }, profileSelection: profile, comparing: false, enforcedProfileName: '', navigateTo: navigateTo }) }), profile != null ? (_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { queryClient: queryClient, profileSource: profile.ProfileSource(), navigateTo: navigateTo }) })) : (_jsx(_Fragment, {}))] }));
7
7
  };
8
8
  export default ProfileExplorerSingle;
@@ -6,5 +6,6 @@ interface ProfileExplorerProps {
6
6
  queryParams: any;
7
7
  navigateTo: NavigateFunction;
8
8
  }
9
+ export declare const getExpressionAsAString: (expression: string | []) => string;
9
10
  declare const ProfileExplorer: ({ queryClient, queryParams, navigateTo, }: ProfileExplorerProps) => JSX.Element;
10
11
  export default ProfileExplorer;
@@ -23,7 +23,7 @@ import ProfileExplorerSingle from './ProfileExplorerSingle';
23
23
  const ErrorContent = ({ errorMessage }) => {
24
24
  return (_jsx("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: _jsx("span", { className: "block sm:inline", children: errorMessage }) }));
25
25
  };
26
- const getExpressionAsAString = (expression) => {
26
+ export const getExpressionAsAString = (expression) => {
27
27
  const x = Array.isArray(expression) ? expression.join() : expression;
28
28
  return x;
29
29
  };
@@ -168,32 +168,7 @@ const ProfileExplorerApp = ({ queryClient, queryParams, navigateTo, }) => {
168
168
  dashboard_items: dashboard_items ?? DEFAULT_DASHBOARD_ITEMS,
169
169
  });
170
170
  };
171
- const compareProfile = () => {
172
- let compareQuery = {
173
- compare_a: 'true',
174
- expression_a: encodeURIComponent(queryA.expression),
175
- from_a: queryA.from.toString(),
176
- to_a: queryA.to.toString(),
177
- time_selection_a: queryA.timeSelection,
178
- compare_b: 'true',
179
- expression_b: encodeURIComponent(queryA.expression),
180
- from_b: queryA.from.toString(),
181
- to_b: queryA.to.toString(),
182
- time_selection_b: queryA.timeSelection,
183
- };
184
- if (profileA != null) {
185
- compareQuery = {
186
- ...SuffixParams(profileA.HistoryParams(), '_a'),
187
- ...compareQuery,
188
- };
189
- }
190
- void navigateTo('/', {
191
- ...compareQuery,
192
- search_string: '',
193
- dashboard_items: dashboard_items ?? DEFAULT_DASHBOARD_ITEMS,
194
- });
195
- };
196
- return (_jsx(ProfileExplorerSingle, { queryClient: queryClient, query: queryA, profile: profileA, selectQuery: selectQuery, selectProfile: selectProfile, compareProfile: compareProfile, navigateTo: navigateTo }));
171
+ return (_jsx(ProfileExplorerSingle, { queryClient: queryClient, query: queryA, profile: profileA, selectQuery: selectQuery, selectProfile: selectProfile, navigateTo: navigateTo }));
197
172
  }
198
173
  const queryB = {
199
174
  expression: expression_b,
@@ -1,6 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import { RpcError } from '@protobuf-ts/runtime-rpc';
3
3
  import { ProfileTypesResponse, QueryServiceClient } from '@parca/client';
4
+ import { type NavigateFunction } from '@parca/utilities';
4
5
  import { ProfileSelection } from '..';
5
6
  export interface QuerySelection {
6
7
  expression: string;
@@ -19,7 +20,7 @@ interface ProfileSelectorProps {
19
20
  enforcedProfileName: string;
20
21
  profileSelection: ProfileSelection | null;
21
22
  comparing: boolean;
22
- onCompareProfile: () => void;
23
+ navigateTo: NavigateFunction;
23
24
  }
24
25
  export interface IProfileTypesResult {
25
26
  loading: boolean;
@@ -27,5 +28,5 @@ export interface IProfileTypesResult {
27
28
  error?: RpcError;
28
29
  }
29
30
  export declare const useProfileTypes: (client: QueryServiceClient) => IProfileTypesResult;
30
- declare const ProfileSelector: ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, onCompareProfile, }: ProfileSelectorProps) => JSX.Element;
31
+ declare const ProfileSelector: ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, navigateTo, }: ProfileSelectorProps) => JSX.Element;
31
32
  export default ProfileSelector;
@@ -20,7 +20,6 @@ import MatchersInput from '../MatchersInput/index';
20
20
  import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
21
21
  import ProfileMetricsGraph, { ProfileMetricsEmptyState } from '../ProfileMetricsGraph';
22
22
  import ProfileTypeSelector from '../ProfileTypeSelector/index';
23
- import CompareButton from './CompareButton';
24
23
  import { useAutoQuerySelector } from './useAutoQuerySelector';
25
24
  export const useProfileTypes = (client) => {
26
25
  const [result, setResult] = useState(undefined);
@@ -39,7 +38,7 @@ export const useProfileTypes = (client) => {
39
38
  }, [client, metadata, loading]);
40
39
  return { loading, data: result, error };
41
40
  };
42
- const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, onCompareProfile, }) => {
41
+ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, navigateTo, }) => {
43
42
  const { loading: profileTypesLoading, data: profileTypesData, error, } = useProfileTypes(queryClient);
44
43
  const { heightStyle } = useMetricsGraphDimensions(comparing);
45
44
  const { viewComponent } = useParcaContext();
@@ -137,17 +136,17 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
137
136
  profileTypesData,
138
137
  setProfileName,
139
138
  setQueryExpression,
139
+ querySelection,
140
+ navigateTo,
140
141
  });
141
- const handleCompareClick = () => onCompareProfile();
142
142
  const searchDisabled = queryExpressionString === undefined ||
143
143
  queryExpressionString === '' ||
144
144
  queryExpressionString === '{}';
145
- const compareDisabled = selectedProfileName === '' || querySelection.expression === undefined;
146
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex gap-2", children: [_jsxs("div", { className: "flex w-full flex-wrap content-start items-center justify-between gap-2", children: [_jsxs("div", { className: "pb-6", children: [_jsx("label", { className: "text-xs", children: "Profile type" }), _jsx(ProfileTypeSelector, { profileTypesData: profileTypesData, loading: profileTypesLoading, selectedKey: selectedProfileName, onSelection: setProfileName, error: error, disabled: viewComponent?.disableProfileTypesDropdown })] }), _jsxs("div", { className: "w-full flex-1 pb-6", children: [_jsxs("div", { className: "mb-0.5 mt-1.5 flex items-center justify-between", children: [_jsx("label", { className: "text-xs", children: "Query" }), (query.matchers.length > 0 || query.inputMatcherString.length > 0) &&
147
- viewComponent !== undefined && _jsx("div", { children: viewComponent?.createViewComponent })] }), _jsx(MatchersInput, { queryClient: queryClient, setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query })] }), _jsx(DateTimeRangePicker, { onRangeSelection: setTimeRangeSelection, range: timeRangeSelection }), _jsxs(ButtonGroup, { children: [!searchDisabled && (_jsx(_Fragment, { children: !comparing && (_jsx(CompareButton, { disabled: compareDisabled, onClick: handleCompareClick })) })), _jsx(Button, { disabled: searchDisabled, onClick: (e) => {
148
- e.preventDefault();
149
- setQueryExpression(true);
150
- }, id: "h-matcher-search-button", children: "Search" })] })] }), _jsx("div", { children: comparing && _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}) }) })] }), _jsx("div", { className: "rounded bg-white shadow dark:border-gray-500 dark:bg-gray-700", children: _jsx("div", { style: { height: heightStyle }, children: querySelection.expression !== undefined &&
145
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex gap-2", children: [_jsxs("div", { className: "flex w-full flex-wrap content-start items-center gap-2", children: [_jsxs("div", { className: "pb-6", children: [_jsx("label", { className: "text-xs", children: "Profile type" }), _jsx(ProfileTypeSelector, { profileTypesData: profileTypesData, loading: profileTypesLoading, selectedKey: selectedProfileName, onSelection: setProfileName, error: error, disabled: viewComponent?.disableProfileTypesDropdown })] }), _jsxs("div", { className: "w-full flex-1 pb-6", children: [_jsxs("div", { className: "mb-0.5 mt-1.5 flex items-center justify-between", children: [_jsx("label", { className: "text-xs", children: "Query" }), (query.matchers.length > 0 || query.inputMatcherString.length > 0) &&
146
+ viewComponent !== undefined && _jsx("div", { children: viewComponent?.createViewComponent })] }), _jsx(MatchersInput, { queryClient: queryClient, setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query })] }), _jsx(DateTimeRangePicker, { onRangeSelection: setTimeRangeSelection, range: timeRangeSelection }), _jsx(ButtonGroup, { children: _jsx(Button, { disabled: searchDisabled, onClick: (e) => {
147
+ e.preventDefault();
148
+ setQueryExpression(true);
149
+ }, id: "h-matcher-search-button", children: "Search" }) })] }), _jsx("div", { children: comparing && _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}) }) })] }), _jsx("div", { className: "rounded bg-white shadow dark:border-gray-500 dark:bg-gray-700", children: _jsx("div", { style: { height: heightStyle }, children: querySelection.expression !== undefined &&
151
150
  querySelection.expression.length > 0 &&
152
151
  querySelection.from !== undefined &&
153
152
  querySelection.to !== undefined ? (_jsx("div", { className: "p-2", children: _jsx(ProfileMetricsGraph, { queryClient: queryClient, queryExpression: querySelection.expression, from: querySelection.from, to: querySelection.to, profile: profileSelection, comparing: comparing, setTimeRange: (range) => {
@@ -1,9 +1,13 @@
1
1
  import { ProfileTypesResponse } from '@parca/client';
2
+ import { type NavigateFunction } from '@parca/utilities';
3
+ import { QuerySelection } from '../ProfileSelector';
2
4
  interface Props {
3
5
  selectedProfileName: string;
4
6
  profileTypesData: ProfileTypesResponse | undefined;
5
7
  setProfileName: (name: string) => void;
6
8
  setQueryExpression: () => void;
9
+ querySelection: QuerySelection;
10
+ navigateTo: NavigateFunction;
7
11
  }
8
- export declare const useAutoQuerySelector: ({ selectedProfileName, profileTypesData, setProfileName, setQueryExpression, }: Props) => void;
12
+ export declare const useAutoQuerySelector: ({ selectedProfileName, profileTypesData, setProfileName, setQueryExpression, querySelection, navigateTo, }: Props) => void;
9
13
  export {};
@@ -12,10 +12,51 @@
12
12
  // limitations under the License.
13
13
  import { useEffect } from 'react';
14
14
  import { selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector } from '@parca/store';
15
+ import { ProfileSelectionFromParams, SuffixParams } from '..';
15
16
  import { constructProfileName } from '../ProfileTypeSelector';
16
- export const useAutoQuerySelector = ({ selectedProfileName, profileTypesData, setProfileName, setQueryExpression, }) => {
17
+ export const useAutoQuerySelector = ({ selectedProfileName, profileTypesData, setProfileName, setQueryExpression, querySelection, navigateTo, }) => {
17
18
  const autoQuery = useAppSelector(selectAutoQuery);
18
19
  const dispatch = useAppDispatch();
20
+ const queryParams = new URLSearchParams(location.search);
21
+ const comparing = queryParams.get('comparing') === 'true';
22
+ const expressionA = queryParams.get('expression_a');
23
+ useEffect(() => {
24
+ if (comparing && expressionA !== null && expressionA !== undefined) {
25
+ if (querySelection.expression === undefined) {
26
+ return;
27
+ }
28
+ const profileA = ProfileSelectionFromParams(querySelection.mergeFrom?.toString(), querySelection.mergeTo?.toString(), querySelection.expression, '');
29
+ const queryA = {
30
+ expression: querySelection.expression,
31
+ from: querySelection.from,
32
+ to: querySelection.to,
33
+ timeSelection: querySelection.timeSelection,
34
+ };
35
+ let compareQuery = {
36
+ compare_a: 'true',
37
+ expression_a: encodeURIComponent(queryA.expression),
38
+ from_a: queryA.from.toString(),
39
+ to_a: queryA.to.toString(),
40
+ time_selection_a: queryA.timeSelection,
41
+ compare_b: 'true',
42
+ expression_b: encodeURIComponent(queryA.expression),
43
+ from_b: queryA.from.toString(),
44
+ to_b: queryA.to.toString(),
45
+ time_selection_b: queryA.timeSelection,
46
+ };
47
+ if (profileA != null) {
48
+ compareQuery = {
49
+ ...SuffixParams(profileA.HistoryParams(), '_a'),
50
+ ...compareQuery,
51
+ };
52
+ }
53
+ void navigateTo('/', {
54
+ ...compareQuery,
55
+ search_string: '',
56
+ dashboard_items: ['icicle'],
57
+ });
58
+ }
59
+ }, [comparing, querySelection, navigateTo, expressionA, dispatch]);
19
60
  // Effect to load some initial data on load when is no selection
20
61
  useEffect(() => {
21
62
  void (async () => {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Props as CallgraphProps } from './Callgraph';
2
- import ProfileExplorer from './ProfileExplorer';
2
+ import ProfileExplorer, { getExpressionAsAString } from './ProfileExplorer';
3
3
  import ProfileTypeSelector from './ProfileTypeSelector';
4
4
  export * from './ProfileIcicleGraph/IcicleGraph';
5
5
  export * from './ProfileIcicleGraph';
@@ -11,4 +11,4 @@ export * from './ProfileTypeSelector';
11
11
  export * from './SourceView';
12
12
  export { default as Callgraph } from './Callgraph';
13
13
  export type { CallgraphProps };
14
- export { ProfileExplorer, ProfileTypeSelector };
14
+ export { ProfileExplorer, ProfileTypeSelector, getExpressionAsAString };
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@
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 ProfileExplorer from './ProfileExplorer';
13
+ import ProfileExplorer, { getExpressionAsAString } from './ProfileExplorer';
14
14
  import ProfileTypeSelector from './ProfileTypeSelector';
15
15
  export * from './ProfileIcicleGraph/IcicleGraph';
16
16
  export * from './ProfileIcicleGraph';
@@ -21,4 +21,4 @@ export * from './utils';
21
21
  export * from './ProfileTypeSelector';
22
22
  export * from './SourceView';
23
23
  export { default as Callgraph } from './Callgraph';
24
- export { ProfileExplorer, ProfileTypeSelector };
24
+ export { ProfileExplorer, ProfileTypeSelector, getExpressionAsAString };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.390",
3
+ "version": "0.16.392",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@headlessui/react": "^1.7.19",
7
7
  "@iconify/react": "^4.0.0",
8
8
  "@parca/client": "0.16.117",
9
- "@parca/components": "0.16.283",
9
+ "@parca/components": "0.16.284",
10
10
  "@parca/dynamicsize": "0.16.65",
11
11
  "@parca/hooks": "0.0.60",
12
12
  "@parca/icons": "0.16.69",
@@ -71,5 +71,5 @@
71
71
  "access": "public",
72
72
  "registry": "https://registry.npmjs.org/"
73
73
  },
74
- "gitHead": "da04231e85bce981a4be8b040e3d319772ea79cb"
74
+ "gitHead": "5355cd41303f7388e15c70b63c26b22d0cf82830"
75
75
  }
@@ -69,7 +69,7 @@ const ProfileExplorerCompare = ({
69
69
  closeProfile={closeProfileA}
70
70
  enforcedProfileName={''}
71
71
  comparing={true}
72
- onCompareProfile={() => {}}
72
+ navigateTo={navigateTo}
73
73
  />
74
74
  </Card>
75
75
  <Card className="mt-2 p-2">
@@ -82,7 +82,7 @@ const ProfileExplorerCompare = ({
82
82
  closeProfile={closeProfileB}
83
83
  enforcedProfileName={Query.parse(queryA.expression).profileName()}
84
84
  comparing={true}
85
- onCompareProfile={() => {}}
85
+ navigateTo={navigateTo}
86
86
  />
87
87
  </Card>
88
88
  </div>
@@ -24,7 +24,6 @@ interface ProfileExplorerSingleProps {
24
24
  selectQuery: (query: QuerySelection) => void;
25
25
  selectProfile: (source: ProfileSelection) => void;
26
26
  profile: ProfileSelection | null;
27
- compareProfile: () => void;
28
27
  navigateTo: NavigateFunction;
29
28
  }
30
29
 
@@ -34,7 +33,6 @@ const ProfileExplorerSingle = ({
34
33
  selectQuery,
35
34
  selectProfile,
36
35
  profile,
37
- compareProfile,
38
36
  navigateTo,
39
37
  }: ProfileExplorerSingleProps): JSX.Element => {
40
38
  return (
@@ -48,8 +46,8 @@ const ProfileExplorerSingle = ({
48
46
  closeProfile={() => {}} // eslint-disable-line @typescript-eslint/no-empty-function
49
47
  profileSelection={profile}
50
48
  comparing={false}
51
- onCompareProfile={compareProfile}
52
49
  enforcedProfileName={''} // TODO
50
+ navigateTo={navigateTo}
53
51
  />
54
52
  </Card>
55
53
  {profile != null ? (
@@ -42,7 +42,7 @@ const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
42
42
  );
43
43
  };
44
44
 
45
- const getExpressionAsAString = (expression: string | []): string => {
45
+ export const getExpressionAsAString = (expression: string | []): string => {
46
46
  const x = Array.isArray(expression) ? expression.join() : expression;
47
47
  return x;
48
48
  };
@@ -263,35 +263,6 @@ const ProfileExplorerApp = ({
263
263
  });
264
264
  };
265
265
 
266
- const compareProfile = (): void => {
267
- let compareQuery = {
268
- compare_a: 'true',
269
- expression_a: encodeURIComponent(queryA.expression),
270
- from_a: queryA.from.toString(),
271
- to_a: queryA.to.toString(),
272
- time_selection_a: queryA.timeSelection,
273
-
274
- compare_b: 'true',
275
- expression_b: encodeURIComponent(queryA.expression),
276
- from_b: queryA.from.toString(),
277
- to_b: queryA.to.toString(),
278
- time_selection_b: queryA.timeSelection,
279
- };
280
-
281
- if (profileA != null) {
282
- compareQuery = {
283
- ...SuffixParams(profileA.HistoryParams(), '_a'),
284
- ...compareQuery,
285
- };
286
- }
287
-
288
- void navigateTo('/', {
289
- ...compareQuery,
290
- search_string: '',
291
- dashboard_items: dashboard_items ?? DEFAULT_DASHBOARD_ITEMS,
292
- });
293
- };
294
-
295
266
  return (
296
267
  <ProfileExplorerSingle
297
268
  queryClient={queryClient}
@@ -299,7 +270,6 @@ const ProfileExplorerApp = ({
299
270
  profile={profileA}
300
271
  selectQuery={selectQuery}
301
272
  selectProfile={selectProfile}
302
- compareProfile={compareProfile}
303
273
  navigateTo={navigateTo}
304
274
  />
305
275
  );
@@ -27,13 +27,13 @@ import {
27
27
  } from '@parca/components';
28
28
  import {CloseIcon} from '@parca/icons';
29
29
  import {Query} from '@parca/parser';
30
+ import {type NavigateFunction} from '@parca/utilities';
30
31
 
31
32
  import {MergedProfileSelection, ProfileSelection} from '..';
32
33
  import MatchersInput from '../MatchersInput/index';
33
34
  import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
34
35
  import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
35
36
  import ProfileTypeSelector from '../ProfileTypeSelector/index';
36
- import CompareButton from './CompareButton';
37
37
  import {useAutoQuerySelector} from './useAutoQuerySelector';
38
38
 
39
39
  export interface QuerySelection {
@@ -54,7 +54,7 @@ interface ProfileSelectorProps {
54
54
  enforcedProfileName: string;
55
55
  profileSelection: ProfileSelection | null;
56
56
  comparing: boolean;
57
- onCompareProfile: () => void;
57
+ navigateTo: NavigateFunction;
58
58
  }
59
59
 
60
60
  export interface IProfileTypesResult {
@@ -92,7 +92,7 @@ const ProfileSelector = ({
92
92
  enforcedProfileName,
93
93
  profileSelection,
94
94
  comparing,
95
- onCompareProfile,
95
+ navigateTo,
96
96
  }: ProfileSelectorProps): JSX.Element => {
97
97
  const {
98
98
  loading: profileTypesLoading,
@@ -217,21 +217,19 @@ const ProfileSelector = ({
217
217
  profileTypesData,
218
218
  setProfileName,
219
219
  setQueryExpression,
220
+ querySelection,
221
+ navigateTo,
220
222
  });
221
223
 
222
- const handleCompareClick = (): void => onCompareProfile();
223
-
224
224
  const searchDisabled =
225
225
  queryExpressionString === undefined ||
226
226
  queryExpressionString === '' ||
227
227
  queryExpressionString === '{}';
228
228
 
229
- const compareDisabled = selectedProfileName === '' || querySelection.expression === undefined;
230
-
231
229
  return (
232
230
  <>
233
231
  <div className="mb-2 flex gap-2">
234
- <div className="flex w-full flex-wrap content-start items-center justify-between gap-2">
232
+ <div className="flex w-full flex-wrap content-start items-center gap-2">
235
233
  <div className="pb-6">
236
234
  <label className="text-xs">Profile type</label>
237
235
  <ProfileTypeSelector
@@ -261,13 +259,6 @@ const ProfileSelector = ({
261
259
  range={timeRangeSelection}
262
260
  />
263
261
  <ButtonGroup>
264
- {!searchDisabled && (
265
- <>
266
- {!comparing && (
267
- <CompareButton disabled={compareDisabled} onClick={handleCompareClick} />
268
- )}
269
- </>
270
- )}
271
262
  <Button
272
263
  disabled={searchDisabled}
273
264
  onClick={(e: React.MouseEvent<HTMLElement>) => {
@@ -15,7 +15,10 @@ import {useEffect} from 'react';
15
15
 
16
16
  import {ProfileTypesResponse} from '@parca/client';
17
17
  import {selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector} from '@parca/store';
18
+ import {type NavigateFunction} from '@parca/utilities';
18
19
 
20
+ import {ProfileSelectionFromParams, SuffixParams} from '..';
21
+ import {QuerySelection} from '../ProfileSelector';
19
22
  import {constructProfileName} from '../ProfileTypeSelector';
20
23
 
21
24
  interface Props {
@@ -23,6 +26,8 @@ interface Props {
23
26
  profileTypesData: ProfileTypesResponse | undefined;
24
27
  setProfileName: (name: string) => void;
25
28
  setQueryExpression: () => void;
29
+ querySelection: QuerySelection;
30
+ navigateTo: NavigateFunction;
26
31
  }
27
32
 
28
33
  export const useAutoQuerySelector = ({
@@ -30,9 +35,62 @@ export const useAutoQuerySelector = ({
30
35
  profileTypesData,
31
36
  setProfileName,
32
37
  setQueryExpression,
38
+ querySelection,
39
+ navigateTo,
33
40
  }: Props): void => {
34
41
  const autoQuery = useAppSelector(selectAutoQuery);
35
42
  const dispatch = useAppDispatch();
43
+ const queryParams = new URLSearchParams(location.search);
44
+
45
+ const comparing = queryParams.get('comparing') === 'true';
46
+ const expressionA = queryParams.get('expression_a');
47
+
48
+ useEffect(() => {
49
+ if (comparing && expressionA !== null && expressionA !== undefined) {
50
+ if (querySelection.expression === undefined) {
51
+ return;
52
+ }
53
+ const profileA = ProfileSelectionFromParams(
54
+ querySelection.mergeFrom?.toString(),
55
+ querySelection.mergeTo?.toString(),
56
+ querySelection.expression,
57
+ ''
58
+ );
59
+ const queryA = {
60
+ expression: querySelection.expression,
61
+ from: querySelection.from,
62
+ to: querySelection.to,
63
+ timeSelection: querySelection.timeSelection,
64
+ };
65
+
66
+ let compareQuery = {
67
+ compare_a: 'true',
68
+ expression_a: encodeURIComponent(queryA.expression),
69
+ from_a: queryA.from.toString(),
70
+ to_a: queryA.to.toString(),
71
+ time_selection_a: queryA.timeSelection,
72
+
73
+ compare_b: 'true',
74
+ expression_b: encodeURIComponent(queryA.expression),
75
+ from_b: queryA.from.toString(),
76
+ to_b: queryA.to.toString(),
77
+ time_selection_b: queryA.timeSelection,
78
+ };
79
+
80
+ if (profileA != null) {
81
+ compareQuery = {
82
+ ...SuffixParams(profileA.HistoryParams(), '_a'),
83
+ ...compareQuery,
84
+ };
85
+ }
86
+
87
+ void navigateTo('/', {
88
+ ...compareQuery,
89
+ search_string: '',
90
+ dashboard_items: ['icicle'],
91
+ });
92
+ }
93
+ }, [comparing, querySelection, navigateTo, expressionA, dispatch]);
36
94
 
37
95
  // Effect to load some initial data on load when is no selection
38
96
  useEffect(() => {
package/src/index.tsx CHANGED
@@ -12,7 +12,7 @@
12
12
  // limitations under the License.
13
13
 
14
14
  import type {Props as CallgraphProps} from './Callgraph';
15
- import ProfileExplorer from './ProfileExplorer';
15
+ import ProfileExplorer, {getExpressionAsAString} from './ProfileExplorer';
16
16
  import ProfileTypeSelector from './ProfileTypeSelector';
17
17
 
18
18
  export * from './ProfileIcicleGraph/IcicleGraph';
@@ -27,4 +27,4 @@ export {default as Callgraph} from './Callgraph';
27
27
 
28
28
  export type {CallgraphProps};
29
29
 
30
- export {ProfileExplorer, ProfileTypeSelector};
30
+ export {ProfileExplorer, ProfileTypeSelector, getExpressionAsAString};