diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx index 442527b77b..fc13220940 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx @@ -59,10 +59,10 @@ const simpleCustomFilter = new AdhocFilter({ }); const options = [ - { type: 'VARCHAR(255)', column_name: 'source' }, - { type: 'VARCHAR(255)', column_name: 'target' }, - { type: 'DOUBLE', column_name: 'value' }, - { saved_metric_name: 'my_custom_metric' }, + { type: 'VARCHAR(255)', column_name: 'source', id: 1 }, + { type: 'VARCHAR(255)', column_name: 'target', id: 2 }, + { type: 'DOUBLE', column_name: 'value', id: 3 }, + { saved_metric_name: 'my_custom_metric', id: 4 }, sumValueAdhocMetric, ]; @@ -91,9 +91,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => { it('passes the new adhocFilter to onChange after onSubjectChange', () => { const { wrapper, onChange } = setup(); - wrapper - .instance() - .onSubjectChange({ type: 'VARCHAR(255)', column_name: 'source' }); + wrapper.instance().onSubjectChange(1); expect(onChange.calledOnce).toBe(true); expect(onChange.lastCall.args[0]).toEqual( simpleAdhocFilter.duplicateWith({ subject: 'source' }), @@ -102,7 +100,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => { it('may alter the clause in onSubjectChange if the old clause is not appropriate', () => { const { wrapper, onChange } = setup(); - wrapper.instance().onSubjectChange(sumValueAdhocMetric); + wrapper.instance().onSubjectChange(sumValueAdhocMetric.optionName); expect(onChange.calledOnce).toBe(true); expect(onChange.lastCall.args[0]).toEqual( simpleAdhocFilter.duplicateWith({ diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx index 5aff466065..98a62bd84a 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx @@ -28,9 +28,9 @@ import AdhocMetricEditPopover from 'src/explore/components/AdhocMetricEditPopove import { AGGREGATES } from 'src/explore/constants'; const columns = [ - { type: 'VARCHAR(255)', column_name: 'source' }, - { type: 'VARCHAR(255)', column_name: 'target' }, - { type: 'DOUBLE', column_name: 'value' }, + { type: 'VARCHAR(255)', column_name: 'source', id: 1 }, + { type: 'VARCHAR(255)', column_name: 'target', id: 2 }, + { type: 'DOUBLE', column_name: 'value', id: 3 }, ]; const sumValueAdhocMetric = new AdhocMetric({ @@ -68,7 +68,7 @@ describe('AdhocMetricEditPopover', () => { it('overwrites the adhocMetric in state with onColumnChange', () => { const { wrapper } = setup(); - wrapper.instance().onColumnChange(columns[0]); + wrapper.instance().onColumnChange(columns[0].id); expect(wrapper.state('adhocMetric')).toEqual( sumValueAdhocMetric.duplicateWith({ column: columns[0] }), ); @@ -95,7 +95,7 @@ describe('AdhocMetricEditPopover', () => { expect(wrapper.find(Button).find({ disabled: true })).not.toExist(); wrapper.instance().onColumnChange(null); expect(wrapper.find(Button).find({ disabled: true })).toExist(); - wrapper.instance().onColumnChange({ column: columns[0] }); + wrapper.instance().onColumnChange(columns[0].id); expect(wrapper.find(Button).find({ disabled: true })).not.toExist(); wrapper.instance().onAggregateChange(null); expect(wrapper.find(Button).find({ disabled: true })).toExist(); @@ -104,7 +104,7 @@ describe('AdhocMetricEditPopover', () => { it('highlights save if changes are present', () => { const { wrapper } = setup(); expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).not.toExist(); - wrapper.instance().onColumnChange({ column: columns[1] }); + wrapper.instance().onColumnChange(columns[1].id); expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).toExist(); }); diff --git a/superset-frontend/src/common/components/Select.tsx b/superset-frontend/src/common/components/Select.tsx index ca2262197e..75bd073912 100644 --- a/superset-frontend/src/common/components/Select.tsx +++ b/superset-frontend/src/common/components/Select.tsx @@ -20,6 +20,10 @@ import { styled } from '@superset-ui/core'; import { Select as BaseSelect } from 'src/common/components'; const StyledSelect = styled(BaseSelect)` + display: block; +`; + +const StyledGraySelect = styled(StyledSelect)` &.ant-select-single { .ant-select-selector { height: 36px; @@ -44,3 +48,7 @@ const StyledOption = BaseSelect.Option; export const Select = Object.assign(StyledSelect, { Option: StyledOption, }); + +export const GraySelect = Object.assign(StyledGraySelect, { + Option: StyledOption, +}); diff --git a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx index a040219397..fa241914fc 100644 --- a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx +++ b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx @@ -19,8 +19,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormGroup } from 'react-bootstrap'; -import { Select } from 'src/components/Select'; -import { t, SupersetClient } from '@superset-ui/core'; +import { Select } from 'src/common/components/Select'; +import { t, SupersetClient, styled } from '@superset-ui/core'; import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../AdhocFilter'; import adhocMetricType from '../propTypes/adhocMetricType'; @@ -36,7 +36,15 @@ import { DISABLE_INPUT_OPERATORS, } from '../constants'; import FilterDefinitionOption from './FilterDefinitionOption'; -import SelectControl from './controls/SelectControl'; + +const SelectWithLabel = styled(Select)` + .ant-select-selector::after { + content: '${({ labelText }) => labelText || '\\A0'}'; + display: inline-block; + white-space: nowrap; + color: ${({ theme }) => theme.colors.grayscale.light1}; + } +`; const propTypes = { adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired, @@ -92,11 +100,8 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon }; this.selectProps = { - isMulti: false, name: 'select-column', - labelKey: 'label', - autosize: false, - clearable: false, + showSearch: true, }; this.menuPortalProps = { @@ -116,7 +121,11 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon } } - onSubjectChange(option) { + onSubjectChange(id) { + const option = this.props.options.find( + option => option.id === id || option.optionName === id, + ); + let subject; let clause; // infer the new clause based on what subject was selected. @@ -247,27 +256,38 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon } } + optionsRemaining() { + const { suggestions } = this.state; + const { comparator } = this.props.adhocFilter; + // if select is multi/value is array, we show the options not selected + const valuesFromSuggestionsLength = Array.isArray(comparator) + ? comparator.filter(v => suggestions.includes(v)).length + : 0; + return suggestions?.length - valuesFromSuggestionsLength ?? 0; + } + + createSuggestionsPlaceholder() { + const optionsRemaining = this.optionsRemaining(); + const placeholder = t('%s option(s)', optionsRemaining); + return optionsRemaining ? placeholder : ''; + } + renderSubjectOptionLabel(option) { return ; } - renderSubjectOptionValue({ value }) { - return {value}; - } - render() { - const { adhocFilter, options: columns, datasource } = this.props; + const { adhocFilter, options, datasource } = this.props; + let columns = options; const { subject, operator, comparator } = adhocFilter; const subjectSelectProps = { - options: columns, - value: subject ? { value: subject } : undefined, + value: subject ?? undefined, onChange: this.onSubjectChange, - optionRenderer: this.renderSubjectOptionLabel, - valueRenderer: this.renderSubjectOptionValue, - valueKey: 'filterOptionName', - noResultsText: t( + notFoundContent: t( 'No such column found. To filter on a metric, try the Custom SQL tab.', ), + filterOption: (input, option) => + option.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0, }; if (datasource.type === 'druid') { @@ -283,19 +303,16 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon adhocFilter.clause === CLAUSES.WHERE ? t('%s column(s)', columns.length) : t('To filter on a metric, use Custom SQL tab.'); - // make sure options have `column_name` - subjectSelectProps.options = columns.filter(option => option.column_name); + columns = options.filter(option => option.column_name); } const operatorSelectProps = { placeholder: t('%s operators(s)', OPERATORS_OPTIONS.length), // like AGGREGTES_OPTIONS, operator options are string - options: OPERATORS_OPTIONS.filter(op => - this.isOperatorRelevant(op, subject), - ), value: operator, onChange: this.onOperatorChange, - getOptionLabel: translateOperator, + filterOption: (input, option) => + option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0, }; return ( @@ -303,36 +320,63 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon {MULTI_OPERATORS.has(operator) || this.state.suggestions.length > 0 ? ( - + placeholder={this.createSuggestionsPlaceholder()} + labelText={ + comparator?.length > 0 && this.createSuggestionsPlaceholder() + } + > + {this.state.suggestions.map(suggestion => ( + + {suggestion} + + ))} + ) : ( + > + {Object.keys(CLAUSES).map(clause => ( + + {clause} + + ))} + WHERE {t('filters by columns')}
diff --git a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx index da1cde11cc..a75406fcf1 100644 --- a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx +++ b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx @@ -21,7 +21,7 @@ import PropTypes from 'prop-types'; import { FormGroup } from 'react-bootstrap'; import Tabs from 'src/common/components/Tabs'; import Button from 'src/components/Button'; -import Select from 'src/components/Select'; +import { Select } from 'src/common/components/Select'; import { styled, t } from '@superset-ui/core'; import { ColumnOption } from '@superset-ui/chart-controls'; @@ -70,27 +70,12 @@ export default class AdhocMetricEditPopover extends React.Component { this.handleAceEditorRef = this.handleAceEditorRef.bind(this); this.refreshAceEditor = this.refreshAceEditor.bind(this); - this.popoverRef = React.createRef(); - this.state = { adhocMetric: this.props.adhocMetric, width: startingWidth, height: startingHeight, }; - this.selectProps = { - labelKey: 'label', - isMulti: false, - autosize: false, - clearable: true, - }; - - this.menuPortalProps = { - menuPosition: 'fixed', - menuPlacement: 'bottom', - menuPortalTarget: this.popoverRef.current, - }; - document.addEventListener('mouseup', this.onMouseUp); } @@ -118,7 +103,8 @@ export default class AdhocMetricEditPopover extends React.Component { this.props.onClose(); } - onColumnChange(column) { + onColumnChange(columnId) { + const column = this.props.columns.find(column => column.id === columnId); this.setState(prevState => ({ adhocMetric: prevState.adhocMetric.duplicateWith({ column, @@ -213,20 +199,23 @@ export default class AdhocMetricEditPopover extends React.Component { const columnSelectProps = { placeholder: t('%s column(s)', columns.length), - options: columns, value: (adhocMetric.column && adhocMetric.column.column_name) || adhocMetric.inferSqlExpressionColumn(), onChange: this.onColumnChange, - optionRenderer: this.renderColumnOption, - valueKey: 'column_name', + allowClear: true, + showSearch: true, + filterOption: (input, option) => + option.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0, }; const aggregateSelectProps = { placeholder: t('%s aggregates(s)', AGGREGATES_OPTIONS.length), - options: AGGREGATES_OPTIONS, value: adhocMetric.aggregate || adhocMetric.inferSqlExpressionAggregate(), onChange: this.onAggregateChange, + allowClear: true, + autoFocus: true, + showSearch: true, }; if (this.props.datasourceType === 'druid') { @@ -241,7 +230,6 @@ export default class AdhocMetricEditPopover extends React.Component {
column - + {columns.map(column => ( + + {this.renderColumnOption(column)} + + ))} + aggregate - + {AGGREGATES_OPTIONS.map(option => ( + + {option} + + ))} +