diff --git a/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts index d1969f341d8..1880f7fc086 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts @@ -74,6 +74,7 @@ describe('AdhocFilters', () => { cy.get('[data-test=adhoc_filters] input[type=text]') .focus() .type('name{enter}'); + cy.get('.adhoc-filter-option').click(); cy.wait('@filterValues'); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts index b422673f5fa..85db968b610 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts @@ -42,6 +42,7 @@ describe('AdhocMetrics', () => { .trigger('mousedown') .click(); + cy.get('[data-test="option-label"]').first().click(); cy.get('[data-test="AdhocMetricEditTitle#trigger"]').click(); cy.get('[data-test="AdhocMetricEditTitle#input"]').type(metricName); cy.get('[data-test="AdhocMetricEdit#save"]').contains('Save').click(); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts index 31291e365d9..b0504bd67ba 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts @@ -74,7 +74,7 @@ describe('Annotations', () => { cy.get('button').click(); }); - cy.get('.popover-content').within(() => { + cy.get('[data-test="popover-content"]').within(() => { cy.get('[data-test=annotation-layer-name-header]') .siblings() .first() 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 dd52849ea18..674974d7616 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts @@ -48,7 +48,9 @@ describe('Datasource control', () => { }); // create new metric - cy.get('a[role="tab"]').contains('Metrics').click(); + cy.get('.modal-content').within(() => { + cy.get('a[role="tab"]').contains('Metrics').click(); + }); cy.get('button').contains('Add Item', { timeout: 10000 }).click(); cy.get('input[value=""]').click(); cy.get('input[value=""]') @@ -65,7 +67,9 @@ describe('Datasource control', () => { // delete metric cy.get('#datasource_menu').click(); cy.get('a').contains('Edit Dataset').click(); - cy.get('a[role="tab"]').contains('Metrics').click(); + cy.get('.modal-content').within(() => { + cy.get('a[role="tab"]').contains('Metrics').click(); + }); cy.get(`input[value="${newMetricName}"]`) .closest('tr') .find('.fa-trash') @@ -142,7 +146,7 @@ describe('Time range filter', () => { }); }); cy.get('#filter-popover button').contains('Ok').click(); - cy.get('#filter-popover').should('not.exist'); + cy.get('#filter-popover').should('not.be.visible'); }); }); diff --git a/superset-frontend/spec/javascripts/components/URLShortLinkButton_spec.jsx b/superset-frontend/spec/javascripts/components/URLShortLinkButton_spec.jsx index 87be28d765d..f58f2aa3fa0 100644 --- a/superset-frontend/spec/javascripts/components/URLShortLinkButton_spec.jsx +++ b/superset-frontend/spec/javascripts/components/URLShortLinkButton_spec.jsx @@ -20,7 +20,7 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import { shallow } from 'enzyme'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import URLShortLinkButton from 'src/components/URLShortLinkButton'; describe('URLShortLinkButton', () => { @@ -40,6 +40,6 @@ describe('URLShortLinkButton', () => { it('renders OverlayTrigger', () => { const wrapper = setup(); - expect(wrapper.find(OverlayTrigger)).toExist(); + expect(wrapper.find(Popover)).toExist(); }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx index 652c16288aa..c8e91c6f744 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx @@ -20,7 +20,7 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { Popover, Tab, Tabs } from 'react-bootstrap'; +import { Tab, Tabs } from 'react-bootstrap'; import Button from 'src/components/Button'; import AdhocFilter, { @@ -81,7 +81,6 @@ function setup(overrides) { describe('AdhocFilterEditPopover', () => { it('renders simple tab content by default', () => { const { wrapper } = setup(); - expect(wrapper.find(Popover)).toExist(); expect(wrapper.find(Tabs)).toExist(); expect(wrapper.find(Tab)).toHaveLength(2); expect(wrapper.find(Button)).toHaveLength(2); @@ -92,7 +91,6 @@ describe('AdhocFilterEditPopover', () => { it('renders sql tab content when the adhoc filter expressionType is sql', () => { const { wrapper } = setup({ adhocFilter: sqlAdhocFilter }); - expect(wrapper.find(Popover)).toExist(); expect(wrapper.find(Tabs)).toExist(); expect(wrapper.find(Tab)).toHaveLength(2); expect(wrapper.find(Button)).toHaveLength(2); diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx index 118ef329e8b..efdd7c14a47 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx @@ -20,7 +20,7 @@ import React from 'react'; import sinon from 'sinon'; import { styledShallow as shallow } from 'spec/helpers/theming'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import Label from 'src/components/Label'; import AdhocFilter, { @@ -53,15 +53,15 @@ function setup(overrides) { describe('AdhocFilterOption', () => { it('renders an overlay trigger wrapper for the label', () => { const { wrapper } = setup(); - const overlay = wrapper.find(OverlayTrigger); + const overlay = wrapper.find(Popover); expect(overlay).toHaveLength(1); - expect(overlay.props().defaultOverlayShown).toBe(false); + expect(overlay.props().defaultVisible).toBe(false); expect(wrapper.find(Label)).toExist(); }); it('should open new filter popup by default', () => { const { wrapper } = setup({ adhocFilter: simpleAdhocFilter.duplicateWith({ isNew: true }), }); - expect(wrapper.find(OverlayTrigger).props().defaultOverlayShown).toBe(true); + expect(wrapper.find(Popover).props().defaultVisible).toBe(true); }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx index 44bfd8ee5cd..78c94e458e3 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx @@ -22,25 +22,17 @@ import sinon from 'sinon'; import { shallow } from 'enzyme'; import { OverlayTrigger } from 'react-bootstrap'; -import AdhocMetric from 'src/explore/AdhocMetric'; import AdhocMetricEditPopoverTitle from 'src/explore/components/AdhocMetricEditPopoverTitle'; -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' }, -]; - -const sumValueAdhocMetric = new AdhocMetric({ - column: columns[2], - aggregate: AGGREGATES.SUM, -}); +const title = { + label: 'Title', + hasCustomLabel: false, +}; function setup(overrides) { const onChange = sinon.spy(); const props = { - adhocMetric: sumValueAdhocMetric, + title, onChange, ...overrides, }; diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx index b08b1d389ba..4886760a622 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx @@ -20,7 +20,7 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { FormGroup, Popover } from 'react-bootstrap'; +import { FormGroup } from 'react-bootstrap'; import Button from 'src/components/Button'; import AdhocMetric, { EXPRESSION_TYPES } from 'src/explore/AdhocMetric'; @@ -62,7 +62,6 @@ function setup(overrides) { describe('AdhocMetricEditPopover', () => { it('renders a popover with edit metric form contents', () => { const { wrapper } = setup(); - expect(wrapper.find(Popover)).toExist(); expect(wrapper.find(FormGroup)).toHaveLength(3); expect(wrapper.find(Button)).toHaveLength(2); }); @@ -91,21 +90,6 @@ describe('AdhocMetricEditPopover', () => { ); }); - it('overwrites the adhocMetric in state with onLabelChange', () => { - const { wrapper } = setup(); - wrapper.instance().onLabelChange({ target: { value: 'new label' } }); - expect(wrapper.state('adhocMetric').label).toBe('new label'); - expect(wrapper.state('adhocMetric').hasCustomLabel).toBe(true); - }); - - it('returns to default labels when the custom label is cleared', () => { - const { wrapper } = setup(); - wrapper.instance().onLabelChange({ target: { value: 'new label' } }); - wrapper.instance().onLabelChange({ target: { value: '' } }); - expect(wrapper.state('adhocMetric').label).toBe('SUM(value)'); - expect(wrapper.state('adhocMetric').hasCustomLabel).toBe(false); - }); - it('prevents saving if no column or aggregate is chosen', () => { const { wrapper } = setup(); expect(wrapper.find(Button).find({ disabled: true })).not.toExist(); diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx index 5b23da24f5f..c2e6629a607 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx @@ -20,8 +20,8 @@ import React from 'react'; import sinon from 'sinon'; import { styledShallow as shallow } from 'spec/helpers/theming'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import Label from 'src/components/Label'; import AdhocMetric from 'src/explore/AdhocMetric'; import AdhocMetricOption from 'src/explore/components/AdhocMetricOption'; @@ -53,7 +53,7 @@ function setup(overrides) { describe('AdhocMetricOption', () => { it('renders an overlay trigger wrapper for the label', () => { const { wrapper } = setup(); - expect(wrapper.find(OverlayTrigger)).toExist(); + expect(wrapper.find(Popover)).toExist(); expect(wrapper.find(Label)).toExist(); }); @@ -61,6 +61,24 @@ describe('AdhocMetricOption', () => { const { wrapper } = setup({ adhocMetric: sumValueAdhocMetric.duplicateWith({ isNew: true }), }); - expect(wrapper.find(OverlayTrigger).props().defaultOverlayShown).toBe(true); + expect(wrapper.find(Popover).props().defaultVisible).toBe(true); + }); + + it('overwrites the adhocMetric in state with onLabelChange', () => { + const { wrapper } = setup(); + wrapper.instance().onLabelChange({ target: { value: 'new label' } }); + expect(wrapper.state('title').label).toBe('new label'); + expect(wrapper.state('title').hasCustomLabel).toBe(true); + }); + + it('returns to default labels when the custom label is cleared', () => { + const { wrapper } = setup(); + wrapper.instance().onLabelChange({ target: { value: 'new label' } }); + wrapper.instance().onLabelChange({ target: { value: '' } }); + // close and open the popover + wrapper.instance().closeMetricEditOverlay(); + wrapper.instance().onOverlayEntered(); + expect(wrapper.state('title').label).toBe('SUM(value)'); + expect(wrapper.state('title').hasCustomLabel).toBe(false); }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx index ee2b7ec7a02..4d675c738bd 100644 --- a/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx @@ -16,16 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -/* eslint-disable no-unused-expressions */ import React from 'react'; import { shallow } from 'enzyme'; -import { OverlayTrigger } from 'react-bootstrap'; import { SketchPicker } from 'react-color'; import { CategoricalScheme, getCategoricalSchemeRegistry, } from '@superset-ui/core'; - +import Popover from 'src/common/components/Popover'; import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl'; import ControlHeader from 'src/explore/components/ControlHeader'; @@ -53,7 +51,7 @@ describe('ColorPickerControl', () => { it('renders a OverlayTrigger', () => { const controlHeader = wrapper.find(ControlHeader); expect(controlHeader).toHaveLength(1); - expect(wrapper.find(OverlayTrigger)).toExist(); + expect(wrapper.find(Popover)).toExist(); }); it('renders a Popover with a SketchPicker', () => { diff --git a/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx index e955722c0fd..fd1984d6a87 100644 --- a/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx @@ -18,10 +18,11 @@ */ /* eslint-disable no-unused-expressions */ import React from 'react'; -import { OverlayTrigger, Popover, Tab, Tabs, Radio } from 'react-bootstrap'; +import { OverlayTrigger, Tab, Tabs, Radio } from 'react-bootstrap'; import sinon from 'sinon'; import { styledMount as mount } from 'spec/helpers/theming'; +import Popover from 'src/common/components/Popover'; import Label from 'src/components/Label'; import DateFilterControl from 'src/explore/components/controls/DateFilterControl'; import ControlHeader from 'src/explore/components/ControlHeader'; @@ -57,15 +58,8 @@ describe('DateFilterControl', () => { expect(controlHeader).toHaveLength(1); }); - it('renders an OverlayTrigger', () => { - expect(wrapper.find(OverlayTrigger)).toExist(); - }); - - it('renders a popover', () => { - const { overlay } = wrapper.find(OverlayTrigger).first().props(); - const overlayWrapper = mount(overlay); - - expect(overlayWrapper.find(Popover)).toExist(); + it('renders an Popover', () => { + expect(wrapper.find(Popover)).toExist(); }); it('calls open/close methods on trigger click', () => { @@ -87,27 +81,26 @@ describe('DateFilterControl', () => { }); it('renders two tabs in popover', () => { - const { overlay } = wrapper.find(OverlayTrigger).first().props(); - const overlayWrapper = mount(overlay); - const popover = overlayWrapper.find(Popover).first(); + const popoverContent = wrapper.find(Popover).first().props().content; + const popoverContentWrapper = mount(popoverContent); - expect(popover.find(Tabs)).toExist(); - expect(popover.find(Tab)).toHaveLength(2); + expect(popoverContentWrapper.find(Tabs)).toExist(); + expect(popoverContentWrapper.find(Tab)).toHaveLength(2); }); it('renders default time options', () => { - const { overlay } = wrapper.find(OverlayTrigger).first().props(); - const overlayWrapper = mount(overlay); - const defaultTab = overlayWrapper.find(Tab).first(); + const popoverContent = wrapper.find(Popover).first().props().content; + const popoverContentWrapper = mount(popoverContent); + const defaultTab = popoverContentWrapper.find(Tab).first(); expect(defaultTab.find(Radio)).toExist(); expect(defaultTab.find(Radio)).toHaveLength(6); }); it('renders tooltips over timeframe options', () => { - const { overlay } = wrapper.find(OverlayTrigger).first().props(); - const overlayWrapper = mount(overlay); - const defaultTab = overlayWrapper.find(Tab).first(); + const popoverContent = wrapper.find(Popover).first().props().content; + const popoverContentWrapper = mount(popoverContent); + const defaultTab = popoverContentWrapper.find(Tab).first(); const radioTrigger = defaultTab.find(OverlayTrigger); expect(radioTrigger).toExist(); @@ -115,9 +108,9 @@ describe('DateFilterControl', () => { }); it('renders the correct time range in tooltip', () => { - const { overlay } = wrapper.find(OverlayTrigger).first().props(); - const overlayWrapper = mount(overlay); - const defaultTab = overlayWrapper.find(Tab).first(); + const popoverContent = wrapper.find(Popover).first().props().content; + const popoverContentWrapper = mount(popoverContent); + const defaultTab = popoverContentWrapper.find(Tab).first(); const triggers = defaultTab.find(OverlayTrigger); const expectedLabels = { diff --git a/superset-frontend/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx b/superset-frontend/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx index 428ddad3baf..e0c51beecd3 100644 --- a/superset-frontend/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { shallow, mount } from 'enzyme'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import sinon from 'sinon'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; @@ -43,7 +43,7 @@ describe('EmbedCodeButton', () => { it('renders overlay trigger', () => { const wrapper = shallow(); - expect(wrapper.find(OverlayTrigger)).toExist(); + expect(wrapper.find(Popover)).toExist(); }); it('should create a short, standalone, explore url', () => { @@ -62,7 +62,7 @@ describe('EmbedCodeButton', () => { shortUrlId: 100, }); - const trigger = wrapper.find(OverlayTrigger); + const trigger = wrapper.find(Popover); trigger.simulate('click'); expect(spy1.callCount).toBe(1); expect(spy2.callCount).toBe(1); diff --git a/superset-frontend/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx index d5300256dc2..50e59276ab8 100644 --- a/superset-frontend/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx @@ -20,8 +20,8 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import FilterBoxItemControl from 'src/explore/components/controls/FilterBoxItemControl'; import FormRow from 'src/components/FormRow'; import datasources from '../../../fixtures/mockDatasource'; @@ -45,8 +45,8 @@ describe('FilterBoxItemControl', () => { inst = wrapper.instance(); }); - it('renders an OverlayTrigger', () => { - expect(wrapper.find(OverlayTrigger)).toExist(); + it('renders a Popover', () => { + expect(wrapper.find(Popover)).toExist(); }); it('renderForms does the job', () => { diff --git a/superset-frontend/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx index 7137ee28803..ff15552e5ec 100644 --- a/superset-frontend/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx @@ -18,10 +18,10 @@ */ /* eslint-disable no-unused-expressions */ import React from 'react'; -import { FormControl, OverlayTrigger } from 'react-bootstrap'; +import { FormControl } from 'react-bootstrap'; import sinon from 'sinon'; import { shallow } from 'enzyme'; - +import Popover from 'src/common/components/Popover'; import TimeSeriesColumnControl from 'src/explore/components/controls/TimeSeriesColumnControl'; const defaultProps = { @@ -39,7 +39,7 @@ describe('SelectControl', () => { }); it('renders an OverlayTrigger', () => { - expect(wrapper.find(OverlayTrigger)).toExist(); + expect(wrapper.find(Popover)).toExist(); }); it('renders an Popover', () => { diff --git a/superset-frontend/spec/javascripts/explore/components/ViewportControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ViewportControl_spec.jsx index 720e5db4ce5..9a1e9b8d1a1 100644 --- a/superset-frontend/spec/javascripts/explore/components/ViewportControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/ViewportControl_spec.jsx @@ -19,7 +19,7 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { styledMount as mount } from 'spec/helpers/theming'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import Label from 'src/components/Label'; import ViewportControl from 'src/explore/components/controls/ViewportControl'; @@ -48,7 +48,7 @@ describe('ViewportControl', () => { it('renders a OverlayTrigger', () => { const controlHeader = wrapper.find(ControlHeader); expect(controlHeader).toHaveLength(1); - expect(wrapper.find(OverlayTrigger)).toExist(); + expect(wrapper.find(Popover)).toExist(); }); it('renders a Popover with 5 TextControl', () => { diff --git a/superset-frontend/spec/javascripts/sqllab/LimitControl_spec.jsx b/superset-frontend/spec/javascripts/sqllab/LimitControl_spec.jsx index ec7ce9e2d61..53fb00d6add 100644 --- a/superset-frontend/spec/javascripts/sqllab/LimitControl_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/LimitControl_spec.jsx @@ -17,11 +17,13 @@ * under the License. */ import React from 'react'; -import { shallow } from 'enzyme'; +import { supersetTheme, ThemeProvider } from '@superset-ui/core'; +import { shallow, mount } from 'enzyme'; import Label from 'src/components/Label'; import LimitControl from 'src/SqlLab/components/LimitControl'; import ControlHeader from 'src/explore/components/ControlHeader'; +import Popover from 'src/common/components/Popover'; describe('LimitControl', () => { const defaultProps = { @@ -48,44 +50,71 @@ describe('LimitControl', () => { wrapper = shallow(factory({ ...defaultProps, value })); expect(wrapper.state().textValue).toEqual(value.toString()); wrapper.find(Label).first().simulate('click'); - expect(wrapper.state().showOverlay).toBe(true); - expect(wrapper.find(ControlHeader).props().validationErrors).toHaveLength( - 0, - ); + expect(wrapper.find(Popover).props().visible).toBe(true); + const popoverContentWrapper = mount(wrapper.instance().renderPopover(), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); + expect( + popoverContentWrapper.find(ControlHeader).props().validationErrors, + ).toHaveLength(0); }); it('handles invalid value', () => { wrapper.find(Label).first().simulate('click'); wrapper.setState({ textValue: 'invalid' }); - expect(wrapper.find(ControlHeader).props().validationErrors).toHaveLength( - 1, - ); + const popoverContentWrapper = mount(wrapper.instance().renderPopover(), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); + expect( + popoverContentWrapper.find(ControlHeader).props().validationErrors, + ).toHaveLength(1); }); it('handles negative value', () => { wrapper.find(Label).first().simulate('click'); wrapper.setState({ textValue: '-1' }); - expect(wrapper.find(ControlHeader).props().validationErrors).toHaveLength( - 1, - ); + const popoverContentWrapper = mount(wrapper.instance().renderPopover(), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); + expect( + popoverContentWrapper.find(ControlHeader).props().validationErrors, + ).toHaveLength(1); }); it('handles value above max row', () => { wrapper.find(Label).first().simulate('click'); wrapper.setState({ textValue: (defaultProps.maxRow + 1).toString() }); - expect(wrapper.find(ControlHeader).props().validationErrors).toHaveLength( - 1, - ); + const popoverContentWrapper = mount(wrapper.instance().renderPopover(), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); + expect( + popoverContentWrapper.find(ControlHeader).props().validationErrors, + ).toHaveLength(1); }); it('opens and closes', () => { wrapper.find(Label).first().simulate('click'); expect(wrapper.state().showOverlay).toBe(true); - wrapper.find('.ok').first().simulate('click'); + const popoverContentWrapper = mount(wrapper.instance().renderPopover(), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); + popoverContentWrapper.find('.ok').first().simulate('click'); expect(wrapper.state().showOverlay).toBe(false); }); it('resets and closes', () => { const value = 100; - wrapper = shallow(factory({ ...defaultProps, value })); + wrapper = mount(factory({ ...defaultProps, value }), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); wrapper.find(Label).first().simulate('click'); expect(wrapper.state().textValue).toEqual(value.toString()); - wrapper.find('.reset').simulate('click'); + const popoverContentWrapper = mount(wrapper.instance().renderPopover(), { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); + popoverContentWrapper.find('.reset').first().simulate('click'); expect(wrapper.state().textValue).toEqual( defaultProps.defaultQueryLimit.toString(), ); diff --git a/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx b/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx index bde316ddb0e..05bc7446247 100644 --- a/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx @@ -19,7 +19,7 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import fetchMock from 'fetch-mock'; import * as featureFlags from 'src/featureFlags'; import { shallow } from 'enzyme'; @@ -88,7 +88,7 @@ describe('ShareSqlLabQuery via /kv/store', () => { it('renders an OverlayTrigger with Button', () => { const wrapper = setup(); - const trigger = wrapper.find(OverlayTrigger); + const trigger = wrapper.find(Popover); const button = trigger.find(Button); expect(trigger).toHaveLength(1); @@ -154,7 +154,7 @@ describe('ShareSqlLabQuery via /kv/store', () => { it('renders an OverlayTrigger with Button', () => { const wrapper = setup(); - const trigger = wrapper.find(OverlayTrigger); + const trigger = wrapper.find(Popover); const button = trigger.find(Button); expect(trigger).toHaveLength(1); diff --git a/superset-frontend/src/SqlLab/components/LimitControl.tsx b/superset-frontend/src/SqlLab/components/LimitControl.tsx index b5cca637564..95348e321a7 100644 --- a/superset-frontend/src/SqlLab/components/LimitControl.tsx +++ b/superset-frontend/src/SqlLab/components/LimitControl.tsx @@ -17,16 +17,11 @@ * under the License. */ import React from 'react'; -import { - FormGroup, - FormControl, - Overlay, - Popover, - FormControlProps, -} from 'react-bootstrap'; -import Button from 'src/components/Button'; +import { FormGroup, FormControl, FormControlProps } from 'react-bootstrap'; import { t, styled } from '@superset-ui/core'; +import Popover from 'src/common/components/Popover'; +import Button from 'src/components/Button'; import Label from 'src/components/Label'; import ControlHeader from '../../explore/components/ControlHeader'; @@ -57,7 +52,7 @@ export default class LimitControl extends React.PureComponent< textValue: (value || defaultQueryLimit).toString(), showOverlay: false, }; - this.handleHide = this.handleHide.bind(this); + this.handleVisibleChange = this.handleVisibleChange.bind(this); this.handleToggle = this.handleToggle.bind(this); this.submitAndClose = this.submitAndClose.bind(this); } @@ -86,8 +81,8 @@ export default class LimitControl extends React.PureComponent< this.setState(prevState => ({ showOverlay: !prevState.showOverlay })); } - handleHide() { - this.setState({ showOverlay: false }); + handleVisibleChange(visible: boolean) { + this.setState({ showOverlay: visible }); } renderPopover() { @@ -99,68 +94,65 @@ export default class LimitControl extends React.PureComponent< ? t(' and not greater than %s', this.props.maxRow) : ''); return ( - - - + + + , + ) => + this.setState({ + textValue: (event.currentTarget?.value as string) ?? '', + }) + } /> - - , - ) => - this.setState({ - textValue: (event.currentTarget?.value as string) ?? '', - }) - } - /> - -
- - -
-
-
+ +
+ + +
+ ); } render() { return (
- - - {this.renderPopover()} - + +
); } diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.jsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.jsx index 0056a5c145f..c20eee95d7e 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.jsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.jsx @@ -18,7 +18,7 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { Popover, OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import { t } from '@superset-ui/core'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; @@ -89,31 +89,29 @@ class ShareSqlLabQuery extends React.Component { renderPopover() { return ( - +
} /> - +
); } render() { return ( - - +
); } } diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx index 231c0bf7172..b500a3f24aa 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx @@ -109,10 +109,6 @@ export default class SqlEditorLeftBar extends React.PureComponent { this.props.actions.addTable(this.props.queryEditor, tableName, schemaName); } - closePopover(ref) { - this.refs[ref].hide(); - } - render() { const shouldShowReset = window.location.search === '?reset=1'; const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above diff --git a/superset-frontend/src/common/components/Popover.tsx b/superset-frontend/src/common/components/Popover.tsx new file mode 100644 index 00000000000..1f8442ca865 --- /dev/null +++ b/superset-frontend/src/common/components/Popover.tsx @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Popover as AntdPopover } from 'src/common/components'; +import { styled } from '@superset-ui/core'; + +const SupersetPopover = styled(AntdPopover)``; + +export default SupersetPopover; diff --git a/superset-frontend/src/common/components/common.stories.tsx b/superset-frontend/src/common/components/common.stories.tsx index 6b564d026ff..d142f2ac189 100644 --- a/superset-frontend/src/common/components/common.stories.tsx +++ b/superset-frontend/src/common/components/common.stories.tsx @@ -18,11 +18,13 @@ */ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean } from '@storybook/addon-knobs'; +import { withKnobs, boolean, select } from '@storybook/addon-knobs'; import Modal from './Modal'; import Tabs, { EditableTabs } from './Tabs'; +import AntdPopover from './Popover'; import { Menu } from '.'; import { Dropdown } from './Dropdown'; +import Button from '../../components/Button'; export default { title: 'Common Components', @@ -116,3 +118,31 @@ export const TabsWithDropdownMenu = () => ( ); + +export const Popover = () => ( + CONTENT} + > + + +); diff --git a/superset-frontend/src/components/Hotkeys.jsx b/superset-frontend/src/components/Hotkeys.jsx index fbe76b2e79d..87a1bf5aa8f 100644 --- a/superset-frontend/src/components/Hotkeys.jsx +++ b/superset-frontend/src/components/Hotkeys.jsx @@ -19,7 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Mousetrap from 'mousetrap'; -import { OverlayTrigger, Popover } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; const propTypes = { hotkeys: PropTypes.arrayOf( @@ -47,9 +47,9 @@ export default class Hotkeys extends React.PureComponent { } renderPopover() { - const { header, hotkeys } = this.props; + const { hotkeys } = this.props; return ( - +
@@ -68,19 +68,20 @@ export default class Hotkeys extends React.PureComponent { ))}
- +
); } render() { return ( - - +
); } } diff --git a/superset-frontend/src/components/URLShortLinkButton.jsx b/superset-frontend/src/components/URLShortLinkButton.jsx index 8681b4f98f8..f35525aa1ee 100644 --- a/superset-frontend/src/components/URLShortLinkButton.jsx +++ b/superset-frontend/src/components/URLShortLinkButton.jsx @@ -18,8 +18,8 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { Popover, OverlayTrigger } from 'react-bootstrap'; import { t } from '@superset-ui/core'; +import Popover from 'src/common/components/Popover'; import CopyToClipboard from './CopyToClipboard'; import { getShortUrl } from '../utils/common'; import withToasts from '../messageToasts/enhancers/withToasts'; @@ -57,7 +57,7 @@ class URLShortLinkButton extends React.Component { renderPopover() { const emailBody = t('%s%s', this.props.emailContent, this.state.shortUrl); return ( - +
- +
); } render() { return ( -   - +
); } } diff --git a/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx b/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx index 9383225da5b..a6650d4f084 100644 --- a/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx +++ b/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx @@ -18,9 +18,8 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { Popover, Tab, Tabs } from 'react-bootstrap'; +import { Tab, Tabs } from 'react-bootstrap'; import Button from 'src/components/Button'; -import { ThemeProvider } from '@superset-ui/core'; import columnType from '../propTypes/columnType'; import adhocMetricType from '../propTypes/adhocMetricType'; @@ -132,80 +131,78 @@ export default class AdhocFilterEditPopover extends React.Component { const hasUnsavedChanges = !adhocFilter.equals(propsAdhocFilter); return ( - - - + - - + + + {!this.props.datasource || + this.props.datasource.type !== 'druid' ? ( + - - - {!this.props.datasource || - this.props.datasource.type !== 'druid' ? ( - - ) : ( -
- Custom SQL Filters are not available on druid datasources -
- )} -
-
-
- - - -
-
-
+ ) : ( +
+ Custom SQL Filters are not available on druid datasources +
+ )} + + +
+ + + +
+ ); } } diff --git a/superset-frontend/src/explore/components/AdhocFilterOption.jsx b/superset-frontend/src/explore/components/AdhocFilterOption.jsx index c7cfb026550..29240e42fb4 100644 --- a/superset-frontend/src/explore/components/AdhocFilterOption.jsx +++ b/superset-frontend/src/explore/components/AdhocFilterOption.jsx @@ -18,7 +18,7 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { OverlayTrigger } from 'react-bootstrap'; +import Popover from 'src/common/components/Popover'; import { t, withTheme } from '@superset-ui/core'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; @@ -48,6 +48,7 @@ class AdhocFilterOption extends React.PureComponent { this.onPopoverResize = this.onPopoverResize.bind(this); this.onOverlayEntered = this.onOverlayEntered.bind(this); this.onOverlayExited = this.onOverlayExited.bind(this); + this.handleVisibleChange = this.handleVisibleChange.bind(this); this.state = { overlayShown: false }; } @@ -68,12 +69,20 @@ class AdhocFilterOption extends React.PureComponent { } closeFilterEditOverlay() { - this.refs.overlay.hide(); + this.setState({ overlayShown: false }); + } + + handleVisibleChange(visible) { + if (visible) { + this.onOverlayEntered(); + } else { + this.onOverlayExited(); + } } render() { - const { adhocFilter, theme } = this.props; - const overlay = ( + const { adhocFilter } = this.props; + const content = ( ); return ( -
e.stopPropagation()}> +
e.stopPropagation()}> {adhocFilter.isExtra && ( )} - - +
); } diff --git a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx index 34108bca9f1..4ee85a3f03b 100644 --- a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx +++ b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx @@ -18,10 +18,10 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { FormGroup, Popover, Tab, Tabs } from 'react-bootstrap'; +import { FormGroup, Tab, Tabs } from 'react-bootstrap'; import Button from 'src/components/Button'; import Select from 'src/components/Select'; -import { t, ThemeProvider } from '@superset-ui/core'; +import { t } from '@superset-ui/core'; import { ColumnOption } from '@superset-ui/chart-controls'; import FormLabel from 'src/components/FormLabel'; @@ -29,7 +29,6 @@ import { SQLEditor } from 'src/components/AsyncAceEditor'; import sqlKeywords from 'src/SqlLab/utils/sqlKeywords'; import { AGGREGATES_OPTIONS } from '../constants'; -import AdhocMetricEditPopoverTitle from './AdhocMetricEditPopoverTitle'; import columnType from '../propTypes/columnType'; import AdhocMetric, { EXPRESSION_TYPES } from '../AdhocMetric'; @@ -40,7 +39,10 @@ const propTypes = { onResize: PropTypes.func.isRequired, columns: PropTypes.arrayOf(columnType), datasourceType: PropTypes.string, - theme: PropTypes.object, + title: PropTypes.shape({ + label: PropTypes.string, + hasCustomLabel: PropTypes.bool, + }), }; const defaultProps = { @@ -57,7 +59,6 @@ export default class AdhocMetricEditPopover extends React.Component { this.onColumnChange = this.onColumnChange.bind(this); this.onAggregateChange = this.onAggregateChange.bind(this); this.onSqlExpressionChange = this.onSqlExpressionChange.bind(this); - this.onLabelChange = this.onLabelChange.bind(this); this.onDragDown = this.onDragDown.bind(this); this.onMouseMove = this.onMouseMove.bind(this); this.onMouseUp = this.onMouseUp.bind(this); @@ -83,7 +84,10 @@ export default class AdhocMetricEditPopover extends React.Component { } onSave() { - this.props.onChange(this.state.adhocMetric); + this.props.onChange({ + ...this.state.adhocMetric, + ...this.props.title, + }); this.props.onClose(); } @@ -115,16 +119,6 @@ export default class AdhocMetricEditPopover extends React.Component { })); } - onLabelChange(e) { - const label = e.target.value; - this.setState(prevState => ({ - adhocMetric: prevState.adhocMetric.duplicateWith({ - label, - hasCustomLabel: true, - }), - })); - } - onDragDown(e) { this.dragStartX = e.clientX; this.dragStartY = e.clientY; @@ -177,7 +171,6 @@ export default class AdhocMetricEditPopover extends React.Component { onClose, onResize, datasourceType, - theme, ...popoverProps } = this.props; @@ -215,123 +208,113 @@ export default class AdhocMetricEditPopover extends React.Component { ); } - const popoverTitle = ( - - ); - const stateIsValid = adhocMetric.isValid(); const hasUnsavedChanges = !adhocMetric.equals(propsAdhocMetric); return ( - - - + - - - - column - - + + + + aggregate + + - - - - {this.props.datasourceType !== 'druid' ? ( - - - - ) : ( -
- Custom SQL Metrics are not available on druid datasources -
- )} -
-
-
- - - -
-
-
+ ) : ( +
+ Custom SQL Metrics are not available on druid datasources +
+ )} + + +
+ + + +
+
); } } diff --git a/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx b/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx index d4eed6f348f..609a8351b55 100644 --- a/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx +++ b/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx @@ -19,10 +19,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormControl, OverlayTrigger, Tooltip } from 'react-bootstrap'; -import AdhocMetric from '../AdhocMetric'; const propTypes = { - adhocMetric: PropTypes.instanceOf(AdhocMetric), + title: PropTypes.shape({ + label: PropTypes.string, + hasCustomLabel: PropTypes.bool, + }), + defaultLabel: PropTypes.string, onChange: PropTypes.func.isRequired, }; @@ -33,6 +36,7 @@ export default class AdhocMetricEditPopoverTitle extends React.Component { this.onMouseOut = this.onMouseOut.bind(this); this.onClick = this.onClick.bind(this); this.onBlur = this.onBlur.bind(this); + this.onInputBlur = this.onInputBlur.bind(this); this.state = { isHovered: false, isEditable: false, @@ -55,8 +59,16 @@ export default class AdhocMetricEditPopoverTitle extends React.Component { this.setState({ isEditable: false }); } + onInputBlur(e) { + if (e.target.value === '') { + e.target.value = this.props.defaultLabel; + this.props.onChange(e); + } + this.onBlur(); + } + render() { - const { adhocMetric, onChange } = this.props; + const { title, onChange } = this.props; const editPrompt = ( Click to edit label @@ -66,10 +78,11 @@ export default class AdhocMetricEditPopoverTitle extends React.Component { ) : ( @@ -86,7 +99,7 @@ export default class AdhocMetricEditPopoverTitle extends React.Component { className="inline-editable" data-test="AdhocMetricEditTitle#trigger" > - {adhocMetric.hasCustomLabel ? adhocMetric.label : 'My Metric'} + {title.hasCustomLabel ? title.label : 'My Metric'}   + ); + + const popoverTitle = ( + ); @@ -83,23 +118,22 @@ class AdhocMetricOption extends React.PureComponent {
e.stopPropagation()} + role="button" + tabIndex={0} + onMouseDown={e => e.stopPropagation()} + onKeyDown={e => e.stopPropagation()} > - { - this.overlay = ref; - }} + - +
); } diff --git a/superset-frontend/src/explore/components/EmbedCodeButton.jsx b/superset-frontend/src/explore/components/EmbedCodeButton.jsx index 0cffd27348f..04efde16fa2 100644 --- a/superset-frontend/src/explore/components/EmbedCodeButton.jsx +++ b/superset-frontend/src/explore/components/EmbedCodeButton.jsx @@ -18,13 +18,13 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { Popover, OverlayTrigger } from 'react-bootstrap'; import { t } from '@superset-ui/core'; +import Popover from 'src/common/components/Popover'; import FormLabel from 'src/components/FormLabel'; import CopyToClipboard from 'src/components/CopyToClipboard'; +import { getShortUrl } from 'src/utils/common'; import { getExploreLongUrl, getURIDirectory } from '../exploreUtils'; -import { getShortUrl } from '../../utils/common'; const propTypes = { latestQueryFormData: PropTypes.object.isRequired, @@ -80,86 +80,80 @@ export default class EmbedCodeButton extends React.Component { ); } - renderPopover() { + renderPopoverContent() { const html = this.generateEmbedHTML(); return ( - -
-
-
-