mirror of
https://github.com/apache/superset.git
synced 2026-06-24 08:59:20 +00:00
Compare commits
1 Commits
chore/ci-f
...
fix/filter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e7f7cd6ee |
@@ -1146,6 +1146,53 @@ test('pasting an non-existent option should not add it if allowNewOptions is fal
|
||||
expect(await findAllSelectOptions()).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Reference for the bug this tests: https://github.com/apache/superset/issues/32645
|
||||
// Dashboard filters with "Dynamically search all filter values" only load a
|
||||
// page of options client-side, so a pasted value outside that page used to be
|
||||
// silently dropped. allowNewOptionsOnPaste keeps such values so the filter can
|
||||
// still apply them.
|
||||
test('keeps pasted values outside loaded options when allowNewOptionsOnPaste is true', async () => {
|
||||
render(
|
||||
<Select
|
||||
{...defaultProps}
|
||||
mode="multiple"
|
||||
allowNewOptions={false}
|
||||
allowNewOptionsOnPaste
|
||||
/>,
|
||||
);
|
||||
const input = getElementByClassName('.ant-select-selection-search-input');
|
||||
const paste = createEvent.paste(input, {
|
||||
clipboardData: {
|
||||
// Liam is a loaded option; OutsideValue is not in the loaded page.
|
||||
getData: () => 'Liam,OutsideValue',
|
||||
},
|
||||
});
|
||||
fireEvent(input, paste);
|
||||
await waitFor(() => {
|
||||
const values = [
|
||||
...getElementsByClassName('.ant-select-selection-item'),
|
||||
].map(value => value.textContent);
|
||||
expect(values).toEqual(expect.arrayContaining(['Liam', 'OutsideValue']));
|
||||
});
|
||||
});
|
||||
|
||||
test('drops pasted values outside loaded options when allowNewOptionsOnPaste is false', async () => {
|
||||
render(<Select {...defaultProps} mode="multiple" allowNewOptions={false} />);
|
||||
const input = getElementByClassName('.ant-select-selection-search-input');
|
||||
const paste = createEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => 'Liam,OutsideValue',
|
||||
},
|
||||
});
|
||||
fireEvent(input, paste);
|
||||
await waitFor(() => {
|
||||
const values = [
|
||||
...getElementsByClassName('.ant-select-selection-item'),
|
||||
].map(value => value.textContent);
|
||||
expect(values).toEqual(['Liam']);
|
||||
});
|
||||
});
|
||||
|
||||
test('does not fire onChange if the same value is selected in single mode', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(<Select {...defaultProps} onChange={onChange} />);
|
||||
|
||||
@@ -91,6 +91,7 @@ const Select = forwardRef(
|
||||
className,
|
||||
allowClear,
|
||||
allowNewOptions = false,
|
||||
allowNewOptionsOnPaste = false,
|
||||
allowSelectAll = true,
|
||||
ariaLabel,
|
||||
autoClearSearchValue = false,
|
||||
@@ -519,7 +520,8 @@ const Select = forwardRef(
|
||||
handleSelectAll();
|
||||
}}
|
||||
>
|
||||
{t('Select all')} {`(${formatNumber('SMART_NUMBER', bulkSelectCounts.selectable)})`}
|
||||
{t('Select all')}{' '}
|
||||
{`(${formatNumber('SMART_NUMBER', bulkSelectCounts.selectable)})`}
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
@@ -536,7 +538,8 @@ const Select = forwardRef(
|
||||
handleDeselectAll();
|
||||
}}
|
||||
>
|
||||
{t('Clear')} {`(${formatNumber('SMART_NUMBER', bulkSelectCounts.deselectable)})`}
|
||||
{t('Clear')}{' '}
|
||||
{`(${formatNumber('SMART_NUMBER', bulkSelectCounts.deselectable)})`}
|
||||
</Button>
|
||||
</StyledBulkActionsContainer>
|
||||
),
|
||||
@@ -693,17 +696,24 @@ const Select = forwardRef(
|
||||
const array = token ? uniq(pastedText.split(token)) : [pastedText];
|
||||
|
||||
const newOptions: SelectOptionsType = [];
|
||||
// When `allowNewOptionsOnPaste` is set, accept pasted values that are
|
||||
// not in the loaded options even if `allowNewOptions` is false. The
|
||||
// full option set is searched server-side and only partially loaded
|
||||
// client-side, so a pasted value can legitimately exist in the dataset
|
||||
// but fall outside the loaded page.
|
||||
const keepUnknownValues = allowNewOptions || allowNewOptionsOnPaste;
|
||||
|
||||
const values = array
|
||||
.map(item => {
|
||||
const option = getOption(item, fullSelectOptions, true);
|
||||
if (!option && allowNewOptions) {
|
||||
if (!option && keepUnknownValues) {
|
||||
const newOption = {
|
||||
label: item,
|
||||
value: item,
|
||||
isNewOption: true,
|
||||
};
|
||||
newOptions.push(newOption);
|
||||
return labelInValue ? { label: item, value: item } : item;
|
||||
}
|
||||
return getPastedTextValue(item);
|
||||
})
|
||||
|
||||
@@ -88,6 +88,16 @@ export interface BaseSelectProps extends AntdExposedProps {
|
||||
* False by default.
|
||||
* */
|
||||
allowNewOptions?: boolean;
|
||||
/**
|
||||
* Accept values pasted into the Select even when they are not part of the
|
||||
* currently loaded options and `allowNewOptions` is false. Useful for
|
||||
* selects whose full option set is searched server-side and only partially
|
||||
* loaded on the client (e.g. dashboard filters with "Dynamically search all
|
||||
* filter values"), where a pasted value can legitimately exist in the
|
||||
* dataset but fall outside the loaded page.
|
||||
* False by default.
|
||||
* */
|
||||
allowNewOptionsOnPaste?: boolean;
|
||||
/**
|
||||
* It adds the aria-label tag for accessibility standards.
|
||||
* Must be plain English and localized.
|
||||
|
||||
@@ -592,6 +592,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
|
||||
allowClear
|
||||
autoClearSearchValue
|
||||
allowNewOptions={!searchAllOptions && creatable !== false}
|
||||
allowNewOptionsOnPaste={multiSelect && searchAllOptions}
|
||||
allowSelectAll={!searchAllOptions}
|
||||
value={multiSelect ? filterState.value || [] : filterState.value}
|
||||
disabled={isDisabled}
|
||||
|
||||
Reference in New Issue
Block a user