fix: Select refactoring known issues (#16666)

* Clean up and reorganize effects

* Enhance optionFilterProps

* Render custom label

* Remove prop filtering

* Create options

* Create option from value in single mode

* Change to customLabel

* Show search by default

* Update superset-frontend/src/components/Select/Select.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/components/Select/Select.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/components/Select/Select.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Apply minor changes

* Fixes a bug that was failing CI

* Adds more tests to the component

* Apply customLabel in ColorSchemeControl

* Remove customLabel from rendered Option

* Hide No data when allowNewOptions

* Remove unnecessary prop from tests

* Adjust loading height

* Show no data with fetchOnlyOnSearch

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
Co-authored-by: Michael S. Molina <michael.s.molina@gmail.com>
This commit is contained in:
Geido
2021-09-14 17:39:27 +03:00
committed by GitHub
parent 9e00e4e8cc
commit fecd4124fa
7 changed files with 309 additions and 163 deletions

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import React, { ReactNode } from 'react';
import rison from 'rison';
import { styled, t, SupersetClient, JsonResponse } from '@superset-ui/core';
import { Steps } from 'src/common/components';
@@ -193,7 +193,6 @@ export default class AddSliceContainer extends React.PureComponent<
this.gotoSlice = this.gotoSlice.bind(this);
this.newLabel = this.newLabel.bind(this);
this.loadDatasources = this.loadDatasources.bind(this);
this.handleFilterOption = this.handleFilterOption.bind(this);
}
exploreUrl() {
@@ -254,16 +253,17 @@ export default class AddSliceContainer extends React.PureComponent<
endpoint: `/api/v1/dataset/?q=${query}`,
}).then((response: JsonResponse) => {
const list: {
customLabel: ReactNode;
label: string;
value: string;
}[] = response.json.result
.map((item: Dataset) => ({
value: `${item.id}__${item.datasource_type}`,
label: this.newLabel(item),
labelText: item.table_name,
customLabel: this.newLabel(item),
label: item.table_name,
}))
.sort((a: { labelText: string }, b: { labelText: string }) =>
a.labelText.localeCompare(b.labelText),
.sort((a: { label: string }, b: { label: string }) =>
a.label.localeCompare(b.label),
);
return {
data: list,
@@ -272,15 +272,6 @@ export default class AddSliceContainer extends React.PureComponent<
});
}
handleFilterOption(
search: string,
option: { label: string; value: number; labelText: string },
) {
const searchValue = search.trim().toLowerCase();
const { labelText } = option;
return labelText.toLowerCase().includes(searchValue);
}
render() {
const isButtonDisabled = this.isBtnDisabled();
return (
@@ -296,7 +287,6 @@ export default class AddSliceContainer extends React.PureComponent<
autoFocus
ariaLabel={t('Dataset')}
name="select-datasource"
filterOption={this.handleFilterOption}
onChange={this.changeDatasource}
options={this.loadDatasources}
placeholder={t('Choose a dataset')}

View File

@@ -31,12 +31,17 @@ const options = [
{
label: 'Such an incredibly awesome long long label',
value: 'Such an incredibly awesome long long label',
custom: 'Secret custom prop',
},
{
label: 'Another incredibly awesome long long label',
value: 'Another incredibly awesome long long label',
},
{ label: 'Just a label', value: 'Just a label' },
{
label: 'JSX Label',
customLabel: <div style={{ color: 'red' }}>JSX Label</div>,
value: 'JSX Label',
},
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
{ label: 'C', value: 'C' },
@@ -137,6 +142,7 @@ InteractiveSelect.args = {
disabled: false,
invertSelection: false,
placeholder: 'Select ...',
optionFilterProps: ['value', 'label', 'custom'],
};
InteractiveSelect.argTypes = {

View File

@@ -32,26 +32,26 @@ const NEW_OPTION = 'Kyle';
const NO_DATA = 'No Data';
const LOADING = 'Loading...';
const OPTIONS = [
{ label: 'John', value: 1 },
{ label: 'Liam', value: 2 },
{ label: 'Olivia', value: 3 },
{ label: 'Emma', value: 4 },
{ label: 'Noah', value: 5 },
{ label: 'Ava', value: 6 },
{ label: 'Oliver', value: 7 },
{ label: 'ElijahH', value: 8 },
{ label: 'Charlotte', value: 9 },
{ label: 'Giovanni', value: 10 },
{ label: 'Franco', value: 11 },
{ label: 'Sandro', value: 12 },
{ label: 'Alehandro', value: 13 },
{ label: 'Johnny', value: 14 },
{ label: 'Nikole', value: 15 },
{ label: 'Igor', value: 16 },
{ label: 'Guilherme', value: 17 },
{ label: 'Irfan', value: 18 },
{ label: 'George', value: 19 },
{ label: 'Ashfaq', value: 20 },
{ label: 'John', value: 1, gender: 'Male' },
{ label: 'Liam', value: 2, gender: 'Male' },
{ label: 'Olivia', value: 3, gender: 'Female' },
{ label: 'Emma', value: 4, gender: 'Female' },
{ label: 'Noah', value: 5, gender: 'Male' },
{ label: 'Ava', value: 6, gender: 'Female' },
{ label: 'Oliver', value: 7, gender: 'Male' },
{ label: 'ElijahH', value: 8, gender: 'Male' },
{ label: 'Charlotte', value: 9, gender: 'Female' },
{ label: 'Giovanni', value: 10, gender: 'Male' },
{ label: 'Franco', value: 11, gender: 'Male' },
{ label: 'Sandro', value: 12, gender: 'Male' },
{ label: 'Alehandro', value: 13, gender: 'Male' },
{ label: 'Johnny', value: 14, gender: 'Male' },
{ label: 'Nikole', value: 15, gender: 'Female' },
{ label: 'Igor', value: 16, gender: 'Male' },
{ label: 'Guilherme', value: 17, gender: 'Male' },
{ label: 'Irfan', value: 18, gender: 'Male' },
{ label: 'George', value: 19, gender: 'Male' },
{ label: 'Ashfaq', value: 20, gender: 'Male' },
];
const loadOptions = async (search: string, page: number, pageSize: number) => {
@@ -100,7 +100,11 @@ const findSelectValue = () =>
const findAllSelectValues = () =>
waitFor(() => getElementsByClassName('.ant-select-selection-item'));
const type = (text: string) => userEvent.type(getSelect(), text, { delay: 10 });
const type = (text: string) => {
const select = getSelect();
userEvent.clear(select);
return userEvent.type(select, text, { delay: 10 });
};
const open = () => waitFor(() => userEvent.click(getSelect()));
@@ -110,6 +114,12 @@ test('displays a header', async () => {
expect(screen.getByText(headerText)).toBeInTheDocument();
});
test('adds a new option if the value is not in the options', async () => {
render(<Select {...defaultProps} options={[]} value={OPTIONS[0]} />);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
});
test('inverts the selection', async () => {
render(<Select {...defaultProps} invertSelection />);
await open();
@@ -141,6 +151,60 @@ test('searches for label or value', async () => {
expect(options[0]).toHaveTextContent(option.label);
});
test('searches for custom fields', async () => {
render(<Select {...defaultProps} optionFilterProps={['label', 'gender']} />);
await type('Liam');
let options = await findAllSelectOptions();
expect(options.length).toBe(1);
expect(options[0]).toHaveTextContent('Liam');
await type('Female');
options = await findAllSelectOptions();
expect(options.length).toBe(5);
expect(options[0]).toHaveTextContent('Olivia');
expect(options[1]).toHaveTextContent('Emma');
expect(options[2]).toHaveTextContent('Ava');
expect(options[3]).toHaveTextContent('Charlotte');
expect(options[4]).toHaveTextContent('Nikole');
await type('1');
expect(screen.getByText(NO_DATA)).toBeInTheDocument();
});
test('renders a custom label', async () => {
const options = [
{ label: 'John', value: 1, customLabel: <h1>John</h1> },
{ label: 'Liam', value: 2, customLabel: <h1>Liam</h1> },
{ label: 'Olivia', value: 3, customLabel: <h1>Olivia</h1> },
];
render(<Select {...defaultProps} options={options} />);
await open();
expect(screen.getByRole('heading', { name: 'John' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Liam' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Olivia' })).toBeInTheDocument();
});
test('searches for a word with a custom label', async () => {
const options = [
{ label: 'John', value: 1, customLabel: <h1>John</h1> },
{ label: 'Liam', value: 2, customLabel: <h1>Liam</h1> },
{ label: 'Olivia', value: 3, customLabel: <h1>Olivia</h1> },
];
render(<Select {...defaultProps} options={options} />);
await type('Liam');
const selectOptions = await findAllSelectOptions();
expect(selectOptions.length).toBe(1);
expect(selectOptions[0]).toHaveTextContent('Liam');
});
test('removes a new option if the user does not select it', async () => {
render(<Select {...defaultProps} allowNewOptions />);
await type(NEW_OPTION);
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
await type('k');
await waitFor(() =>
expect(screen.queryByText(NEW_OPTION)).not.toBeInTheDocument(),
);
});
test('clear all the values', async () => {
const onClear = jest.fn();
render(
@@ -157,7 +221,7 @@ test('clear all the values', async () => {
expect(values.length).toBe(0);
});
test('does not add a new option if allowNewValue is false', async () => {
test('does not add a new option if allowNewOptions is false', async () => {
render(<Select {...defaultProps} options={loadOptions} />);
await open();
await type(NEW_OPTION);
@@ -202,12 +266,18 @@ test('static - changes the selected item in single mode', async () => {
userEvent.click(await findSelectOption(firstOption.label));
expect(await findSelectValue()).toHaveTextContent(firstOption.label);
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining(firstOption),
expect.objectContaining({
label: firstOption.label,
value: firstOption.value,
}),
firstOption,
);
userEvent.click(await findSelectOption(secondOption.label));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining(secondOption),
expect.objectContaining({
label: secondOption.label,
value: secondOption.value,
}),
secondOption,
);
expect(await findSelectValue()).toHaveTextContent(secondOption.label);
@@ -236,6 +306,34 @@ test('static - adds a new option if none is available and allowNewOptions is tru
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
});
test('static - shows "No data" when allowNewOptions is false and a new option is entered', async () => {
render(<Select {...defaultProps} allowNewOptions={false} />);
await open();
await type(NEW_OPTION);
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
});
test('static - does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
render(<Select {...defaultProps} allowNewOptions />);
await open();
await type(NEW_OPTION);
expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
});
test('static - does not show "Loading..." when allowNewOptions is false and a new option is entered', async () => {
render(<Select {...defaultProps} allowNewOptions={false} />);
await open();
await type(NEW_OPTION);
expect(screen.queryByText(LOADING)).not.toBeInTheDocument();
});
test('static - shows "Loading..." when allowNewOptions is true and a new option is entered', async () => {
render(<Select {...defaultProps} allowNewOptions />);
await open();
await type(NEW_OPTION);
expect(await screen.findByText(LOADING)).toBeInTheDocument();
});
test('static - does not add a new option if the option already exists', async () => {
render(<Select {...defaultProps} allowNewOptions />);
const option = OPTIONS[0].label;
@@ -334,13 +432,19 @@ test('async - changes the selected item in single mode', async () => {
const [firstOption, secondOption] = OPTIONS;
userEvent.click(await findSelectOption(firstOption.label));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining(firstOption),
expect.objectContaining({
label: firstOption.label,
value: firstOption.value,
}),
firstOption,
);
expect(await findSelectValue()).toHaveTextContent(firstOption.label);
userEvent.click(await findSelectOption(secondOption.label));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining(secondOption),
expect.objectContaining({
label: secondOption.label,
value: secondOption.value,
}),
secondOption,
);
expect(await findSelectValue()).toHaveTextContent(secondOption.label);
@@ -382,6 +486,27 @@ test('async - does not add a new option if the option already exists', async ()
});
});
test('async - shows "No data" when allowNewOptions is false and a new option is entered', async () => {
render(
<Select
{...defaultProps}
options={loadOptions}
allowNewOptions={false}
showSearch
/>,
);
await open();
await type(NEW_OPTION);
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
});
test('async - does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
render(<Select {...defaultProps} options={loadOptions} allowNewOptions />);
await open();
await type(NEW_OPTION);
expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
});
test('async - sets a initial value in single mode', async () => {
render(<Select {...defaultProps} options={loadOptions} value={OPTIONS[0]} />);
expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
@@ -469,9 +594,9 @@ test('async - does not fire a new request for the same search input', async () =
});
/*
TODO: Add tests that require scroll interaction. Needs further investigation.
- Fetches more data when scrolling and more data is available
- Doesn't fetch more data when no more data is available
- Requests the correct page and page size
- Sets the page to zero when a new search is made
*/
TODO: Add tests that require scroll interaction. Needs further investigation.
- Fetches more data when scrolling and more data is available
- Doesn't fetch more data when no more data is available
- Requests the correct page and page size
- Sets the page to zero when a new search is made
*/

View File

@@ -41,6 +41,8 @@ import Icons from 'src/components/Icons';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import { hasOption } from './utils';
const { Option } = AntdSelect;
type AntdSelectAllProps = AntdSelectProps<AntdSelectValue>;
type PickedSelectProps = Pick<
@@ -60,7 +62,12 @@ type PickedSelectProps = Pick<
| 'value'
>;
export type OptionsType = Exclude<AntdSelectAllProps['options'], undefined>;
type OptionsProps = Exclude<AntdSelectAllProps['options'], undefined>;
export interface OptionsType extends Omit<OptionsProps, 'label'> {
label?: string;
customLabel?: ReactNode;
}
export type OptionsTypePage = {
data: OptionsType;
@@ -80,6 +87,7 @@ export interface SelectProps extends PickedSelectProps {
lazyLoading?: boolean;
mode?: 'single' | 'multiple';
name?: string; // discourage usage
optionFilterProps?: string[];
options: OptionsType | OptionsPagePromise;
pageSize?: number;
invertSelection?: boolean;
@@ -138,9 +146,10 @@ const StyledSpin = styled(Spin)`
margin-top: ${({ theme }) => -theme.gridUnit}px;
`;
const StyledLoadingText = styled.span`
const StyledLoadingText = styled.div`
${({ theme }) => `
margin-left: ${theme.gridUnit * 3}px;
line-height: ${theme.gridUnit * 8}px;
color: ${theme.colors.grayscale.light1};
`}
`;
@@ -169,12 +178,14 @@ const Select = ({
loading,
mode = 'single',
name,
notFoundContent,
onChange,
onClear,
optionFilterProps = ['label', 'value'],
options,
pageSize = DEFAULT_PAGE_SIZE,
placeholder = t('Select ...'),
showSearch,
showSearch = true,
value,
...props
}: SelectProps) => {
@@ -186,6 +197,9 @@ const Select = ({
const [selectOptions, setSelectOptions] = useState<OptionsType>(
initialOptions,
);
const shouldUseChildrenOptions = !!selectOptions.find(
opt => opt?.customLabel,
);
const [selectValue, setSelectValue] = useState(value);
const [searchedValue, setSearchedValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
@@ -202,39 +216,7 @@ const Select = ({
? 'tags'
: 'multiple';
useEffect(() => {
fetchedQueries.current.clear();
setSelectOptions(
options && Array.isArray(options) ? options : EMPTY_OPTIONS,
);
}, [options]);
useEffect(() => {
setSelectValue(value);
}, [value]);
useEffect(() => {
if (isAsync && selectValue) {
const array: AntdLabeledValue[] = Array.isArray(selectValue)
? (selectValue as AntdLabeledValue[])
: [selectValue as AntdLabeledValue];
const options: AntdLabeledValue[] = [];
array.forEach(element => {
const found = selectOptions.find(
option => option.value === element.value,
);
if (!found) {
options.push(element);
}
});
if (options.length > 0) {
setSelectOptions([...selectOptions, ...options]);
}
}
}, [isAsync, selectOptions, selectValue]);
// TODO: Simplify the code. We're only accepting label, value options.
// TODO: Remove labelInValue prop.
// TODO: Don't assume that isAsync is always labelInValue
const handleTopOptions = useCallback(
(selectedValue: AntdSelectValue | undefined) => {
// bringing selected options to the top of the list
@@ -393,53 +375,30 @@ const Select = ({
() =>
debounce((search: string) => {
const searchValue = search.trim();
// enables option creation
if (allowNewOptions && isSingleMode) {
const firstOption =
selectOptions.length > 0 && selectOptions[0].value;
// replaces the last search value entered with the new one
// only when the value wasn't part of the original options
if (
searchValue &&
firstOption === searchedValue &&
!initialOptions.find(o => o.value === searchedValue)
) {
selectOptions.shift();
setSelectOptions(selectOptions);
}
if (searchValue && !hasOption(searchValue, selectOptions)) {
const newOption = {
const newOption = searchValue &&
!hasOption(searchValue, selectOptions) && {
label: searchValue,
value: searchValue,
};
// adds a custom option
const newOptions = [...selectOptions, newOption];
setSelectOptions(newOptions);
setSelectValue(newOption);
const newOptions = newOption
? [
newOption,
...selectOptions.filter(opt => opt.value !== searchedValue),
]
: [...selectOptions.filter(opt => opt.value !== searchedValue)];
if (onChange) {
onChange(searchValue, newOptions);
}
}
setSelectOptions(newOptions);
}
if (!searchValue || searchValue === searchedValue) {
setIsTyping(false);
}
setSearchedValue(searchValue);
}, DEBOUNCE_TIMEOUT),
[
allowNewOptions,
initialOptions,
isSingleMode,
onChange,
searchedValue,
selectOptions,
],
[allowNewOptions, isSingleMode, searchedValue, selectOptions],
);
// Stop the invocation of the debounced function after unmounting
useEffect(() => () => handleOnSearch.cancel(), [handleOnSearch]);
const handlePagination = (e: UIEvent<HTMLElement>) => {
const vScroll = e.currentTarget;
const thresholdReached =
@@ -460,13 +419,15 @@ const Select = ({
if (filterOption) {
const searchValue = search.trim().toLowerCase();
const { value, label } = option;
const valueText = String(value);
const labelText = String(label);
return (
valueText.toLowerCase().includes(searchValue) ||
labelText.toLowerCase().includes(searchValue)
);
if (optionFilterProps && optionFilterProps.length) {
return optionFilterProps.some(prop => {
const optionProp = option?.[prop]
? String(option[prop]).trim().toLowerCase()
: '';
return optionProp.includes(searchValue);
});
}
}
return false;
@@ -486,34 +447,6 @@ const Select = ({
}
};
useEffect(() => {
const allowFetch = !fetchOnlyOnSearch || searchedValue;
if (isAsync && loadingEnabled && allowFetch) {
const page = 0;
handlePaginatedFetch(searchedValue, page, pageSize);
setPage(page);
}
}, [
isAsync,
searchedValue,
pageSize,
handlePaginatedFetch,
loadingEnabled,
fetchOnlyOnSearch,
]);
useEffect(() => {
if (isSingleMode) {
handleTopOptions(selectValue);
}
}, [handleTopOptions, isSingleMode, selectValue]);
useEffect(() => {
if (loading !== undefined && loading !== isLoading) {
setIsLoading(loading);
}
}, [isLoading, loading]);
const dropdownRender = (
originNode: ReactElement & { ref?: RefObject<HTMLElement> },
) => {
@@ -549,6 +482,75 @@ const Select = ({
}
};
useEffect(() => {
fetchedQueries.current.clear();
setSelectOptions(
options && Array.isArray(options) ? options : EMPTY_OPTIONS,
);
}, [options]);
useEffect(() => {
setSelectValue(value);
}, [value]);
useEffect(() => {
if (selectValue) {
const array = Array.isArray(selectValue)
? (selectValue as AntdLabeledValue[])
: [selectValue as AntdLabeledValue | string | number];
const options: AntdLabeledValue[] = [];
const isLabeledValue = isAsync || labelInValue;
array.forEach(element => {
const found = selectOptions.find((option: { value: string | number }) =>
isLabeledValue
? option.value === (element as AntdLabeledValue).value
: option.value === element,
);
if (!found) {
options.push(
isLabeledValue
? (element as AntdLabeledValue)
: ({ value: element, label: element } as AntdLabeledValue),
);
}
});
if (options.length > 0) {
setSelectOptions([...options, ...selectOptions]);
}
}
}, [labelInValue, isAsync, selectOptions, selectValue]);
// Stop the invocation of the debounced function after unmounting
useEffect(() => () => handleOnSearch.cancel(), [handleOnSearch]);
useEffect(() => {
const allowFetch = !fetchOnlyOnSearch || searchedValue;
if (isAsync && loadingEnabled && allowFetch) {
const page = 0;
handlePaginatedFetch(searchedValue, page, pageSize);
setPage(page);
}
}, [
isAsync,
searchedValue,
pageSize,
handlePaginatedFetch,
loadingEnabled,
fetchOnlyOnSearch,
]);
useEffect(() => {
if (isSingleMode) {
handleTopOptions(selectValue);
}
}, [handleTopOptions, isSingleMode, selectValue]);
useEffect(() => {
if (loading !== undefined && loading !== isLoading) {
setIsLoading(loading);
}
}, [isLoading, loading]);
return (
<StyledContainer>
{header}
@@ -560,6 +562,13 @@ const Select = ({
labelInValue={isAsync || labelInValue}
maxTagCount={MAX_TAG_COUNT}
mode={mappedMode}
notFoundContent={
allowNewOptions && !fetchOnlyOnSearch ? (
<StyledLoadingText>{t('Loading...')}</StyledLoadingText>
) : (
notFoundContent
)
}
onDeselect={handleOnDeselect}
onDropdownVisibleChange={handleOnDropdownVisibleChange}
onInputKeyDown={onInputKeyDown}
@@ -568,7 +577,7 @@ const Select = ({
onSelect={handleOnSelect}
onClear={handleClear}
onChange={onChange}
options={selectOptions}
options={shouldUseChildrenOptions ? undefined : selectOptions}
placeholder={placeholder}
showSearch={shouldShowSearch}
showArrow
@@ -583,7 +592,21 @@ const Select = ({
)
}
{...props}
/>
>
{shouldUseChildrenOptions &&
selectOptions.map(opt => {
const isOptObject = typeof opt === 'object';
const label = isOptObject ? opt?.label || opt.value : opt;
const value = (isOptObject && opt.value) || opt;
const { customLabel, ...optProps } = opt;
return (
<Option {...optProps} key={value} label={label} value={value}>
{isOptObject && customLabel ? customLabel : label}
</Option>
);
})}
</StyledSelect>
</StyledContainer>
);
};

View File

@@ -67,8 +67,8 @@ export function hasOption(search: string, options: AntdOptionsType) {
const labelText = String(label);
const valueText = String(value);
return (
valueText.toLowerCase().includes(searchOption) ||
labelText.toLowerCase().includes(searchOption)
valueText.toLowerCase() === searchOption ||
labelText.toLowerCase() === searchOption
);
});
}

View File

@@ -722,15 +722,14 @@ const FiltersConfigForm = (
!doLoadedDatasetsHaveTemporalColumns;
return {
value: filterType,
label: isDisabled ? (
label: mappedName || name,
customLabel: isDisabled ? (
<Tooltip
title={t('Datasets do not contain a temporal column')}
>
{mappedName || name}
</Tooltip>
) : (
mappedName || name
),
) : undefined,
disabled: isDisabled,
};
})}

View File

@@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
import { isFunction } from 'lodash';
import { Select } from 'src/components';
import { Tooltip } from 'src/components/Tooltip';
import { t } from '@superset-ui/core';
import ControlHeader from '../ControlHeader';
const propTypes = {
@@ -112,10 +113,12 @@ export default class ColorSchemeControl extends React.PureComponent {
const options = (isFunction(choices) ? choices() : choices).map(
([value]) => ({
value,
label: this.renderOption(value),
label: this.schemes?.[value]?.label || value,
customLabel: this.renderOption(value),
}),
);
const selectProps = {
ariaLabel: t('Select color scheme'),
allowClear: this.props.clearable,
defaultValue: this.props.default,
name: `select-${this.props.name}`,