refactor: Use Antd Modals instead of react-bootstrap Modals (#11366)

* Make Modal responsive

* ErrorAlert

* OmniContainer

* dashboard/PropertiesModal

* Fix e2e tests

* Lint fix

* E2E test fix

* Fix test

* Use grid units

* Change padding to padding-left/right
This commit is contained in:
Kamil Gabryjelski 2020-10-27 21:08:04 +01:00 committed by GitHub
parent a99d795eaf
commit 155b5edec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 212 additions and 198 deletions

View File

@ -87,19 +87,19 @@ describe('Dashboard edit action', () => {
const dashboardTitle = `Test dashboard [${shortid.generate()}]`;
// update title
cy.get('.modal-body')
cy.get('.ant-modal-body')
.should('be.visible')
.contains('Title')
.siblings('input')
.type(`{selectall}{backspace}${dashboardTitle}`);
// save edit changes
cy.get('.modal-footer')
cy.get('.ant-modal-footer')
.contains('Save')
.click()
.then(() => {
// assert that modal edit window has closed
cy.get('.modal-body').should('not.exist');
cy.get('.ant-modal-body').should('not.exist');
// assert title has been updated
cy.get('.editable-title input').should('have.value', dashboardTitle);

View File

@ -90,7 +90,7 @@ describe('Dashboard save action', () => {
openDashboardEditProperties();
// open color scheme dropdown
cy.get('.modal-body')
cy.get('.ant-modal-body')
.contains('Color Scheme')
.parents('.ControlHeader')
.next('.Select')
@ -105,7 +105,7 @@ describe('Dashboard save action', () => {
});
// remove json metadata
cy.get('.modal-body')
cy.get('.ant-modal-body')
.contains('Advanced')
.click()
.then(() => {
@ -113,18 +113,18 @@ describe('Dashboard save action', () => {
});
// update title
cy.get('.modal-body')
cy.get('.ant-modal-body')
.contains('Title')
.siblings('input')
.type(`{selectall}{backspace}${dashboardTitle}`);
// save edit changes
cy.get('.modal-footer')
cy.get('.ant-modal-footer')
.contains('Save')
.click()
.then(() => {
// assert that modal edit window has closed
cy.get('.modal-body').should('not.exist');
cy.get('.ant-modal-body').should('not.exist');
// save dashboard changes
cy.get('.dashboard-header').contains('Save').click();

View File

@ -101,11 +101,9 @@ describe('Dashboard card view', () => {
cy.get('.ant-dropdown-trigger').last().trigger('mouseover');
cy.get('.ant-dropdown-menu-item').contains('Edit').should('exist');
cy.get('.ant-dropdown-menu-item').contains('Edit').click();
cy.get('.modal-dialog').should('be.visible');
cy.get('.modal-dialog input[name="dashboard_title"]').should(
'not.have.value',
);
cy.get('.modal-dialog input[name="slug"]').should('not.have.value');
cy.get('.modal-dialog .btn-default').contains('Cancel').click();
cy.get('.ant-modal').should('be.visible');
cy.get('.ant-modal input[name="dashboard_title"]').should('not.have.value');
cy.get('.ant-modal input[name="slug"]').should('not.have.value');
cy.get('.ant-modal .btn-default').contains('Cancel').click();
});
});

View File

@ -47,10 +47,12 @@ interface StyledModalProps extends SupersetThemeProps {
}
const StyledModal = styled(BaseModal)<StyledModalProps>`
${({ responsive, maxWidth }) =>
${({ theme, responsive, maxWidth }) =>
responsive &&
css`
max-width: ${maxWidth ?? '900px'};
padding-left: ${theme.gridUnit * 3}px;
padding-right: ${theme.gridUnit * 3}px;
`}
.ant-modal-header {
@ -137,7 +139,7 @@ export default function Modal({
]
: footer;
const modalWidth = width || responsive ? 'calc(100vw - 24px)' : '600px';
const modalWidth = width || (responsive ? '100vw' : '600px');
return (
<StyledModal
centered={!!centered}
@ -154,6 +156,7 @@ export default function Modal({
</span>
}
footer={!hideFooter ? modalFooter : null}
data-test={`${title}-modal`}
{...rest}
>
{children}

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 './Modal';
export { default } from './Modal';

View File

@ -17,9 +17,9 @@
* under the License.
*/
import React, { useState, ReactNode } from 'react';
import { Modal } from 'react-bootstrap';
import { styled, supersetTheme, t } from '@superset-ui/core';
import { noOp } from 'src/utils/common';
import Modal from 'src/common/components/Modal';
import Button from 'src/components/Button';
import Icon from '../Icon';
@ -64,17 +64,10 @@ const ErrorModal = styled(Modal)<{ level: ErrorLevel }>`
}
.header {
display: flex;
align-items: center;
background-color: ${({ level, theme }) => theme.colors[level].light2};
display: flex;
justify-content: space-between;
font-size: ${({ theme }) => theme.typography.sizes.l}px;
// Remove clearfix hack as Superset is only used on modern browsers
::before,
::after {
content: unset;
}
}
`;
@ -157,46 +150,41 @@ export default function ErrorAlert({
level={level}
show={isModalOpen}
onHide={() => setIsModalOpen(false)}
>
<Modal.Header className="header">
<LeftSideContent>
title={
<div className="header">
<Icon
className="icon"
name={level === 'error' ? 'error-solid' : 'warning-solid'}
color={supersetTheme.colors[level].base}
/>
<div className="title">{title}</div>
</LeftSideContent>
<span
role="button"
tabIndex={0}
onClick={() => setIsModalOpen(false)}
>
<Icon name="close" />
</span>
</Modal.Header>
<Modal.Body>
</div>
}
footer={
<>
{copyText && (
<CopyToClipboard
text={copyText}
shouldShowText={false}
wrapped={false}
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
/>
)}
<Button
cta
buttonStyle="primary"
onClick={() => setIsModalOpen(false)}
>
{t('Close')}
</Button>
</>
}
>
<>
<p>{subtitle}</p>
<br />
{body}
</Modal.Body>
<Modal.Footer>
{copyText && (
<CopyToClipboard
text={copyText}
shouldShowText={false}
wrapped={false}
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
/>
)}
<Button
cta
buttonStyle="primary"
onClick={() => setIsModalOpen(false)}
>
{t('Close')}
</Button>
</Modal.Footer>
</>
</ErrorModal>
)}
</ErrorAlertDiv>

View File

@ -17,11 +17,11 @@
* under the License.
*/
import React from 'react';
import { Modal } from 'react-bootstrap';
import PropTypes from 'prop-types';
import { t, SupersetClient } from '@superset-ui/core';
import { styled, t, SupersetClient } from '@superset-ui/core';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import Omnibar from 'omnibar';
import Modal from 'src/common/components/Modal';
import { LOG_ACTIONS_OMNIBAR_TRIGGERED } from '../logger/LogUtils';
const propTypes = {
@ -44,6 +44,14 @@ const getDashboards = query =>
title: t('An error occurred while fethching Dashboards'),
}));
const OmniModal = styled(Modal)`
margin-top: 20%;
.ant-modal-body {
padding: 0;
}
`;
class OmniContainer extends React.Component {
constructor(props) {
super(props);
@ -79,13 +87,13 @@ class OmniContainer extends React.Component {
render() {
return (
<Modal show={this.state.showOmni} style={{ marginTop: '25%' }}>
<OmniModal show={this.state.showOmni} hideFooter closable={false}>
<Omnibar
className="Omnibar"
placeholder="Search all dashboards"
extensions={[getDashboards]}
/>
</Modal>
</OmniModal>
);
}
}

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col, Modal, FormControl } from 'react-bootstrap';
import { Row, Col, FormControl } from 'react-bootstrap';
import Button from 'src/components/Button';
import Dialog from 'react-bootstrap-dialog';
import { AsyncSelect } from 'src/components/Select';
@ -30,6 +30,7 @@ import {
getCategoricalSchemeRegistry,
} from '@superset-ui/core';
import Modal from 'src/common/components/Modal';
import FormLabel from 'src/components/FormLabel';
import { JsonEditor } from 'src/components/AsyncAceEditor';
@ -292,145 +293,141 @@ class PropertiesModal extends React.PureComponent {
const saveLabel = onlyApply ? t('Apply') : t('Save');
return (
<Modal show={this.props.show} onHide={this.props.onHide} bsSize="lg">
<Modal
show={this.props.show}
onHide={this.props.onHide}
title={t('Dashboard Properties')}
footer={
<>
<Button
type="button"
buttonSize="sm"
onClick={onHide}
data-test="properties-modal-cancel-button"
cta
>
{t('Cancel')}
</Button>
<Button
onClick={this.submit}
buttonSize="sm"
buttonStyle="primary"
className="m-r-5"
disabled={errors.length > 0}
cta
>
{saveLabel}
</Button>
<Dialog
ref={ref => {
this.dialog = ref;
}}
/>
</>
}
responsive
>
<form onSubmit={this.submit}>
<Modal.Header closeButton data-test="dashboard-properties-modal">
<Modal.Title>
<div>
<span className="float-left">{t('Dashboard Properties')}</span>
</div>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Row>
<Col md={12}>
<h3>{t('Basic Information')}</h3>
</Col>
</Row>
<Row>
<Col md={6}>
<FormLabel htmlFor="embed-height">{t('Title')}</FormLabel>
<FormControl
data-test="dashboard-title-input"
name="dashboard_title"
type="text"
bsSize="sm"
value={values.dashboard_title}
onChange={this.onChange}
disabled={!isDashboardLoaded}
/>
</Col>
<Col md={6}>
<FormLabel htmlFor="embed-height">{t('URL Slug')}</FormLabel>
<FormControl
name="slug"
type="text"
bsSize="sm"
value={values.slug || ''}
onChange={this.onChange}
disabled={!isDashboardLoaded}
/>
<p className="help-block">
{t('A readable URL for your dashboard')}
</p>
</Col>
</Row>
<Row>
<Col md={6}>
<h3 style={{ marginTop: '1em' }}>{t('Access')}</h3>
<FormLabel htmlFor="owners">{t('Owners')}</FormLabel>
<AsyncSelect
name="owners"
isMulti
value={values.owners}
loadOptions={this.loadOwnerOptions}
defaultOptions // load options on render
cacheOptions
onChange={this.onOwnersChange}
disabled={!isDashboardLoaded}
filterOption={null} // options are filtered at the api
/>
<p className="help-block">
{t(
'Owners is a list of users who can alter the dashboard. Searchable by name or username.',
)}
</p>
</Col>
<Col md={6}>
<h3 style={{ marginTop: '1em' }}>{t('Colors')}</h3>
<ColorSchemeControlWrapper
onChange={this.onColorSchemeChange}
colorScheme={values.colorScheme}
/>
</Col>
</Row>
<Row>
<Col md={12}>
<h3 style={{ marginTop: '1em' }}>
<Button buttonStyle="link" onClick={this.toggleAdvanced}>
<i
className={`fa fa-angle-${
isAdvancedOpen ? 'down' : 'right'
}`}
style={{ minWidth: '1em' }}
/>
{t('Advanced')}
</Button>
</h3>
{isAdvancedOpen && (
<>
<FormLabel htmlFor="json_metadata">
{t('JSON Metadata')}
</FormLabel>
<StyledJsonEditor
showLoadingForImport
name="json_metadata"
defaultValue={this.defaultMetadataValue}
value={values.json_metadata}
onChange={this.onMetadataChange}
tabSize={2}
width="100%"
height="200px"
wrapEnabled
/>
<p className="help-block">
{t(
'This JSON object is generated dynamically when clicking the save or overwrite button in the dashboard view. It is exposed here for reference and for power users who may want to alter specific parameters.',
)}
</p>
</>
)}
</Col>
</Row>
</Modal.Body>
<Modal.Footer>
<span className="float-right">
<Button
type="button"
buttonSize="sm"
onClick={onHide}
data-test="properties-modal-cancel-button"
cta
>
{t('Cancel')}
</Button>
<Button
type="submit"
buttonSize="sm"
buttonStyle="primary"
className="m-r-5"
disabled={errors.length > 0}
cta
>
{saveLabel}
</Button>
<Dialog
ref={ref => {
this.dialog = ref;
}}
<Row>
<Col md={12}>
<h3>{t('Basic Information')}</h3>
</Col>
</Row>
<Row>
<Col md={6}>
<FormLabel htmlFor="embed-height">{t('Title')}</FormLabel>
<FormControl
data-test="dashboard-title-input"
name="dashboard_title"
type="text"
bsSize="sm"
value={values.dashboard_title}
onChange={this.onChange}
disabled={!isDashboardLoaded}
/>
</span>
</Modal.Footer>
</Col>
<Col md={6}>
<FormLabel htmlFor="embed-height">{t('URL Slug')}</FormLabel>
<FormControl
name="slug"
type="text"
bsSize="sm"
value={values.slug || ''}
onChange={this.onChange}
disabled={!isDashboardLoaded}
/>
<p className="help-block">
{t('A readable URL for your dashboard')}
</p>
</Col>
</Row>
<Row>
<Col md={6}>
<h3 style={{ marginTop: '1em' }}>{t('Access')}</h3>
<FormLabel htmlFor="owners">{t('Owners')}</FormLabel>
<AsyncSelect
name="owners"
isMulti
value={values.owners}
loadOptions={this.loadOwnerOptions}
defaultOptions // load options on render
cacheOptions
onChange={this.onOwnersChange}
disabled={!isDashboardLoaded}
filterOption={null} // options are filtered at the api
/>
<p className="help-block">
{t(
'Owners is a list of users who can alter the dashboard. Searchable by name or username.',
)}
</p>
</Col>
<Col md={6}>
<h3 style={{ marginTop: '1em' }}>{t('Colors')}</h3>
<ColorSchemeControlWrapper
onChange={this.onColorSchemeChange}
colorScheme={values.colorScheme}
/>
</Col>
</Row>
<Row>
<Col md={12}>
<h3 style={{ marginTop: '1em' }}>
<Button buttonStyle="link" onClick={this.toggleAdvanced}>
<i
className={`fa fa-angle-${
isAdvancedOpen ? 'down' : 'right'
}`}
style={{ minWidth: '1em' }}
/>
{t('Advanced')}
</Button>
</h3>
{isAdvancedOpen && (
<>
<FormLabel htmlFor="json_metadata">
{t('JSON Metadata')}
</FormLabel>
<StyledJsonEditor
showLoadingForImport
name="json_metadata"
defaultValue={this.defaultMetadataValue}
value={values.json_metadata}
onChange={this.onMetadataChange}
tabSize={2}
width="100%"
height="200px"
wrapEnabled
/>
<p className="help-block">
{t(
'This JSON object is generated dynamically when clicking the save or overwrite button in the dashboard view. It is exposed here for reference and for power users who may want to alter specific parameters.',
)}
</p>
</>
)}
</Col>
</Row>
</form>
</Modal>
);