refactor: Replace react-bootstrap tabs with Antd tabs (#11118)

* Replace tabs in BuilderComponentPane

* Replace tabs in ControlPanelsContainer

* Replace tabs in AdhocMetricEditPopover

* Replace Tabs in DatasourceEditor

* Replace tabs in AdhocFilterEditPopover

* Replace tabs in DateFilterControl

* Bug fix

* Change Tab styles

* Fix tests

* Fix cypress tests

* Lint fix

* Fix tests

* Change Tabs style in ControlPanelsContainer

* Change tabs content height

* Lint fix

* Add data test

* Fix e2e test

* Move Tabs file to separate dir

* Fix after rebase

* Fix e2e tests

* Fix after rebase
This commit is contained in:
Kamil Gabryjelski 2020-10-31 06:05:31 +01:00 committed by GitHub
parent 01ddbd0697
commit 55a3404b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 188 additions and 140 deletions

View File

@ -47,8 +47,9 @@ describe('Dashboard edit mode', () => {
});
cy.get('[data-test="dashboard-builder-component-pane-tabs-navigation"]')
.children()
.last()
.within(() => {
cy.get('.ant-tabs-tab').last();
})
.click();
// find box plot is available from list

View File

@ -43,6 +43,12 @@ describe('AdhocFilters', () => {
cy.get('input[type=text]').focus().type('name{enter}');
});
// antd tabs do lazy loading, so we need to click on tab with ace editor
cy.get('#filter-edit-popover').within(() => {
cy.get('.ant-tabs-tab').contains('Custom SQL').click();
cy.get('.ant-tabs-tab').contains('Simple').click();
});
cy.get('script').then(nodes => {
// should load new script chunks for SQL editor
expect(nodes.length).to.greaterThan(numScripts);

View File

@ -72,11 +72,6 @@ describe('AdhocMetrics', () => {
.should('have.text', 'num')
.click();
cy.get('[data-test=option-label]')
.should('have.text', 'SUM(num)')
.first()
.click();
// add custom SQL
cy.get('#adhoc-metric-edit-tabs-tab-SQL').click();
cy.get('[data-test=metrics-edit-popover]').within(() => {
@ -103,9 +98,6 @@ describe('AdhocMetrics', () => {
cy.get('[data-test=metrics]')
.find('[data-test="metric-option"]')
.should('have.length', 2);
cy.get('[data-test=metrics]').within(() => {
cy.contains('[data-test="metric-option"]', 'SUM(sum_girls)').click();
});
cy.get('#metrics-edit-popover').within(() => {
cy.get('#adhoc-metric-edit-tabs-tab-SQL').click();

View File

@ -70,7 +70,9 @@ describe('Datasource control', () => {
cy.get('[data-test="datasource-menu-trigger"]').click();
cy.get('[data-test="edit-dataset"]').click();
cy.get('.ant-modal-content').within(() => {
cy.get('a[role="tab"]').contains('Metrics').click();
cy.get('[data-test="collection-tab-Metrics"]')
.contains('Metrics')
.click();
});
cy.get(`input[value="${newMetricName}"]`)
.closest('tr')
@ -140,7 +142,7 @@ describe('Time range filter', () => {
});
cy.get('#filter-popover').within(() => {
cy.get('div.tab-pane.active').within(() => {
cy.get('div.ant-tabs-tabpane-active').within(() => {
cy.get('div.PopoverSection :not(.dimmed)').within(() => {
cy.get('input[value="100 years ago"]');
cy.get('input[value="now"]');

View File

@ -17,12 +17,12 @@
* under the License.
*/
import React from 'react';
import { Tabs } from 'react-bootstrap';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import Tabs from 'src/common/components/Tabs';
import DatasourceEditor from 'src/datasource/DatasourceEditor';
import Field from 'src/CRUD/Field';
import mockDatasource from '../../fixtures/mockDatasource';

View File

@ -20,9 +20,9 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { Tab, Tabs } from 'react-bootstrap';
import Button from 'src/components/Button';
import Tabs from 'src/common/components/Tabs';
import AdhocFilter, {
EXPRESSION_TYPES,
CLAUSES,
@ -82,7 +82,7 @@ describe('AdhocFilterEditPopover', () => {
it('renders simple tab content by default', () => {
const { wrapper } = setup();
expect(wrapper.find(Tabs)).toExist();
expect(wrapper.find(Tab)).toHaveLength(2);
expect(wrapper.find(Tabs.TabPane)).toHaveLength(2);
expect(wrapper.find(Button)).toHaveLength(2);
expect(wrapper.find(AdhocFilterEditPopoverSimpleTabContent)).toHaveLength(
1,
@ -92,7 +92,7 @@ describe('AdhocFilterEditPopover', () => {
it('renders sql tab content when the adhoc filter expressionType is sql', () => {
const { wrapper } = setup({ adhocFilter: sqlAdhocFilter });
expect(wrapper.find(Tabs)).toExist();
expect(wrapper.find(Tab)).toHaveLength(2);
expect(wrapper.find(Tabs.TabPane)).toHaveLength(2);
expect(wrapper.find(Button)).toHaveLength(2);
expect(wrapper.find(AdhocFilterEditPopoverSqlTabContent)).toExist();
});

View File

@ -18,11 +18,12 @@
*/
/* eslint-disable no-unused-expressions */
import React from 'react';
import { OverlayTrigger, Tab, Tabs, Radio } from 'react-bootstrap';
import { OverlayTrigger, Radio } from 'react-bootstrap';
import sinon from 'sinon';
import { styledMount as mount } from 'spec/helpers/theming';
import Popover from 'src/common/components/Popover';
import Tabs from 'src/common/components/Tabs';
import Label from 'src/components/Label';
import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
import ControlHeader from 'src/explore/components/ControlHeader';
@ -85,13 +86,13 @@ describe('DateFilterControl', () => {
const popoverContentWrapper = mount(popoverContent);
expect(popoverContentWrapper.find(Tabs)).toExist();
expect(popoverContentWrapper.find(Tab)).toHaveLength(2);
expect(popoverContentWrapper.find(Tabs.TabPane)).toHaveLength(2);
});
it('renders default time options', () => {
const popoverContent = wrapper.find(Popover).first().props().content;
const popoverContentWrapper = mount(popoverContent);
const defaultTab = popoverContentWrapper.find(Tab).first();
const defaultTab = popoverContentWrapper.find(Tabs.TabPane).first();
expect(defaultTab.find(Radio)).toExist();
expect(defaultTab.find(Radio)).toHaveLength(6);
@ -100,7 +101,7 @@ describe('DateFilterControl', () => {
it('renders tooltips over timeframe options', () => {
const popoverContent = wrapper.find(Popover).first().props().content;
const popoverContentWrapper = mount(popoverContent);
const defaultTab = popoverContentWrapper.find(Tab).first();
const defaultTab = popoverContentWrapper.find(Tabs.TabPane).first();
const radioTrigger = defaultTab.find(OverlayTrigger);
expect(radioTrigger).toExist();
@ -110,7 +111,7 @@ describe('DateFilterControl', () => {
it('renders the correct time range in tooltip', () => {
const popoverContent = wrapper.find(Popover).first().props().content;
const popoverContentWrapper = mount(popoverContent);
const defaultTab = popoverContentWrapper.find(Tab).first();
const defaultTab = popoverContentWrapper.find(Tabs.TabPane).first();
const triggers = defaultTab.find(OverlayTrigger);
const expectedLabels = {

View File

@ -30,6 +30,10 @@ const notForwardedProps = ['fullWidth'];
const StyledTabs = styled(AntdTabs, {
shouldForwardProp: prop => !notForwardedProps.includes(prop),
})<TabsProps>`
.ant-tabs-content-holder {
overflow: auto;
}
.ant-tabs-tab {
flex: 1 1 auto;
@ -120,5 +124,15 @@ EditableTabs.TabPane.defaultProps = {
),
};
const StyledCardTabs = styled(EditableTabs)``;
const CardTabs = Object.assign(StyledCardTabs, {
TabPane: StyledTabPane,
});
CardTabs.defaultProps = {
type: 'card',
};
export default Tabs;
export { EditableTabs };
export { CardTabs, EditableTabs };

View File

@ -0,0 +1,20 @@
/**
* 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.
*/
export * from './Tabs';
export { default } from './Tabs';

View File

@ -19,7 +19,7 @@
/* eslint-env browser */
import PropTypes from 'prop-types';
import React from 'react';
import { Tabs, Tab } from 'react-bootstrap';
import Tabs from 'src/common/components/Tabs';
import { StickyContainer, Sticky } from 'react-sticky';
import { ParentSize } from '@vx/responsive';
@ -48,23 +48,24 @@ class BuilderComponentPane extends React.PureComponent {
const { isSticky } = this.props;
return (
<Tabs
className="m-t-10 tabs-components"
id="tabs"
className="tabs-components"
style={{ marginTop: '10px' }}
data-test="dashboard-builder-component-pane-tabs-navigation"
>
<Tab eventKey={1} title={t('Components')}>
<Tabs.TabPane key={1} tab={t('Components')}>
<NewTabs />
<NewRow />
<NewColumn />
<NewHeader />
<NewMarkdown />
<NewDivider />
</Tab>
<Tab eventKey={2} title={t('Charts')} className="tab-charts">
</Tabs.TabPane>
<Tabs.TabPane key={2} tab={t('Charts')} className="tab-charts">
<SliceAdder
height={height + (isSticky ? SUPERSET_HEADER_HEIGHT : 0)}
/>
</Tab>
</Tabs.TabPane>
</Tabs>
);
}

View File

@ -18,10 +18,11 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Badge, Col, Radio, Tabs, Tab, Well } from 'react-bootstrap';
import { Alert, Badge, Col, Radio, Well } from 'react-bootstrap';
import shortid from 'shortid';
import { styled, SupersetClient, t } from '@superset-ui/core';
import Tabs from 'src/common/components/Tabs';
import Button from 'src/components/Button';
import CertifiedIconWithTooltip from 'src/components/CertifiedIconWithTooltip';
import DatabaseSelector from 'src/components/DatabaseSelector';
@ -596,11 +597,9 @@ class DatasourceEditor extends React.PureComponent {
const { datasource } = this.state;
const { spatials, all_cols: allCols } = datasource;
return (
<Tab
title={
<CollectionTabTitle collection={spatials} title={t('Spatial')} />
}
eventKey={4}
<Tabs.TabPane
tab={<CollectionTabTitle collection={spatials} title={t('Spatial')} />}
key={4}
>
<CollectionTable
tableColumns={['name', 'config']}
@ -621,7 +620,7 @@ class DatasourceEditor extends React.PureComponent {
),
}}
/>
</Tab>
</Tabs.TabPane>
);
}
@ -905,94 +904,89 @@ class DatasourceEditor extends React.PureComponent {
</Alert>
</div>
<Tabs
fullWidth={false}
id="table-tabs"
data-test="edit-dataset-tabs"
onSelect={this.handleTabSelect}
onChange={this.handleTabSelect}
defaultActiveKey={activeTabKey}
>
<Tab eventKey={0} title={t('Source')}>
{activeTabKey === 0 && this.renderSourceFieldset()}
</Tab>
<Tab
title={
<Tabs.TabPane key={0} tab={t('Source')}>
{this.renderSourceFieldset()}
</Tabs.TabPane>
<Tabs.TabPane
tab={
<CollectionTabTitle
collection={datasource.metrics}
title={t('Metrics')}
/>
}
eventKey={1}
key={1}
>
{activeTabKey === 1 && this.renderMetricCollection()}
</Tab>
<Tab
title={
{this.renderMetricCollection()}
</Tabs.TabPane>
<Tabs.TabPane
tab={
<CollectionTabTitle
collection={this.state.databaseColumns}
title={t('Columns')}
/>
}
eventKey={2}
key={2}
>
{activeTabKey === 2 && (
<div>
<ColumnCollectionTable
columns={this.state.databaseColumns}
onChange={databaseColumns =>
this.setColumns({ databaseColumns })
}
/>
<Button
buttonStyle="primary"
onClick={this.syncMetadata}
className="sync-from-source"
>
{t('Sync columns from source')}
</Button>
{this.state.metadataLoading && <Loading />}
</div>
)}
</Tab>
<Tab
title={
<div>
<ColumnCollectionTable
columns={this.state.databaseColumns}
onChange={databaseColumns =>
this.setColumns({ databaseColumns })
}
/>
<Button
buttonStyle="primary"
onClick={this.syncMetadata}
className="sync-from-source"
>
{t('Sync columns from source')}
</Button>
{this.state.metadataLoading && <Loading />}
</div>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<CollectionTabTitle
collection={this.state.calculatedColumns}
title={t('Calculated Columns')}
/>
}
eventKey={3}
key={3}
>
{activeTabKey === 3 && (
<ColumnCollectionTable
columns={this.state.calculatedColumns}
onChange={calculatedColumns =>
this.setColumns({ calculatedColumns })
}
editableColumnName
showExpression
allowAddItem
allowEditDataType
itemGenerator={() => ({
column_name: '<new column>',
filterable: true,
groupby: true,
expression: '<enter SQL expression here>',
__expanded: true,
})}
/>
)}
</Tab>
<Tab eventKey={4} title={t('Settings')}>
{activeTabKey === 4 && (
<div>
<Col md={6}>
<FormContainer>{this.renderSettingsFieldset()}</FormContainer>
</Col>
<Col md={6}>
<FormContainer>{this.renderAdvancedFieldset()}</FormContainer>
</Col>
</div>
)}
</Tab>
<ColumnCollectionTable
columns={this.state.calculatedColumns}
onChange={calculatedColumns =>
this.setColumns({ calculatedColumns })
}
editableColumnName
showExpression
allowAddItem
allowEditDataType
itemGenerator={() => ({
column_name: '<new column>',
filterable: true,
groupby: true,
expression: '<enter SQL expression here>',
__expanded: true,
})}
/>
</Tabs.TabPane>
<Tabs.TabPane key={4} tab={t('Settings')}>
<div>
<Col md={6}>
<FormContainer>{this.renderSettingsFieldset()}</FormContainer>
</Col>
<Col md={6}>
<FormContainer>{this.renderAdvancedFieldset()}</FormContainer>
</Col>
</div>
</Tabs.TabPane>
</Tabs>
</DatasourceContainer>
);

View File

@ -18,10 +18,10 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Tab, Tabs } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/core';
import Tabs from 'src/common/components/Tabs';
import columnType from '../propTypes/columnType';
import adhocMetricType from '../propTypes/adhocMetricType';
import AdhocFilter, { EXPRESSION_TYPES } from '../AdhocFilter';
@ -46,7 +46,7 @@ const propTypes = {
};
const startingWidth = 300;
const startingHeight = 190;
const startingHeight = 240;
export default class AdhocFilterEditPopover extends React.Component {
constructor(props) {
@ -144,10 +144,10 @@ export default class AdhocFilterEditPopover extends React.Component {
data-test="adhoc-filter-edit-tabs"
style={{ height: this.state.height, width: this.state.width }}
>
<Tab
<Tabs.TabPane
className="adhoc-filter-edit-tab"
eventKey={EXPRESSION_TYPES.SIMPLE}
title="Simple"
key={EXPRESSION_TYPES.SIMPLE}
tab="Simple"
>
<AdhocFilterEditPopoverSimpleTabContent
adhocFilter={this.state.adhocFilter}
@ -157,11 +157,11 @@ export default class AdhocFilterEditPopover extends React.Component {
onHeightChange={this.adjustHeight}
partitionColumn={partitionColumn}
/>
</Tab>
<Tab
</Tabs.TabPane>
<Tabs.TabPane
className="adhoc-filter-edit-tab"
eventKey={EXPRESSION_TYPES.SQL}
title="Custom SQL"
key={EXPRESSION_TYPES.SQL}
tab="Custom SQL"
>
{!this.props.datasource ||
this.props.datasource.type !== 'druid' ? (
@ -176,7 +176,7 @@ export default class AdhocFilterEditPopover extends React.Component {
Custom SQL Filters are not available on druid datasources
</div>
)}
</Tab>
</Tabs.TabPane>
</Tabs>
<div>
<Button buttonSize="small" onClick={this.props.onClose} cta>

View File

@ -18,7 +18,8 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup, Tab, Tabs } from 'react-bootstrap';
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 { t } from '@superset-ui/core';
@ -50,7 +51,7 @@ const defaultProps = {
};
const startingWidth = 300;
const startingHeight = 180;
const startingHeight = 240;
export default class AdhocMetricEditPopover extends React.Component {
constructor(props) {
@ -222,13 +223,12 @@ export default class AdhocMetricEditPopover extends React.Component {
defaultActiveKey={adhocMetric.expressionType}
className="adhoc-metric-edit-tabs"
style={{ height: this.state.height, width: this.state.width }}
onSelect={this.refreshAceEditor}
animation={false}
onChange={this.refreshAceEditor}
>
<Tab
<Tabs.TabPane
className="adhoc-metric-edit-tab"
eventKey={EXPRESSION_TYPES.SIMPLE}
title="Simple"
key={EXPRESSION_TYPES.SIMPLE}
tab="Simple"
>
<FormGroup>
<FormLabel>
@ -251,11 +251,11 @@ export default class AdhocMetricEditPopover extends React.Component {
autoFocus
/>
</FormGroup>
</Tab>
<Tab
</Tabs.TabPane>
<Tabs.TabPane
className="adhoc-metric-edit-tab"
eventKey={EXPRESSION_TYPES.SQL}
title="Custom SQL"
key={EXPRESSION_TYPES.SQL}
tab="Custom SQL"
data-test="adhoc-metric-edit-tab#custom"
>
{this.props.datasourceType !== 'druid' ? (
@ -282,7 +282,7 @@ export default class AdhocMetricEditPopover extends React.Component {
Custom SQL Metrics are not available on druid datasources
</div>
)}
</Tab>
</Tabs.TabPane>
</Tabs>
<div>
<Button

View File

@ -21,9 +21,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Alert, Tab, Tabs } from 'react-bootstrap';
import { Alert } from 'react-bootstrap';
import { css } from '@emotion/core';
import { t, styled } from '@superset-ui/core';
import Tabs from 'src/common/components/Tabs';
import ControlPanelSection from './ControlPanelSection';
import ControlRow from './ControlRow';
import Control from './Control';
@ -44,7 +46,7 @@ const Styles = styled.div`
height: 100%;
max-height: 100%;
.remove-alert {
cursor: 'pointer';
cursor: pointer;
}
#controlSections {
display: flex;
@ -61,6 +63,15 @@ const Styles = styled.div`
}
`;
const ControlPanelsTabs = styled(Tabs)`
${({ fullWidth }) =>
css`
.ant-tabs-nav-list {
width: ${fullWidth ? '100%' : '50%'};
}
`}
`;
class ControlPanelsContainer extends React.Component {
constructor(props) {
super(props);
@ -193,6 +204,7 @@ class ControlPanelsContainer extends React.Component {
}
});
const showCustomizeTab = displaySectionsToRender.length > 0;
return (
<Styles>
{this.props.alert && (
@ -208,16 +220,20 @@ class ControlPanelsContainer extends React.Component {
/>
</Alert>
)}
<Tabs id="controlSections" data-test="control-tabs">
<Tab eventKey="query" title={t('Data')}>
<ControlPanelsTabs
id="controlSections"
data-test="control-tabs"
fullWidth={showCustomizeTab}
>
<Tabs.TabPane key="query" tab={t('Data')}>
{querySectionsToRender.map(this.renderControlPanelSection)}
</Tab>
{displaySectionsToRender.length > 0 && (
<Tab eventKey="display" title={t('Customize')}>
</Tabs.TabPane>
{showCustomizeTab && (
<Tabs.TabPane key="display" tab={t('Customize')}>
{displaySectionsToRender.map(this.renderControlPanelSection)}
</Tab>
</Tabs.TabPane>
)}
</Tabs>
</ControlPanelsTabs>
</Styles>
);
}

View File

@ -26,8 +26,6 @@ import {
MenuItem,
OverlayTrigger,
Radio,
Tab,
Tabs,
Tooltip,
} from 'react-bootstrap';
import Popover from 'src/common/components/Popover';
@ -37,6 +35,7 @@ import 'react-datetime/css/react-datetime.css';
import moment from 'moment';
import { t, styled, withTheme } from '@superset-ui/core';
import Tabs from 'src/common/components/Tabs';
import {
buildTimeRangeString,
formatTimeRange,
@ -435,15 +434,17 @@ class DateFilterControl extends React.Component {
}}
>
<Tabs
defaultActiveKey={this.state.tab === TABS.DEFAULTS ? 1 : 2}
defaultActiveKey={this.state.tab === TABS.DEFAULTS ? '1' : '2'}
id="type"
className="time-filter-tabs"
onSelect={this.changeTab}
>
<Tab eventKey={1} title="Defaults">
<FormGroup>{timeFrames}</FormGroup>
</Tab>
<Tab eventKey={2} title="Custom">
<Tabs.TabPane key="1" tab="Defaults" forceRender>
<div style={{ marginLeft: '8px' }}>
<FormGroup>{timeFrames}</FormGroup>
</div>
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="Custom">
<FormGroup>
<PopoverSection
title="Relative to today"
@ -573,7 +574,7 @@ class DateFilterControl extends React.Component {
</div>
</PopoverSection>
</FormGroup>
</Tab>
</Tabs.TabPane>
</Tabs>
<div className="clearfix">
<Button