@parca/profile 0.19.45 → 0.19.46
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 +4 -0
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +5 -1
- package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/index.js +6 -5
- package/package.json +4 -4
- package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +15 -3
- package/src/ProfileView/components/ProfileFilters/index.tsx +23 -3
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.46](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.45...@parca/profile@0.19.46) (2025-08-28)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
6
10
|
## [0.19.45](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.44...@parca/profile@0.19.45) (2025-08-28)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/GroupByLabelsDropdown/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/GroupByLabelsDropdown/index.tsx"],"names":[],"mappings":"AAwBA,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC9C;AAED,QAAA,MAAM,qBAAqB,GAAI,uCAAqC,KAAK,KAAG,GAAG,CAAC,OAoF/E,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -12,9 +12,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
import Select from 'react-select';
|
|
15
|
+
import { testId } from '@parca/test-utils';
|
|
15
16
|
import { FIELD_LABELS } from '../../../ProfileFlameGraph/FlameGraphArrow';
|
|
16
17
|
const GroupByLabelsDropdown = ({ labels, groupBy, setGroupByLabels }) => {
|
|
17
|
-
return (_jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("label", { className: "text-sm", children: "Group by" }) }), _jsx(Select, {
|
|
18
|
+
return (_jsxs("div", { className: "flex flex-col", ...testId('GROUP_BY_CONTAINER'), children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("label", { className: "text-sm", ...testId('GROUP_BY_LABEL'), children: "Group by" }) }), _jsx(Select, { isMulti: true, defaultMenuIsOpen: false, defaultValue: undefined, name: "labels", options: labels.map(label => ({ label, value: `${FIELD_LABELS}.${label}` })), className: "parca-select-container text-sm rounded-md bg-white", classNamePrefix: "parca-select", menuPortalTarget: document.body, components: {
|
|
19
|
+
// eslint-disable-next-line react/prop-types
|
|
20
|
+
MenuList: ({ children, innerProps }) => (_jsx("div", { ...testId('GROUP_BY_SELECT_FLYOUT'), ...innerProps, children: children })),
|
|
21
|
+
}, value: groupBy
|
|
18
22
|
.filter(l => l.startsWith(FIELD_LABELS))
|
|
19
23
|
.map(l => ({ value: l, label: l.slice(FIELD_LABELS.length + 1) })), onChange: newValue => {
|
|
20
24
|
setGroupByLabels(newValue.map(option => option.value));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/index.tsx"],"names":[],"mappings":"AAuBA,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,OAyN1E,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -15,6 +15,7 @@ import { useCallback } from 'react';
|
|
|
15
15
|
import { Icon } from '@iconify/react';
|
|
16
16
|
import cx from 'classnames';
|
|
17
17
|
import { Button, Input, Select } from '@parca/components';
|
|
18
|
+
import { testId } from '@parca/test-utils';
|
|
18
19
|
import { useProfileViewContext } from '../../context/ProfileViewContext';
|
|
19
20
|
import { getPresetByKey, getPresetsForProfileType, isPresetKey } from './filterPresets';
|
|
20
21
|
import { useProfileFilters } from './useProfileFilters';
|
|
@@ -154,11 +155,11 @@ const ProfileFilters = ({ readOnly = false } = {}) => {
|
|
|
154
155
|
}
|
|
155
156
|
}, [onApplyFilters]);
|
|
156
157
|
const filtersToRender = localFilters.length > 0 ? localFilters : appliedFilters ?? [];
|
|
157
|
-
return (_jsxs("div", { className: "flex gap-2 w-full items-start", children: [_jsxs("div", { className: "flex-1 flex flex-wrap gap-2", children: [filtersToRender.map(filter => {
|
|
158
|
+
return (_jsxs("div", { className: "flex gap-2 w-full items-start", ...testId('PROFILE_FILTERS_CONTAINER'), children: [_jsxs("div", { className: "flex-1 flex flex-wrap gap-2", children: [filtersToRender.map(filter => {
|
|
158
159
|
const isNumberField = filter.field === 'address' || filter.field === 'line_number';
|
|
159
160
|
const matchTypeItems = isNumberField ? numberMatchTypeItems : stringMatchTypeItems;
|
|
160
161
|
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", disabled: readOnly, onSelection: key => {
|
|
162
|
+
return (_jsxs("div", { className: "flex items-center gap-0", children: [_jsx(Select, { items: filterTypeItems, selectedKey: filter.type, placeholder: "Select Filter", disabled: readOnly, ...testId('FILTER_TYPE_SELECT'), flyoutTestId: "filter-type-select-flyout", onSelection: key => {
|
|
162
163
|
// Check if this is a preset selection
|
|
163
164
|
if (isPresetKey(key)) {
|
|
164
165
|
const preset = getPresetByKey(key);
|
|
@@ -190,7 +191,7 @@ const ProfileFilters = ({ readOnly = false } = {}) => {
|
|
|
190
191
|
});
|
|
191
192
|
}
|
|
192
193
|
}
|
|
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
|
+
}, 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, ...testId('FILTER_FIELD_SELECT'), flyoutTestId: "filter-field-select-flyout", onSelection: key => {
|
|
194
195
|
const newField = key;
|
|
195
196
|
const isNewFieldNumber = newField === 'address' || newField === 'line_number';
|
|
196
197
|
const isCurrentFieldNumber = filter.field === 'address' || filter.field === 'line_number';
|
|
@@ -203,7 +204,7 @@ const ProfileFilters = ({ readOnly = false } = {}) => {
|
|
|
203
204
|
else {
|
|
204
205
|
updateFilter(filter.id, { field: newField });
|
|
205
206
|
}
|
|
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
|
+
}, 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, ...testId('FILTER_MATCH_TYPE_SELECT'), flyoutTestId: "filter-match-type-select-flyout", 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", ...testId('FILTER_VALUE_INPUT') })] })), !readOnly && (_jsx(Button, { variant: "neutral", ...testId('FILTER_REMOVE_BUTTON'), onClick: () => {
|
|
207
208
|
// If we're displaying local filters and this is the last one, reset everything
|
|
208
209
|
if (localFilters.length > 0 && localFilters.length === 1) {
|
|
209
210
|
resetFilters();
|
|
@@ -219,6 +220,6 @@ const ProfileFilters = ({ readOnly = false } = {}) => {
|
|
|
219
220
|
}, className: cx('h-[38px] p-3', filter.type != null
|
|
220
221
|
? 'rounded-none rounded-r-md'
|
|
221
222
|
: '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" }) }))] }));
|
|
223
|
+
}), !readOnly && localFilters.length > 0 && (_jsx(Button, { variant: "neutral", onClick: addFilter, className: "p-3 h-[38px]", ...testId('ADD_FILTER_BUTTON'), 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", ...testId('ADD_FILTER_BUTTON'), 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'), ...testId('APPLY_FILTERS_BUTTON'), children: _jsx("span", { children: "Apply" }) }))] }));
|
|
223
224
|
};
|
|
224
225
|
export default ProfileFilters;
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.46",
|
|
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.
|
|
10
|
+
"@parca/components": "0.16.366",
|
|
11
11
|
"@parca/dynamicsize": "0.16.65",
|
|
12
12
|
"@parca/hooks": "0.0.100",
|
|
13
13
|
"@parca/icons": "0.16.72",
|
|
14
14
|
"@parca/parser": "0.16.79",
|
|
15
15
|
"@parca/store": "0.16.184",
|
|
16
|
-
"@parca/test-utils": "0.0.
|
|
16
|
+
"@parca/test-utils": "0.0.8",
|
|
17
17
|
"@parca/utilities": "0.0.107",
|
|
18
18
|
"@popperjs/core": "^2.11.8",
|
|
19
19
|
"@protobuf-ts/runtime-rpc": "^2.5.0",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"access": "public",
|
|
80
80
|
"registry": "https://registry.npmjs.org/"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "bc38b540d40201fc382ace82bc0803bf84034bfc"
|
|
83
83
|
}
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import Select from 'react-select';
|
|
15
15
|
|
|
16
|
+
import {testId} from '@parca/test-utils';
|
|
17
|
+
|
|
16
18
|
import {FIELD_LABELS} from '../../../ProfileFlameGraph/FlameGraphArrow';
|
|
17
19
|
|
|
18
20
|
interface LabelOption {
|
|
@@ -28,13 +30,14 @@ interface Props {
|
|
|
28
30
|
|
|
29
31
|
const GroupByLabelsDropdown = ({labels, groupBy, setGroupByLabels}: Props): JSX.Element => {
|
|
30
32
|
return (
|
|
31
|
-
<div className="flex flex-col">
|
|
33
|
+
<div className="flex flex-col" {...testId('GROUP_BY_CONTAINER')}>
|
|
32
34
|
<div className="flex items-center justify-between">
|
|
33
|
-
<label className="text-sm"
|
|
35
|
+
<label className="text-sm" {...testId('GROUP_BY_LABEL')}>
|
|
36
|
+
Group by
|
|
37
|
+
</label>
|
|
34
38
|
</div>
|
|
35
39
|
|
|
36
40
|
<Select<LabelOption, true>
|
|
37
|
-
id="h-group-by-labels-selector"
|
|
38
41
|
isMulti
|
|
39
42
|
defaultMenuIsOpen={false}
|
|
40
43
|
defaultValue={undefined}
|
|
@@ -42,6 +45,15 @@ const GroupByLabelsDropdown = ({labels, groupBy, setGroupByLabels}: Props): JSX.
|
|
|
42
45
|
options={labels.map(label => ({label, value: `${FIELD_LABELS}.${label}`}))}
|
|
43
46
|
className="parca-select-container text-sm rounded-md bg-white"
|
|
44
47
|
classNamePrefix="parca-select"
|
|
48
|
+
menuPortalTarget={document.body}
|
|
49
|
+
components={{
|
|
50
|
+
// eslint-disable-next-line react/prop-types
|
|
51
|
+
MenuList: ({children, innerProps}) => (
|
|
52
|
+
<div {...testId('GROUP_BY_SELECT_FLYOUT')} {...innerProps}>
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
}}
|
|
45
57
|
value={groupBy
|
|
46
58
|
.filter(l => l.startsWith(FIELD_LABELS))
|
|
47
59
|
.map(l => ({value: l, label: l.slice(FIELD_LABELS.length + 1)}))}
|
|
@@ -17,6 +17,7 @@ import {Icon} from '@iconify/react';
|
|
|
17
17
|
import cx from 'classnames';
|
|
18
18
|
|
|
19
19
|
import {Button, Input, Select, type SelectItem} from '@parca/components';
|
|
20
|
+
import {testId} from '@parca/test-utils';
|
|
20
21
|
|
|
21
22
|
import {useProfileViewContext} from '../../context/ProfileViewContext';
|
|
22
23
|
import {getPresetByKey, getPresetsForProfileType, isPresetKey} from './filterPresets';
|
|
@@ -204,7 +205,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
204
205
|
const filtersToRender = localFilters.length > 0 ? localFilters : appliedFilters ?? [];
|
|
205
206
|
|
|
206
207
|
return (
|
|
207
|
-
<div className="flex gap-2 w-full items-start">
|
|
208
|
+
<div className="flex gap-2 w-full items-start" {...testId('PROFILE_FILTERS_CONTAINER')}>
|
|
208
209
|
<div className="flex-1 flex flex-wrap gap-2">
|
|
209
210
|
{filtersToRender.map(filter => {
|
|
210
211
|
const isNumberField = filter.field === 'address' || filter.field === 'line_number';
|
|
@@ -218,6 +219,8 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
218
219
|
selectedKey={filter.type}
|
|
219
220
|
placeholder="Select Filter"
|
|
220
221
|
disabled={readOnly}
|
|
222
|
+
{...testId('FILTER_TYPE_SELECT')}
|
|
223
|
+
flyoutTestId="filter-type-select-flyout"
|
|
221
224
|
onSelection={key => {
|
|
222
225
|
// Check if this is a preset selection
|
|
223
226
|
if (isPresetKey(key)) {
|
|
@@ -266,6 +269,8 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
266
269
|
items={fieldItems}
|
|
267
270
|
selectedKey={filter.field ?? ''}
|
|
268
271
|
disabled={readOnly}
|
|
272
|
+
{...testId('FILTER_FIELD_SELECT')}
|
|
273
|
+
flyoutTestId="filter-field-select-flyout"
|
|
269
274
|
onSelection={key => {
|
|
270
275
|
const newField = key as ProfileFilter['field'];
|
|
271
276
|
const isNewFieldNumber = newField === 'address' || newField === 'line_number';
|
|
@@ -292,6 +297,8 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
292
297
|
items={matchTypeItems}
|
|
293
298
|
selectedKey={filter.matchType ?? ''}
|
|
294
299
|
disabled={readOnly}
|
|
300
|
+
{...testId('FILTER_MATCH_TYPE_SELECT')}
|
|
301
|
+
flyoutTestId="filter-match-type-select-flyout"
|
|
295
302
|
onSelection={key =>
|
|
296
303
|
updateFilter(filter.id, {matchType: key as ProfileFilter['matchType']})
|
|
297
304
|
}
|
|
@@ -309,6 +316,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
309
316
|
onChange={e => updateFilter(filter.id, {value: e.target.value})}
|
|
310
317
|
onKeyDown={handleKeyDown}
|
|
311
318
|
className="rounded-none w-36 text-sm focus:outline-1"
|
|
319
|
+
{...testId('FILTER_VALUE_INPUT')}
|
|
312
320
|
/>
|
|
313
321
|
</>
|
|
314
322
|
)}
|
|
@@ -316,6 +324,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
316
324
|
{!readOnly && (
|
|
317
325
|
<Button
|
|
318
326
|
variant="neutral"
|
|
327
|
+
{...testId('FILTER_REMOVE_BUTTON')}
|
|
319
328
|
onClick={() => {
|
|
320
329
|
// If we're displaying local filters and this is the last one, reset everything
|
|
321
330
|
if (localFilters.length > 0 && localFilters.length === 1) {
|
|
@@ -345,13 +354,23 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
345
354
|
})}
|
|
346
355
|
|
|
347
356
|
{!readOnly && localFilters.length > 0 && (
|
|
348
|
-
<Button
|
|
357
|
+
<Button
|
|
358
|
+
variant="neutral"
|
|
359
|
+
onClick={addFilter}
|
|
360
|
+
className="p-3 h-[38px]"
|
|
361
|
+
{...testId('ADD_FILTER_BUTTON')}
|
|
362
|
+
>
|
|
349
363
|
<Icon icon="mdi:filter-plus-outline" className="h-4 w-4" />
|
|
350
364
|
</Button>
|
|
351
365
|
)}
|
|
352
366
|
|
|
353
367
|
{!readOnly && localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (
|
|
354
|
-
<Button
|
|
368
|
+
<Button
|
|
369
|
+
variant="neutral"
|
|
370
|
+
onClick={addFilter}
|
|
371
|
+
className="flex items-center gap-2"
|
|
372
|
+
{...testId('ADD_FILTER_BUTTON')}
|
|
373
|
+
>
|
|
355
374
|
<Icon icon="mdi:filter-outline" className="h-4 w-4" />
|
|
356
375
|
<span>Filter</span>
|
|
357
376
|
</Button>
|
|
@@ -364,6 +383,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
|
|
|
364
383
|
onClick={onApplyFilters}
|
|
365
384
|
disabled={!hasUnsavedChanges || !localFilters.some(isFilterComplete)}
|
|
366
385
|
className={cx('flex items-center gap-2 sticky top-0')}
|
|
386
|
+
{...testId('APPLY_FILTERS_BUTTON')}
|
|
367
387
|
>
|
|
368
388
|
<span>Apply</span>
|
|
369
389
|
</Button>
|