@parca/profile 0.19.35 → 0.19.37

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 (31) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/MatchersInput/index.d.ts.map +1 -1
  3. package/dist/MatchersInput/index.js +3 -2
  4. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  5. package/dist/ProfileFlameGraph/index.js +2 -1
  6. package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
  7. package/dist/ProfileSelector/QueryControls.js +6 -5
  8. package/dist/ProfileTypeSelector/index.d.ts.map +1 -1
  9. package/dist/ProfileTypeSelector/index.js +2 -1
  10. package/dist/ProfileView/components/ProfileFilters/index.d.ts +4 -1
  11. package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
  12. package/dist/ProfileView/components/ProfileFilters/index.js +8 -6
  13. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  14. package/dist/ProfileView/components/Toolbars/index.js +2 -1
  15. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  16. package/dist/SimpleMatchers/index.js +3 -2
  17. package/dist/ViewMatchers/index.d.ts.map +1 -1
  18. package/dist/ViewMatchers/index.js +2 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -0
  22. package/package.json +4 -3
  23. package/src/MatchersInput/index.tsx +6 -1
  24. package/src/ProfileFlameGraph/index.tsx +6 -1
  25. package/src/ProfileSelector/QueryControls.tsx +34 -9
  26. package/src/ProfileTypeSelector/index.tsx +2 -0
  27. package/src/ProfileView/components/ProfileFilters/index.tsx +55 -32
  28. package/src/ProfileView/components/Toolbars/index.tsx +2 -0
  29. package/src/SimpleMatchers/index.tsx +9 -1
  30. package/src/ViewMatchers/index.tsx +2 -1
  31. package/src/index.tsx +3 -0
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.19.37](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.36...@parca/profile@0.19.37) (2025-08-21)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.19.36](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.35...@parca/profile@0.19.36) (2025-08-15)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.19.35](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.34...@parca/profile@0.19.35) (2025-08-12)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MatchersInput/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAgB,cAAc,EAAE,kBAAkB,EAAgB,MAAM,eAAe,CAAC;AAE/F,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAGpC,OAAO,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAKrD,UAAU,kBAAkB;IAC1B,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,GACxB,QAAQ,kBAAkB,EAC1B,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,QAAQ,MAAM,EAAE,KACf,aAyBF,CAAC;AAEF,UAAU,cAAc;IACtB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,EAAE,KAAK,CAAC;KACf,CAAC;IACF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,cAAc,GACzB,QAAQ,kBAAkB,EAC1B,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,cA0BF,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,WAAW,MAAM,EACjB,oBAAoB,iBAAiB,KACpC,MAAM,EASR,CAAC;AAiMF,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,KAAK,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAWxF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MatchersInput/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAgB,cAAc,EAAE,kBAAkB,EAAgB,MAAM,eAAe,CAAC;AAE/F,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAIpC,OAAO,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAKrD,UAAU,kBAAkB;IAC1B,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,GACxB,QAAQ,kBAAkB,EAC1B,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,QAAQ,MAAM,EAAE,KACf,aAyBF,CAAC;AAEF,UAAU,cAAc;IACtB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,EAAE,KAAK,CAAC;KACf,CAAC;IACF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,cAAc,GACzB,QAAQ,kBAAkB,EAC1B,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,cA0BF,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,WAAW,MAAM,EACjB,oBAAoB,iBAAiB,KACpC,MAAM,EASR,CAAC;AAqMF,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,KAAK,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAWxF"}
@@ -17,6 +17,7 @@ import cx from 'classnames';
17
17
  import TextareaAutosize from 'react-textarea-autosize';
18
18
  import { useGrpcMetadata } from '@parca/components';
19
19
  import { Query } from '@parca/parser';
20
+ import { testId } from '@parca/test-utils';
20
21
  import { millisToProtoTimestamp, sanitizeLabelValue } from '@parca/utilities';
21
22
  import { LabelsProvider, useLabels } from '../contexts/MatchersInputLabelsContext';
22
23
  import useGrpcQuery from '../useGrpcQuery';
@@ -193,9 +194,9 @@ const MatchersInput = ({ setMatchersString, runQuery, currentQuery, }) => {
193
194
  setFocusedInput(false);
194
195
  };
195
196
  const profileSelected = currentQuery.profileName() === '';
196
- return (_jsxs("div", { className: "w-full min-w-[300px] flex-1 font-mono relative", children: [_jsx(TextareaAutosize, { ref: inputRef, className: cx('block h-[38px] w-full flex-1 rounded-md border bg-white px-2 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900', profileSelected && 'cursor-not-allowed'), placeholder: profileSelected
197
+ return (_jsxs("div", { className: "w-full min-w-[300px] flex-1 font-mono relative", ...testId('MATCHERS_INPUT_CONTAINER'), children: [_jsx(TextareaAutosize, { ref: inputRef, className: cx('block h-[38px] w-full flex-1 rounded-md border bg-white px-2 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900', profileSelected && 'cursor-not-allowed'), placeholder: profileSelected
197
198
  ? 'Select a profile first to enter a filter...'
198
- : 'filter profiles... eg. node="test"', onChange: onChange, value: value, onBlur: unfocus, onFocus: focus, disabled: profileSelected, title: profileSelected
199
+ : 'filter profiles... eg. node="test"', onChange: onChange, value: value, onBlur: unfocus, ...testId('MATCHERS_TEXTAREA'), onFocus: focus, disabled: profileSelected, title: profileSelected
199
200
  ? 'Select a profile first to enter a filter...'
200
201
  : 'filter profiles... eg. node="test"', id: "matchers-input" }), _jsx(SuggestionsList, { isLabelNamesLoading: isLabelNamesLoading, suggestions: suggestionSections, applySuggestion: applySuggestion, inputRef: inputRef.current, runQuery: runQuery, focusedInput: focusedInput, isLabelValuesLoading: isLabelValuesLoading && lastCompleted.type === 'literal', shouldTrimPrefix: shouldHandlePrefixes })] }));
201
202
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileFlameGraph/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAM7F,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAO9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAMpE,OAAO,EAAC,gBAAgB,EAA0B,MAAM,yBAAyB,CAAC;AAIlF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC;IACxD,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAUD,eAAO,MAAM,uBAAuB,GAClC,eAAe,mBAAmB,KACjC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAIpE,CAAC;AAEF,QAAA,MAAM,iBAAiB,GAAqC,oPAmBzD,sBAAsB,KAAG,GAAG,CAAC,OA8S/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileFlameGraph/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAM7F,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAO9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAI1C,OAAO,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAMpE,OAAO,EAAC,gBAAgB,EAA0B,MAAM,yBAAyB,CAAC;AAIlF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC;IACxD,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAUD,eAAO,MAAM,uBAAuB,GAClC,eAAe,mBAAmB,KACjC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAIpE,CAAC;AAEF,QAAA,MAAM,iBAAiB,GAAqC,oPAmBzD,sBAAsB,KAAG,GAAG,CAAC,OAkT/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -16,6 +16,7 @@ import cx from 'classnames';
16
16
  import { AnimatePresence, motion } from 'framer-motion';
17
17
  import { useMeasure } from 'react-use';
18
18
  import { FlameGraphSkeleton, SandwichFlameGraphSkeleton, useParcaContext, useURLState, } from '@parca/components';
19
+ import { testId } from '@parca/test-utils';
19
20
  import { capitalizeOnlyFirstLetter, divide } from '@parca/utilities';
20
21
  import DiffLegend from '../ProfileView/components/DiffLegend';
21
22
  import { useProfileViewContext } from '../ProfileView/context/ProfileViewContext';
@@ -162,6 +163,6 @@ const ProfileFlameGraph = function ProfileFlameGraphNonMemo({ arrow, total, filt
162
163
  }
163
164
  return (_jsx(ErrorContent, { errorMessage: _jsxs(_Fragment, { children: [_jsx("span", { children: capitalizeOnlyFirstLetter(error.message) }), isFlameChart ? flamechartHelpText ?? null : null] }) }));
164
165
  }
165
- return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsx("div", { className: cx(!isInSandwichView ? 'min-h-48' : ''), id: "h-flame-graph", children: _jsx(_Fragment, { children: flameGraph }) }), !isInSandwichView && (_jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] }))] }, "flame-graph-loaded") }));
166
+ return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsx("div", { className: cx(!isInSandwichView ? 'min-h-48' : ''), id: "h-flame-graph", ...testId('FLAMEGRAPH_CONTAINER'), children: _jsx(_Fragment, { children: flameGraph }) }), !isInSandwichView && (_jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] }))] }, "flame-graph-loaded") }));
166
167
  };
167
168
  export default ProfileFlameGraph;
@@ -1 +1 @@
1
- {"version":3,"file":"QueryControls.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/QueryControls.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAClD,OAAe,EAAC,KAAK,cAAc,EAAC,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAC,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACvE,OAAO,EAAS,aAAa,EAAsB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAC,WAAW,EAAE,KAAK,EAAC,MAAM,eAAe,CAAC;AAYjD,UAAU,kBAAkB;IAC1B,uBAAuB,EAAE,OAAO,CAAC;IACjC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0BAA0B,EAAE,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACnD,iBAAiB,CAAC,EAAE,QAAQ,CAAC;IAC7B,aAAa,CAAC,EAAE;QACd,2BAA2B,CAAC,EAAE,OAAO,CAAC;QACtC,0BAA0B,CAAC,EAAE,OAAO,CAAC;QACrC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,mBAAmB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;KACvC,CAAC;IACF,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,2BAA2B,EAAE,OAAO,CAAC;IACrC,8BAA8B,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,kBAAkB,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,KAAK,EAAE,KAAK,CAAC;IACb,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,kBAAkB,EAAE,aAAa,CAAC;IAClC,qBAAqB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACjD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAC1C,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAAC,EAC5B,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,2BAA2B,EAC3B,8BAA8B,EAC9B,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,WAAW,EACX,MAAM,EACN,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,iBAAiB,GAClB,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAyJlC"}
1
+ {"version":3,"file":"QueryControls.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/QueryControls.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAClD,OAAe,EAAC,KAAK,cAAc,EAAC,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAC,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACvE,OAAO,EAAS,aAAa,EAAsB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAC,WAAW,EAAE,KAAK,EAAC,MAAM,eAAe,CAAC;AAajD,UAAU,kBAAkB;IAC1B,uBAAuB,EAAE,OAAO,CAAC;IACjC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0BAA0B,EAAE,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACnD,iBAAiB,CAAC,EAAE,QAAQ,CAAC;IAC7B,aAAa,CAAC,EAAE;QACd,2BAA2B,CAAC,EAAE,OAAO,CAAC;QACtC,0BAA0B,CAAC,EAAE,OAAO,CAAC;QACrC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,mBAAmB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;KACvC,CAAC;IACF,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,2BAA2B,EAAE,OAAO,CAAC;IACrC,8BAA8B,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,kBAAkB,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,KAAK,EAAE,KAAK,CAAC;IACb,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,kBAAkB,EAAE,aAAa,CAAC;IAClC,qBAAqB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACjD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAC1C,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAAC,EAC5B,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,2BAA2B,EAC3B,8BAA8B,EAC9B,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,WAAW,EACX,MAAM,EACN,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,iBAAiB,GAClB,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAiLlC"}
@@ -14,17 +14,18 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
14
14
  import { Switch } from '@headlessui/react';
15
15
  import Select from 'react-select';
16
16
  import { Button, DateTimeRangePicker } from '@parca/components';
17
+ import { testId } from '@parca/test-utils';
17
18
  import MatchersInput from '../MatchersInput';
18
19
  import ProfileTypeSelector from '../ProfileTypeSelector';
19
20
  import SimpleMatchers from '../SimpleMatchers';
20
21
  import ViewMatchers from '../ViewMatchers';
21
22
  export function QueryControls({ showProfileTypeSelector, profileTypesData, profileTypesLoading, selectedProfileName, setProfileName, viewComponent, setQueryBrowserMode, advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser, setMatchersString, setQueryExpression, query, queryBrowserRef, timeRangeSelection, setTimeRangeSelection, searchDisabled, queryClient, labels, sumBySelection, sumBySelectionLoading, setUserSumBySelection, sumByRef, profileType, showSumBySelector, profileTypesError, }) {
22
- return (_jsxs("div", { className: "flex w-full flex-wrap items-start gap-2", children: [showProfileTypeSelector && (_jsxs("div", { children: [_jsx("label", { className: "text-xs", children: "Profile type" }), _jsx(ProfileTypeSelector, { profileTypesData: profileTypesData, loading: profileTypesLoading, selectedKey: selectedProfileName, onSelection: setProfileName, error: profileTypesError, disabled: viewComponent?.disableProfileTypesDropdown })] })), _jsxs("div", { className: "w-full flex-1 flex flex-col gap-1 mt-auto", ref: queryBrowserRef, children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("label", { className: "text-xs", children: "Query" }), viewComponent?.disableExplorativeQuerying !== true && (_jsxs(_Fragment, { children: [_jsxs(Switch, { checked: advancedModeForQueryBrowser, onChange: () => {
23
+ return (_jsxs("div", { className: "flex w-full flex-wrap items-start gap-2", ...testId('QUERY_CONTROLS_CONTAINER'), children: [showProfileTypeSelector && (_jsxs("div", { children: [_jsx("label", { className: "text-xs", ...testId('PROFILE_TYPE_LABEL'), children: "Profile type" }), _jsx(ProfileTypeSelector, { profileTypesData: profileTypesData, loading: profileTypesLoading, selectedKey: selectedProfileName, onSelection: setProfileName, error: profileTypesError, disabled: viewComponent?.disableProfileTypesDropdown })] })), _jsxs("div", { className: "w-full flex-1 flex flex-col gap-1 mt-auto", ref: queryBrowserRef, ...testId('QUERY_BROWSER_CONTAINER'), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("label", { className: "text-xs", ...testId('QUERY_LABEL'), children: "Query" }), viewComponent?.disableExplorativeQuerying !== true && (_jsxs(_Fragment, { children: [_jsxs(Switch, { checked: advancedModeForQueryBrowser, onChange: () => {
23
24
  setAdvancedModeForQueryBrowser(!advancedModeForQueryBrowser);
24
25
  setQueryBrowserMode(advancedModeForQueryBrowser ? 'simple' : 'advanced');
25
- }, className: `${advancedModeForQueryBrowser ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-800'} relative inline-flex h-[20px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`, children: [_jsx("span", { className: "sr-only", children: "Use setting" }), _jsx("span", { "aria-hidden": "true", className: `${advancedModeForQueryBrowser ? 'translate-x-6' : 'translate-x-0'} pointer-events-none inline-block h-[16px] w-[16px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out` })] }), _jsx("label", { className: "text-xs", children: "Advanced Mode" })] }))] }), viewComponent?.createViewComponent] }), viewComponent?.disableExplorativeQuerying === true &&
26
+ }, className: `${advancedModeForQueryBrowser ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-800'} relative inline-flex h-[20px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`, ...testId('ADVANCED_MODE_SWITCH'), children: [_jsx("span", { className: "sr-only", children: "Use setting" }), _jsx("span", { "aria-hidden": "true", className: `${advancedModeForQueryBrowser ? 'translate-x-6' : 'translate-x-0'} pointer-events-none inline-block h-[16px] w-[16px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out` })] }), _jsx("label", { className: "text-xs", ...testId('QUERY_MODE_LABEL'), children: "Advanced Mode" })] }))] }), viewComponent?.createViewComponent] }), viewComponent?.disableExplorativeQuerying === true &&
26
27
  viewComponent?.labelnames !== undefined &&
27
- viewComponent?.labelnames.length >= 1 ? (_jsx(ViewMatchers, { labelNames: viewComponent.labelnames, setMatchersString: setMatchersString, profileType: selectedProfileName, runQuery: setQueryExpression, currentQuery: query, queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs() })) : advancedModeForQueryBrowser ? (_jsx(MatchersInput, { setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName, queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs() })) : (_jsx(SimpleMatchers, { setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName, queryBrowserRef: queryBrowserRef, queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs() }, query.toString()))] }), showSumBySelector && (_jsxs("div", { children: [_jsx("div", { className: "mb-0.5 mt-1.5 flex items-center justify-between", children: _jsx("label", { className: "text-xs", children: "Sum by" }) }), _jsx(Select, { id: "h-sum-by-selector", defaultValue: [], isMulti: true, isClearable: false, name: "colors", options: labels.map(label => ({ label, value: label })), className: "parca-select-container text-sm w-full max-w-80", classNamePrefix: "parca-select", value: (sumBySelection ?? []).map(sumBy => ({ label: sumBy, value: sumBy })), onChange: newValue => {
28
+ viewComponent?.labelnames.length >= 1 ? (_jsx(ViewMatchers, { labelNames: viewComponent.labelnames, setMatchersString: setMatchersString, profileType: selectedProfileName, runQuery: setQueryExpression, currentQuery: query, queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs() })) : advancedModeForQueryBrowser ? (_jsx(MatchersInput, { setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName, queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs() })) : (_jsx(SimpleMatchers, { setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName, queryBrowserRef: queryBrowserRef, queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs() }, query.toString()))] }), showSumBySelector && (_jsxs("div", { ...testId('SUM_BY_CONTAINER'), children: [_jsx("div", { className: "mb-0.5 mt-1.5 flex items-center justify-between", children: _jsx("label", { className: "text-xs", ...testId('SUM_BY_LABEL'), children: "Sum by" }) }), _jsx(Select, { id: "h-sum-by-selector", "data-testid": testId('SUM_BY_SELECT')['data-testid'], defaultValue: [], isMulti: true, isClearable: false, name: "colors", options: labels.map(label => ({ label, value: label })), className: "parca-select-container text-sm w-full max-w-80", classNamePrefix: "parca-select", value: (sumBySelection ?? []).map(sumBy => ({ label: sumBy, value: sumBy })), onChange: newValue => {
28
29
  setUserSumBySelection(newValue.map(option => option.value));
29
30
  }, placeholder: "Labels...", styles: {
30
31
  indicatorSeparator: () => ({ display: 'none' }),
@@ -47,8 +48,8 @@ export function QueryControls({ showProfileTypeSelector, profileTypesData, profi
47
48
  setQueryExpression(true);
48
49
  currentRef.blur();
49
50
  }
50
- } })] })), _jsx(DateTimeRangePicker, { onRangeSelection: setTimeRangeSelection, range: timeRangeSelection }), _jsxs("div", { children: [_jsx("label", { className: "text-xs", children: "\u00A0" }), _jsx(Button, { disabled: searchDisabled, onClick: (e) => {
51
+ } })] })), _jsx(DateTimeRangePicker, { onRangeSelection: setTimeRangeSelection, range: timeRangeSelection, ...testId('DATE_TIME_RANGE_PICKER') }), _jsxs("div", { children: [_jsx("label", { className: "text-xs", ...testId('SEARCH_BUTTON_LABEL'), children: "\u00A0" }), _jsx(Button, { disabled: searchDisabled, onClick: (e) => {
51
52
  e.preventDefault();
52
53
  setQueryExpression(true);
53
- }, id: "h-matcher-search-button", children: "Search" })] })] }));
54
+ }, id: "h-matcher-search-button", ...testId('SEARCH_BUTTON'), children: "Search" })] })] }));
54
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileTypeSelector/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,eAAe,CAAC;AAChE,OAAO,EAAS,KAAK,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAE7D,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iBAAiB;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACjC;AAED,eAAO,MAAM,iBAAiB,EAAE,iBAiF/B,CAAC;AAEF,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAU3F;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,8BAA8B,EAAE,OAAO,GACtC,aAAa,CAiBf;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,WAAW,KAAG,MAIxD,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,OAAO,WAAW,EAAE,KAAG,MAAM,EAItE,CAAC;AAEF,UAAU,KAAK;IACb,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,mBAAmB,GAAI,2GAQ1B,KAAK,KAAG,GAAG,CAAC,OAyBd,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileTypeSelector/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,eAAe,CAAC;AAChE,OAAO,EAAS,KAAK,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAG7D,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iBAAiB;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACjC;AAED,eAAO,MAAM,iBAAiB,EAAE,iBAiF/B,CAAC;AAEF,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAU3F;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,8BAA8B,EAAE,OAAO,GACtC,aAAa,CAiBf;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,WAAW,KAAG,MAIxD,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,OAAO,WAAW,EAAE,KAAG,MAAM,EAItE,CAAC;AAEF,UAAU,KAAK;IACb,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,mBAAmB,GAAI,2GAQ1B,KAAK,KAAG,GAAG,CAAC,OA0Bd,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -13,6 +13,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
13
13
  // limitations under the License.
14
14
  import { useMemo } from 'react';
15
15
  import { Select } from '@parca/components';
16
+ import { testId } from '@parca/test-utils';
16
17
  export const wellKnownProfiles = {
17
18
  'block:contentions:count:contentions:count': {
18
19
  name: 'Block Contentions Total',
@@ -138,6 +139,6 @@ const ProfileTypeSelector = ({ profileTypesData, loading = false, error, selecte
138
139
  key: name,
139
140
  element: profileSelectElement(name, flexibleKnownProfilesDetection),
140
141
  }));
141
- return (_jsx(Select, { items: profileLabels, selectedKey: selectedKey, onSelection: onSelection, placeholder: "Select profile type...", loading: loading, className: "bg-white h-profile-type-dropdown", disabled: disabled }));
142
+ return (_jsx(Select, { items: profileLabels, selectedKey: selectedKey, onSelection: onSelection, placeholder: "Select profile type...", loading: loading, className: "bg-white h-profile-type-dropdown", disabled: disabled, ...testId('PROFILE_TYPE_SELECTOR') }));
142
143
  };
143
144
  export default ProfileTypeSelector;
@@ -1,5 +1,8 @@
1
1
  import { type ProfileFilter } from './useProfileFilters';
2
2
  export declare const isFilterComplete: (filter: ProfileFilter) => boolean;
3
- declare const ProfileFilters: () => JSX.Element;
3
+ export interface ProfileFiltersProps {
4
+ readOnly?: boolean;
5
+ }
6
+ declare const ProfileFilters: ({ readOnly }?: ProfileFiltersProps) => JSX.Element;
4
7
  export default ProfileFilters;
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,EAAoB,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAE1E,eAAO,MAAM,gBAAgB,GAAI,QAAQ,aAAa,KAAG,OASxD,CAAC;AAyIF,QAAA,MAAM,cAAc,QAAO,GAAG,CAAC,OAmL9B,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,EAAoB,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAE1E,eAAO,MAAM,gBAAgB,GAAI,QAAQ,aAAa,KAAG,OASxD,CAAC;AAyIF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,cAAc,GAAI,eAAoB,mBAAwB,KAAG,GAAG,CAAC,OAsM1E,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -139,7 +139,7 @@ const numberMatchTypeItems = [
139
139
  },
140
140
  },
141
141
  ];
142
- const ProfileFilters = () => {
142
+ const ProfileFilters = ({ readOnly = false } = {}) => {
143
143
  const { profileSource } = useProfileViewContext();
144
144
  const currentProfileType = profileSource?.ProfileType()?.toString();
145
145
  const filterTypeItems = getFilterTypeItems(currentProfileType);
@@ -158,7 +158,7 @@ const ProfileFilters = () => {
158
158
  const isNumberField = filter.field === 'address' || filter.field === 'line_number';
159
159
  const matchTypeItems = isNumberField ? numberMatchTypeItems : stringMatchTypeItems;
160
160
  const isPresetFilter = filter.type != null && isPresetKey(filter.type);
161
- return (_jsxs("div", { className: "flex items-center gap-0", children: [_jsx(Select, { items: filterTypeItems, selectedKey: filter.type, placeholder: "Select Filter", onSelection: key => {
161
+ return (_jsxs("div", { className: "flex items-center gap-0", children: [_jsx(Select, { items: filterTypeItems, selectedKey: filter.type, placeholder: "Select Filter", disabled: readOnly, onSelection: key => {
162
162
  // Check if this is a preset selection
163
163
  if (isPresetKey(key)) {
164
164
  const preset = getPresetByKey(key);
@@ -190,7 +190,7 @@ const ProfileFilters = () => {
190
190
  });
191
191
  }
192
192
  }
193
- }, className: cx('rounded-l-md pr-1 gap-0 focus:z-50 focus:relative focus:outline-1', isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none', filter.type != null ? 'border-r-0 w-auto' : 'w-32') }), filter.type != null && !isPresetFilter && (_jsxs(_Fragment, { children: [_jsx(Select, { items: fieldItems, selectedKey: filter.field ?? '', onSelection: key => {
193
+ }, className: cx('gap-0 focus:z-50 focus:relative focus:outline-1', readOnly ? '' : 'pr-1', readOnly && isPresetFilter ? 'rounded-md' : 'rounded-l-md rounded-r-none', !readOnly && (isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none'), readOnly ? 'w-auto' : filter.type != null ? 'border-r-0 w-auto' : 'w-32'), hideCaretDropdown: readOnly }), filter.type != null && !isPresetFilter && (_jsxs(_Fragment, { children: [_jsx(Select, { items: fieldItems, selectedKey: filter.field ?? '', disabled: readOnly, onSelection: key => {
194
194
  const newField = key;
195
195
  const isNewFieldNumber = newField === 'address' || newField === 'line_number';
196
196
  const isCurrentFieldNumber = filter.field === 'address' || filter.field === 'line_number';
@@ -203,7 +203,7 @@ const ProfileFilters = () => {
203
203
  else {
204
204
  updateFilter(filter.id, { field: newField });
205
205
  }
206
- }, className: "rounded-none border-r-0 w-32 pr-1 gap-0 focus:z-50 focus:relative focus:outline-1" }), _jsx(Select, { items: matchTypeItems, selectedKey: filter.matchType ?? '', onSelection: key => updateFilter(filter.id, { matchType: key }), className: "rounded-none border-r-0 pr-1 gap-0 focus:z-50 focus:relative focus:outline-1" }), _jsx(Input, { placeholder: "Value", value: filter.value, onChange: e => updateFilter(filter.id, { value: e.target.value }), onKeyDown: handleKeyDown, className: "rounded-none w-36 text-sm focus:outline-1" })] })), _jsx(Button, { variant: "neutral", onClick: () => {
206
+ }, className: cx('rounded-none border-r-0 w-32 gap-0 focus:z-50 focus:relative focus:outline-1', readOnly ? '' : 'pr-1'), hideCaretDropdown: readOnly }), _jsx(Select, { items: matchTypeItems, selectedKey: filter.matchType ?? '', disabled: readOnly, onSelection: key => updateFilter(filter.id, { matchType: key }), className: cx('rounded-none border-r-0 gap-0 focus:z-50 focus:relative focus:outline-1', readOnly ? '' : 'pr-1'), hideCaretDropdown: readOnly }), _jsx(Input, { placeholder: "Value", value: filter.value, disabled: readOnly, onChange: e => updateFilter(filter.id, { value: e.target.value }), onKeyDown: handleKeyDown, className: "rounded-none w-36 text-sm focus:outline-1" })] })), !readOnly && (_jsx(Button, { variant: "neutral", onClick: () => {
207
207
  // If we're displaying local filters and this is the last one, reset everything
208
208
  if (localFilters.length > 0 && localFilters.length === 1) {
209
209
  resetFilters();
@@ -216,7 +216,9 @@ const ProfileFilters = () => {
216
216
  else {
217
217
  removeFilter(filter.id);
218
218
  }
219
- }, className: cx('h-[38px] p-3', filter.type != null ? 'rounded-none rounded-r-md' : 'rounded-l-none rounded-r-md'), children: _jsx(Icon, { icon: "mdi:close", className: "h-4 w-4" }) })] }, filter.id));
220
- }), localFilters.length > 0 && (_jsx(Button, { variant: "neutral", onClick: addFilter, className: "p-3 h-[38px]", children: _jsx(Icon, { icon: "mdi:filter-plus-outline", className: "h-4 w-4" }) })), localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (_jsxs(Button, { variant: "neutral", onClick: addFilter, className: "flex items-center gap-2", children: [_jsx(Icon, { icon: "mdi:filter-outline", className: "h-4 w-4" }), _jsx("span", { children: "Filter" })] }))] }), localFilters.length > 0 && (_jsx(Button, { variant: "primary", onClick: onApplyFilters, disabled: !hasUnsavedChanges || !localFilters.some(isFilterComplete), className: cx('flex items-center gap-2 sticky top-0'), children: _jsx("span", { children: "Apply" }) }))] }));
219
+ }, className: cx('h-[38px] p-3', filter.type != null
220
+ ? 'rounded-none rounded-r-md'
221
+ : 'rounded-l-none rounded-r-md'), children: _jsx(Icon, { icon: "mdi:close", className: "h-4 w-4" }) }))] }, filter.id));
222
+ }), !readOnly && localFilters.length > 0 && (_jsx(Button, { variant: "neutral", onClick: addFilter, className: "p-3 h-[38px]", children: _jsx(Icon, { icon: "mdi:filter-plus-outline", className: "h-4 w-4" }) })), !readOnly && localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (_jsxs(Button, { variant: "neutral", onClick: addFilter, className: "flex items-center gap-2", children: [_jsx(Icon, { icon: "mdi:filter-outline", className: "h-4 w-4" }), _jsx("span", { children: "Filter" })] }))] }), !readOnly && localFilters.length > 0 && (_jsx(Button, { variant: "primary", onClick: onApplyFilters, disabled: !hasUnsavedChanges || !localFilters.some(isFilterComplete), className: cx('flex items-center gap-2 sticky top-0'), children: _jsx("span", { children: "Apply" }) }))] }));
221
223
  };
222
224
  export default ProfileFilters;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/Toolbars/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,EAAE,EAAC,MAAM,OAAO,CAAC;AAIzB,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAC,gBAAgB,EAAC,MAAM,kDAAkD,CAAC;AAClF,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAUrD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,aAAa,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAClD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6BAA6B,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChD,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,aAAa,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,8BAA8B;IAC7C,yBAAyB,EAAE,MAAM,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAQ9C,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAiBxD,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,8BAA8B,CAmBxE,CAAC;AAMF,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,yBAAyB,CAyF9D,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/Toolbars/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,EAAE,EAAC,MAAM,OAAO,CAAC;AAIzB,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAC,gBAAgB,EAAC,MAAM,kDAAkD,CAAC;AAClF,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAUrD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,aAAa,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAClD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6BAA6B,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChD,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,aAAa,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,8BAA8B;IAC7C,yBAAyB,EAAE,MAAM,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAQ9C,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAkBxD,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,8BAA8B,CAmBxE,CAAC;AAMF,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,yBAAyB,CAyF9D,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Icon } from '@iconify/react';
3
3
  import { Button } from '@parca/components';
4
+ import { testId } from '@parca/test-utils';
4
5
  import { useDashboard } from '../../context/DashboardContext';
5
6
  import GroupByDropdown from '../ActionButtons/GroupByDropdown';
6
7
  import InvertCallStack from '../InvertCallStack';
@@ -13,7 +14,7 @@ export const TableToolbar = ({ profileType, total, filtered }) => {
13
14
  return (_jsx(_Fragment, { children: _jsx("div", { className: "flex w-full gap-2 items-end", children: _jsx(TableColumnsDropdown, { profileType: profileType, total: total, filtered: filtered }) }) }));
14
15
  };
15
16
  export const FlameGraphToolbar = ({ curPath, setNewCurPath }) => {
16
- return (_jsx(_Fragment, { children: _jsx("div", { className: "flex w-full gap-2 items-end", children: _jsxs(Button, { variant: "neutral", className: "gap-2 w-max h-fit", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, id: "h-reset-graph", children: ["Reset graph", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }) }) }));
17
+ return (_jsx(_Fragment, { children: _jsx("div", { className: "flex w-full gap-2 items-end", children: _jsxs(Button, { variant: "neutral", className: "gap-2 w-max h-fit", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, id: "h-reset-graph", ...testId('FLAMEGRAPH_RESET_BUTTON'), children: ["Reset graph", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }) }) }));
17
18
  };
18
19
  export const SandwichFlameGraphToolbar = ({ resetSandwichFunctionName, sandwichFunctionName, }) => {
19
20
  return (_jsx(_Fragment, { children: _jsx("div", { className: "flex w-full gap-2 items-end justify-between", children: _jsx(Button, { color: "neutral", onClick: () => resetSandwichFunctionName(), className: "w-auto", variant: "neutral", disabled: sandwichFunctionName === undefined || sandwichFunctionName.length === 0, children: "Reset view" }) }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SimpleMatchers/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAKpC,OAAe,EAAC,KAAK,UAAU,EAAC,MAAM,UAAU,CAAC;AAEjD,UAAU,KAAK;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAoBD,eAAO,MAAM,wBAAwB,GAAI,YAAY,MAAM,EAAE,KAAG,UAAU,EAQzE,CAAC;AAoWF,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAoB3E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SimpleMatchers/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAMpC,OAAe,EAAC,KAAK,UAAU,EAAC,MAAM,UAAU,CAAC;AAEjD,UAAU,KAAK;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAoBD,eAAO,MAAM,wBAAwB,GAAI,YAAY,MAAM,EAAE,KAAG,UAAU,EAQzE,CAAC;AA2WF,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAoB3E"}
@@ -16,6 +16,7 @@ import { Icon } from '@iconify/react';
16
16
  import { useQueryClient } from '@tanstack/react-query';
17
17
  import cx from 'classnames';
18
18
  import { useGrpcMetadata } from '@parca/components';
19
+ import { testId } from '@parca/test-utils';
19
20
  import { millisToProtoTimestamp, sanitizeLabelValue } from '@parca/utilities';
20
21
  import { LabelProvider, useLabels } from '../contexts/SimpleMatchersLabelContext';
21
22
  import { useUtilizationLabels } from '../contexts/UtilizationLabelsContext';
@@ -241,9 +242,9 @@ const SimpleMatchers = ({ queryClient, setMatchersString, currentQuery, profileT
241
242
  };
242
243
  }, [queryRows, fetchLabelValuesUnified]);
243
244
  const isRowRegex = (row) => row.operator === '=~' || row.operator === '!~';
244
- return (_jsxs("div", { className: `flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`, id: "simple-matchers", children: [visibleRows.map((row, index) => (_jsxs("div", { className: "flex items-center", children: [_jsx(Select, { items: labelNameOptions, onSelection: value => handleUpdateRow(index, 'labelName', value), placeholder: "Select label name", selectedKey: row.labelName, className: "rounded-tr-none rounded-br-none ring-0 focus:ring-0 outline-none", loading: labelNamesLoading, searchable: true }), _jsx(Select, { items: operatorOptions, onSelection: value => handleUpdateRow(index, 'operator', value), selectedKey: row.operator, className: "rounded-none ring-0 focus:ring-0 outline-none" }), _jsx(Select, { items: transformLabelsForSelect(row.labelValues), onSelection: value => handleUpdateRow(index, 'labelValue', value), placeholder: "Select label value", selectedKey: row.labelValue, className: "rounded-none ring-0 focus:ring-0 outline-none max-w-48", optionsClassname: cx('max-w-[300px]', {
245
+ return (_jsxs("div", { className: `flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`, id: "simple-matchers", ...testId('SIMPLE_MATCHERS_CONTAINER'), children: [visibleRows.map((row, index) => (_jsxs("div", { className: "flex items-center", ...testId('SIMPLE_MATCHER_ROW'), children: [_jsx(Select, { items: labelNameOptions, onSelection: value => handleUpdateRow(index, 'labelName', value), placeholder: "Select label name", selectedKey: row.labelName, className: "rounded-tr-none rounded-br-none ring-0 focus:ring-0 outline-none", loading: labelNamesLoading, searchable: true, ...testId('LABEL_NAME_SELECT') }), _jsx(Select, { items: operatorOptions, onSelection: value => handleUpdateRow(index, 'operator', value), selectedKey: row.operator, className: "rounded-none ring-0 focus:ring-0 outline-none", ...testId('OPERATOR_SELECT') }), _jsx(Select, { items: transformLabelsForSelect(row.labelValues), onSelection: value => handleUpdateRow(index, 'labelValue', value), placeholder: "Select label value", selectedKey: row.labelValue, className: "rounded-none ring-0 focus:ring-0 outline-none max-w-48", optionsClassname: cx('max-w-[300px]', {
245
246
  'w-[300px]': isRowRegex(row),
246
- }), searchable: true, disabled: row.labelName === '', loading: row.isLoading, onButtonClick: () => handleLabelValueClick(index), editable: isRowRegex(row) }), _jsx("button", { onClick: () => removeRow(index), className: cx('p-2 border-gray-200 border rounded rounded-tl-none rounded-bl-none focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900'), children: _jsx(Icon, { icon: "carbon:close", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) })] }, index))), queryRows.length > 3 && (_jsx("button", { onClick: () => setShowAll(!showAll), className: "mr-2 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900", children: showAll ? 'Show less' : `Show ${hiddenRowsCount} more` })), _jsx("button", { onClick: addNewRow, className: "p-2 border-gray-200 dark:bg-gray-900 dark:border-gray-600 border rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", children: _jsx(Icon, { icon: "material-symbols:add", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) })] }));
247
+ }), searchable: true, disabled: row.labelName === '', loading: row.isLoading, onButtonClick: () => handleLabelValueClick(index), editable: isRowRegex(row), ...testId('LABEL_VALUE_SELECT') }), _jsx("button", { onClick: () => removeRow(index), className: cx('p-2 border-gray-200 border rounded rounded-tl-none rounded-bl-none focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900'), ...testId('REMOVE_MATCHER_BUTTON'), children: _jsx(Icon, { icon: "carbon:close", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) })] }, index))), queryRows.length > 3 && (_jsx("button", { onClick: () => setShowAll(!showAll), className: "mr-2 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900", ...testId(showAll ? 'SHOW_LESS_BUTTON' : 'SHOW_MORE_BUTTON'), children: showAll ? 'Show less' : `Show ${hiddenRowsCount} more` })), _jsx("button", { onClick: addNewRow, className: "p-2 border-gray-200 dark:bg-gray-900 dark:border-gray-600 border rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", ...testId('ADD_MATCHER_BUTTON'), children: _jsx(Icon, { icon: "material-symbols:add", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) })] }));
247
248
  };
248
249
  export default function SimpleMathersWithProvider(props) {
249
250
  const labelNameFromMatchers = useMemo(() => {
@@ -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;AAKpC,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,CAqKjC,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -15,6 +15,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
15
15
  import { Icon } from '@iconify/react';
16
16
  import cx from 'classnames';
17
17
  import { useGrpcMetadata } from '@parca/components';
18
+ import { testId } from '@parca/test-utils';
18
19
  import { millisToProtoTimestamp, sanitizeLabelValue } from '@parca/utilities';
19
20
  import CustomSelect from '../SimpleMatchers/Select';
20
21
  const ViewMatchers = ({ labelNames, profileType, queryClient, runQuery, setMatchersString, start, end, currentQuery, }) => {
@@ -108,6 +109,6 @@ const ViewMatchers = ({ labelNames, profileType, queryClient, runQuery, setMatch
108
109
  element: { active: _jsx(_Fragment, { children: value }), expanded: _jsx(_Fragment, { children: value }) },
109
110
  }));
110
111
  }, []);
111
- return (_jsx("div", { className: "flex flex-wrap gap-2", children: labelNames.map(labelName => (_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "relative border shadow-sm px-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm flex gap-2 items-center justify-between bg-gray-100 dark:bg-gray-700 rounded-l-md border-gray-300 dark:border-gray-600", children: labelName }), _jsx(CustomSelect, { searchable: true, placeholder: "Select value", items: transformValuesForSelect(labelValuesMap[labelName] ?? []), onSelection: (value) => handleSelection(labelName, value), selectedKey: selectionsRef.current[labelName] ?? undefined, className: cx('rounded-l-none border-l-0', selectionsRef.current[labelName] != null && 'border-r-0 rounded-r-none'), loading: isLoading[labelName] ?? false }), selectionsRef.current[labelName] != null && (_jsx("button", { onClick: () => handleReset(labelName), className: "p-2 border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-600 border rounded-r-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", "aria-label": `Reset ${labelName} selection`, children: _jsx(Icon, { icon: "mdi:close", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) }))] }, labelName))) }));
112
+ return (_jsx("div", { className: "flex flex-wrap gap-2", ...testId('VIEW_MATCHERS_CONTAINER'), children: labelNames.map(labelName => (_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "relative border shadow-sm px-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm flex gap-2 items-center justify-between bg-gray-100 dark:bg-gray-700 rounded-l-md border-gray-300 dark:border-gray-600", children: labelName }), _jsx(CustomSelect, { searchable: true, placeholder: "Select value", items: transformValuesForSelect(labelValuesMap[labelName] ?? []), onSelection: (value) => handleSelection(labelName, value), selectedKey: selectionsRef.current[labelName] ?? undefined, className: cx('rounded-l-none border-l-0', selectionsRef.current[labelName] != null && 'border-r-0 rounded-r-none'), loading: isLoading[labelName] ?? false }), selectionsRef.current[labelName] != null && (_jsx("button", { onClick: () => handleReset(labelName), className: "p-2 border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-600 border rounded-r-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", "aria-label": `Reset ${labelName} selection`, children: _jsx(Icon, { icon: "mdi:close", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) }))] }, labelName))) }));
112
113
  };
113
114
  export default ViewMatchers;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export * from './utils';
11
11
  export * from './ProfileTypeSelector';
12
12
  export * from './SourceView';
13
13
  export * from './ProfileMetricsGraph';
14
+ export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
15
+ export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
14
16
  export declare const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: {
15
17
  dashboard_items: string;
16
18
  };
@@ -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,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,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"}
package/dist/index.js CHANGED
@@ -23,6 +23,8 @@ export * from './utils';
23
23
  export * from './ProfileTypeSelector';
24
24
  export * from './SourceView';
25
25
  export * from './ProfileMetricsGraph';
26
+ export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
27
+ export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
26
28
  export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
27
29
  dashboard_items: 'flamegraph',
28
30
  };
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.19.35",
3
+ "version": "0.19.37",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@floating-ui/react": "^0.27.12",
7
7
  "@headlessui/react": "^1.7.19",
8
8
  "@iconify/react": "^4.0.0",
9
9
  "@parca/client": "0.17.3",
10
- "@parca/components": "0.16.356",
10
+ "@parca/components": "0.16.358",
11
11
  "@parca/dynamicsize": "0.16.65",
12
12
  "@parca/hooks": "0.0.99",
13
13
  "@parca/icons": "0.16.72",
14
14
  "@parca/parser": "0.16.79",
15
15
  "@parca/store": "0.16.183",
16
+ "@parca/test-utils": "0.0.2",
16
17
  "@parca/utilities": "0.0.106",
17
18
  "@popperjs/core": "^2.11.8",
18
19
  "@protobuf-ts/runtime-rpc": "^2.5.0",
@@ -78,5 +79,5 @@
78
79
  "access": "public",
79
80
  "registry": "https://registry.npmjs.org/"
80
81
  },
81
- "gitHead": "c0da0b21aecf50944c7502483b8216ea11634ef2"
82
+ "gitHead": "7a6143408ac1e4108e8bfe4725f67f4650b613d4"
82
83
  }
@@ -20,6 +20,7 @@ import TextareaAutosize from 'react-textarea-autosize';
20
20
  import {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
21
21
  import {useGrpcMetadata} from '@parca/components';
22
22
  import {Query} from '@parca/parser';
23
+ import {testId} from '@parca/test-utils';
23
24
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
24
25
 
25
26
  import {UtilizationLabels} from '../ProfileSelector';
@@ -289,7 +290,10 @@ const MatchersInput = ({
289
290
  const profileSelected = currentQuery.profileName() === '';
290
291
 
291
292
  return (
292
- <div className="w-full min-w-[300px] flex-1 font-mono relative">
293
+ <div
294
+ className="w-full min-w-[300px] flex-1 font-mono relative"
295
+ {...testId('MATCHERS_INPUT_CONTAINER')}
296
+ >
293
297
  <TextareaAutosize
294
298
  ref={inputRef}
295
299
  className={cx(
@@ -304,6 +308,7 @@ const MatchersInput = ({
304
308
  onChange={onChange}
305
309
  value={value}
306
310
  onBlur={unfocus}
311
+ {...testId('MATCHERS_TEXTAREA')}
307
312
  onFocus={focus}
308
313
  disabled={profileSelected} // Disable input if no profile has been selected
309
314
  title={
@@ -25,6 +25,7 @@ import {
25
25
  useURLState,
26
26
  } from '@parca/components';
27
27
  import {ProfileType} from '@parca/parser';
28
+ import {testId} from '@parca/test-utils';
28
29
  import {capitalizeOnlyFirstLetter, divide} from '@parca/utilities';
29
30
 
30
31
  import {MergedProfileSource, ProfileSource} from '../ProfileSource';
@@ -380,7 +381,11 @@ const ProfileFlameGraph = function ProfileFlameGraphNonMemo({
380
381
  transition={{duration: 0.5}}
381
382
  >
382
383
  {compareMode ? <DiffLegend /> : null}
383
- <div className={cx(!isInSandwichView ? 'min-h-48' : '')} id="h-flame-graph">
384
+ <div
385
+ className={cx(!isInSandwichView ? 'min-h-48' : '')}
386
+ id="h-flame-graph"
387
+ {...testId('FLAMEGRAPH_CONTAINER')}
388
+ >
384
389
  <>{flameGraph}</>
385
390
  </div>
386
391
  {!isInSandwichView && (
@@ -18,6 +18,7 @@ import Select, {type SelectInstance} from 'react-select';
18
18
  import {ProfileTypesResponse, QueryServiceClient} from '@parca/client';
19
19
  import {Button, DateTimeRange, DateTimeRangePicker} from '@parca/components';
20
20
  import {ProfileType, Query} from '@parca/parser';
21
+ import {testId} from '@parca/test-utils';
21
22
 
22
23
  import MatchersInput from '../MatchersInput';
23
24
  import ProfileTypeSelector from '../ProfileTypeSelector';
@@ -92,10 +93,15 @@ export function QueryControls({
92
93
  profileTypesError,
93
94
  }: QueryControlsProps): JSX.Element {
94
95
  return (
95
- <div className="flex w-full flex-wrap items-start gap-2">
96
+ <div
97
+ className="flex w-full flex-wrap items-start gap-2"
98
+ {...testId('QUERY_CONTROLS_CONTAINER')}
99
+ >
96
100
  {showProfileTypeSelector && (
97
101
  <div>
98
- <label className="text-xs">Profile type</label>
102
+ <label className="text-xs" {...testId('PROFILE_TYPE_LABEL')}>
103
+ Profile type
104
+ </label>
99
105
  <ProfileTypeSelector
100
106
  profileTypesData={profileTypesData}
101
107
  loading={profileTypesLoading}
@@ -107,10 +113,16 @@ export function QueryControls({
107
113
  </div>
108
114
  )}
109
115
 
110
- <div className="w-full flex-1 flex flex-col gap-1 mt-auto" ref={queryBrowserRef}>
116
+ <div
117
+ className="w-full flex-1 flex flex-col gap-1 mt-auto"
118
+ ref={queryBrowserRef}
119
+ {...testId('QUERY_BROWSER_CONTAINER')}
120
+ >
111
121
  <div className="flex items-center justify-between">
112
122
  <div className="flex items-center gap-3">
113
- <label className="text-xs">Query</label>
123
+ <label className="text-xs" {...testId('QUERY_LABEL')}>
124
+ Query
125
+ </label>
114
126
  {viewComponent?.disableExplorativeQuerying !== true && (
115
127
  <>
116
128
  <Switch
@@ -122,6 +134,7 @@ export function QueryControls({
122
134
  className={`${
123
135
  advancedModeForQueryBrowser ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-800'
124
136
  } relative inline-flex h-[20px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`}
137
+ {...testId('ADVANCED_MODE_SWITCH')}
125
138
  >
126
139
  <span className="sr-only">Use setting</span>
127
140
  <span
@@ -131,7 +144,9 @@ export function QueryControls({
131
144
  } pointer-events-none inline-block h-[16px] w-[16px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
132
145
  />
133
146
  </Switch>
134
- <label className="text-xs">Advanced Mode</label>
147
+ <label className="text-xs" {...testId('QUERY_MODE_LABEL')}>
148
+ Advanced Mode
149
+ </label>
135
150
  </>
136
151
  )}
137
152
  </div>
@@ -177,12 +192,15 @@ export function QueryControls({
177
192
  </div>
178
193
 
179
194
  {showSumBySelector && (
180
- <div>
195
+ <div {...testId('SUM_BY_CONTAINER')}>
181
196
  <div className="mb-0.5 mt-1.5 flex items-center justify-between">
182
- <label className="text-xs">Sum by</label>
197
+ <label className="text-xs" {...testId('SUM_BY_LABEL')}>
198
+ Sum by
199
+ </label>
183
200
  </div>
184
201
  <Select<SelectOption, true>
185
202
  id="h-sum-by-selector"
203
+ data-testid={testId('SUM_BY_SELECT')['data-testid']}
186
204
  defaultValue={[]}
187
205
  isMulti
188
206
  isClearable={false}
@@ -226,10 +244,16 @@ export function QueryControls({
226
244
  </div>
227
245
  )}
228
246
 
229
- <DateTimeRangePicker onRangeSelection={setTimeRangeSelection} range={timeRangeSelection} />
247
+ <DateTimeRangePicker
248
+ onRangeSelection={setTimeRangeSelection}
249
+ range={timeRangeSelection}
250
+ {...testId('DATE_TIME_RANGE_PICKER')}
251
+ />
230
252
 
231
253
  <div>
232
- <label className="text-xs">&nbsp;</label>
254
+ <label className="text-xs" {...testId('SEARCH_BUTTON_LABEL')}>
255
+ &nbsp;
256
+ </label>
233
257
  <Button
234
258
  disabled={searchDisabled}
235
259
  onClick={(e: React.MouseEvent<HTMLElement>) => {
@@ -237,6 +261,7 @@ export function QueryControls({
237
261
  setQueryExpression(true);
238
262
  }}
239
263
  id="h-matcher-search-button"
264
+ {...testId('SEARCH_BUTTON')}
240
265
  >
241
266
  Search
242
267
  </Button>
@@ -17,6 +17,7 @@ import {RpcError} from '@protobuf-ts/runtime-rpc';
17
17
 
18
18
  import {ProfileType, ProfileTypesResponse} from '@parca/client';
19
19
  import {Select, type SelectElement} from '@parca/components';
20
+ import {testId} from '@parca/test-utils';
20
21
 
21
22
  interface WellKnownProfile {
22
23
  name: string;
@@ -197,6 +198,7 @@ const ProfileTypeSelector = ({
197
198
  loading={loading}
198
199
  className="bg-white h-profile-type-dropdown"
199
200
  disabled={disabled}
201
+ {...testId('PROFILE_TYPE_SELECTOR')}
200
202
  />
201
203
  );
202
204
  };
@@ -168,7 +168,11 @@ const numberMatchTypeItems: SelectItem[] = [
168
168
  },
169
169
  ];
170
170
 
171
- const ProfileFilters = (): JSX.Element => {
171
+ export interface ProfileFiltersProps {
172
+ readOnly?: boolean;
173
+ }
174
+
175
+ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Element => {
172
176
  const {profileSource} = useProfileViewContext();
173
177
  const currentProfileType = profileSource?.ProfileType()?.toString();
174
178
  const filterTypeItems = getFilterTypeItems(currentProfileType);
@@ -213,6 +217,7 @@ const ProfileFilters = (): JSX.Element => {
213
217
  items={filterTypeItems}
214
218
  selectedKey={filter.type}
215
219
  placeholder="Select Filter"
220
+ disabled={readOnly}
216
221
  onSelection={key => {
217
222
  // Check if this is a preset selection
218
223
  if (isPresetKey(key)) {
@@ -246,10 +251,13 @@ const ProfileFilters = (): JSX.Element => {
246
251
  }
247
252
  }}
248
253
  className={cx(
249
- 'rounded-l-md pr-1 gap-0 focus:z-50 focus:relative focus:outline-1',
250
- isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none',
251
- filter.type != null ? 'border-r-0 w-auto' : 'w-32'
254
+ 'gap-0 focus:z-50 focus:relative focus:outline-1',
255
+ readOnly ? '' : 'pr-1',
256
+ readOnly && isPresetFilter ? 'rounded-md' : 'rounded-l-md rounded-r-none',
257
+ !readOnly && (isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none'),
258
+ readOnly ? 'w-auto' : filter.type != null ? 'border-r-0 w-auto' : 'w-32'
252
259
  )}
260
+ hideCaretDropdown={readOnly}
253
261
  />
254
262
 
255
263
  {filter.type != null && !isPresetFilter && (
@@ -257,6 +265,7 @@ const ProfileFilters = (): JSX.Element => {
257
265
  <Select
258
266
  items={fieldItems}
259
267
  selectedKey={filter.field ?? ''}
268
+ disabled={readOnly}
260
269
  onSelection={key => {
261
270
  const newField = key as ProfileFilter['field'];
262
271
  const isNewFieldNumber = newField === 'address' || newField === 'line_number';
@@ -272,21 +281,31 @@ const ProfileFilters = (): JSX.Element => {
272
281
  updateFilter(filter.id, {field: newField});
273
282
  }
274
283
  }}
275
- className="rounded-none border-r-0 w-32 pr-1 gap-0 focus:z-50 focus:relative focus:outline-1"
284
+ className={cx(
285
+ 'rounded-none border-r-0 w-32 gap-0 focus:z-50 focus:relative focus:outline-1',
286
+ readOnly ? '' : 'pr-1'
287
+ )}
288
+ hideCaretDropdown={readOnly}
276
289
  />
277
290
 
278
291
  <Select
279
292
  items={matchTypeItems}
280
293
  selectedKey={filter.matchType ?? ''}
294
+ disabled={readOnly}
281
295
  onSelection={key =>
282
296
  updateFilter(filter.id, {matchType: key as ProfileFilter['matchType']})
283
297
  }
284
- className="rounded-none border-r-0 pr-1 gap-0 focus:z-50 focus:relative focus:outline-1"
298
+ className={cx(
299
+ 'rounded-none border-r-0 gap-0 focus:z-50 focus:relative focus:outline-1',
300
+ readOnly ? '' : 'pr-1'
301
+ )}
302
+ hideCaretDropdown={readOnly}
285
303
  />
286
304
 
287
305
  <Input
288
306
  placeholder="Value"
289
307
  value={filter.value}
308
+ disabled={readOnly}
290
309
  onChange={e => updateFilter(filter.id, {value: e.target.value})}
291
310
  onKeyDown={handleKeyDown}
292
311
  className="rounded-none w-36 text-sm focus:outline-1"
@@ -294,40 +313,44 @@ const ProfileFilters = (): JSX.Element => {
294
313
  </>
295
314
  )}
296
315
 
297
- <Button
298
- variant="neutral"
299
- onClick={() => {
300
- // If we're displaying local filters and this is the last one, reset everything
301
- if (localFilters.length > 0 && localFilters.length === 1) {
302
- resetFilters();
303
- }
304
- // If we're displaying applied filters and this is the last one, reset everything
305
- else if (localFilters.length === 0 && filtersToRender.length === 1) {
306
- resetFilters();
307
- }
308
- // Otherwise, just remove this specific filter
309
- else {
310
- removeFilter(filter.id);
311
- }
312
- }}
313
- className={cx(
314
- 'h-[38px] p-3',
315
- filter.type != null ? 'rounded-none rounded-r-md' : 'rounded-l-none rounded-r-md'
316
- )}
317
- >
318
- <Icon icon="mdi:close" className="h-4 w-4" />
319
- </Button>
316
+ {!readOnly && (
317
+ <Button
318
+ variant="neutral"
319
+ onClick={() => {
320
+ // If we're displaying local filters and this is the last one, reset everything
321
+ if (localFilters.length > 0 && localFilters.length === 1) {
322
+ resetFilters();
323
+ }
324
+ // If we're displaying applied filters and this is the last one, reset everything
325
+ else if (localFilters.length === 0 && filtersToRender.length === 1) {
326
+ resetFilters();
327
+ }
328
+ // Otherwise, just remove this specific filter
329
+ else {
330
+ removeFilter(filter.id);
331
+ }
332
+ }}
333
+ className={cx(
334
+ 'h-[38px] p-3',
335
+ filter.type != null
336
+ ? 'rounded-none rounded-r-md'
337
+ : 'rounded-l-none rounded-r-md'
338
+ )}
339
+ >
340
+ <Icon icon="mdi:close" className="h-4 w-4" />
341
+ </Button>
342
+ )}
320
343
  </div>
321
344
  );
322
345
  })}
323
346
 
324
- {localFilters.length > 0 && (
347
+ {!readOnly && localFilters.length > 0 && (
325
348
  <Button variant="neutral" onClick={addFilter} className="p-3 h-[38px]">
326
349
  <Icon icon="mdi:filter-plus-outline" className="h-4 w-4" />
327
350
  </Button>
328
351
  )}
329
352
 
330
- {localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (
353
+ {!readOnly && localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (
331
354
  <Button variant="neutral" onClick={addFilter} className="flex items-center gap-2">
332
355
  <Icon icon="mdi:filter-outline" className="h-4 w-4" />
333
356
  <span>Filter</span>
@@ -335,7 +358,7 @@ const ProfileFilters = (): JSX.Element => {
335
358
  )}
336
359
  </div>
337
360
 
338
- {localFilters.length > 0 && (
361
+ {!readOnly && localFilters.length > 0 && (
339
362
  <Button
340
363
  variant="primary"
341
364
  onClick={onApplyFilters}
@@ -18,6 +18,7 @@ import {Icon} from '@iconify/react';
18
18
  import {QueryServiceClient} from '@parca/client';
19
19
  import {Button} from '@parca/components';
20
20
  import {ProfileType} from '@parca/parser';
21
+ import {testId} from '@parca/test-utils';
21
22
 
22
23
  import {CurrentPathFrame} from '../../../ProfileFlameGraph/FlameGraphArrow/utils';
23
24
  import {ProfileSource} from '../../../ProfileSource';
@@ -87,6 +88,7 @@ export const FlameGraphToolbar: FC<FlameGraphToolbarProps> = ({curPath, setNewCu
87
88
  onClick={() => setNewCurPath([])}
88
89
  disabled={curPath.length === 0}
89
90
  id="h-reset-graph"
91
+ {...testId('FLAMEGRAPH_RESET_BUTTON')}
90
92
  >
91
93
  Reset graph
92
94
  <Icon icon="system-uicons:reset" width={20} />
@@ -20,6 +20,7 @@ import cx from 'classnames';
20
20
  import {QueryServiceClient} from '@parca/client';
21
21
  import {useGrpcMetadata} from '@parca/components';
22
22
  import {Query} from '@parca/parser';
23
+ import {testId} from '@parca/test-utils';
23
24
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
24
25
 
25
26
  import {LabelProvider, useLabels} from '../contexts/SimpleMatchersLabelContext';
@@ -356,9 +357,10 @@ const SimpleMatchers = ({
356
357
  <div
357
358
  className={`flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`}
358
359
  id="simple-matchers"
360
+ {...testId('SIMPLE_MATCHERS_CONTAINER')}
359
361
  >
360
362
  {visibleRows.map((row, index) => (
361
- <div key={index} className="flex items-center">
363
+ <div key={index} className="flex items-center" {...testId('SIMPLE_MATCHER_ROW')}>
362
364
  <Select
363
365
  items={labelNameOptions}
364
366
  onSelection={value => handleUpdateRow(index, 'labelName', value)}
@@ -367,12 +369,14 @@ const SimpleMatchers = ({
367
369
  className="rounded-tr-none rounded-br-none ring-0 focus:ring-0 outline-none"
368
370
  loading={labelNamesLoading}
369
371
  searchable={true}
372
+ {...testId('LABEL_NAME_SELECT')}
370
373
  />
371
374
  <Select
372
375
  items={operatorOptions}
373
376
  onSelection={value => handleUpdateRow(index, 'operator', value)}
374
377
  selectedKey={row.operator}
375
378
  className="rounded-none ring-0 focus:ring-0 outline-none"
379
+ {...testId('OPERATOR_SELECT')}
376
380
  />
377
381
  <Select
378
382
  items={transformLabelsForSelect(row.labelValues)}
@@ -388,12 +392,14 @@ const SimpleMatchers = ({
388
392
  loading={row.isLoading}
389
393
  onButtonClick={() => handleLabelValueClick(index)}
390
394
  editable={isRowRegex(row)}
395
+ {...testId('LABEL_VALUE_SELECT')}
391
396
  />
392
397
  <button
393
398
  onClick={() => removeRow(index)}
394
399
  className={cx(
395
400
  'p-2 border-gray-200 border rounded rounded-tl-none rounded-bl-none focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900'
396
401
  )}
402
+ {...testId('REMOVE_MATCHER_BUTTON')}
397
403
  >
398
404
  <Icon icon="carbon:close" className="h-5 w-5 text-gray-400" aria-hidden="true" />
399
405
  </button>
@@ -404,6 +410,7 @@ const SimpleMatchers = ({
404
410
  <button
405
411
  onClick={() => setShowAll(!showAll)}
406
412
  className="mr-2 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900"
413
+ {...testId(showAll ? 'SHOW_LESS_BUTTON' : 'SHOW_MORE_BUTTON')}
407
414
  >
408
415
  {showAll ? 'Show less' : `Show ${hiddenRowsCount} more`}
409
416
  </button>
@@ -412,6 +419,7 @@ const SimpleMatchers = ({
412
419
  <button
413
420
  onClick={addNewRow}
414
421
  className="p-2 border-gray-200 dark:bg-gray-900 dark:border-gray-600 border rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
422
+ {...testId('ADD_MATCHER_BUTTON')}
415
423
  >
416
424
  <Icon icon="material-symbols:add" className="h-5 w-5 text-gray-400" aria-hidden="true" />
417
425
  </button>
@@ -19,6 +19,7 @@ import cx from 'classnames';
19
19
  import {QueryServiceClient} from '@parca/client';
20
20
  import {useGrpcMetadata} from '@parca/components';
21
21
  import {Query} from '@parca/parser';
22
+ import {testId} from '@parca/test-utils';
22
23
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
23
24
 
24
25
  import CustomSelect, {SelectItem} from '../SimpleMatchers/Select';
@@ -168,7 +169,7 @@ const ViewMatchers: React.FC<Props> = ({
168
169
  }, []);
169
170
 
170
171
  return (
171
- <div className="flex flex-wrap gap-2">
172
+ <div className="flex flex-wrap gap-2" {...testId('VIEW_MATCHERS_CONTAINER')}>
172
173
  {labelNames.map(labelName => (
173
174
  <div key={labelName} className="flex items-center">
174
175
  <div className="relative border shadow-sm px-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm flex gap-2 items-center justify-between bg-gray-100 dark:bg-gray-700 rounded-l-md border-gray-300 dark:border-gray-600">
package/src/index.tsx CHANGED
@@ -26,6 +26,9 @@ export * from './ProfileTypeSelector';
26
26
  export * from './SourceView';
27
27
  export * from './ProfileMetricsGraph';
28
28
 
29
+ export {default as ProfileFilters} from './ProfileView/components/ProfileFilters';
30
+ export {useProfileFiltersUrlState} from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
31
+
29
32
  export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
30
33
  dashboard_items: 'flamegraph',
31
34
  };