mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
refactor: Use Modals from Antd instead of react-bootstrap (#11330)
* Refactor ModalTrigger to use antd modal * Refactor a few components * dynamic width * Fix unit tests * Use i18n for button text
This commit is contained in:
parent
144b279aa2
commit
894ca3c09b
@ -18,7 +18,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import CssEditor from 'src/dashboard/components/CssEditor';
|
||||
|
||||
describe('CssEditor', () => {
|
||||
@ -29,7 +29,12 @@ describe('CssEditor', () => {
|
||||
expect(React.isValidElement(<CssEditor {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders the trigger node', () => {
|
||||
const wrapper = mount(<CssEditor {...mockedProps} />);
|
||||
const wrapper = mount(<CssEditor {...mockedProps} />, {
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('.fa-edit')).toExist();
|
||||
});
|
||||
});
|
||||
|
@ -22,6 +22,15 @@ import { mount, shallow } from 'enzyme';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
const getMountWrapper = props =>
|
||||
mount(<RefreshIntervalModal {...props} />, {
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
});
|
||||
|
||||
describe('RefreshIntervalModal', () => {
|
||||
const mockedProps = {
|
||||
@ -36,15 +45,15 @@ describe('RefreshIntervalModal', () => {
|
||||
).toBe(true);
|
||||
});
|
||||
it('renders the trigger node', () => {
|
||||
const wrapper = mount(<RefreshIntervalModal {...mockedProps} />);
|
||||
const wrapper = getMountWrapper(mockedProps);
|
||||
expect(wrapper.find('.fa-edit')).toExist();
|
||||
});
|
||||
it('should render a interval seconds', () => {
|
||||
const wrapper = mount(<RefreshIntervalModal {...mockedProps} />);
|
||||
const wrapper = getMountWrapper(mockedProps);
|
||||
expect(wrapper.prop('refreshFrequency')).toEqual(10);
|
||||
});
|
||||
it('should change refreshFrequency with edit mode', () => {
|
||||
const wrapper = mount(<RefreshIntervalModal {...mockedProps} />);
|
||||
const wrapper = getMountWrapper(mockedProps);
|
||||
wrapper.instance().handleFrequencyChange({ value: 30 });
|
||||
wrapper.instance().onSave();
|
||||
expect(mockedProps.onChange).toHaveBeenCalled();
|
||||
|
@ -126,7 +126,7 @@ describe('Tabs', () => {
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
wrapper.find('.icon-button').simulate('click');
|
||||
|
||||
const modal = document.getElementsByClassName('modal');
|
||||
const modal = document.getElementsByClassName('ant-modal');
|
||||
expect(modal).toHaveLength(1);
|
||||
expect(deleteComponent.callCount).toBe(0);
|
||||
});
|
||||
|
@ -21,6 +21,7 @@ import { mount } from 'enzyme';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import { DisplayQueryButton } from 'src/explore/components/DisplayQueryButton';
|
||||
import { MenuItem } from 'react-bootstrap';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
describe('DisplayQueryButton', () => {
|
||||
const defaultProps = {
|
||||
@ -43,7 +44,12 @@ describe('DisplayQueryButton', () => {
|
||||
);
|
||||
});
|
||||
it('renders a dropdown', () => {
|
||||
const wrapper = mount(<DisplayQueryButton {...defaultProps} />);
|
||||
const wrapper = mount(<DisplayQueryButton {...defaultProps} />, {
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
});
|
||||
expect(wrapper.find(ModalTrigger)).toHaveLength(3);
|
||||
expect(wrapper.find(MenuItem)).toHaveLength(5);
|
||||
});
|
||||
|
@ -22,6 +22,7 @@ import { mount, shallow } from 'enzyme';
|
||||
|
||||
import HighlightedSql from 'src/SqlLab/components/HighlightedSql';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
describe('HighlightedSql', () => {
|
||||
const sql =
|
||||
@ -45,6 +46,12 @@ describe('HighlightedSql', () => {
|
||||
shrink
|
||||
maxWidth={5}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
const pre = wrapper.find('pre');
|
||||
expect(pre).toHaveLength(1);
|
||||
|
@ -20,6 +20,7 @@ import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
import Link from 'src/components/Link';
|
||||
import TableElement from 'src/SqlLab/components/TableElement';
|
||||
@ -54,6 +55,12 @@ describe('TableElement', () => {
|
||||
<Provider store={store}>
|
||||
<TableElement {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
it('sorts columns', () => {
|
||||
@ -71,6 +78,12 @@ describe('TableElement', () => {
|
||||
<Provider store={store}>
|
||||
<TableElement {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(mockedActions.collapseTable.called).toBe(false);
|
||||
wrapper.find('.table-name').simulate('click');
|
||||
|
@ -142,7 +142,6 @@ const QueryTable = props => {
|
||||
if (q.resultsKey) {
|
||||
q.output = (
|
||||
<ModalTrigger
|
||||
bsSize="large"
|
||||
className="ResultsModal"
|
||||
triggerNode={
|
||||
<Label bsStyle="info" className="pointer">
|
||||
@ -161,6 +160,7 @@ const QueryTable = props => {
|
||||
displayLimit={props.displayLimit}
|
||||
/>
|
||||
}
|
||||
responsive
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
@ -466,11 +466,11 @@ div.tablePopover {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ResultsModal .modal-body {
|
||||
min-height: 600px;
|
||||
.ResultsModal .ant-modal-body {
|
||||
min-height: 560px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
.ant-modal-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,10 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { isNil } from 'lodash';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { styled, SupersetThemeProps, t } from '@superset-ui/core';
|
||||
import { Modal as BaseModal } from 'src/common/components';
|
||||
import Button from 'src/components/Button';
|
||||
import { css } from '@emotion/core';
|
||||
|
||||
interface ModalProps {
|
||||
className?: string;
|
||||
@ -33,12 +34,25 @@ interface ModalProps {
|
||||
show: boolean;
|
||||
title: React.ReactNode;
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
responsive?: boolean;
|
||||
hideFooter?: boolean;
|
||||
centered?: boolean;
|
||||
footer?: React.ReactNode;
|
||||
}
|
||||
|
||||
const StyledModal = styled(BaseModal)`
|
||||
interface StyledModalProps extends SupersetThemeProps {
|
||||
maxWidth?: string;
|
||||
responsive?: boolean;
|
||||
}
|
||||
|
||||
const StyledModal = styled(BaseModal)<StyledModalProps>`
|
||||
${({ responsive, maxWidth }) =>
|
||||
responsive &&
|
||||
css`
|
||||
max-width: ${maxWidth ?? '900px'};
|
||||
`}
|
||||
|
||||
.ant-modal-header {
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light4};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px
|
||||
@ -94,11 +108,13 @@ export default function Modal({
|
||||
disablePrimaryButton = false,
|
||||
onHide,
|
||||
onHandledPrimaryAction,
|
||||
primaryButtonName,
|
||||
primaryButtonName = t('OK'),
|
||||
primaryButtonType = 'primary',
|
||||
show,
|
||||
title,
|
||||
width,
|
||||
maxWidth,
|
||||
responsive = false,
|
||||
centered,
|
||||
footer,
|
||||
hideFooter,
|
||||
@ -121,12 +137,15 @@ export default function Modal({
|
||||
]
|
||||
: footer;
|
||||
|
||||
const modalWidth = width || responsive ? 'calc(100vw - 24px)' : '600px';
|
||||
return (
|
||||
<StyledModal
|
||||
centered={!!centered}
|
||||
onOk={onHandledPrimaryAction}
|
||||
onCancel={onHide}
|
||||
width={width || '600px'}
|
||||
width={modalWidth}
|
||||
maxWidth={maxWidth}
|
||||
responsive={responsive}
|
||||
visible={show}
|
||||
title={title}
|
||||
closeIcon={
|
||||
|
@ -222,11 +222,10 @@ export default class AlteredSliceTag extends React.Component {
|
||||
// differences in the slice
|
||||
return (
|
||||
<ModalTrigger
|
||||
animation
|
||||
triggerNode={this.renderTriggerNode()}
|
||||
modalTitle={t('Chart changes')}
|
||||
bsSize="large"
|
||||
modalBody={this.renderModalBody()}
|
||||
responsive
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -18,13 +18,12 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, MenuItem } from 'react-bootstrap';
|
||||
|
||||
import { MenuItem } from 'react-bootstrap';
|
||||
import Modal from 'src/common/components/Modal';
|
||||
import Button from 'src/components/Button';
|
||||
|
||||
const propTypes = {
|
||||
dialogClassName: PropTypes.string,
|
||||
animation: PropTypes.bool,
|
||||
triggerNode: PropTypes.node.isRequired,
|
||||
modalTitle: PropTypes.node,
|
||||
modalBody: PropTypes.node, // not required because it can be generated by beforeOpen
|
||||
@ -33,19 +32,18 @@ const propTypes = {
|
||||
onExit: PropTypes.func,
|
||||
isButton: PropTypes.bool,
|
||||
isMenuItem: PropTypes.bool,
|
||||
bsSize: PropTypes.oneOf(['large', 'small']), // react-bootstrap also supports 'sm', 'lg' but we're keeping it simple.
|
||||
className: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
backdrop: PropTypes.oneOf(['static', true, false]),
|
||||
width: PropTypes.string,
|
||||
maxWidth: PropTypes.string,
|
||||
responsive: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
animation: true,
|
||||
beforeOpen: () => {},
|
||||
onExit: () => {},
|
||||
isButton: false,
|
||||
isMenuItem: false,
|
||||
bsSize: null,
|
||||
className: '',
|
||||
modalTitle: '',
|
||||
};
|
||||
@ -73,24 +71,18 @@ export default class ModalTrigger extends React.Component {
|
||||
renderModal() {
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName={this.props.dialogClassName}
|
||||
animation={this.props.animation}
|
||||
wrapClassName={this.props.dialogClassName}
|
||||
className={this.props.className}
|
||||
show={this.state.showModal}
|
||||
onHide={this.close}
|
||||
onExit={this.props.onExit}
|
||||
bsSize={this.props.bsSize}
|
||||
className={this.props.className}
|
||||
backdrop={this.props.backdrop}
|
||||
afterClose={this.props.onExit}
|
||||
title={this.props.modalTitle}
|
||||
footer={this.props.modalFooter}
|
||||
hideFooter={!this.props.modalFooter}
|
||||
maxWidth={this.props.maxWidth}
|
||||
responsive={this.props.responsive}
|
||||
>
|
||||
{this.props.modalTitle && (
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{this.props.modalTitle}</Modal.Title>
|
||||
</Modal.Header>
|
||||
)}
|
||||
<Modal.Body>{this.props.modalBody}</Modal.Body>
|
||||
{this.props.modalFooter && (
|
||||
<Modal.Footer>{this.props.modalFooter}</Modal.Footer>
|
||||
)}
|
||||
{this.props.modalBody}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -58,16 +58,12 @@ SyntaxHighlighter.registerLanguage('json', jsonSyntax);
|
||||
|
||||
const propTypes = {
|
||||
onOpenInEditor: PropTypes.func,
|
||||
animation: PropTypes.bool,
|
||||
queryResponse: PropTypes.object,
|
||||
chartStatus: PropTypes.string,
|
||||
chartHeight: PropTypes.string.isRequired,
|
||||
latestQueryFormData: PropTypes.object.isRequired,
|
||||
slice: PropTypes.object,
|
||||
};
|
||||
const defaultProps = {
|
||||
animation: true,
|
||||
};
|
||||
|
||||
export class DisplayQueryButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@ -234,7 +230,7 @@ export class DisplayQueryButton extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { animation, chartHeight, slice } = this.props;
|
||||
const { chartHeight, slice } = this.props;
|
||||
return (
|
||||
<DropdownButton
|
||||
noCaret
|
||||
@ -259,38 +255,34 @@ export class DisplayQueryButton extends React.PureComponent {
|
||||
show={this.state.isPropertiesModalOpen}
|
||||
onHide={this.closePropertiesModal}
|
||||
onSave={this.props.sliceUpdated}
|
||||
animation={animation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ModalTrigger
|
||||
isMenuItem
|
||||
animation={animation}
|
||||
triggerNode={
|
||||
<span data-test="view-query-menu-item">{t('View query')}</span>
|
||||
}
|
||||
modalTitle={t('View query')}
|
||||
bsSize="large"
|
||||
beforeOpen={() => this.beforeOpen('query')}
|
||||
modalBody={this.renderQueryModalBody()}
|
||||
responsive
|
||||
/>
|
||||
<ModalTrigger
|
||||
isMenuItem
|
||||
animation={animation}
|
||||
triggerNode={<span>{t('View results')}</span>}
|
||||
modalTitle={t('View results')}
|
||||
bsSize="large"
|
||||
beforeOpen={() => this.beforeOpen('results')}
|
||||
modalBody={this.renderResultsModalBody()}
|
||||
responsive
|
||||
/>
|
||||
<ModalTrigger
|
||||
isMenuItem
|
||||
animation={animation}
|
||||
triggerNode={<span>{t('View samples')}</span>}
|
||||
modalTitle={t('View samples')}
|
||||
bsSize="large"
|
||||
beforeOpen={() => this.beforeOpen('samples')}
|
||||
modalBody={this.renderSamplesModalBody()}
|
||||
responsive
|
||||
/>
|
||||
{this.state.sqlSupported && (
|
||||
<MenuItem eventKey="3" onClick={this.redirectSQLLab.bind(this)}>
|
||||
@ -315,7 +307,6 @@ export class DisplayQueryButton extends React.PureComponent {
|
||||
}
|
||||
|
||||
DisplayQueryButton.propTypes = propTypes;
|
||||
DisplayQueryButton.defaultProps = defaultProps;
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({ sliceUpdated }, dispatch);
|
||||
|
@ -124,7 +124,6 @@ export default class TextAreaControl extends React.Component {
|
||||
{this.renderEditor()}
|
||||
{this.props.offerEditInModal && (
|
||||
<ModalTrigger
|
||||
bsSize="large"
|
||||
modalTitle={controlHeader}
|
||||
triggerNode={
|
||||
<Button buttonSize="small" className="m-t-5">
|
||||
@ -133,6 +132,7 @@ export default class TextAreaControl extends React.Component {
|
||||
</Button>
|
||||
}
|
||||
modalBody={this.renderModalBody(true)}
|
||||
responsive
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user