@parca/profile 0.19.144 → 0.19.145

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 (28) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/MatchersInput/SuggestionsList.d.ts.map +1 -1
  3. package/dist/MatchersInput/SuggestionsList.js +73 -63
  4. package/dist/MatchersInput/index.d.ts.map +1 -1
  5. package/dist/MatchersInput/index.js +4 -5
  6. package/dist/ProfileTypeSelector/index.d.ts.map +1 -1
  7. package/dist/ProfileTypeSelector/index.js +33 -52
  8. package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
  9. package/dist/ProfileView/components/ColorStackLegend.js +70 -118
  10. package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
  11. package/dist/ProfileView/components/ProfileFilters/index.js +0 -1
  12. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  13. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +319 -123
  14. package/dist/SelectWithRefresh/index.js +38 -33
  15. package/dist/SimpleMatchers/Select.d.ts.map +1 -1
  16. package/dist/SimpleMatchers/Select.js +63 -68
  17. package/dist/SourceView/Highlighter.d.ts.map +1 -1
  18. package/dist/SourceView/Highlighter.js +1 -1
  19. package/package.json +3 -3
  20. package/src/MatchersInput/SuggestionsList.tsx +71 -86
  21. package/src/MatchersInput/index.tsx +61 -74
  22. package/src/ProfileTypeSelector/index.tsx +4 -7
  23. package/src/ProfileView/components/ColorStackLegend.tsx +15 -23
  24. package/src/ProfileView/components/ProfileFilters/index.tsx +8 -13
  25. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +113 -115
  26. package/src/SelectWithRefresh/index.tsx +28 -28
  27. package/src/SimpleMatchers/Select.tsx +29 -37
  28. package/src/SourceView/Highlighter.tsx +2 -2
@@ -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} from 'react';
14
+ import React from 'react';
15
15
 
16
16
  import {Icon} from '@iconify/react';
17
17
  import cx from 'classnames';
@@ -41,31 +41,23 @@ const ColorStackLegend = ({mappings, compareMode = false, loading}: Props): Reac
41
41
 
42
42
  const {appliedFilters, removeExcludeBinary, excludeBinary} = useProfileFilters();
43
43
 
44
- // Get current binary filters from the new ProfileFilters system
45
- const currentBinaryFilters = useMemo(() => {
46
- return (appliedFilters ?? [])
47
- .filter(f => f.type === 'frame' && f.field === 'binary')
48
- .map(f => f.value);
49
- }, [appliedFilters]);
44
+ const currentBinaryFilters = (appliedFilters ?? [])
45
+ .filter(f => f.type === 'frame' && f.field === 'binary')
46
+ .map(f => f.value);
50
47
 
51
48
  const mappingsList = useMappingList(mappings);
52
49
 
53
- const mappingColors = useMemo(() => {
54
- const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
55
- return colors;
56
- }, [isDarkMode, mappingsList, currentColorProfile]);
57
-
58
- const stackColorArray = useMemo(() => {
59
- return Object.entries(mappingColors).sort(([featureA], [featureB]) => {
60
- if (featureA === EVERYTHING_ELSE) {
61
- return 1;
62
- }
63
- if (featureB === EVERYTHING_ELSE) {
64
- return -1;
65
- }
66
- return featureA?.localeCompare(featureB ?? '') ?? 0;
67
- });
68
- }, [mappingColors]);
50
+ const mappingColors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
51
+
52
+ const stackColorArray = Object.entries(mappingColors).sort(([featureA], [featureB]) => {
53
+ if (featureA === EVERYTHING_ELSE) {
54
+ return 1;
55
+ }
56
+ if (featureB === EVERYTHING_ELSE) {
57
+ return -1;
58
+ }
59
+ return featureA?.localeCompare(featureB ?? '') ?? 0;
60
+ });
69
61
 
70
62
  if (stackColorArray.length === 0 && loading === false) {
71
63
  return <></>;
@@ -11,8 +11,6 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useCallback} from 'react';
15
-
16
14
  import {Icon} from '@iconify/react';
17
15
  import cx from 'classnames';
18
16
 
@@ -203,18 +201,15 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
203
201
  resetFilters,
204
202
  } = useProfileFilters();
205
203
 
206
- const handleKeyDown = useCallback(
207
- (e: React.KeyboardEvent<HTMLInputElement>) => {
208
- if (e.key === 'Enter') {
209
- e.preventDefault();
210
- if (e.currentTarget.value.trim() === '') {
211
- return;
212
- }
213
- onApplyFilters();
204
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
205
+ if (e.key === 'Enter') {
206
+ e.preventDefault();
207
+ if (e.currentTarget.value.trim() === '') {
208
+ return;
214
209
  }
215
- },
216
- [onApplyFilters]
217
- );
210
+ onApplyFilters();
211
+ }
212
+ };
218
213
 
219
214
  const filtersToRender = localFilters.length > 0 ? localFilters : appliedFilters ?? [];
220
215
 
@@ -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 {useEffect, useMemo, useState} from 'react';
14
+ import {useEffect, useState} from 'react';
15
15
 
16
16
  import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
17
17
  import {useQueryState} from 'nuqs';
@@ -37,121 +37,118 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
37
37
 
38
38
  const columnHelper = createColumnHelper<Row>();
39
39
 
40
- const unit: string = useMemo(() => profileType?.sampleUnit ?? '', [profileType?.sampleUnit]);
40
+ const unit: string = profileType?.sampleUnit ?? '';
41
41
 
42
- const columns = useMemo<Array<ColumnDef<Row>>>(() => {
43
- return [
44
- columnHelper.accessor('flat', {
45
- id: 'flat',
46
- header: 'Flat',
47
- cell: info => valueFormatter(info.getValue(), unit, 2),
48
- size: 80,
49
- meta: {
50
- align: 'right',
51
- },
52
- invertSorting: true,
53
- }),
54
- columnHelper.accessor('flat', {
55
- id: 'flatPercentage',
56
- header: 'Flat (%)',
57
- cell: info => {
58
- return getRatioString(info.getValue(), total, filtered);
59
- },
60
- size: 120,
61
- meta: {
62
- align: 'right',
63
- },
64
- invertSorting: true,
65
- }),
66
- columnHelper.accessor('flatDiff', {
67
- id: 'flatDiff',
68
- header: 'Flat Diff',
69
- cell: info => addPlusSign(valueFormatter(info.getValue(), unit, 2)),
70
- size: 120,
71
- meta: {
72
- align: 'right',
73
- },
74
- invertSorting: true,
75
- }),
76
- columnHelper.accessor('flatDiff', {
77
- id: 'flatDiffPercentage',
78
- header: 'Flat Diff (%)',
79
- cell: info => {
80
- return getRatioString(info.getValue(), total, filtered);
81
- },
82
- size: 120,
83
- meta: {
84
- align: 'right',
85
- },
86
- invertSorting: true,
87
- }),
88
- columnHelper.accessor('cumulative', {
89
- id: 'cumulative',
90
- header: 'Cumulative',
91
- cell: info => valueFormatter(info.getValue(), unit, 2),
92
- size: 150,
93
- meta: {
94
- align: 'right',
95
- },
96
- invertSorting: true,
97
- }),
98
- columnHelper.accessor('cumulative', {
99
- id: 'cumulativePercentage',
100
- header: 'Cumulative (%)',
101
- cell: info => {
102
- return getRatioString(info.getValue(), total, filtered);
103
- },
104
- size: 150,
105
- meta: {
106
- align: 'right',
107
- },
108
- invertSorting: true,
109
- }),
110
- columnHelper.accessor('cumulativeDiff', {
111
- id: 'cumulativeDiff',
112
- header: 'Cumulative Diff',
113
- cell: info => addPlusSign(valueFormatter(info.getValue(), unit, 2)),
114
- size: 170,
115
- meta: {
116
- align: 'right',
117
- },
118
- invertSorting: true,
119
- }),
120
- columnHelper.accessor('cumulativeDiff', {
121
- id: 'cumulativeDiffPercentage',
122
- header: 'Cumulative Diff (%)',
123
- cell: info => {
124
- return getRatioString(info.getValue(), total, filtered);
125
- },
126
- size: 170,
127
- meta: {
128
- align: 'right',
129
- },
130
- invertSorting: true,
131
- }),
132
- columnHelper.accessor('name', {
133
- id: 'name',
134
- header: 'Name',
135
- cell: info => info.getValue(),
136
- }),
137
- columnHelper.accessor('functionSystemName', {
138
- id: 'functionSystemName',
139
- header: 'Function System Name',
140
- cell: info => info.getValue(),
141
- }),
142
- columnHelper.accessor('functionFileName', {
143
- id: 'functionFileName',
144
- header: 'Function File Name',
145
- cell: info => info.getValue(),
146
- }),
147
- columnHelper.accessor('mappingFile', {
148
- id: 'mappingFile',
149
- header: 'Mapping File',
150
- cell: info => info.getValue(),
151
- }),
152
- ];
153
- // eslint-disable-next-line react-hooks/exhaustive-deps
154
- }, [profileType, unit]);
42
+ const columns: Array<ColumnDef<Row>> = [
43
+ columnHelper.accessor('flat', {
44
+ id: 'flat',
45
+ header: 'Flat',
46
+ cell: info => valueFormatter(info.getValue(), unit, 2),
47
+ size: 80,
48
+ meta: {
49
+ align: 'right',
50
+ },
51
+ invertSorting: true,
52
+ }),
53
+ columnHelper.accessor('flat', {
54
+ id: 'flatPercentage',
55
+ header: 'Flat (%)',
56
+ cell: info => {
57
+ return getRatioString(info.getValue(), total, filtered);
58
+ },
59
+ size: 120,
60
+ meta: {
61
+ align: 'right',
62
+ },
63
+ invertSorting: true,
64
+ }),
65
+ columnHelper.accessor('flatDiff', {
66
+ id: 'flatDiff',
67
+ header: 'Flat Diff',
68
+ cell: info => addPlusSign(valueFormatter(info.getValue(), unit, 2)),
69
+ size: 120,
70
+ meta: {
71
+ align: 'right',
72
+ },
73
+ invertSorting: true,
74
+ }),
75
+ columnHelper.accessor('flatDiff', {
76
+ id: 'flatDiffPercentage',
77
+ header: 'Flat Diff (%)',
78
+ cell: info => {
79
+ return getRatioString(info.getValue(), total, filtered);
80
+ },
81
+ size: 120,
82
+ meta: {
83
+ align: 'right',
84
+ },
85
+ invertSorting: true,
86
+ }),
87
+ columnHelper.accessor('cumulative', {
88
+ id: 'cumulative',
89
+ header: 'Cumulative',
90
+ cell: info => valueFormatter(info.getValue(), unit, 2),
91
+ size: 150,
92
+ meta: {
93
+ align: 'right',
94
+ },
95
+ invertSorting: true,
96
+ }),
97
+ columnHelper.accessor('cumulative', {
98
+ id: 'cumulativePercentage',
99
+ header: 'Cumulative (%)',
100
+ cell: info => {
101
+ return getRatioString(info.getValue(), total, filtered);
102
+ },
103
+ size: 150,
104
+ meta: {
105
+ align: 'right',
106
+ },
107
+ invertSorting: true,
108
+ }),
109
+ columnHelper.accessor('cumulativeDiff', {
110
+ id: 'cumulativeDiff',
111
+ header: 'Cumulative Diff',
112
+ cell: info => addPlusSign(valueFormatter(info.getValue(), unit, 2)),
113
+ size: 170,
114
+ meta: {
115
+ align: 'right',
116
+ },
117
+ invertSorting: true,
118
+ }),
119
+ columnHelper.accessor('cumulativeDiff', {
120
+ id: 'cumulativeDiffPercentage',
121
+ header: 'Cumulative Diff (%)',
122
+ cell: info => {
123
+ return getRatioString(info.getValue(), total, filtered);
124
+ },
125
+ size: 170,
126
+ meta: {
127
+ align: 'right',
128
+ },
129
+ invertSorting: true,
130
+ }),
131
+ columnHelper.accessor('name', {
132
+ id: 'name',
133
+ header: 'Name',
134
+ cell: info => info.getValue(),
135
+ }),
136
+ columnHelper.accessor('functionSystemName', {
137
+ id: 'functionSystemName',
138
+ header: 'Function System Name',
139
+ cell: info => info.getValue(),
140
+ }),
141
+ columnHelper.accessor('functionFileName', {
142
+ id: 'functionFileName',
143
+ header: 'Function File Name',
144
+ cell: info => info.getValue(),
145
+ }),
146
+ columnHelper.accessor('mappingFile', {
147
+ id: 'mappingFile',
148
+ header: 'Mapping File',
149
+ cell: info => info.getValue(),
150
+ }),
151
+ ];
155
152
 
156
153
  const [columnVisibility, setColumnVisibility] = useState(() => {
157
154
  return {
@@ -173,6 +170,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
173
170
 
174
171
  useEffect(() => {
175
172
  if (Array.isArray(tableColumns)) {
173
+ // eslint-disable-next-line react-hooks/set-state-in-effect
176
174
  setColumnVisibility(prevState => {
177
175
  const newState = {...prevState};
178
176
  (Object.keys(newState) as ColumnName[]).forEach(column => {
@@ -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 {useCallback, useState} from 'react';
14
+ import {useState} from 'react';
15
15
 
16
16
  import ReactSelect, {type MenuListProps, type Props as ReactSelectProps} from 'react-select';
17
17
 
@@ -39,7 +39,7 @@ export function SelectWithRefresh<Option, IsMulti extends boolean = false>(
39
39
 
40
40
  const [isRefreshing, setIsRefreshing] = useState(false);
41
41
 
42
- const handleRefetch = useCallback(async () => {
42
+ const handleRefetch = async (): Promise<void> => {
43
43
  if (onRefresh == null || isRefreshing) return;
44
44
 
45
45
  setIsRefreshing(true);
@@ -50,35 +50,35 @@ export function SelectWithRefresh<Option, IsMulti extends boolean = false>(
50
50
  } finally {
51
51
  setIsRefreshing(false);
52
52
  }
53
- }, [onRefresh, isRefreshing]);
53
+ };
54
54
 
55
- const MenuListWithRefresh = useCallback(
56
- ({children, innerProps}: MenuListProps<Option, IsMulti>) => {
57
- const testIdProps = menuTestId != null ? {'data-testid': menuTestId} : {};
55
+ const MenuListWithRefresh = ({
56
+ children,
57
+ innerProps,
58
+ }: MenuListProps<Option, IsMulti>): JSX.Element => {
59
+ const testIdProps = menuTestId != null ? {'data-testid': menuTestId} : {};
58
60
 
59
- return (
60
- <div className="flex flex-col" style={{maxHeight: '332px'}}>
61
- <div
62
- className="overflow-y-auto flex-1"
63
- {...innerProps}
64
- {...testIdProps}
65
- style={{...innerProps?.style, fontSize: '14px'}}
66
- >
67
- {children}
68
- </div>
69
- {onRefresh != null && (
70
- <RefreshButton
71
- onClick={() => void handleRefetch()}
72
- disabled={isRefreshing}
73
- title={refreshTitle}
74
- testId={refreshTestId}
75
- />
76
- )}
61
+ return (
62
+ <div className="flex flex-col" style={{maxHeight: '332px'}}>
63
+ <div
64
+ className="overflow-y-auto flex-1"
65
+ {...innerProps}
66
+ {...testIdProps}
67
+ style={{...innerProps?.style, fontSize: '14px'}}
68
+ >
69
+ {children}
77
70
  </div>
78
- );
79
- },
80
- [onRefresh, isRefreshing, handleRefetch, refreshTitle, refreshTestId, menuTestId]
81
- );
71
+ {onRefresh != null && (
72
+ <RefreshButton
73
+ onClick={() => void handleRefetch()}
74
+ disabled={isRefreshing}
75
+ title={refreshTitle}
76
+ testId={refreshTestId}
77
+ />
78
+ )}
79
+ </div>
80
+ );
81
+ };
82
82
 
83
83
  const combinedLoadingState = isRefreshing || (selectProps.isLoading ?? false);
84
84
 
@@ -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, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
14
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {Icon} from '@iconify/react';
17
17
  import {useVirtualizer} from '@tanstack/react-virtual';
@@ -91,7 +91,7 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
91
91
  const optionsRef = useRef<HTMLDivElement>(null);
92
92
  const searchInputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
93
93
 
94
- const handleRefetch = useCallback(async () => {
94
+ const handleRefetch = async (): Promise<void> => {
95
95
  if (refetchValues == null || isRefetching) return;
96
96
 
97
97
  setIsRefetching(true);
@@ -100,23 +100,21 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
100
100
  } finally {
101
101
  setIsRefetching(false);
102
102
  }
103
- }, [refetchValues, isRefetching]);
103
+ };
104
104
 
105
105
  useEffect(() => {
106
106
  const timer = setTimeout(() => setDebouncedSearchTerm(searchTerm), 150);
107
107
  return () => clearTimeout(timer);
108
108
  }, [searchTerm]);
109
109
 
110
- const items = useMemo<TypedSelectItem[]>(() => {
111
- if (itemsProp[0] != null && 'type' in itemsProp[0]) {
112
- return (itemsProp as GroupedSelectItem[]).flatMap(item =>
113
- item.values.map(v => ({...v, type: item.type}))
114
- );
115
- }
116
- return (itemsProp as SelectItem[]).map(item => ({...item, type: ''}));
117
- }, [itemsProp]);
110
+ const items: TypedSelectItem[] =
111
+ itemsProp[0] != null && 'type' in itemsProp[0]
112
+ ? (itemsProp as GroupedSelectItem[]).flatMap(item =>
113
+ item.values.map(v => ({...v, type: item.type}))
114
+ )
115
+ : (itemsProp as SelectItem[]).map(item => ({...item, type: ''}));
118
116
 
119
- const filteredItems = useMemo(() => {
117
+ const computeFilteredItems = (): TypedSelectItem[] => {
120
118
  if (!searchable) return items;
121
119
  const lowerSearch = debouncedSearchTerm.toLowerCase();
122
120
  const filtered = items.filter(item =>
@@ -129,7 +127,8 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
129
127
  (a, b) =>
130
128
  levenshtein.get(a.key, debouncedSearchTerm) - levenshtein.get(b.key, debouncedSearchTerm)
131
129
  );
132
- }, [items, debouncedSearchTerm, searchable]);
130
+ };
131
+ const filteredItems = computeFilteredItems();
133
132
 
134
133
  const selection = editable ? selectedKey : items.find(v => v.key === selectedKey);
135
134
 
@@ -228,24 +227,20 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
228
227
  e.target.value = value;
229
228
  };
230
229
 
231
- const groupedFilteredItems = useMemo(() => {
232
- return filteredItems
233
- .reduce((acc: GroupedSelectItem[], item) => {
234
- const group = acc.find(g => g.type === item.type);
235
- if (group != null) {
236
- group.values.push(item);
237
- } else {
238
- acc.push({type: item.type, values: [item]});
239
- }
240
- return acc;
241
- }, [])
242
- .sort((a, b) => a.values.length - b.values.length);
243
- }, [filteredItems]);
244
-
245
- const showHeaders = useMemo(
246
- () => groupedFilteredItems.length > 1 && groupedFilteredItems.every(g => g.type !== ''),
247
- [groupedFilteredItems]
248
- );
230
+ const groupedFilteredItems = filteredItems
231
+ .reduce((acc: GroupedSelectItem[], item) => {
232
+ const group = acc.find(g => g.type === item.type);
233
+ if (group != null) {
234
+ group.values.push(item);
235
+ } else {
236
+ acc.push({type: item.type, values: [item]});
237
+ }
238
+ return acc;
239
+ }, [])
240
+ .sort((a, b) => a.values.length - b.values.length);
241
+
242
+ const showHeaders =
243
+ groupedFilteredItems.length > 1 && groupedFilteredItems.every(g => g.type !== '');
249
244
 
250
245
  const flatList = useMemo(() => {
251
246
  const list: Array<
@@ -264,12 +259,9 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
264
259
  return list;
265
260
  }, [groupedFilteredItems, showHeaders]);
266
261
 
267
- const longestKey = useMemo(
268
- () =>
269
- filteredItems.reduce((a, b) => (a.key.length > b.key.length ? a : b), filteredItems[0])
270
- ?.key ?? '',
271
- [filteredItems]
272
- );
262
+ const longestKey =
263
+ filteredItems.reduce((a, b) => (a.key.length > b.key.length ? a : b), filteredItems[0])?.key ??
264
+ '';
273
265
 
274
266
  const rowVirtualizer = useVirtualizer({
275
267
  count: flatList.length,
@@ -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 {MouseEventHandler, useId, useMemo} from 'react';
14
+ import {MouseEventHandler, useId} from 'react';
15
15
 
16
16
  import cx from 'classnames';
17
17
  import {scaleLinear} from 'd3-scale';
@@ -193,7 +193,7 @@ export const profileAwareRenderer = (
193
193
 
194
194
  export const Highlighter = ({file, content, renderer}: HighlighterProps): JSX.Element => {
195
195
  const {isDarkMode} = useParcaContext();
196
- const language = useMemo(() => langaugeFromFile(file), [file]);
196
+ const language = langaugeFromFile(file);
197
197
 
198
198
  return (
199
199
  <div className="relative">