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 3f0756f  chore: Select component refactoring - SelectControl - 
Iteration 5 (#16510)
3f0756f is described below

commit 3f0756f637660cb721285e645e419ad835f688b4
Author: Geido <[email protected]>
AuthorDate: Mon Oct 4 18:24:41 2021 +0300

    chore: Select component refactoring - SelectControl - Iteration 5 (#16510)
    
    * Refactor Select DatasourceEditor
    
    * Fire onChange with allowNewOptions
    
    * Clean up
    
    * Refactor Select in AnnotationLayer
    
    * Handle on clear
    
    * Update tests
    
    * Refactor Select in SpatialControl
    
    * Show search
    
    * Refactor Select in FilterBox
    
    * Remove search where unnecessary
    
    * Update SelectControl - WIP
    
    * Refactor Controls
    
    * Update SelectControl tests
    
    * Clean up
    
    * Test allowNewOptions false
    
    * Use SelectControl AnnotationLayer
    
    * Use SelectControl SpatialControl
    
    * Clean up
    
    * Render custom label
    
    * Show search
    
    * Implement filterOption
    
    * Improve filterOption
    
    * Update Cypress
    
    * Update Cypress table test
    
    * Use value for defaultValue
    
    * Merge with latest changes
    
    * Reconcile with latest Select changes
    
    * Update 
superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx
    
    Co-authored-by: Michael S. Molina 
<[email protected]>
    
    * Update 
superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx
    
    Co-authored-by: Michael S. Molina 
<[email protected]>
    
    * Revert changes to test
    
    * Call onPopoverClear when v value is undefined
    
    Co-authored-by: Michael S. Molina 
<[email protected]>
---
 .../integration/explore/advanced_analytics.test.ts |  10 +-
 .../cypress/integration/explore/control.test.ts    |   4 +-
 .../explore/visualizations/table.test.ts           |   2 +-
 .../explore/components/SelectControl_spec.jsx      | 137 ++---------
 .../explore/components/TextArea_spec.jsx           |   2 +-
 .../spec/javascripts/explore/fixtures.tsx          |   3 -
 .../src/datasource/DatasourceEditor.jsx            |  17 +-
 .../AnnotationLayerControl/AnnotationLayer.jsx     |  89 +++----
 .../AnnotationLayer.test.tsx                       |  53 +++-
 .../controls/AnnotationLayerControl/index.jsx      |  19 +-
 .../controls/FilterBoxItemControl/index.jsx        |  13 +-
 .../explore/components/controls/SelectControl.jsx  | 268 ++++++++-------------
 .../explore/components/controls/SpatialControl.jsx |   1 +
 superset-frontend/src/explore/controls.jsx         |   7 +-
 14 files changed, 223 insertions(+), 402 deletions(-)

diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/advanced_analytics.test.ts
 
b/superset-frontend/cypress-base/cypress/integration/explore/advanced_analytics.test.ts
index 77ebfbe..ff88c33 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/advanced_analytics.test.ts
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/advanced_analytics.test.ts
@@ -29,13 +29,13 @@ describe('Advanced analytics', () => {
 
     cy.get('.ant-collapse-header').contains('Advanced Analytics').click();
 
-    cy.get('[data-test=time_compare]').find('.Select__control').click();
+    cy.get('[data-test=time_compare]').find('.ant-select').click();
     cy.get('[data-test=time_compare]')
-      .find('input[type=text]')
+      .find('input[type=search]')
       .type('28 days{enter}');
 
     cy.get('[data-test=time_compare]')
-      .find('input[type=text]')
+      .find('input[type=search]')
       .type('1 year{enter}');
 
     cy.get('button[data-test="run-query-button"]').click();
@@ -48,10 +48,10 @@ describe('Advanced analytics', () => {
 
     cy.get('.ant-collapse-header').contains('Advanced Analytics').click();
     cy.get('[data-test=time_compare]')
-      .find('.Select__multi-value__label')
+      .find('.ant-select-selector')
       .contains('28 days');
     cy.get('[data-test=time_compare]')
-      .find('.Select__multi-value__label')
+      .find('.ant-select-selector')
       .contains('1 year');
   });
 });
diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts 
b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
index f9eeecb..c57369e 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
@@ -235,8 +235,8 @@ describe('Groupby control', () => {
     cy.verifySliceSuccess({ waitAlias: '@chartData' });
 
     cy.get('[data-test=groupby]').within(() => {
-      cy.get('.Select__control').click();
-      cy.get('input[type=text]').type('state{enter}');
+      cy.get('.ant-select').click();
+      cy.get('input[type=search]').type('state{enter}');
     });
     cy.get('button[data-test="run-query-button"]').click();
     cy.verifySliceSuccess({ waitAlias: '@chartData', chartSelector: 'svg' });
diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
 
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
index 95915fb..2a4258b 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
@@ -54,7 +54,7 @@ describe('Visualization > Table', () => {
       granularity_sqla: undefined,
       metrics: ['count'],
     });
-    cy.get('input[name="select-granularity_sqla"]').should('have.value', 'ds');
+    cy.get('[data-test=granularity_sqla] .column-option-label').contains('ds');
   });
 
   it('Format non-numeric metrics correctly', () => {
diff --git 
a/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx 
b/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx
index b9c76e3..bc16485 100644
--- 
a/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx
+++ 
b/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx
@@ -20,8 +20,7 @@
 import React from 'react';
 import sinon from 'sinon';
 import { shallow } from 'enzyme';
-import { Select, CreatableSelect } from 'src/components/Select';
-import OnPasteSelect from 'src/components/Select/OnPasteSelect';
+import { Select as SelectComponent } from 'src/components';
 import SelectControl from 'src/explore/components/controls/SelectControl';
 import { styledMount as mount } from 'spec/helpers/theming';
 
@@ -48,59 +47,35 @@ describe('SelectControl', () => {
     wrapper = shallow(<SelectControl {...defaultProps} />);
   });
 
-  it('uses Select in onPasteSelect when freeForm=false', () => {
-    wrapper = shallow(<SelectControl {...defaultProps} multi />);
-    const select = wrapper.find(OnPasteSelect);
-    expect(select.props().selectWrap).toBe(Select);
-  });
-
-  it('uses Creatable in onPasteSelect when freeForm=true', () => {
-    wrapper = shallow(<SelectControl {...defaultProps} multi freeForm />);
-    const select = wrapper.find(OnPasteSelect);
-    expect(select.props().selectWrap).toBe(CreatableSelect);
-  });
-
   it('calls props.onChange when select', () => {
     const select = wrapper.instance();
-    select.onChange({ value: 50 });
+    select.onChange(50);
     expect(defaultProps.onChange.calledWith(50)).toBe(true);
   });
 
-  it('returns all options on select all', () => {
-    const expectedValues = ['one', 'two'];
-    const selectAllProps = {
-      multi: true,
-      allowAll: true,
-      choices: expectedValues,
-      name: 'row_limit',
-      label: 'Row Limit',
-      valueKey: 'value',
-      onChange: sinon.spy(),
-    };
-    wrapper.setProps(selectAllProps);
-    wrapper.instance().onChange([{ meta: true, value: 'Select all' }]);
-    expect(selectAllProps.onChange.calledWith(expectedValues)).toBe(true);
-  });
-
   describe('render', () => {
     it('renders with Select by default', () => {
-      expect(wrapper.find(OnPasteSelect)).not.toExist();
-      expect(wrapper.findWhere(x => x.type() === Select)).toHaveLength(1);
+      expect(wrapper.find(SelectComponent)).toExist();
     });
 
-    it('renders with OnPasteSelect when multi', () => {
+    it('renders as mode multiple', () => {
       wrapper.setProps({ multi: true });
-      expect(wrapper.find(OnPasteSelect)).toExist();
-      expect(wrapper.findWhere(x => x.type() === Select)).toHaveLength(0);
+      expect(wrapper.find(SelectComponent)).toExist();
+      expect(wrapper.find(SelectComponent).prop('mode')).toBe('multiple');
     });
 
-    it('renders with Creatable when freeForm', () => {
+    it('renders with allowNewOptions when freeForm', () => {
       wrapper.setProps({ freeForm: true });
-      expect(wrapper.find(OnPasteSelect)).not.toExist();
-      expect(wrapper.findWhere(x => x.type() === 
CreatableSelect)).toHaveLength(
-        1,
-      );
+      expect(wrapper.find(SelectComponent)).toExist();
+      expect(wrapper.find(SelectComponent).prop('allowNewOptions')).toBe(true);
+    });
+
+    it('renders with allowNewOptions=false when freeForm=false', () => {
+      wrapper.setProps({ freeForm: false });
+      expect(wrapper.find(SelectComponent)).toExist();
+      
expect(wrapper.find(SelectComponent).prop('allowNewOptions')).toBe(false);
     });
+
     describe('empty placeholder', () => {
       describe('withMulti', () => {
         it('does not show a placeholder if there are no choices', () => {
@@ -161,16 +136,6 @@ describe('SelectControl', () => {
         );
         expect(wrapper.html()).not.toContain('add something');
       });
-      it('shows numbers of options as a placeholder by default', () => {
-        wrapper = mount(<SelectControl {...defaultProps} multi />);
-        expect(wrapper.html()).toContain('2 option(s');
-      });
-      it('reduces the number of options in the placeholder by the value 
length', () => {
-        wrapper = mount(
-          <SelectControl {...defaultProps} multi value={['today']} />,
-        );
-        expect(wrapper.html()).toContain('1 option(s');
-      });
     });
     describe('when select is single', () => {
       it('does not render the placeholder when a selection has been made', () 
=> {
@@ -186,82 +151,12 @@ describe('SelectControl', () => {
     });
   });
 
-  describe('optionsRemaining', () => {
-    describe('isMulti', () => {
-      it('returns the options minus selected values', () => {
-        const wrapper = mount(
-          <SelectControl {...defaultProps} multi value={['today']} />,
-        );
-        expect(wrapper.instance().optionsRemaining()).toEqual(1);
-      });
-    });
-    describe('is not multi', () => {
-      it('returns the length of all options', () => {
-        wrapper = mount(
-          <SelectControl
-            {...defaultProps}
-            value={50}
-            placeholder="add something"
-          />,
-        );
-        expect(wrapper.instance().optionsRemaining()).toEqual(2);
-      });
-    });
-    describe('with Select all', () => {
-      it('does not count it', () => {
-        const props = { ...defaultProps, multi: true, allowAll: true };
-        const wrapper = mount(<SelectControl {...props} />);
-        expect(wrapper.instance().getOptions(props).length).toEqual(3);
-        expect(wrapper.instance().optionsRemaining()).toEqual(2);
-      });
-    });
-  });
-
   describe('getOptions', () => {
     it('returns the correct options', () => {
       wrapper.setProps(defaultProps);
       expect(wrapper.instance().getOptions(defaultProps)).toEqual(options);
     });
-
-    it('shows Select-All when enabled', () => {
-      const selectAllProps = {
-        choices: ['one', 'two'],
-        name: 'name',
-        freeForm: true,
-        allowAll: true,
-        multi: true,
-        valueKey: 'value',
-      };
-      wrapper.setProps(selectAllProps);
-      expect(wrapper.instance().getOptions(selectAllProps)).toContainEqual({
-        label: 'Select all',
-        meta: true,
-        value: 'Select all',
-      });
-    });
-
-    it('returns the correct options when freeform is set to true', () => {
-      const freeFormProps = {
-        choices: [],
-        freeForm: true,
-        value: ['one', 'two'],
-        name: 'row_limit',
-        label: 'Row Limit',
-        valueKey: 'custom_value_key',
-        onChange: sinon.spy(),
-      };
-      // the last added option is at the top
-      const expectedNewOptions = [
-        { custom_value_key: 'two', label: 'two' },
-        { custom_value_key: 'one', label: 'one' },
-      ];
-      wrapper.setProps(freeFormProps);
-      expect(wrapper.instance().getOptions(freeFormProps)).toEqual(
-        expectedNewOptions,
-      );
-    });
   });
-
   describe('UNSAFE_componentWillReceiveProps', () => {
     it('sets state.options if props.choices has changed', () => {
       const updatedOptions = [
diff --git 
a/superset-frontend/spec/javascripts/explore/components/TextArea_spec.jsx 
b/superset-frontend/spec/javascripts/explore/components/TextArea_spec.jsx
index fe804b5..6dbc4b5 100644
--- a/superset-frontend/spec/javascripts/explore/components/TextArea_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/TextArea_spec.jsx
@@ -31,7 +31,7 @@ const defaultProps = {
   onChange: sinon.spy(),
 };
 
-describe('SelectControl', () => {
+describe('TextArea', () => {
   let wrapper;
   beforeEach(() => {
     wrapper = shallow(<TextAreaControl {...defaultProps} />);
diff --git a/superset-frontend/spec/javascripts/explore/fixtures.tsx 
b/superset-frontend/spec/javascripts/explore/fixtures.tsx
index d9185cf..e4327cc 100644
--- a/superset-frontend/spec/javascripts/explore/fixtures.tsx
+++ b/superset-frontend/spec/javascripts/explore/fixtures.tsx
@@ -93,13 +93,10 @@ export const controlPanelSectionsChartOptionsTable: 
ControlPanelSectionConfig[]
             default: [],
             description: t('Columns to display'),
             optionRenderer: c => <ColumnOption column={c} showType />,
-            valueRenderer: c => <ColumnOption column={c} />,
             valueKey: 'column_name',
-            allowAll: true,
             mapStateToProps: stateRef => ({
               options: stateRef.datasource ? stateRef.datasource.columns : [],
             }),
-            commaChoosesOption: false,
             freeForm: true,
           } as ControlConfig<'SelectControl', ColumnMeta>,
         },
diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx 
b/superset-frontend/src/datasource/DatasourceEditor.jsx
index a848176..e502a3e 100644
--- a/superset-frontend/src/datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/datasource/DatasourceEditor.jsx
@@ -40,7 +40,7 @@ import { getClientErrorObject } from 
'src/utils/getClientErrorObject';
 
 import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
 import TextControl from 'src/explore/components/controls/TextControl';
-import SelectControl from 'src/explore/components/controls/SelectControl';
+import { Select } from 'src/components';
 import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
 import SelectAsyncControl from 
'src/explore/components/controls/SelectAsyncControl';
 import SpatialControl from 'src/explore/components/controls/SpatialControl';
@@ -121,7 +121,12 @@ const StyledLabelWrapper = styled.div`
 const checkboxGenerator = (d, onChange) => (
   <CheckboxControl value={d} onChange={onChange} />
 );
-const DATA_TYPES = ['STRING', 'NUMERIC', 'DATETIME', 'BOOLEAN'];
+const DATA_TYPES = [
+  { value: 'STRING', label: 'STRING' },
+  { value: 'NUMERIC', label: 'NUMERIC' },
+  { value: 'DATETIME', label: 'DATETIME' },
+  { value: 'BOOLEAN', label: 'BOOLEAN' },
+];
 
 const DATASOURCE_TYPES_ARR = [
   { key: 'physical', label: t('Physical (table or view)') },
@@ -207,7 +212,13 @@ function ColumnCollectionTable({
                 fieldKey="type"
                 label={t('Data type')}
                 control={
-                  <SelectControl choices={DATA_TYPES} name="type" freeForm />
+                  <Select
+                    ariaLabel={t('Data type')}
+                    options={DATA_TYPES}
+                    name="type"
+                    allowNewOptions
+                    allowClear
+                  />
                 }
               />
             )}
diff --git 
a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
 
b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
index 2042cb8..355b38d 100644
--- 
a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
+++ 
b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
@@ -70,8 +70,6 @@ const propTypes = {
   addAnnotationLayer: PropTypes.func,
   removeAnnotationLayer: PropTypes.func,
   close: PropTypes.func,
-
-  onPopoverClear: PropTypes.func,
 };
 
 const defaultProps = {
@@ -95,7 +93,6 @@ const defaultProps = {
   addAnnotationLayer: () => {},
   removeAnnotationLayer: () => {},
   close: () => {},
-  onPopoverClear: () => {},
 };
 
 export default class AnnotationLayer extends React.PureComponent {
@@ -172,7 +169,6 @@ export default class AnnotationLayer extends 
React.PureComponent {
     );
     this.handleValue = this.handleValue.bind(this);
     this.isValidForm = this.isValidForm.bind(this);
-    this.popoverClearWrapper = this.popoverClearWrapper.bind(this);
   }
 
   componentDidMount() {
@@ -238,15 +234,6 @@ export default class AnnotationLayer extends 
React.PureComponent {
     return !errors.filter(x => x).length;
   }
 
-  popoverClearWrapper(value, actionMeta, callback) {
-    if (callback) {
-      callback(value);
-    }
-    if (actionMeta?.action === 'clear') {
-      this.props.onPopoverClear(true);
-    }
-  }
-
   handleAnnotationType(annotationType) {
     this.setState({
       annotationType,
@@ -266,7 +253,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
   handleValue(value) {
     this.setState({
       value,
-      descriptionColumns: null,
+      descriptionColumns: [],
       intervalEndColumn: null,
       timeColumn: null,
       titleColumn: null,
@@ -409,6 +396,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
     if (requiresQuery(sourceType)) {
       return (
         <SelectControl
+          ariaLabel={t('Annotation layer value')}
           name="annotation-layer-value"
           showHeader
           hovered
@@ -418,9 +406,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
           options={valueOptions}
           isLoading={isLoadingOptions}
           value={value}
-          onChange={(value, _, actionMeta) =>
-            this.popoverClearWrapper(value, actionMeta, this.handleValue)
-          }
+          onChange={this.handleValue}
           validationErrors={!value ? ['Mandatory'] : []}
           optionRenderer={this.renderOption}
         />
@@ -479,14 +465,17 @@ export default class AnnotationLayer extends 
React.PureComponent {
             {(annotationType === ANNOTATION_TYPES.EVENT ||
               annotationType === ANNOTATION_TYPES.INTERVAL) && (
               <SelectControl
+                ariaLabel={t('Annotation layer time column')}
                 hovered
                 name="annotation-layer-time-column"
                 label={
                   annotationType === ANNOTATION_TYPES.INTERVAL
-                    ? 'Interval start column'
-                    : 'Event time column'
+                    ? t('Interval start column')
+                    : t('Event time column')
                 }
-                description="This column must contain date/time information."
+                description={t(
+                  'This column must contain date/time information.',
+                )}
                 validationErrors={!timeColumn ? ['Mandatory'] : []}
                 clearable={false}
                 options={timeColumnOptions}
@@ -496,48 +485,42 @@ export default class AnnotationLayer extends 
React.PureComponent {
             )}
             {annotationType === ANNOTATION_TYPES.INTERVAL && (
               <SelectControl
+                ariaLabel={t('Annotation layer interval end')}
                 hovered
                 name="annotation-layer-intervalEnd"
-                label="Interval End column"
-                description="This column must contain date/time information."
+                label={t('Interval End column')}
+                description={t(
+                  'This column must contain date/time information.',
+                )}
                 validationErrors={!intervalEndColumn ? ['Mandatory'] : []}
                 options={columns}
                 value={intervalEndColumn}
-                onChange={(value, _, actionMeta) =>
-                  this.popoverClearWrapper(value, actionMeta, v =>
-                    this.setState({ intervalEndColumn: v }),
-                  )
-                }
+                onChange={value => this.setState({ intervalEndColumn: value })}
               />
             )}
             <SelectControl
+              ariaLabel={t('Annotation layer title column')}
               hovered
               name="annotation-layer-title"
-              label="Title Column"
-              description="Pick a title for you annotation."
+              label={t('Title Column')}
+              description={t('Pick a title for you annotation.')}
               options={[{ value: '', label: 'None' }].concat(columns)}
               value={titleColumn}
-              onChange={(value, _, actionMeta) =>
-                this.popoverClearWrapper(value, actionMeta, v =>
-                  this.setState({ titleColumn: v }),
-                )
-              }
+              onChange={value => this.setState({ titleColumn: value })}
             />
             {annotationType !== ANNOTATION_TYPES.TIME_SERIES && (
               <SelectControl
+                ariaLabel={t('Annotation layer description columns')}
                 hovered
                 name="annotation-layer-title"
-                label="Description Columns"
-                description={`Pick one or more columns that should be shown in 
the
-                  annotation. If you don't select a column all of them will be 
shown.`}
+                label={t('Description Columns')}
+                description={t(
+                  "Pick one or more columns that should be shown in the 
annotation. If you don't select a column all of them will be shown.",
+                )}
                 multi
                 options={columns}
                 value={descriptionColumns}
-                onChange={(value, _, actionMeta) =>
-                  this.popoverClearWrapper(value, actionMeta, v =>
-                    this.setState({ descriptionColumns: v }),
-                  )
-                }
+                onChange={value => this.setState({ descriptionColumns: value 
})}
               />
             )}
             <div style={{ marginTop: '1rem' }}>
@@ -629,6 +612,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
         info={t('Configure your how you overlay is displayed here.')}
       >
         <SelectControl
+          ariaLabel={t('Annotation layer stroke')}
           name="annotation-layer-stroke"
           label={t('Style')}
           // see '../../../visualizations/nvd3_vis.css'
@@ -643,6 +627,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
           onChange={v => this.setState({ style: v })}
         />
         <SelectControl
+          ariaLabel={t('Annotation layer opacity')}
           name="annotation-layer-opacity"
           label={t('Opacity')}
           // see '../../../visualizations/nvd3_vis.css'
@@ -653,11 +638,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
             { value: 'opacityHigh', label: '0.8' },
           ]}
           value={opacity}
-          onChange={(value, _, actionMeta) =>
-            this.popoverClearWrapper(value, actionMeta, v =>
-              this.setState({ opacity: v }),
-            )
-          }
+          onChange={value => this.setState({ opacity: value })}
         />
         <div>
           <ControlHeader label={t('Color')} />
@@ -746,6 +727,7 @@ export default class AnnotationLayer extends 
React.PureComponent {
                 onChange={v => this.setState({ show: !v })}
               />
               <SelectControl
+                ariaLabel={t('Annotation layer type')}
                 hovered
                 description={t('Choose the annotation layer type')}
                 label={t('Annotation layer type')}
@@ -757,19 +739,14 @@ export default class AnnotationLayer extends 
React.PureComponent {
               />
               {supportedSourceTypes.length > 0 && (
                 <SelectControl
+                  ariaLabel={t('Annotation source type')}
                   hovered
-                  description="Choose the source of your annotations"
-                  label="Annotation Source"
+                  description={t('Choose the source of your annotations')}
+                  label={t('Annotation Source')}
                   name="annotation-source-type"
                   options={supportedSourceTypes}
                   value={sourceType}
-                  onChange={(value, _, actionMeta) =>
-                    this.popoverClearWrapper(
-                      value,
-                      actionMeta,
-                      this.handleAnnotationSourceType,
-                    )
-                  }
+                  onChange={this.handleAnnotationSourceType}
                   validationErrors={!sourceType ? [t('Mandatory')] : []}
                 />
               )}
diff --git 
a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx
 
b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx
index beec955..ed8ae44 100644
--- 
a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx
+++ 
b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx
@@ -84,10 +84,22 @@ test('renders extra checkboxes when type is time series', 
async () => {
 });
 
 test('enables apply and ok buttons', async () => {
-  await waitForRender();
-  userEvent.type(screen.getByLabelText('Name'), 'Test');
-  userEvent.type(screen.getByLabelText('Formula'), '2x');
-  await waitFor(() => {
+  const { container } = render(<AnnotationLayer {...defaultProps} />);
+
+  waitFor(() => {
+    expect(container).toBeInTheDocument();
+  });
+
+  const nameInput = screen.getByRole('textbox', { name: 'Name' });
+  const formulaInput = screen.getByRole('textbox', { name: 'Formula' });
+
+  expect(nameInput).toBeInTheDocument();
+  expect(formulaInput).toBeInTheDocument();
+
+  userEvent.type(nameInput, 'Name');
+  userEvent.type(formulaInput, '2x');
+
+  waitFor(() => {
     expect(screen.getByRole('button', { name: 'Apply' })).toBeEnabled();
     expect(screen.getByRole('button', { name: 'OK' })).toBeEnabled();
   });
@@ -134,12 +146,17 @@ test('renders chart options', async () => {
   await waitForRender({
     annotationType: ANNOTATION_TYPES_METADATA.EVENT.value,
   });
-  userEvent.click(screen.getByText('2 option(s)'));
-  userEvent.click(screen.getByText('Superset annotation'));
-  expect(await screen.findByLabelText('Annotation layer')).toBeInTheDocument();
+  userEvent.click(
+    screen.getByRole('combobox', { name: 'Annotation source type' }),
+  );
   userEvent.click(screen.getByText('Superset annotation'));
+  expect(screen.getByText('Annotation layer')).toBeInTheDocument();
+
+  userEvent.click(
+    screen.getByRole('combobox', { name: 'Annotation source type' }),
+  );
   userEvent.click(screen.getByText('Table'));
-  expect(await screen.findByLabelText('Chart')).toBeInTheDocument();
+  expect(screen.getByText('Chart')).toBeInTheDocument();
 });
 
 test('keeps apply disabled when missing required fields', async () => {
@@ -147,18 +164,28 @@ test('keeps apply disabled when missing required fields', 
async () => {
     annotationType: ANNOTATION_TYPES_METADATA.EVENT.value,
     sourceType: 'Table',
   });
-  userEvent.click(screen.getByText('1 option(s)'));
-  await waitFor(() => userEvent.click(screen.getByText('Chart A')));
+  userEvent.click(
+    screen.getByRole('combobox', { name: 'Annotation layer value' }),
+  );
+  userEvent.click(await screen.findByText('Chart A'));
   expect(
     screen.getByText('Annotation Slice Configuration'),
   ).toBeInTheDocument();
 
   userEvent.click(screen.getByRole('button', { name: 'Automatic Color' }));
-  userEvent.click(screen.getByLabelText('Title Column'));
+  userEvent.click(
+    screen.getByRole('combobox', { name: 'Annotation layer title column' }),
+  );
   userEvent.click(screen.getByText('None'));
-  userEvent.click(screen.getByLabelText('Style'));
+  userEvent.click(screen.getByText('Style'));
+  userEvent.click(
+    screen.getByRole('combobox', { name: 'Annotation layer stroke' }),
+  );
   userEvent.click(screen.getByText('Dashed'));
-  userEvent.click(screen.getByLabelText('Opacity'));
+  userEvent.click(screen.getByText('Opacity'));
+  userEvent.click(
+    screen.getByRole('combobox', { name: 'Annotation layer opacity' }),
+  );
   userEvent.click(screen.getByText('0.5'));
 
   const checkboxes = screen.getAllByRole('checkbox');
diff --git 
a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
 
b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
index c94fe88..dc0130f 100644
--- 
a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
+++ 
b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
@@ -62,12 +62,10 @@ class AnnotationLayerControl extends React.PureComponent {
     this.state = {
       popoverVisible: {},
       addedAnnotationIndex: null,
-      popoverClear: false,
     };
     this.addAnnotationLayer = this.addAnnotationLayer.bind(this);
     this.removeAnnotationLayer = this.removeAnnotationLayer.bind(this);
     this.handleVisibleChange = this.handleVisibleChange.bind(this);
-    this.handlePopoverClear = this.handlePopoverClear.bind(this);
   }
 
   componentDidMount() {
@@ -105,19 +103,9 @@ class AnnotationLayerControl extends React.PureComponent {
   }
 
   handleVisibleChange(visible, popoverKey) {
-    if (!this.state.popoverClear) {
-      this.setState(prevState => ({
-        popoverVisible: { ...prevState.popoverVisible, [popoverKey]: visible },
-      }));
-    } else {
-      this.handlePopoverClear(false);
-    }
-  }
-
-  handlePopoverClear(popoverClear) {
-    this.setState({
-      popoverClear,
-    });
+    this.setState(prevState => ({
+      popoverVisible: { ...prevState.popoverVisible, [popoverKey]: visible },
+    }));
   }
 
   removeAnnotationLayer(annotation) {
@@ -143,7 +131,6 @@ class AnnotationLayerControl extends React.PureComponent {
             this.handleVisibleChange(false, popoverKey);
             this.setState({ addedAnnotationIndex: null });
           }}
-          onPopoverClear={this.handlePopoverClear}
         />
       </div>
     );
diff --git 
a/superset-frontend/src/explore/components/controls/FilterBoxItemControl/index.jsx
 
b/superset-frontend/src/explore/components/controls/FilterBoxItemControl/index.jsx
index e04ce8b..25aa79f 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterBoxItemControl/index.jsx
+++ 
b/superset-frontend/src/explore/components/controls/FilterBoxItemControl/index.jsx
@@ -23,7 +23,7 @@ import { InfoTooltipWithTrigger } from 
'@superset-ui/chart-controls';
 
 import Popover from 'src/components/Popover';
 import FormRow from 'src/components/FormRow';
-import SelectControl from 'src/explore/components/controls/SelectControl';
+import { Select } from 'src/components';
 import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
 import TextControl from 'src/explore/components/controls/TextControl';
 import { FILTER_CONFIG_ATTRIBUTES } from 'src/explore/constants';
@@ -136,12 +136,12 @@ export default class FilterBoxItemControl extends 
React.Component {
         <FormRow
           label={t('Column')}
           control={
-            <SelectControl
+            <Select
+              ariaLabel={t('Column')}
               value={this.state.column}
               name="column"
-              clearable={false}
               options={this.props.datasource.columns
-                .filter(col => col !== this.state.column)
+                .filter(col => col.column_name !== this.state.column)
                 .map(col => ({
                   value: col.column_name,
                   label: col.column_name,
@@ -184,11 +184,12 @@ export default class FilterBoxItemControl extends 
React.Component {
           label={t('Sort metric')}
           tooltip={t('Metric to sort the results by')}
           control={
-            <SelectControl
+            <Select
+              ariaLabel={t('Sort metric')}
               value={this.state.metric}
               name="column"
               options={this.props.datasource.metrics
-                .filter(metric => metric !== this.state.metric)
+                .filter(m => m.metric_name !== this.state.metric)
                 .map(m => ({
                   value: m.metric_name,
                   label: m.metric_name,
diff --git 
a/superset-frontend/src/explore/components/controls/SelectControl.jsx 
b/superset-frontend/src/explore/components/controls/SelectControl.jsx
index f14c748..4342485 100644
--- a/superset-frontend/src/explore/components/controls/SelectControl.jsx
+++ b/superset-frontend/src/explore/components/controls/SelectControl.jsx
@@ -18,11 +18,12 @@
  */
 import React from 'react';
 import PropTypes from 'prop-types';
-import { t, css } from '@superset-ui/core';
-import { Select, CreatableSelect, OnPasteSelect } from 'src/components/Select';
+import { css, t } from '@superset-ui/core';
+import { Select } from 'src/components';
 import ControlHeader from 'src/explore/components/ControlHeader';
 
 const propTypes = {
+  ariaLabel: PropTypes.string,
   autoFocus: PropTypes.bool,
   choices: PropTypes.array,
   clearable: PropTypes.bool,
@@ -30,10 +31,8 @@ const propTypes = {
   disabled: PropTypes.bool,
   freeForm: PropTypes.bool,
   isLoading: PropTypes.bool,
-  label: PropTypes.string,
   multi: PropTypes.bool,
   isMulti: PropTypes.bool,
-  allowAll: PropTypes.bool,
   name: PropTypes.string.isRequired,
   onChange: PropTypes.func,
   onFocus: PropTypes.func,
@@ -42,21 +41,29 @@ const propTypes = {
     PropTypes.number,
     PropTypes.array,
   ]),
+  default: PropTypes.oneOfType([
+    PropTypes.string,
+    PropTypes.number,
+    PropTypes.array,
+  ]),
   showHeader: PropTypes.bool,
   optionRenderer: PropTypes.func,
-  valueRenderer: PropTypes.func,
   valueKey: PropTypes.string,
   options: PropTypes.array,
   placeholder: PropTypes.string,
-  noResultsText: PropTypes.string,
-  selectRef: PropTypes.func,
   filterOption: PropTypes.func,
-  promptTextCreator: PropTypes.func,
-  commaChoosesOption: PropTypes.bool,
-  menuPortalTarget: PropTypes.element,
-  menuPosition: PropTypes.string,
-  menuPlacement: PropTypes.string,
-  forceOverflow: PropTypes.bool,
+
+  // ControlHeader props
+  label: PropTypes.string,
+  renderTrigger: PropTypes.bool,
+  validationErrors: PropTypes.array,
+  rightNode: PropTypes.node,
+  leftNode: PropTypes.node,
+  onClick: PropTypes.func,
+  hovered: PropTypes.bool,
+  tooltipOnClick: PropTypes.func,
+  warning: PropTypes.string,
+  danger: PropTypes.string,
 };
 
 const defaultProps = {
@@ -73,10 +80,6 @@ const defaultProps = {
   onFocus: () => {},
   showHeader: true,
   valueKey: 'value',
-  noResultsText: t('No results found'),
-  promptTextCreator: label => `Create Option ${label}`,
-  commaChoosesOption: true,
-  allowAll: false,
 };
 
 export default class SelectControl extends React.PureComponent {
@@ -84,13 +87,9 @@ export default class SelectControl extends 
React.PureComponent {
     super(props);
     this.state = {
       options: this.getOptions(props),
-      value: this.props.value,
     };
     this.onChange = this.onChange.bind(this);
-    this.createMetaSelectAllOption = this.createMetaSelectAllOption.bind(this);
-    this.select = null; // pointer to the react-select instance
-    this.getSelectRef = this.getSelectRef.bind(this);
-    this.handleKeyDownForCreate = this.handleKeyDownForCreate.bind(this);
+    this.handleFilterOptions = this.handleFilterOptions.bind(this);
   }
 
   UNSAFE_componentWillReceiveProps(nextProps) {
@@ -105,194 +104,130 @@ export default class SelectControl extends 
React.PureComponent {
 
   // Beware: This is acting like an on-click instead of an on-change
   // (firing every time user chooses vs firing only if a new option is chosen).
-  onChange(opt, actionMeta) {
-    let optionValue = this.props.multi ? [] : null;
-    if (opt) {
-      if (this.props.multi) {
-        opt.forEach(o => {
-          // select all options
-          if (o.meta === true) {
-            optionValue = this.getOptions(this.props)
-              .filter(x => !x.meta)
-              .map(x => x[this.props.valueKey]);
-            return;
-          }
-          optionValue.push(o[this.props.valueKey] || o);
-        });
-      } else if (opt.meta === true) {
-        return;
-      } else {
-        optionValue = opt[this.props.valueKey];
-      }
-    }
+  onChange(val) {
     // will eventually call `exploreReducer`: SET_FIELD_VALUE
-    this.props.onChange(optionValue, [], actionMeta);
-  }
+    const { valueKey } = this.props;
+    let onChangeVal = val;
 
-  getSelectRef(instance) {
-    this.select = instance;
-    if (this.props.selectRef) {
-      this.props.selectRef(instance);
+    if (Array.isArray(val)) {
+      const values = val.map(v => v?.[valueKey] || v);
+      onChangeVal = values;
+    }
+    if (typeof val === 'object' && val?.[valueKey]) {
+      onChangeVal = val[valueKey];
     }
+    this.props.onChange(onChangeVal, []);
   }
 
   getOptions(props) {
+    const { choices, optionRenderer, valueKey } = props;
     let options = [];
     if (props.options) {
-      options = props.options.map(x => x);
-    } else if (props.choices) {
+      options = props.options.map(o => ({
+        ...o,
+        value: o[valueKey],
+        label: o.label || o[valueKey],
+        customLabel: optionRenderer ? optionRenderer(o) : undefined,
+      }));
+    } else if (choices) {
       // Accepts different formats of input
-      options = props.choices.map(c => {
+      options = choices.map(c => {
         if (Array.isArray(c)) {
           const [value, label] = c.length > 1 ? c : [c[0], c[0]];
-          return { label, [props.valueKey]: value };
+          return {
+            value,
+            label,
+          };
         }
         if (Object.is(c)) {
-          return c;
+          return {
+            ...c,
+            value: c[valueKey],
+            label: c.label || c[valueKey],
+          };
         }
-        return { label: c, [props.valueKey]: c };
+        return { value: c, label: c };
       });
     }
-    // For FreeFormSelect, insert newly created values into options
-    if (props.freeForm && props.value) {
-      const existingOptionValues = new Set(options.map(c => 
c[props.valueKey]));
-      const selectedValues = Array.isArray(props.value)
-        ? props.value
-        : [props.value];
-      selectedValues.forEach(v => {
-        if (!existingOptionValues.has(v)) {
-          // place the newly created options at the top
-          options.unshift({ label: v, [props.valueKey]: v });
-        }
-      });
-    }
-    if (props.allowAll === true && props.multi === true) {
-      if (!this.optionsIncludesSelectAll(options)) {
-        options.unshift(this.createMetaSelectAllOption());
-      }
-    } else {
-      options = options.filter(o => !this.isMetaSelectAllOption(o));
-    }
     return options;
   }
 
-  handleKeyDownForCreate(event) {
-    const { key } = event;
-    if (key === 'Tab' || (this.props.commaChoosesOption && key === ',')) {
-      // simulate an Enter event
-      if (this.select) {
-        this.select.onKeyDown({ ...event, key: 'Enter' });
-      }
-    }
-  }
-
-  isMetaSelectAllOption(o) {
-    return o.meta && o.meta === true && o.label === 'Select all';
-  }
-
-  optionsIncludesSelectAll(o) {
-    return o.findIndex(o => this.isMetaSelectAllOption(o)) >= 0;
-  }
-
-  optionsRemaining() {
-    const { options } = this.state;
-    const { value } = this.props;
-    // if select is multi/value is array, we show the options not selected
-    let remainingOptions = Array.isArray(value)
-      ? options.length - value.length
-      : options.length;
-    if (this.optionsIncludesSelectAll(options)) {
-      remainingOptions -= 1;
-    }
-    return remainingOptions < 0 ? 0 : remainingOptions;
-  }
-
-  createMetaSelectAllOption() {
-    const option = { label: 'Select all', meta: true };
-    option[this.props.valueKey] = 'Select all';
-    return option;
+  handleFilterOptions(text, option) {
+    const { filterOption } = this.props;
+    return filterOption({ data: option }, text);
   }
 
   render() {
-    //  Tab, comma or Enter will trigger a new option created for 
FreeFormSelect
     const {
+      ariaLabel,
       autoFocus,
       clearable,
       disabled,
       filterOption,
+      freeForm,
       isLoading,
+      isMulti,
       label,
-      menuPlacement,
+      multi,
       name,
-      noResultsText,
+      placeholder,
       onFocus,
       optionRenderer,
-      promptTextCreator,
+      showHeader,
       value,
-      valueKey,
-      valueRenderer,
-      forceOverflow,
-      menuPortalTarget,
-      menuPosition,
+      // ControlHeader props
+      description,
+      renderTrigger,
+      rightNode,
+      leftNode,
+      validationErrors,
+      onClick,
+      hovered,
+      tooltipOnClick,
+      warning,
+      danger,
     } = this.props;
 
-    const optionsRemaining = this.optionsRemaining();
-    const optionRemaingText = optionsRemaining
-      ? t('%s option(s)', optionsRemaining)
-      : '';
-    const placeholder = this.props.placeholder || optionRemaingText;
-    const isMulti = this.props.isMulti || this.props.multi;
-
-    let assistiveText;
-    if (
-      isMulti &&
-      optionsRemaining &&
-      Array.isArray(this.state.value) &&
-      Array.isArray(value) &&
-      !!value.length
-    ) {
-      assistiveText = optionRemaingText;
-    }
+    const headerProps = {
+      name,
+      label,
+      description,
+      renderTrigger,
+      rightNode,
+      leftNode,
+      validationErrors,
+      onClick,
+      hovered,
+      tooltipOnClick,
+      warning,
+      danger,
+    };
 
     const selectProps = {
+      allowNewOptions: freeForm,
       autoFocus,
-      'aria-label': label,
-      clearable,
+      ariaLabel:
+        ariaLabel || (typeof label === 'string' ? label : t('Select ...')),
+      allowClear: clearable,
       disabled,
-      filterOption,
-      ignoreAccents: false,
-      isLoading,
-      isMulti,
-      labelKey: 'label',
-      menuPlacement,
-      forceOverflow,
-      menuPortalTarget,
-      menuPosition,
+      filterOption:
+        filterOption && typeof filterOption === 'function'
+          ? this.handleFilterOptions
+          : true,
+      header: showHeader && <ControlHeader {...headerProps} />,
+      loading: isLoading,
+      mode: isMulti || multi ? 'multiple' : 'single',
       name: `select-${name}`,
-      noResultsText,
       onChange: this.onChange,
       onFocus,
       optionRenderer,
-      value,
       options: this.state.options,
       placeholder,
-      assistiveText,
-      promptTextCreator,
-      selectRef: this.getSelectRef,
-      valueKey,
-      valueRenderer,
+      value:
+        value ||
+        (this.props.default !== undefined ? this.props.default : undefined),
     };
 
-    let SelectComponent;
-    if (this.props.freeForm) {
-      SelectComponent = CreatableSelect;
-      // Don't create functions in `render` because React relies on shallow
-      // compare to decide weathere to rerender child components.
-      selectProps.onKeyDown = this.handleKeyDownForCreate;
-    } else {
-      SelectComponent = Select;
-    }
-
     return (
       <div
         css={theme => css`
@@ -307,12 +242,7 @@ export default class SelectControl extends 
React.PureComponent {
           }
         `}
       >
-        {this.props.showHeader && <ControlHeader {...this.props} />}
-        {isMulti ? (
-          <OnPasteSelect {...selectProps} selectWrap={SelectComponent} />
-        ) : (
-          <SelectComponent {...selectProps} />
-        )}
+        <Select {...selectProps} />
       </div>
     );
   }
diff --git 
a/superset-frontend/src/explore/components/controls/SpatialControl.jsx 
b/superset-frontend/src/explore/components/controls/SpatialControl.jsx
index cf9ae6b..23456db 100644
--- a/superset-frontend/src/explore/components/controls/SpatialControl.jsx
+++ b/superset-frontend/src/explore/components/controls/SpatialControl.jsx
@@ -134,6 +134,7 @@ export default class SpatialControl extends React.Component 
{
   renderSelect(name, type) {
     return (
       <SelectControl
+        ariaLabel={name}
         name={name}
         choices={this.props.choices}
         value={this.state[name]}
diff --git a/superset-frontend/src/explore/controls.jsx 
b/superset-frontend/src/explore/controls.jsx
index e54f7ab..f28a197 100644
--- a/superset-frontend/src/explore/controls.jsx
+++ b/superset-frontend/src/explore/controls.jsx
@@ -125,15 +125,12 @@ const groupByControl = {
   includeTime: false,
   description: t('One or many controls to group by'),
   optionRenderer: c => <StyledColumnOption column={c} showType />,
-  valueRenderer: c => <StyledColumnOption column={c} />,
   valueKey: 'column_name',
-  allowAll: true,
   filterOption: ({ data: opt }, text) =>
     (opt.column_name &&
       opt.column_name.toLowerCase().indexOf(text.toLowerCase()) >= 0) ||
     (opt.verbose_name &&
       opt.verbose_name.toLowerCase().indexOf(text.toLowerCase()) >= 0),
-  promptTextCreator: label => label,
   mapStateToProps: (state, control) => {
     const newState = {};
     if (state.datasource) {
@@ -144,7 +141,6 @@ const groupByControl = {
     }
     return newState;
   },
-  commaChoosesOption: false,
 };
 
 const metrics = {
@@ -266,7 +262,7 @@ export const controls = {
     type: 'SelectControl',
     freeForm: true,
     label: TIME_FILTER_LABELS.granularity,
-    default: 'one day',
+    default: 'P1D',
     choices: [
       [null, 'all'],
       ['PT5S', '5 seconds'],
@@ -304,7 +300,6 @@ export const controls = {
     ),
     clearable: false,
     optionRenderer: c => <StyledColumnOption column={c} showType />,
-    valueRenderer: c => <StyledColumnOption column={c} />,
     valueKey: 'column_name',
     mapStateToProps: state => {
       const props = {};

Reply via email to