chore: Select component refactoring - FilterControl - Iteration 5 (#15777)

* Refactor Select for AdhocFilterEditPopoverSqlTabContent

* Refactor Selects

* Fix numeric options

* Clean up

* Fix Select value

* Add showSearch

* Display null label

* Implement StyledSelect

* Update superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/index.jsx

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

* Fix aria-label

* Revert MetricControls changes

* Update ariaLabel

* Reconcile with latest Select changes

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
This commit is contained in:
Geido 2021-09-27 17:23:52 +03:00 committed by GitHub
parent 0f16177bde
commit 8ad03c4f25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 128 deletions

View File

@ -52,7 +52,7 @@ describe('AdhocFilters', () => {
}); });
xit('Set simple adhoc filter', () => { xit('Set simple adhoc filter', () => {
cy.get('[data-test=adhoc-filter-simple-value] .Select__control').click(); cy.get('[aria-label="Comparator option"] .Select__control').click();
cy.get('[data-test=adhoc-filter-simple-value] input[type=text]') cy.get('[data-test=adhoc-filter-simple-value] input[type=text]')
.focus() .focus()
.type('Jack{enter}', { delay: 20 }); .type('Jack{enter}', { delay: 20 });

View File

@ -68,11 +68,6 @@ const FilterPopoverContentContainer = styled.div`
max-width: none; max-width: none;
} }
.filter-edit-clause-dropdown {
width: ${({ theme }) => theme.gridUnit * 30}px;
margin-right: ${({ theme }) => theme.gridUnit}px;
}
.filter-edit-clause-info { .filter-edit-clause-info {
font-size: ${({ theme }) => theme.typography.sizes.xs}px; font-size: ${({ theme }) => theme.typography.sizes.xs}px;
padding-left: ${({ theme }) => theme.gridUnit}px; padding-left: ${({ theme }) => theme.gridUnit}px;

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { NativeSelect as Select } from 'src/components/Select'; import { Select } from 'src/components';
import { t, SupersetClient, styled } from '@superset-ui/core'; import { t, SupersetClient, styled } from '@superset-ui/core';
import { import {
Operators, Operators,
@ -36,25 +36,7 @@ import AdhocFilter, {
EXPRESSION_TYPES, EXPRESSION_TYPES,
CLAUSES, CLAUSES,
} from 'src/explore/components/controls/FilterControl/AdhocFilter'; } from 'src/explore/components/controls/FilterControl/AdhocFilter';
import { Input, SelectProps } from 'src/common/components'; import { Input } from 'src/common/components';
const SelectWithLabel = styled(Select)`
.ant-select-selector {
margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
}
.ant-select-selector::after {
content: '${(
pr: SelectProps<any> & {
labelText: string | boolean;
},
) => pr.labelText || '\\A0'}';
display: inline-block;
white-space: nowrap;
color: ${({ theme }) => theme.colors.grayscale.light1};
width: max-content;
}
`;
export interface SimpleColumnType { export interface SimpleColumnType {
id: number; id: number;
@ -132,10 +114,10 @@ export const useSimpleTabFilterProps = (props: Props) => {
HAVING_OPERATORS.indexOf(operator) === -1) HAVING_OPERATORS.indexOf(operator) === -1)
); );
}; };
const onSubjectChange = (id: string | number) => { const onSubjectChange = (id: string) => {
const option = props.options.find( const option = props.options.find(
option => option =>
('id' in option && option.id === id) || ('column_name' in option && option.column_name === id) ||
('optionName' in option && option.optionName === id), ('optionName' in option && option.optionName === id),
); );
@ -222,10 +204,6 @@ export const useSimpleTabFilterProps = (props: Props) => {
}; };
const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => { const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
const selectProps = {
name: 'select-column',
showSearch: true,
};
const { const {
onSubjectChange, onSubjectChange,
onOperatorChange, onOperatorChange,
@ -233,7 +211,6 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
onComparatorChange, onComparatorChange,
} = useSimpleTabFilterProps(props); } = useSimpleTabFilterProps(props);
const [suggestions, setSuggestions] = useState<Record<string, any>>([]); const [suggestions, setSuggestions] = useState<Record<string, any>>([]);
const [currentSuggestionSearch, setCurrentSuggestionSearch] = useState('');
const [ const [
loadingComparatorSuggestions, loadingComparatorSuggestions,
setLoadingComparatorSuggestions, setLoadingComparatorSuggestions,
@ -279,10 +256,6 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
<FilterDefinitionOption option={option} /> <FilterDefinitionOption option={option} />
); );
const clearSuggestionSearch = () => {
setCurrentSuggestionSearch('');
};
const getOptionsRemaining = () => { const getOptionsRemaining = () => {
const { comparator } = props.adhocFilter; const { comparator } = props.adhocFilter;
// if select is multi/value is array, we show the options not selected // if select is multi/value is array, we show the options not selected
@ -299,7 +272,9 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
let columns = props.options; let columns = props.options;
const { subject, operator, comparator, operatorId } = props.adhocFilter; const { subject, operator, comparator, operatorId } = props.adhocFilter;
const subjectSelectProps = { const subjectSelectProps = {
ariaLabel: t('Select subject'),
value: subject ?? undefined, value: subject ?? undefined,
onChange: onSubjectChange, onChange: onSubjectChange,
notFoundContent: t( notFoundContent: t(
@ -332,33 +307,44 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
'%s operator(s)', '%s operator(s)',
OPERATORS_OPTIONS.filter(op => isOperatorRelevant(op, subject)).length, OPERATORS_OPTIONS.filter(op => isOperatorRelevant(op, subject)).length,
), ),
value: OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId]?.display, value: operatorId,
onChange: onOperatorChange, onChange: onOperatorChange,
autoFocus: !!subjectSelectProps.value && !operator, autoFocus: !!subjectSelectProps.value && !operator,
name: 'select-operator', ariaLabel: t('Select operator'),
}; };
const shouldFocusComparator = const shouldFocusComparator =
!!subjectSelectProps.value && !!operatorSelectProps.value; !!subjectSelectProps.value && !!operatorSelectProps.value;
const comparatorSelectProps: SelectProps<any> & { const comparatorSelectProps = {
labelText: string | boolean;
} = {
allowClear: true, allowClear: true,
showSearch: true, allowNewOptions: true,
mode: MULTI_OPERATORS.has(operatorId) ? 'tags' : undefined, ariaLabel: t('Comparator option'),
tokenSeparators: [',', '\n', '\t', ';'], mode: MULTI_OPERATORS.has(operatorId)
? ('multiple' as const)
: ('single' as const),
loading: loadingComparatorSuggestions, loading: loadingComparatorSuggestions,
value: comparator, value: comparator,
onChange: onComparatorChange, onChange: onComparatorChange,
notFoundContent: t('Type a value here'), notFoundContent: t('Type a value here'),
disabled: DISABLE_INPUT_OPERATORS.includes(operatorId), disabled: DISABLE_INPUT_OPERATORS.includes(operatorId),
placeholder: createSuggestionsPlaceholder(), placeholder: createSuggestionsPlaceholder(),
labelText:
comparator && comparator.length > 0 && createSuggestionsPlaceholder(),
autoFocus: shouldFocusComparator, autoFocus: shouldFocusComparator,
}; };
const labelText =
comparator && comparator.length > 0 && createSuggestionsPlaceholder();
const SelectWithLabel = styled(Select)`
.ant-select-selector::after {
content: ${() => labelText || '\\A0'};
display: inline-block;
white-space: nowrap;
color: ${({ theme }) => theme.colors.grayscale.light1};
width: max-content;
}
`;
return ( return (
<> <>
<Select <Select
@ -366,81 +352,42 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
marginTop: theme.gridUnit * 4, marginTop: theme.gridUnit * 4,
marginBottom: theme.gridUnit * 4, marginBottom: theme.gridUnit * 4,
})} })}
{...selectProps} options={columns.map(column => ({
value:
('column_name' in column && column.column_name) ||
('optionName' in column && column.optionName) ||
'',
label:
('saved_metric_name' in column && column.saved_metric_name) ||
('column_name' in column && column.column_name) ||
('label' in column && column.label),
key:
('id' in column && column.id) ||
('optionName' in column && column.optionName) ||
undefined,
customLabel: renderSubjectOptionLabel(column),
}))}
{...subjectSelectProps} {...subjectSelectProps}
filterOption={(input, option) => />
option && option.filterBy
? option.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0
: false
}
getPopupContainer={triggerNode => triggerNode.parentNode}
>
{columns.map(column => (
<Select.Option
value={
('id' in column && column.id) ||
('optionName' in column && column.optionName) ||
''
}
filterBy={
('saved_metric_name' in column && column.saved_metric_name) ||
('column_name' in column && column.column_name) ||
('label' in column && column.label)
}
key={
('id' in column && column.id) ||
('optionName' in column && column.optionName) ||
undefined
}
>
{renderSubjectOptionLabel(column)}
</Select.Option>
))}
</Select>
<Select <Select
css={theme => ({ marginBottom: theme.gridUnit * 4 })} css={theme => ({ marginBottom: theme.gridUnit * 4 })}
{...selectProps} options={OPERATORS_OPTIONS.filter(op =>
isOperatorRelevant(op, subject),
).map(option => ({
value: option,
label: OPERATOR_ENUM_TO_OPERATOR_TYPE[option].display,
key: option,
}))}
{...operatorSelectProps} {...operatorSelectProps}
filterOption={(input, option) => />
option && option.children
? option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
: false
}
getPopupContainer={triggerNode => triggerNode.parentNode}
>
{OPERATORS_OPTIONS.filter(op => isOperatorRelevant(op, subject)).map(
option => (
<Select.Option value={option} key={option}>
{OPERATOR_ENUM_TO_OPERATOR_TYPE[option].display}
</Select.Option>
),
)}
</Select>
{MULTI_OPERATORS.has(operatorId) || suggestions.length > 0 ? ( {MULTI_OPERATORS.has(operatorId) || suggestions.length > 0 ? (
<SelectWithLabel <SelectWithLabel
data-test="adhoc-filter-simple-value" options={suggestions.map((suggestion: string) => ({
value: suggestion,
label: String(suggestion),
}))}
{...comparatorSelectProps} {...comparatorSelectProps}
getPopupContainer={triggerNode => triggerNode.parentNode} />
onSearch={val => setCurrentSuggestionSearch(val)}
onSelect={clearSuggestionSearch}
onBlur={clearSuggestionSearch}
>
{suggestions.map((suggestion: string) => (
<Select.Option value={suggestion} key={suggestion}>
{String(suggestion)}
</Select.Option>
))}
{/* enable selecting an option not included in suggestions */}
{currentSuggestionSearch &&
!suggestions.some(
(suggestion: string) => suggestion === currentSuggestionSearch,
) && (
<Select.Option value={currentSuggestionSearch}>
{currentSuggestionSearch}
</Select.Option>
)}
</SelectWithLabel>
) : ( ) : (
<Input <Input
data-test="adhoc-filter-simple-value" data-test="adhoc-filter-simple-value"

View File

@ -18,8 +18,8 @@
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { NativeSelect as Select } from 'src/components/Select'; import { Select } from 'src/components';
import { t } from '@superset-ui/core'; import { styled, t } from '@superset-ui/core';
import { SQLEditor } from 'src/components/AsyncAceEditor'; import { SQLEditor } from 'src/components/AsyncAceEditor';
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords'; import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
@ -44,6 +44,13 @@ const propTypes = {
activeKey: PropTypes.string.isRequired, activeKey: PropTypes.string.isRequired,
}; };
const StyledSelect = styled(Select)`
${({ theme }) => `
width: ${theme.gridUnit * 30}px;
marginRight: ${theme.gridUnit}px;
`}
`;
export default class AdhocFilterEditPopoverSqlTabContent extends React.Component { export default class AdhocFilterEditPopoverSqlTabContent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -54,7 +61,7 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
this.handleAceEditorRef = this.handleAceEditorRef.bind(this); this.handleAceEditorRef = this.handleAceEditorRef.bind(this);
this.selectProps = { this.selectProps = {
name: 'select-column', ariaLabel: t('Select column'),
}; };
} }
@ -111,22 +118,19 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
}) })
.filter(Boolean), .filter(Boolean),
); );
const selectOptions = Object.keys(CLAUSES).map(clause => ({
label: clause,
value: clause,
}));
return ( return (
<span> <span>
<div className="filter-edit-clause-section"> <div className="filter-edit-clause-section">
<Select <StyledSelect
options={selectOptions}
{...this.selectProps} {...this.selectProps}
{...clauseSelectProps} {...clauseSelectProps}
className="filter-edit-clause-dropdown" />
getPopupContainer={triggerNode => triggerNode.parentNode}
>
{Object.keys(CLAUSES).map(clause => (
<Select.Option value={clause} key={clause}>
{clause}
</Select.Option>
))}
</Select>
<span className="filter-edit-clause-info"> <span className="filter-edit-clause-info">
<strong>WHERE</strong> {t('Filters by columns')} <strong>WHERE</strong> {t('Filters by columns')}
<br /> <br />