This is an automated email from the ASF dual-hosted git repository.
diegopucci pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new be1b8d6751 feat(Native Filters): Exclude Filter Values (#33054)
be1b8d6751 is described below
commit be1b8d6751c72b71e44b015a2f3c1dd739411243
Author: amaannawab923 <[email protected]>
AuthorDate: Thu Apr 17 20:26:26 2025 +0530
feat(Native Filters): Exclude Filter Values (#33054)
Co-authored-by: Amaan Nawab <[email protected]>
---
.../src/components/DropdownContainer/index.tsx | 36 +++++
superset-frontend/src/components/Select/Select.tsx | 4 +-
superset-frontend/src/components/Select/types.ts | 4 +
.../FilterBar/FilterControls/FilterControl.tsx | 47 +++++--
.../components/Select/SelectFilterPlugin.test.tsx | 122 +++++++++++++++--
.../components/Select/SelectFilterPlugin.tsx | 147 +++++++++++++++------
superset-frontend/src/filters/utils.ts | 4 +-
7 files changed, 305 insertions(+), 59 deletions(-)
diff --git a/superset-frontend/src/components/DropdownContainer/index.tsx
b/superset-frontend/src/components/DropdownContainer/index.tsx
index 0735ba289e..908e79ed8d 100644
--- a/superset-frontend/src/components/DropdownContainer/index.tsx
+++ b/superset-frontend/src/components/DropdownContainer/index.tsx
@@ -179,6 +179,42 @@ const DropdownContainer = forwardRef(
[items, overflowingIndex],
);
+ useEffect(() => {
+ const container = current?.children.item(0);
+ if (!container) return;
+
+ const childrenArray = Array.from(container.children);
+
+ const resizeObserver = new ResizeObserver(() => {
+ recalculateItemWidths();
+ });
+
+ childrenArray.map(child => resizeObserver.observe(child));
+
+ // eslint-disable-next-line consistent-return
+ return () => {
+ childrenArray.map(child => resizeObserver.unobserve(child));
+ resizeObserver.disconnect();
+ };
+ }, [items.length]);
+
+ // callback to update item widths so that the useLayoutEffect runs whenever
+ // width of any of the child changes
+ const recalculateItemWidths = () => {
+ const container = current?.children.item(0);
+ if (container) {
+ const { children } = container;
+ const childrenArray = Array.from(children);
+
+ const currentWidths = childrenArray.map(
+ child => child.getBoundingClientRect().width,
+ );
+
+ // Update state with new widths
+ setItemsWidth(currentWidths);
+ }
+ };
+
useLayoutEffect(() => {
if (popoverVisible) {
return;
diff --git a/superset-frontend/src/components/Select/Select.tsx
b/superset-frontend/src/components/Select/Select.tsx
index 0a3e04f88d..273eb2fa3a 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -89,6 +89,7 @@ import { customTagRender } from './CustomTag';
const Select = forwardRef(
(
{
+ className,
allowClear,
allowNewOptions = false,
allowSelectAll = true,
@@ -121,6 +122,7 @@ const Select = forwardRef(
getPopupContainer,
oneLine,
maxTagCount: propsMaxTagCount,
+
...props
}: SelectProps,
ref: RefObject<HTMLInputElement>,
@@ -604,7 +606,7 @@ const Select = forwardRef(
};
return (
- <StyledContainer headerPosition={headerPosition}>
+ <StyledContainer className={className} headerPosition={headerPosition}>
{header && (
<StyledHeader headerPosition={headerPosition}>{header}</StyledHeader>
)}
diff --git a/superset-frontend/src/components/Select/types.ts
b/superset-frontend/src/components/Select/types.ts
index 85d380bdfa..404253ef15 100644
--- a/superset-frontend/src/components/Select/types.ts
+++ b/superset-frontend/src/components/Select/types.ts
@@ -72,6 +72,10 @@ export type AntdExposedProps = Pick<
export type SelectOptionsType = Exclude<AntdProps['options'], undefined>;
export interface BaseSelectProps extends AntdExposedProps {
+ /**
+ * Optional CSS class name to apply to the select container
+ */
+ className?: string;
/**
* It enables the user to create new options.
* Can be used with standard or async select types.
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
index 08bc03fb8b..76a162e7c5 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
@@ -109,7 +109,9 @@ const HorizontalOverflowFilterControlContainer = styled(
}
`;
-const VerticalFormItem = styled(StyledFormItem)`
+const VerticalFormItem = styled(StyledFormItem)<{
+ inverseSelection: boolean;
+}>`
.ant-form-item-label {
overflow: visible;
label.ant-form-item-required:not(.ant-form-item-required-mark-optional) {
@@ -118,9 +120,19 @@ const VerticalFormItem = styled(StyledFormItem)`
}
}
}
+
+ .select-container {
+ ${({ inverseSelection }) =>
+ inverseSelection &&
+ `
+ width: 140px;
+ `}
+ }
`;
-const HorizontalFormItem = styled(StyledFormItem)`
+const HorizontalFormItem = styled(StyledFormItem)<{
+ inverseSelection: boolean;
+}>`
&& {
margin-bottom: 0;
align-items: center;
@@ -142,7 +154,15 @@ const HorizontalFormItem = styled(StyledFormItem)`
}
.ant-form-item-control {
- width: ${({ theme }) => theme.gridUnit * 41}px;
+ width: ${({ inverseSelection }) => (inverseSelection ? 252 : 164)}px;
+ }
+
+ .select-container {
+ ${({ inverseSelection, theme }) =>
+ inverseSelection &&
+ `
+ width: 164px;
+ `}
}
`;
@@ -151,31 +171,41 @@ const HorizontalOverflowFormItem = VerticalFormItem;
const useFilterControlDisplay = (
orientation: FilterBarOrientation,
overflow: boolean,
+ inverseSelection: boolean,
) =>
useMemo(() => {
if (orientation === FilterBarOrientation.Horizontal) {
if (overflow) {
return {
FilterControlContainer: HorizontalOverflowFilterControlContainer,
- FormItem: HorizontalOverflowFormItem,
+ FormItem: (props: any) => (
+ <HorizontalOverflowFormItem
+ {...props}
+ inverseSelection={inverseSelection}
+ />
+ ),
FilterControlTitleBox: HorizontalOverflowFilterControlTitleBox,
FilterControlTitle: HorizontalOverflowFilterControlTitle,
};
}
return {
FilterControlContainer: HorizontalFilterControlContainer,
- FormItem: HorizontalFormItem,
+ FormItem: (props: any) => (
+ <HorizontalFormItem {...props} inverseSelection={inverseSelection} />
+ ),
FilterControlTitleBox: HorizontalFilterControlTitleBox,
FilterControlTitle: HorizontalFilterControlTitle,
};
}
return {
FilterControlContainer: VerticalFilterControlContainer,
- FormItem: VerticalFormItem,
+ FormItem: (props: any) => (
+ <VerticalFormItem {...props} inverseSelection={inverseSelection} />
+ ),
FilterControlTitleBox: VerticalFilterControlTitleBox,
FilterControlTitle: VerticalFilterControlTitle,
};
- }, [orientation, overflow]);
+ }, [orientation, overflow, inverseSelection]);
const ToolTipContainer = styled.div`
font-size: ${({ theme }) => theme.typography.sizes.m}px;
@@ -243,13 +273,14 @@ const FilterControl = ({
checkIsMissingRequiredValue(filter, filter.dataMask?.filterState);
const validateStatus = isMissingRequiredValue ? 'error' : undefined;
const isRequired = !!filter.controlValues?.enableEmptyFilter;
+ const inverseSelection = !!filter.controlValues?.inverseSelection;
const {
FilterControlContainer,
FormItem,
FilterControlTitleBox,
FilterControlTitle,
- } = useFilterControlDisplay(orientation, overflow);
+ } = useFilterControlDisplay(orientation, overflow, inverseSelection);
const label = useMemo(
() => (
diff --git
a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx
b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx
index ec6b33d95b..cb1f2de7cc 100644
---
a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx
+++
b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx
@@ -47,8 +47,10 @@ const selectMultipleProps = {
urlParams: {},
vizType: 'filter_select',
inputRef: { current: null },
+ nativeFilterId: 'test-filter',
},
height: 20,
+ width: 220,
hooks: {},
ownState: {},
filterState: { value: ['boy'] },
@@ -62,7 +64,6 @@ const selectMultipleProps = {
rejected_filters: [],
},
],
- width: 220,
behaviors: ['NATIVE_FILTER'],
isRefreshing: false,
appSection: AppSection.Dashboard,
@@ -80,7 +81,38 @@ describe('SelectFilterPlugin', () => {
formData: { ...selectMultipleProps.formData, ...props },
})}
setDataMask={setDataMask}
+ showOverflow={false}
/>,
+ {
+ useRedux: true,
+ initialState: {
+ nativeFilters: {
+ filters: {
+ 'test-filter': {
+ name: 'Test Filter',
+ },
+ },
+ },
+ dataMask: {
+ 'test-filter': {
+ extraFormData: {
+ filters: [
+ {
+ col: 'gender',
+ op: 'IN',
+ val: ['boy'],
+ },
+ ],
+ },
+ filterState: {
+ value: ['boy'],
+ label: 'boy',
+ excludeFilterValues: true,
+ },
+ },
+ },
+ },
+ },
);
beforeEach(() => {
@@ -102,9 +134,12 @@ describe('SelectFilterPlugin', () => {
filterState: {
label: 'boy',
value: ['boy'],
+ excludeFilterValues: true,
},
});
- userEvent.click(screen.getByRole('combobox'));
+
+ const filterSelect = screen.getAllByRole('combobox')[0];
+ userEvent.click(filterSelect);
userEvent.click(screen.getByTitle('girl'));
expect(await screen.findByTitle(/girl/i)).toBeInTheDocument();
expect(setDataMask).toHaveBeenCalledWith({
@@ -120,6 +155,7 @@ describe('SelectFilterPlugin', () => {
filterState: {
label: 'boy, girl',
value: ['boy', 'girl'],
+ excludeFilterValues: true,
},
});
});
@@ -145,6 +181,7 @@ describe('SelectFilterPlugin', () => {
filterState: {
label: undefined,
value: null,
+ excludeFilterValues: true,
},
});
});
@@ -162,13 +199,18 @@ describe('SelectFilterPlugin', () => {
filterState: {
label: undefined,
value: null,
+ excludeFilterValues: true,
},
});
});
test('Select single values with inverse', async () => {
getWrapper({ multiSelect: false, inverseSelection: true });
- userEvent.click(screen.getByRole('combobox'));
+
+ // Get the main filter select (second combobox)
+ const filterSelect = screen.getAllByRole('combobox')[1];
+ userEvent.click(filterSelect);
+
expect(await screen.findByTitle('girl')).toBeInTheDocument();
userEvent.click(screen.getByTitle('girl'));
expect(setDataMask).toHaveBeenCalledWith({
@@ -184,13 +226,15 @@ describe('SelectFilterPlugin', () => {
filterState: {
label: 'girl (excluded)',
value: ['girl'],
+ excludeFilterValues: true,
},
});
});
test('Select single null (empty) value', async () => {
getWrapper();
- userEvent.click(screen.getByRole('combobox'));
+ const filterSelect = screen.getAllByRole('combobox')[0];
+ userEvent.click(filterSelect);
expect(await screen.findByRole('combobox')).toBeInTheDocument();
userEvent.click(screen.getByTitle(NULL_STRING));
expect(setDataMask).toHaveBeenLastCalledWith({
@@ -206,13 +250,15 @@ describe('SelectFilterPlugin', () => {
filterState: {
label: `boy, ${NULL_STRING}`,
value: ['boy', null],
+ excludeFilterValues: true,
},
});
});
test('receives the correct filter when search all options', async () => {
getWrapper({ searchAllOptions: true, multiSelect: false });
- userEvent.click(screen.getByRole('combobox'));
+ const filterSelect = screen.getAllByRole('combobox')[0];
+ userEvent.click(filterSelect);
expect(await screen.findByRole('combobox')).toBeInTheDocument();
userEvent.click(screen.getByTitle('girl'));
expect(setDataMask).toHaveBeenLastCalledWith(
@@ -229,14 +275,14 @@ describe('SelectFilterPlugin', () => {
}),
);
});
+
test('number of fired queries when searching', async () => {
getWrapper({ searchAllOptions: true });
- userEvent.click(screen.getByRole('combobox'));
+ const filterSelect = screen.getAllByRole('combobox')[0];
+ userEvent.click(filterSelect);
expect(await screen.findByRole('combobox')).toBeInTheDocument();
await userEvent.type(screen.getByRole('combobox'), 'a');
- // Closes the select
userEvent.tab();
- // One call for the search term and other for the empty search
expect(setDataMask).toHaveBeenCalledTimes(2);
});
@@ -253,21 +299,77 @@ describe('SelectFilterPlugin', () => {
coltypeMap={{ bval: 1 }}
data={[{ bval: bigValue }]}
setDataMask={jest.fn()}
+ showOverflow={false}
/>,
+ {
+ useRedux: true,
+ initialState: {
+ nativeFilters: {
+ filters: {
+ 'test-filter': {
+ name: 'Test Filter',
+ },
+ },
+ },
+ dataMask: {
+ 'test-filter': {
+ extraFormData: {},
+ filterState: {
+ value: [],
+ label: '',
+ excludeFilterValues: true,
+ },
+ },
+ },
+ },
+ },
);
- userEvent.click(screen.getByRole('combobox'));
+ const filterSelect = screen.getAllByRole('combobox')[0];
+ userEvent.click(filterSelect);
expect(await screen.findByRole('combobox')).toBeInTheDocument();
await userEvent.type(screen.getByRole('combobox'), '1');
expect(screen.queryByLabelText(String(bigValue))).toBeInTheDocument();
});
+ test('Is/Is Not select is visible when inverseSelection is true', () => {
+ getWrapper({ inverseSelection: true });
+ expect(screen.getByText('is not')).toBeInTheDocument();
+ });
+
+ test('Is/Is Not select is not visible when inverseSelection is false', () =>
{
+ getWrapper({ inverseSelection: false });
+ expect(screen.queryByText('is not')).not.toBeInTheDocument();
+ });
+
+ test('Is/Is Not select toggles correctly', async () => {
+ getWrapper({ inverseSelection: true });
+
+ const isNotSelect = screen.getByText('is not');
+ expect(isNotSelect).toBeInTheDocument();
+
+ // Click to open dropdown
+ userEvent.click(isNotSelect);
+
+ // Click "is" option
+ userEvent.click(screen.getByText('is'));
+
+ // Should update excludeFilterValues to false
+ expect(setDataMask).toHaveBeenCalledWith(
+ expect.objectContaining({
+ filterState: expect.objectContaining({
+ excludeFilterValues: false,
+ }),
+ }),
+ );
+ });
+
test('Should not allow for new values when creatable is false', () => {
getWrapper({ creatable: false });
userEvent.type(screen.getByRole('combobox'), 'new value');
expect(screen.queryByTitle('new value')).not.toBeInTheDocument();
});
- test('Should allow for new values when creatable is false', async () => {
+ test('Should allow for new values when creatable is true', async () => {
getWrapper({ creatable: true });
userEvent.type(screen.getByRole('combobox'), 'new value');
expect(await screen.findByTitle('new value')).toBeInTheDocument();
diff --git
a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
index ca8fbda8df..0efb93a89b 100644
--- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
@@ -29,25 +29,32 @@ import {
finestTemporalGrainFormatter,
t,
tn,
+ styled,
} from '@superset-ui/core';
// eslint-disable-next-line no-restricted-imports
import { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; // TODO:
Remove antd
-import { debounce } from 'lodash';
+import { debounce, isUndefined } from 'lodash';
import { useImmerReducer } from 'use-immer';
import { Select } from 'src/components';
+// eslint-disable-next-line no-restricted-imports
+import { Space } from 'antd'; // Import Space directly from antd
import { SLOW_DEBOUNCE } from 'src/constants';
import { hasOption, propertyComparator } from 'src/components/Select/utils';
import { FilterBarOrientation } from 'src/dashboard/types';
-import { PluginFilterSelectProps, SelectValue } from './types';
-import { FilterPluginStyle, StatusMessage, StyledFormItem } from '../common';
import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils';
+import { FilterPluginStyle, StatusMessage, StyledFormItem } from '../common';
+import { PluginFilterSelectProps, SelectValue } from './types';
type DataMaskAction =
| { type: 'ownState'; ownState: JsonObject }
| {
type: 'filterState';
extraFormData: ExtraFormData;
- filterState: { value: SelectValue; label?: string };
+ filterState: {
+ value: SelectValue;
+ label?: string;
+ excludeFilterValues?: boolean;
+ };
};
function reducer(draft: DataMask, action: DataMaskAction) {
@@ -77,6 +84,24 @@ function reducer(draft: DataMask, action: DataMaskAction) {
}
}
+const StyledSpace = styled(Space)<{ $inverseSelection: boolean }>`
+ display: flex;
+ align-items: center;
+ width: 100%;
+
+ .exclude-select {
+ width: 80px;
+ flex-shrink: 0;
+ }
+
+ &.ant-space {
+ .ant-space-item {
+ width: ${({ $inverseSelection }) =>
+ !$inverseSelection ? '100%' : 'auto'};
+ }
+ }
+`;
+
export default function PluginFilterSelect(props: PluginFilterSelectProps) {
const {
coltypeMap,
@@ -107,6 +132,7 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
defaultToFirstItem,
searchAllOptions,
} = formData;
+
const groupby = useMemo(
() => ensureIsArray(formData.groupby).map(getColumnLabel),
[formData.groupby],
@@ -126,6 +152,11 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
}),
[data, col],
);
+ const [excludeFilterValues, setExcludeFilterValues] = useState(
+ isUndefined(filterState?.excludeFilterValues)
+ ? true
+ : filterState?.excludeFilterValues,
+ );
const updateDataMask = useCallback(
(values: SelectValue) => {
@@ -139,7 +170,7 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
col,
values,
emptyFilter,
- inverseSelection,
+ excludeFilterValues && inverseSelection,
),
filterState: {
...filterState,
@@ -152,6 +183,7 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
appSection === AppSection.FilterConfigModal && defaultToFirstItem
? undefined
: values,
+ excludeFilterValues,
},
});
},
@@ -164,6 +196,7 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
dispatchDataMask,
enableEmptyFilter,
inverseSelection,
+ excludeFilterValues,
JSON.stringify(filterState),
labelFormatter,
],
@@ -278,6 +311,7 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
defaultToFirstItem,
enableEmptyFilter,
inverseSelection,
+ excludeFilterValues,
updateDataMask,
data,
groupby,
@@ -288,45 +322,82 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
setDataMask(dataMask);
}, [JSON.stringify(dataMask)]);
+ useEffect(() => {
+ dispatchDataMask({
+ type: 'filterState',
+ extraFormData: getSelectExtraFormData(
+ col,
+ filterState.value,
+ !filterState.value?.length,
+ excludeFilterValues && inverseSelection,
+ ),
+ filterState: {
+ ...(filterState as {
+ value: SelectValue;
+ label?: string;
+ excludeFilterValues?: boolean;
+ }),
+ excludeFilterValues,
+ },
+ });
+ }, [excludeFilterValues]);
+
+ const handleExclusionToggle = (value: string) => {
+ setExcludeFilterValues(value === 'true');
+ };
+
return (
<FilterPluginStyle height={height} width={width}>
<StyledFormItem
validateStatus={filterState.validateStatus}
extra={formItemExtra}
>
- <Select
- name={formData.nativeFilterId}
- allowClear
- allowNewOptions={!searchAllOptions && creatable !== false}
- allowSelectAll={!searchAllOptions}
- // @ts-ignore
- value={filterState.value || []}
- disabled={isDisabled}
- getPopupContainer={
- showOverflow
- ? () => (parentRef?.current as HTMLElement) || document.body
- : (trigger: HTMLElement) =>
- (trigger?.parentNode as HTMLElement) || document.body
- }
- showSearch={showSearch}
- mode={multiSelect ? 'multiple' : 'single'}
- placeholder={placeholderText}
- onClear={() => onSearch('')}
- onSearch={onSearch}
- onBlur={handleBlur}
- onFocus={setFocusedFilter}
- onMouseEnter={setHoveredFilter}
- onMouseLeave={unsetHoveredFilter}
- // @ts-ignore
- onChange={handleChange}
- ref={inputRef}
- loading={isRefreshing}
- oneLine={filterBarOrientation === FilterBarOrientation.Horizontal}
- invertSelection={inverseSelection}
- options={options}
- sortComparator={sortComparator}
- onDropdownVisibleChange={setFilterActive}
- />
+ <StyledSpace $inverseSelection={inverseSelection}>
+ {inverseSelection && (
+ <Select
+ className="exclude-select"
+ value={`${excludeFilterValues}`}
+ options={[
+ { value: 'true', label: t('is not') },
+ { value: 'false', label: t('is') },
+ ]}
+ onChange={handleExclusionToggle}
+ />
+ )}
+ <Select
+ name={formData.nativeFilterId}
+ allowClear
+ allowNewOptions={!searchAllOptions && creatable !== false}
+ allowSelectAll={!searchAllOptions}
+ value={filterState.value || []}
+ disabled={isDisabled}
+ getPopupContainer={
+ showOverflow
+ ? () => (parentRef?.current as HTMLElement) || document.body
+ : (trigger: HTMLElement) =>
+ (trigger?.parentNode as HTMLElement) || document.body
+ }
+ showSearch={showSearch}
+ mode={multiSelect ? 'multiple' : 'single'}
+ placeholder={placeholderText}
+ onClear={() => onSearch('')}
+ onSearch={onSearch}
+ onBlur={handleBlur}
+ onFocus={setFocusedFilter}
+ onMouseEnter={setHoveredFilter}
+ onMouseLeave={unsetHoveredFilter}
+ // @ts-ignore
+ onChange={handleChange}
+ ref={inputRef}
+ loading={isRefreshing}
+ oneLine={filterBarOrientation === FilterBarOrientation.Horizontal}
+ invertSelection={inverseSelection && excludeFilterValues}
+ options={options}
+ sortComparator={sortComparator}
+ onDropdownVisibleChange={setFilterActive}
+ className="select-container"
+ />
+ </StyledSpace>
</StyledFormItem>
</FilterPluginStyle>
);
diff --git a/superset-frontend/src/filters/utils.ts
b/superset-frontend/src/filters/utils.ts
index e08f5b93b7..24c0236a11 100644
--- a/superset-frontend/src/filters/utils.ts
+++ b/superset-frontend/src/filters/utils.ts
@@ -34,7 +34,7 @@ export const getSelectExtraFormData = (
col: string,
value?: null | (string | number | boolean | null)[],
emptyFilter = false,
- inverseSelection = false,
+ shouldExcludeFilter = false,
): ExtraFormData => {
const extra: ExtraFormData = {};
if (emptyFilter) {
@@ -49,7 +49,7 @@ export const getSelectExtraFormData = (
extra.filters = [
{
col,
- op: inverseSelection ? ('NOT IN' as const) : ('IN' as const),
+ op: shouldExcludeFilter ? ('NOT IN' as const) : ('IN' as const),
// @ts-ignore
val: value,
},