@parca/profile 0.19.9 → 0.19.11

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 (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  3. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +2 -2
  4. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts +0 -1
  5. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts.map +1 -1
  6. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +4 -141
  7. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts +8 -0
  8. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -0
  9. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +57 -0
  10. package/dist/ProfileView/components/InvertCallStack/index.d.ts +3 -0
  11. package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -0
  12. package/dist/ProfileView/components/InvertCallStack/index.js +21 -0
  13. package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
  14. package/dist/ProfileView/components/ShareButton/index.js +1 -1
  15. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts +3 -0
  16. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  17. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +78 -20
  18. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.d.ts +9 -0
  19. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.d.ts.map +1 -0
  20. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +22 -0
  21. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  22. package/dist/ProfileView/components/Toolbars/index.js +4 -1
  23. package/dist/ProfileView/components/ViewSelector/Dropdown.js +1 -1
  24. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  25. package/dist/ProfileView/components/ViewSelector/index.js +9 -12
  26. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  27. package/dist/ProfileView/hooks/useVisualizationState.js +16 -6
  28. package/dist/styles.css +1 -1
  29. package/package.json +2 -2
  30. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +7 -2
  31. package/src/ProfileView/components/ActionButtons/GroupByDropdown.tsx +7 -323
  32. package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +92 -0
  33. package/src/ProfileView/components/InvertCallStack/index.tsx +34 -0
  34. package/src/ProfileView/components/ShareButton/index.tsx +8 -4
  35. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +134 -22
  36. package/src/ProfileView/components/Toolbars/SwitchMenuItem.tsx +50 -0
  37. package/src/ProfileView/components/Toolbars/index.tsx +25 -9
  38. package/src/ProfileView/components/ViewSelector/Dropdown.tsx +1 -1
  39. package/src/ProfileView/components/ViewSelector/index.tsx +15 -14
  40. package/src/ProfileView/hooks/useVisualizationState.ts +25 -6
@@ -11,341 +11,25 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useEffect, useRef, useState} from 'react';
14
+ import React from 'react';
15
15
 
16
- import {Transition} from '@headlessui/react';
17
- import {Icon} from '@iconify/react';
18
- import Select from 'react-select';
19
-
20
- import {Button} from '@parca/components';
21
-
22
- import {
23
- FIELD_FUNCTION_FILE_NAME,
24
- FIELD_FUNCTION_NAME,
25
- FIELD_LABELS,
26
- FIELD_LOCATION_ADDRESS,
27
- FIELD_MAPPING_FILE,
28
- } from '../../../ProfileIcicleGraph/IcicleGraphArrow';
29
-
30
- interface LabelSelectorProps {
31
- labels: string[];
32
- groupBy: string[];
33
- setGroupByLabels: (labels: string[]) => void;
34
- isOpen: boolean;
35
- labelsButtonRef: React.RefObject<HTMLDivElement>;
36
- setIsLabelSelectorOpen: (isOpen: boolean) => void;
37
- }
38
-
39
- interface LabelSelectorProps {
40
- labels: string[];
41
- groupBy: string[];
42
- setGroupByLabels: (labels: string[]) => void;
43
- }
44
-
45
- interface LabelOption {
46
- label: string;
47
- value: string;
48
- }
49
-
50
- interface GroupByDropdownProps {
51
- groupBy: string[];
52
- toggleGroupBy: (key: string) => void;
53
- onLabelClick: () => void;
54
- labelsButtonRef: React.RefObject<HTMLDivElement>;
55
- }
56
-
57
- const groupByOptions = [
58
- {
59
- value: FIELD_FUNCTION_NAME,
60
- label: 'Function Name',
61
- description: 'Stacktraces are grouped by function names.',
62
- disabled: true,
63
- },
64
- {
65
- value: FIELD_FUNCTION_FILE_NAME,
66
- label: 'Filename',
67
- description: 'Stacktraces are grouped by filenames.',
68
- disabled: false,
69
- },
70
- {
71
- value: FIELD_LOCATION_ADDRESS,
72
- label: 'Address',
73
- description: 'Stacktraces are grouped by addresses.',
74
- disabled: false,
75
- },
76
- {
77
- value: FIELD_MAPPING_FILE,
78
- label: 'Binary',
79
- description: 'Stacktraces are grouped by binaries.',
80
- disabled: false,
81
- },
82
- ];
83
-
84
- const LabelSelector: React.FC<LabelSelectorProps> = ({
85
- labels,
86
- groupBy,
87
- setGroupByLabels,
88
- isOpen,
89
- labelsButtonRef,
90
- setIsLabelSelectorOpen,
91
- }) => {
92
- const [position, setPosition] = useState({top: 0, left: 0});
93
-
94
- useEffect(() => {
95
- if (isOpen && labelsButtonRef.current !== null) {
96
- const rect = labelsButtonRef.current.getBoundingClientRect();
97
- const parentRect = labelsButtonRef.current.offsetParent?.getBoundingClientRect() ?? {
98
- top: 0,
99
- left: 0,
100
- };
101
-
102
- setPosition({
103
- top: rect.bottom - parentRect.top,
104
- left: rect.right - parentRect.left + 4,
105
- });
106
- }
107
- }, [isOpen, labelsButtonRef]);
108
-
109
- if (!isOpen) return null;
110
-
111
- return (
112
- <div
113
- className="absolute w-64 ml-4 z-20"
114
- style={{
115
- top: `${position.top}px`,
116
- left: `${position.left}px`,
117
- }}
118
- >
119
- <Select<LabelOption, true>
120
- isMulti
121
- name="labels"
122
- options={labels.map(label => ({label, value: `${FIELD_LABELS}.${label}`}))}
123
- className="parca-select-container text-sm w-full border-gray-300 border rounded-md"
124
- classNamePrefix="parca-select"
125
- value={groupBy
126
- .filter(l => l.startsWith(FIELD_LABELS))
127
- .map(l => ({value: l, label: l.slice(FIELD_LABELS.length + 1)}))}
128
- onChange={newValue => {
129
- setGroupByLabels(newValue.map(option => option.value));
130
- setIsLabelSelectorOpen(false);
131
- }}
132
- placeholder="Select labels..."
133
- styles={{
134
- menu: provided => ({
135
- ...provided,
136
- position: 'relative',
137
- marginBottom: 0,
138
- boxShadow: 'none',
139
- marginTop: 0,
140
- }),
141
- control: provided => ({
142
- ...provided,
143
- boxShadow: 'none',
144
- borderBottom: '1px solid #e2e8f0',
145
- borderRight: 0,
146
- borderLeft: 0,
147
- borderTop: 0,
148
- borderBottomLeftRadius: 0,
149
- borderBottomRightRadius: 0,
150
- ':hover': {
151
- borderColor: '#e2e8f0',
152
- borderBottomLeftRadius: 0,
153
- borderBottomRightRadius: 0,
154
- },
155
- }),
156
- }}
157
- menuIsOpen={true}
158
- />
159
- </div>
160
- );
161
- };
162
-
163
- const GroupByDropdown: React.FC<GroupByDropdownProps> = ({
164
- groupBy,
165
- toggleGroupBy,
166
- onLabelClick,
167
- labelsButtonRef,
168
- }) => {
169
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
170
- const dropdownRef = useRef<HTMLDivElement>(null);
171
-
172
- useEffect(() => {
173
- const handleClickOutside = (event: MouseEvent): void => {
174
- if (
175
- isDropdownOpen &&
176
- dropdownRef.current != null &&
177
- !dropdownRef.current.contains(event.target as Node)
178
- ) {
179
- setIsDropdownOpen(false);
180
- }
181
- };
182
-
183
- document.addEventListener('mousedown', handleClickOutside);
184
- return () => {
185
- document.removeEventListener('mousedown', handleClickOutside);
186
- };
187
- }, [isDropdownOpen]);
188
-
189
- const label =
190
- groupBy.length === 0
191
- ? 'Nothing'
192
- : groupBy.length === 1
193
- ? groupByOptions.find(option => option.value === groupBy[0])?.label
194
- : 'Multiple';
195
-
196
- const selectedLabels = groupBy
197
- .filter(l => l.startsWith(FIELD_LABELS))
198
- .map(l => l.slice(FIELD_LABELS.length + 1));
199
-
200
- return (
201
- <div className="relative" ref={dropdownRef}>
202
- <label className="text-sm">Group by</label>
203
- <div className="relative text-left" id="h-group-by-filter">
204
- <Button
205
- variant="neutral"
206
- onClick={() => setIsDropdownOpen(!isDropdownOpen)}
207
- className="relative w-max cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm dark:border-gray-600 dark:bg-gray-900 sm:text-sm"
208
- >
209
- <span className="block overflow-x-hidden text-ellipsis">{label}</span>
210
- <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400">
211
- <Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
212
- </span>
213
- </Button>
214
-
215
- <Transition
216
- as="div"
217
- leave="transition ease-in duration-100"
218
- leaveFrom="opacity-100"
219
- leaveTo="opacity-0"
220
- show={isDropdownOpen}
221
- >
222
- <div className="absolute left-0 z-10 mt-1 min-w-[400px] overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm">
223
- <div className="p-4">
224
- <fieldset>
225
- <div className="space-y-5">
226
- {groupByOptions.map(({value, label, description, disabled}) => (
227
- <div key={value} className="relative flex items-start">
228
- <div className="flex h-6 items-center">
229
- <input
230
- id={value}
231
- name={value}
232
- type="checkbox"
233
- disabled={disabled}
234
- className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
235
- checked={groupBy.includes(value)}
236
- onChange={() => toggleGroupBy(value)}
237
- />
238
- </div>
239
- <div className="ml-3 text-sm leading-6">
240
- <label
241
- htmlFor={value}
242
- className="font-medium text-gray-900 dark:text-gray-200"
243
- >
244
- {label}
245
- </label>
246
- <p className="text-gray-500 dark:text-gray-400">{description}</p>
247
- </div>
248
- </div>
249
- ))}
250
- <div
251
- className="ml-7 flex flex-col items-start text-sm leading-6 cursor-pointer"
252
- onClick={onLabelClick}
253
- ref={labelsButtonRef}
254
- >
255
- <div className="flex justify-between w-full items-center">
256
- <div>
257
- <span className="font-medium text-gray-900 dark:text-gray-200">Labels</span>
258
- <p className="text-gray-500 dark:text-gray-400">
259
- Stacktraces are grouped by labels.
260
- </p>
261
- </div>
262
-
263
- <Icon icon="flowbite:caret-right-solid" className="h-[14px] w-[14px]" />
264
- </div>
265
-
266
- {selectedLabels.length > 0 && (
267
- <div className="flex gap-2 flex-wrap">
268
- <span className="text-gray-500 dark:text-gray-200">Selected labels:</span>
269
-
270
- <div className="flex flex-wrap gap-3">
271
- {selectedLabels.map(label => (
272
- <span
273
- key={label}
274
- className="mr-2 px-3 py-1 text-xs text-gray-700 dark:text-gray-200 bg-gray-200 rounded-md dark:bg-gray-800"
275
- >
276
- {label}
277
- </span>
278
- ))}
279
- </div>
280
- </div>
281
- )}
282
- </div>
283
- </div>
284
- </fieldset>
285
- </div>
286
- </div>
287
- </Transition>
288
- </div>
289
- </div>
290
- );
291
- };
16
+ import GroupByLabelsDropdown from '../GroupByLabelsDropdown';
292
17
 
293
18
  interface GroupByControlsProps {
294
19
  groupBy: string[];
295
20
  labels: string[];
296
- toggleGroupBy: (key: string) => void;
297
21
  setGroupByLabels: (labels: string[]) => void;
298
22
  }
299
23
 
300
- const GroupByControls: React.FC<GroupByControlsProps> = ({
301
- groupBy,
302
- labels,
303
- toggleGroupBy,
304
- setGroupByLabels,
305
- }) => {
306
- const [isLabelSelectorOpen, setIsLabelSelectorOpen] = useState(false);
307
-
308
- const labelsButton = useRef<HTMLDivElement>(null);
309
- const labelSelectorRef = useRef<HTMLDivElement>(null);
310
-
311
- useEffect(() => {
312
- const handleClickOutside = (event: MouseEvent): void => {
313
- if (
314
- isLabelSelectorOpen &&
315
- labelSelectorRef.current !== null &&
316
- !labelSelectorRef.current.contains(event.target as Node) &&
317
- labelsButton.current !== null &&
318
- !labelsButton.current.contains(event.target as Node)
319
- ) {
320
- setIsLabelSelectorOpen(false);
321
- }
322
- };
323
-
324
- document.addEventListener('mousedown', handleClickOutside);
325
- return () => {
326
- document.removeEventListener('mousedown', handleClickOutside);
327
- };
328
- }, [isLabelSelectorOpen]);
329
-
24
+ const GroupByControls: React.FC<GroupByControlsProps> = ({groupBy, labels, setGroupByLabels}) => {
330
25
  return (
331
26
  <div className="inline-flex items-start">
332
- <div className="relative flex items-start">
333
- <GroupByDropdown
27
+ <div className="relative flex gap-3 items-start">
28
+ <GroupByLabelsDropdown
29
+ labels={labels}
334
30
  groupBy={groupBy}
335
- toggleGroupBy={toggleGroupBy}
336
- onLabelClick={() => setIsLabelSelectorOpen(!isLabelSelectorOpen)}
337
- labelsButtonRef={labelsButton}
31
+ setGroupByLabels={setGroupByLabels}
338
32
  />
339
- <div ref={labelSelectorRef}>
340
- <LabelSelector
341
- labels={labels}
342
- groupBy={groupBy}
343
- setGroupByLabels={setGroupByLabels}
344
- isOpen={isLabelSelectorOpen}
345
- labelsButtonRef={labelsButton}
346
- setIsLabelSelectorOpen={setIsLabelSelectorOpen}
347
- />
348
- </div>
349
33
  </div>
350
34
  </div>
351
35
  );
@@ -0,0 +1,92 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import Select from 'react-select';
15
+
16
+ import {FIELD_LABELS} from '../../../ProfileIcicleGraph/IcicleGraphArrow';
17
+
18
+ interface LabelOption {
19
+ label: string;
20
+ value: string;
21
+ }
22
+
23
+ interface Props {
24
+ labels: string[];
25
+ groupBy: string[];
26
+ setGroupByLabels: (labels: string[]) => void;
27
+ }
28
+
29
+ const GroupByLabelsDropdown = ({labels, groupBy, setGroupByLabels}: Props): JSX.Element => {
30
+ return (
31
+ <div>
32
+ <div className="flex items-center justify-between">
33
+ <label className="text-sm">Group by</label>
34
+ </div>
35
+
36
+ <Select<LabelOption, true>
37
+ id="h-group-by-labels-selector"
38
+ isMulti
39
+ defaultMenuIsOpen={false}
40
+ defaultValue={undefined}
41
+ name="labels"
42
+ options={labels.map(label => ({label, value: `${FIELD_LABELS}.${label}`}))}
43
+ className="parca-select-container text-sm w-full rounded-md bg-white"
44
+ classNamePrefix="parca-select"
45
+ value={groupBy
46
+ .filter(l => l.startsWith(FIELD_LABELS))
47
+ .map(l => ({value: l, label: l.slice(FIELD_LABELS.length + 1)}))}
48
+ onChange={newValue => {
49
+ setGroupByLabels(newValue.map(option => option.value));
50
+ }}
51
+ placeholder="Select labels..."
52
+ styles={{
53
+ menu: provided => ({
54
+ ...provided,
55
+ marginBottom: 0,
56
+ boxShadow: 'none',
57
+ marginTop: 0,
58
+ zIndex: 1000,
59
+ minWidth: '320px',
60
+ }),
61
+ control: provided => ({
62
+ ...provided,
63
+ position: 'relative',
64
+ boxShadow: 'none',
65
+ borderBottom: '1px solid #e2e8f0',
66
+ borderRight: '1px solid #e2e8f0',
67
+ borderLeft: '1px solid #e2e8f0',
68
+ borderTop: '1px solid #e2e8f0',
69
+ ':hover': {
70
+ borderColor: '#e2e8f0',
71
+ borderBottomLeftRadius: 0,
72
+ borderBottomRightRadius: 0,
73
+ },
74
+ }),
75
+ option: provided => ({
76
+ ...provided,
77
+ ':hover': {
78
+ backgroundColor: '#4f46e5',
79
+ color: '#ffffff',
80
+ },
81
+ ':focus': {
82
+ backgroundColor: '#4f46e5',
83
+ color: '#ffffff',
84
+ },
85
+ }),
86
+ }}
87
+ />
88
+ </div>
89
+ );
90
+ };
91
+
92
+ export default GroupByLabelsDropdown;
@@ -0,0 +1,34 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {Icon} from '@iconify/react';
15
+
16
+ import {Button, useURLState} from '@parca/components';
17
+
18
+ const InvertCallStack = (): JSX.Element => {
19
+ const [invertStack = '', setInvertStack] = useURLState('invert_call_stack');
20
+ const isInvert = invertStack === 'true';
21
+
22
+ return (
23
+ <Button
24
+ variant="neutral"
25
+ className="flex items-center gap-2"
26
+ onClick={() => setInvertStack(isInvert ? '' : 'true')}
27
+ >
28
+ <Icon icon={isInvert ? 'ph:sort-ascending' : 'ph:sort-descending'} className="h-4 w-4" />
29
+ {isInvert ? 'Original' : 'Invert'} Call Stack
30
+ </Button>
31
+ );
32
+ };
33
+
34
+ export default InvertCallStack;
@@ -180,18 +180,22 @@ const ShareButton = ({
180
180
  <Dropdown
181
181
  dropdownWidth="w-48"
182
182
  element={
183
- <Button variant="neutral">
183
+ <Button
184
+ variant="neutral"
185
+ className="flex items-center gap-2"
186
+ id="h-share-dropdown-button"
187
+ >
188
+ <Icon icon="material-symbols:share" className="h-4 w-4" />
184
189
  Share
185
- <Icon icon="material-symbols:share" className="h-5 w-5 ml-2" />
186
190
  </Button>
187
191
  }
188
192
  >
189
193
  <span className="text-xs text-gray-400 capitalize px-2">actions</span>
190
194
  {actions.map(item => (
191
195
  <Dropdown.Item key={item.key} onSelect={item.onSelect}>
192
- <div id={item.id} className="flex items-center">
196
+ <div id={item.id} className="flex items-center gap-2">
197
+ <Icon icon={item.icon} className="h-4 w-4" />
193
198
  <span>{item.label}</span>
194
- <Icon icon={item.icon} className="ml-2 h-4 w-4" />
195
199
  </div>
196
200
  </Dropdown.Item>
197
201
  ))}