@parca/profile 0.19.41 → 0.19.42

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,10 @@
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.19.42](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.41...@parca/profile@0.19.42) (2025-08-25)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
6
10
  ## [0.19.41](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.40...@parca/profile@0.19.41) (2025-08-25)
7
11
 
8
12
  **Note:** Version bump only for package @parca/profile
@@ -6,6 +6,7 @@ export interface ProfileFilter {
6
6
  matchType?: 'equal' | 'not_equal' | 'contains' | 'not_contains';
7
7
  value: string;
8
8
  }
9
+ export declare const convertFromProtoFilters: (protoFilters: Filter[]) => ProfileFilter[];
9
10
  export declare const convertToProtoFilters: (profileFilters: ProfileFilter[]) => Filter[];
10
11
  export declare const useProfileFilters: () => {
11
12
  localFilters: ProfileFilter[];
@@ -1 +1 @@
1
- {"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,eAAe,CAAC;AAK1C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IAClC,KAAK,CAAC,EAAE,eAAe,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;IAC5F,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IAChE,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,eAAO,MAAM,qBAAqB,GAAI,gBAAgB,aAAa,EAAE,KAAG,MAAM,EAqG7E,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO;IACnC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,MAAM,IAAI,CAAC;CAsL1B,CAAC"}
1
+ {"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAA6C,MAAM,eAAe,CAAC;AAKtF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IAClC,KAAK,CAAC,EAAE,eAAe,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;IAC5F,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IAChE,KAAK,EAAE,MAAM,CAAC;CACf;AAqBD,eAAO,MAAM,uBAAuB,GAAI,cAAc,MAAM,EAAE,KAAG,aAAa,EA+E7E,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAAI,gBAAgB,aAAa,EAAE,KAAG,MAAM,EAmF7E,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO;IACnC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,MAAM,IAAI,CAAC;CAsL1B,CAAC"}
@@ -13,6 +13,91 @@
13
13
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
14
  import { getPresetByKey, isPresetKey } from './filterPresets';
15
15
  import { useProfileFiltersUrlState } from './useProfileFiltersUrlState';
16
+ const createStringCondition = (matchType, value) => ({
17
+ condition: matchType === 'equal'
18
+ ? { oneofKind: 'equal', equal: value }
19
+ : matchType === 'not_equal'
20
+ ? { oneofKind: 'notEqual', notEqual: value }
21
+ : matchType === 'contains'
22
+ ? { oneofKind: 'contains', contains: value }
23
+ : { oneofKind: 'notContains', notContains: value },
24
+ });
25
+ const createNumberCondition = (matchType, value) => ({
26
+ condition: matchType === 'equal'
27
+ ? { oneofKind: 'equal', equal: value }
28
+ : { oneofKind: 'notEqual', notEqual: value },
29
+ });
30
+ // Convert protobuf Filter[] back to ProfileFilter[] format for editing
31
+ export const convertFromProtoFilters = (protoFilters) => {
32
+ const profileFilters = [];
33
+ for (const [index, protoFilter] of protoFilters.entries()) {
34
+ if (protoFilter?.filter == null)
35
+ continue;
36
+ const filter = protoFilter.filter;
37
+ let type;
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ let criteria;
40
+ if (filter.oneofKind === 'stackFilter' &&
41
+ filter.stackFilter?.filter?.oneofKind === 'criteria') {
42
+ type = 'stack';
43
+ criteria = filter.stackFilter.filter.criteria;
44
+ }
45
+ else if (filter.oneofKind === 'frameFilter' &&
46
+ filter.frameFilter?.filter?.oneofKind === 'criteria') {
47
+ type = 'frame';
48
+ criteria = filter.frameFilter.filter.criteria;
49
+ }
50
+ else {
51
+ continue;
52
+ }
53
+ for (const [fieldName, condition] of Object.entries(criteria)) {
54
+ if (condition === undefined || typeof condition !== 'object')
55
+ continue;
56
+ const conditionObj = condition;
57
+ if (conditionObj.condition?.oneofKind === undefined)
58
+ continue;
59
+ let matchType;
60
+ let value;
61
+ switch (conditionObj.condition.oneofKind) {
62
+ case 'equal':
63
+ matchType = 'equal';
64
+ value = String(conditionObj.condition.equal);
65
+ break;
66
+ case 'notEqual':
67
+ matchType = 'not_equal';
68
+ value = String(conditionObj.condition.notEqual);
69
+ break;
70
+ case 'contains':
71
+ matchType = 'contains';
72
+ value = conditionObj.condition.contains ?? '';
73
+ break;
74
+ case 'notContains':
75
+ matchType = 'not_contains';
76
+ value = conditionObj.condition.notContains ?? '';
77
+ break;
78
+ default:
79
+ continue;
80
+ }
81
+ const fieldMap = {
82
+ functionName: 'function_name',
83
+ binary: 'binary',
84
+ systemName: 'system_name',
85
+ filename: 'filename',
86
+ address: 'address',
87
+ lineNumber: 'line_number',
88
+ };
89
+ const field = fieldMap[fieldName] ?? fieldName;
90
+ profileFilters.push({
91
+ id: `parsed-${index}-${fieldName}`,
92
+ type: type,
93
+ field: field,
94
+ matchType: matchType,
95
+ value,
96
+ });
97
+ }
98
+ }
99
+ return profileFilters;
100
+ };
16
101
  // Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
17
102
  export const convertToProtoFilters = (profileFilters) => {
18
103
  // First, expand any preset filters to their constituent filters
@@ -41,26 +126,9 @@ export const convertToProtoFilters = (profileFilters) => {
41
126
  .map(f => {
42
127
  // Build the condition based on field type
43
128
  const isNumberField = f.field === 'address' || f.field === 'line_number';
44
- let condition;
45
- if (isNumberField) {
46
- const numValue = BigInt(f.value);
47
- condition = {
48
- condition: f.matchType === 'equal'
49
- ? { oneofKind: 'equal', equal: numValue }
50
- : { oneofKind: 'notEqual', notEqual: numValue },
51
- };
52
- }
53
- else {
54
- condition = {
55
- condition: f.matchType === 'equal'
56
- ? { oneofKind: 'equal', equal: f.value }
57
- : f.matchType === 'not_equal'
58
- ? { oneofKind: 'notEqual', notEqual: f.value }
59
- : f.matchType === 'contains'
60
- ? { oneofKind: 'contains', contains: f.value }
61
- : { oneofKind: 'notContains', notContains: f.value },
62
- };
63
- }
129
+ const condition = isNumberField
130
+ ? createNumberCondition(f.matchType, BigInt(f.value))
131
+ : createStringCondition(f.matchType, f.value);
64
132
  // Create FilterCriteria
65
133
  const criteria = {};
66
134
  switch (f.field) {
@@ -1 +1 @@
1
- {"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAaA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAIjF,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAsDvD,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,aAAa,EAuCnE,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO;IAC3C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,iBAAiB,EAAE,sBAAsB,CAAC,aAAa,EAAE,CAAC,CAAC;CAoB5D,CAAC"}
1
+ {"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAeA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAIjF,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAoEvD,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,aAAa,EAgDnE,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO;IAC3C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,iBAAiB,EAAE,sBAAsB,CAAC,aAAa,EAAE,CAAC,CAAC;CAwB5D,CAAC"}
@@ -10,6 +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 { useMemo } from 'react';
13
14
  import { useURLStateCustom } from '@parca/components';
14
15
  import { safeDecode } from '@parca/utilities';
15
16
  import { isPresetKey } from './filterPresets';
@@ -58,6 +59,16 @@ const encodeProfileFilters = (filters) => {
58
59
  })
59
60
  .join(',');
60
61
  };
62
+ const generateFilterId = (filter, index) => {
63
+ const parts = [
64
+ filter.type ?? '',
65
+ filter.field ?? '',
66
+ filter.matchType ?? '',
67
+ filter.value,
68
+ index.toString(),
69
+ ];
70
+ return `filter-${parts.join('-').replace(/[^a-zA-Z0-9-]/g, '_')}`;
71
+ };
61
72
  // Decode filters from compact string format
62
73
  export const decodeProfileFilters = (encoded) => {
63
74
  if (encoded === '' || encoded === undefined)
@@ -71,8 +82,9 @@ export const decodeProfileFilters = (encoded) => {
71
82
  if (parts[0] === 'p' && parts.length >= 3) {
72
83
  const presetKey = parts[1];
73
84
  const value = parts.slice(2).join(':'); // Handle values with colons
85
+ const filterData = { type: presetKey, value };
74
86
  return {
75
- id: `filter-${Date.now()}-${index}`,
87
+ id: generateFilterId(filterData, index),
76
88
  type: presetKey,
77
89
  value,
78
90
  };
@@ -81,7 +93,12 @@ export const decodeProfileFilters = (encoded) => {
81
93
  const [type, field, match, ...valueParts] = parts;
82
94
  const value = valueParts.join(':'); // Handle values with colons
83
95
  const decodedFilter = {
84
- id: `filter-${Date.now()}-${index}`,
96
+ id: generateFilterId({
97
+ type: TYPE_MAP_REVERSE[type],
98
+ field: FIELD_MAP_REVERSE[field],
99
+ matchType: MATCH_MAP_REVERSE[match],
100
+ value,
101
+ }, index),
85
102
  type: TYPE_MAP_REVERSE[type],
86
103
  field: FIELD_MAP_REVERSE[field],
87
104
  matchType: MATCH_MAP_REVERSE[match],
@@ -105,8 +122,11 @@ export const useProfileFiltersUrlState = () => {
105
122
  },
106
123
  defaultValue: [],
107
124
  });
125
+ const memoizedAppliedFilters = useMemo(() => {
126
+ return appliedFilters ?? [];
127
+ }, [appliedFilters]);
108
128
  return {
109
- appliedFilters: appliedFilters ?? [],
129
+ appliedFilters: memoizedAppliedFilters,
110
130
  setAppliedFilters,
111
131
  };
112
132
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ViewMatchers/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAKtE,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAMpC,UAAU,KAAK;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAqKjC,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ViewMatchers/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAKtE,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAMpC,UAAU,KAAK;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAyKjC,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -44,6 +44,9 @@ const ViewMatchers = ({ labelNames, profileType, queryClient, runQuery, setMatch
44
44
  useEffect(() => {
45
45
  runQueryRef.current = runQuery;
46
46
  }, [runQuery]);
47
+ useEffect(() => {
48
+ selectionsRef.current = initialSelections;
49
+ }, [initialSelections]);
47
50
  const fetchLabelValues = useCallback(async (labelName) => {
48
51
  try {
49
52
  const response = await queryClient.values({
package/dist/index.d.ts CHANGED
@@ -4,13 +4,14 @@ import ProfileTypeSelector from './ProfileTypeSelector';
4
4
  import CustomSelect from './SimpleMatchers/Select';
5
5
  export * from './ProfileFlameGraph';
6
6
  export * from './ProfileSource';
7
- export { convertToProtoFilters } from './ProfileView/components/ProfileFilters/useProfileFilters';
7
+ export { convertToProtoFilters, convertFromProtoFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
8
8
  export * from './ProfileView';
9
9
  export * from './ProfileViewWithData';
10
10
  export * from './utils';
11
11
  export * from './ProfileTypeSelector';
12
12
  export * from './SourceView';
13
13
  export * from './ProfileMetricsGraph';
14
+ export * from './useSumBy';
14
15
  export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
15
16
  export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
16
17
  export declare const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: {
@@ -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,YAAY,MAAM,yBAAyB,CAAC;AAEnD,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAC,qBAAqB,EAAC,MAAM,2DAA2D,CAAC;AAChG,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AAEtC,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,EAAC,eAAe,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAC,CAAC"}
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,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,EAAC,eAAe,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAC,CAAC"}
package/dist/index.js CHANGED
@@ -16,13 +16,14 @@ import ProfileTypeSelector from './ProfileTypeSelector';
16
16
  import CustomSelect from './SimpleMatchers/Select';
17
17
  export * from './ProfileFlameGraph';
18
18
  export * from './ProfileSource';
19
- export { convertToProtoFilters } from './ProfileView/components/ProfileFilters/useProfileFilters';
19
+ export { convertToProtoFilters, convertFromProtoFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
20
20
  export * from './ProfileView';
21
21
  export * from './ProfileViewWithData';
22
22
  export * from './utils';
23
23
  export * from './ProfileTypeSelector';
24
24
  export * from './SourceView';
25
25
  export * from './ProfileMetricsGraph';
26
+ export * from './useSumBy';
26
27
  export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
27
28
  export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
28
29
  export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.19.41",
3
+ "version": "0.19.42",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@floating-ui/react": "^0.27.12",
@@ -79,5 +79,5 @@
79
79
  "access": "public",
80
80
  "registry": "https://registry.npmjs.org/"
81
81
  },
82
- "gitHead": "955b455bf184681c297d1a2af7917fbeadfa9049"
82
+ "gitHead": "3d789485ed269766c55c137aee80bad8c68dd351"
83
83
  }
@@ -13,7 +13,7 @@
13
13
 
14
14
  import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
- import {type Filter} from '@parca/client';
16
+ import {type Filter, type NumberCondition, type StringCondition} from '@parca/client';
17
17
 
18
18
  import {getPresetByKey, isPresetKey} from './filterPresets';
19
19
  import {useProfileFiltersUrlState} from './useProfileFiltersUrlState';
@@ -26,6 +26,106 @@ export interface ProfileFilter {
26
26
  value: string;
27
27
  }
28
28
 
29
+ const createStringCondition = (matchType: string, value: string): StringCondition => ({
30
+ condition:
31
+ matchType === 'equal'
32
+ ? {oneofKind: 'equal' as const, equal: value}
33
+ : matchType === 'not_equal'
34
+ ? {oneofKind: 'notEqual' as const, notEqual: value}
35
+ : matchType === 'contains'
36
+ ? {oneofKind: 'contains' as const, contains: value}
37
+ : {oneofKind: 'notContains' as const, notContains: value},
38
+ });
39
+
40
+ const createNumberCondition = (matchType: string, value: bigint): NumberCondition => ({
41
+ condition:
42
+ matchType === 'equal'
43
+ ? {oneofKind: 'equal' as const, equal: value}
44
+ : {oneofKind: 'notEqual' as const, notEqual: value},
45
+ });
46
+
47
+ // Convert protobuf Filter[] back to ProfileFilter[] format for editing
48
+ export const convertFromProtoFilters = (protoFilters: Filter[]): ProfileFilter[] => {
49
+ const profileFilters: ProfileFilter[] = [];
50
+
51
+ for (const [index, protoFilter] of protoFilters.entries()) {
52
+ if (protoFilter?.filter == null) continue;
53
+
54
+ const filter = protoFilter.filter;
55
+ let type: string;
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ let criteria: Record<string, any>;
58
+
59
+ if (
60
+ filter.oneofKind === 'stackFilter' &&
61
+ filter.stackFilter?.filter?.oneofKind === 'criteria'
62
+ ) {
63
+ type = 'stack';
64
+ criteria = filter.stackFilter.filter.criteria;
65
+ } else if (
66
+ filter.oneofKind === 'frameFilter' &&
67
+ filter.frameFilter?.filter?.oneofKind === 'criteria'
68
+ ) {
69
+ type = 'frame';
70
+ criteria = filter.frameFilter.filter.criteria;
71
+ } else {
72
+ continue;
73
+ }
74
+
75
+ for (const [fieldName, condition] of Object.entries(criteria)) {
76
+ if (condition === undefined || typeof condition !== 'object') continue;
77
+
78
+ const conditionObj = condition;
79
+ if (conditionObj.condition?.oneofKind === undefined) continue;
80
+
81
+ let matchType: string;
82
+ let value: string;
83
+
84
+ switch (conditionObj.condition.oneofKind) {
85
+ case 'equal':
86
+ matchType = 'equal';
87
+ value = String(conditionObj.condition.equal);
88
+ break;
89
+ case 'notEqual':
90
+ matchType = 'not_equal';
91
+ value = String(conditionObj.condition.notEqual);
92
+ break;
93
+ case 'contains':
94
+ matchType = 'contains';
95
+ value = conditionObj.condition.contains ?? '';
96
+ break;
97
+ case 'notContains':
98
+ matchType = 'not_contains';
99
+ value = conditionObj.condition.notContains ?? '';
100
+ break;
101
+ default:
102
+ continue;
103
+ }
104
+
105
+ const fieldMap: Record<string, string> = {
106
+ functionName: 'function_name',
107
+ binary: 'binary',
108
+ systemName: 'system_name',
109
+ filename: 'filename',
110
+ address: 'address',
111
+ lineNumber: 'line_number',
112
+ };
113
+
114
+ const field = fieldMap[fieldName] ?? fieldName;
115
+
116
+ profileFilters.push({
117
+ id: `parsed-${index}-${fieldName}`,
118
+ type: type as ProfileFilter['type'],
119
+ field: field as ProfileFilter['field'],
120
+ matchType: matchType as ProfileFilter['matchType'],
121
+ value,
122
+ });
123
+ }
124
+ }
125
+
126
+ return profileFilters;
127
+ };
128
+
29
129
  // Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
30
130
  export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[] => {
31
131
  // First, expand any preset filters to their constituent filters
@@ -56,30 +156,12 @@ export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[]
56
156
  // Build the condition based on field type
57
157
  const isNumberField = f.field === 'address' || f.field === 'line_number';
58
158
 
59
- let condition: any;
60
- if (isNumberField) {
61
- const numValue = BigInt(f.value);
62
- condition = {
63
- condition:
64
- f.matchType === 'equal'
65
- ? {oneofKind: 'equal' as const, equal: numValue}
66
- : {oneofKind: 'notEqual' as const, notEqual: numValue},
67
- };
68
- } else {
69
- condition = {
70
- condition:
71
- f.matchType === 'equal'
72
- ? {oneofKind: 'equal' as const, equal: f.value}
73
- : f.matchType === 'not_equal'
74
- ? {oneofKind: 'notEqual' as const, notEqual: f.value}
75
- : f.matchType === 'contains'
76
- ? {oneofKind: 'contains' as const, contains: f.value}
77
- : {oneofKind: 'notContains' as const, notContains: f.value},
78
- };
79
- }
159
+ const condition: StringCondition | NumberCondition = isNumberField
160
+ ? createNumberCondition(f.matchType as string, BigInt(f.value))
161
+ : createStringCondition(f.matchType as string, f.value);
80
162
 
81
163
  // Create FilterCriteria
82
- const criteria: any = {};
164
+ const criteria: Record<string, StringCondition | NumberCondition> = {};
83
165
  switch (f.field) {
84
166
  case 'function_name':
85
167
  criteria.functionName = condition;
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ import {useMemo} from 'react';
15
+
14
16
  import {useURLStateCustom, type ParamValueSetterCustom} from '@parca/components';
15
17
  import {safeDecode} from '@parca/utilities';
16
18
 
@@ -68,6 +70,20 @@ const encodeProfileFilters = (filters: ProfileFilter[]): string => {
68
70
  .join(',');
69
71
  };
70
72
 
73
+ const generateFilterId = (
74
+ filter: {type?: string; field?: string; matchType?: string; value: string},
75
+ index: number
76
+ ): string => {
77
+ const parts = [
78
+ filter.type ?? '',
79
+ filter.field ?? '',
80
+ filter.matchType ?? '',
81
+ filter.value,
82
+ index.toString(),
83
+ ];
84
+ return `filter-${parts.join('-').replace(/[^a-zA-Z0-9-]/g, '_')}`;
85
+ };
86
+
71
87
  // Decode filters from compact string format
72
88
  export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
73
89
  if (encoded === '' || encoded === undefined) return [];
@@ -84,8 +100,9 @@ export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
84
100
  const presetKey = parts[1];
85
101
  const value = parts.slice(2).join(':'); // Handle values with colons
86
102
 
103
+ const filterData = {type: presetKey, value};
87
104
  return {
88
- id: `filter-${Date.now()}-${index}`,
105
+ id: generateFilterId(filterData, index),
89
106
  type: presetKey,
90
107
  value,
91
108
  };
@@ -96,7 +113,15 @@ export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
96
113
  const value = valueParts.join(':'); // Handle values with colons
97
114
 
98
115
  const decodedFilter = {
99
- id: `filter-${Date.now()}-${index}`,
116
+ id: generateFilterId(
117
+ {
118
+ type: TYPE_MAP_REVERSE[type],
119
+ field: FIELD_MAP_REVERSE[field],
120
+ matchType: MATCH_MAP_REVERSE[match],
121
+ value,
122
+ },
123
+ index
124
+ ),
100
125
  type: TYPE_MAP_REVERSE[type] as ProfileFilter['type'],
101
126
  field: FIELD_MAP_REVERSE[field] as ProfileFilter['field'],
102
127
  matchType: MATCH_MAP_REVERSE[match] as ProfileFilter['matchType'],
@@ -128,8 +153,12 @@ export const useProfileFiltersUrlState = (): {
128
153
  }
129
154
  );
130
155
 
156
+ const memoizedAppliedFilters = useMemo(() => {
157
+ return appliedFilters ?? [];
158
+ }, [appliedFilters]);
159
+
131
160
  return {
132
- appliedFilters: appliedFilters ?? [],
161
+ appliedFilters: memoizedAppliedFilters,
133
162
  setAppliedFilters,
134
163
  };
135
164
  };
@@ -78,6 +78,10 @@ const ViewMatchers: React.FC<Props> = ({
78
78
  runQueryRef.current = runQuery;
79
79
  }, [runQuery]);
80
80
 
81
+ useEffect(() => {
82
+ selectionsRef.current = initialSelections;
83
+ }, [initialSelections]);
84
+
81
85
  const fetchLabelValues = useCallback(
82
86
  async (labelName: string): Promise<string[]> => {
83
87
  try {
package/src/index.tsx CHANGED
@@ -18,13 +18,17 @@ import CustomSelect from './SimpleMatchers/Select';
18
18
 
19
19
  export * from './ProfileFlameGraph';
20
20
  export * from './ProfileSource';
21
- export {convertToProtoFilters} from './ProfileView/components/ProfileFilters/useProfileFilters';
21
+ export {
22
+ convertToProtoFilters,
23
+ convertFromProtoFilters,
24
+ } from './ProfileView/components/ProfileFilters/useProfileFilters';
22
25
  export * from './ProfileView';
23
26
  export * from './ProfileViewWithData';
24
27
  export * from './utils';
25
28
  export * from './ProfileTypeSelector';
26
29
  export * from './SourceView';
27
30
  export * from './ProfileMetricsGraph';
31
+ export * from './useSumBy';
28
32
 
29
33
  export {default as ProfileFilters} from './ProfileView/components/ProfileFilters';
30
34
  export {useProfileFiltersUrlState} from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';