diff --git a/superset-frontend/spec/javascripts/sqllab/SaveQuery_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SaveQuery_spec.jsx index 514b52ff3568a..6baa96e25ed47 100644 --- a/superset-frontend/spec/javascripts/sqllab/SaveQuery_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/SaveQuery_spec.jsx @@ -21,7 +21,7 @@ import { FormControl } from 'react-bootstrap'; import { shallow } from 'enzyme'; import * as sinon from 'sinon'; import SaveQuery from 'src/SqlLab/components/SaveQuery'; -import ModalTrigger from 'src/components/ModalTrigger'; +import Modal from 'src/common/components/Modal'; import Button from 'src/components/Button'; describe('SavedQuery', () => { @@ -40,24 +40,27 @@ describe('SavedQuery', () => { it('is valid with props', () => { expect(React.isValidElement()).toBe(true); }); - it('has a ModalTrigger', () => { + it('has a Modal', () => { const wrapper = shallow(); - expect(wrapper.find(ModalTrigger)).toExist(); + expect(wrapper.find(Modal)).toExist(); }); it('has a cancel button', () => { const wrapper = shallow(); - const modal = shallow(wrapper.instance().renderModalBody()); + const modal = wrapper.find(Modal); + expect(modal.find('.cancelQuery')).toHaveLength(1); }); it('has 2 FormControls', () => { const wrapper = shallow(); - const modal = shallow(wrapper.instance().renderModalBody()); + const modal = wrapper.find(Modal); + expect(modal.find(FormControl)).toHaveLength(2); }); it('has a save button if this is a new query', () => { const saveSpy = sinon.spy(); const wrapper = shallow(); - const modal = shallow(wrapper.instance().renderModalBody()); + const modal = wrapper.find(Modal); + expect(modal.find(Button)).toHaveLength(2); modal.find(Button).at(0).simulate('click'); expect(saveSpy.calledOnce).toBe(true); @@ -72,7 +75,8 @@ describe('SavedQuery', () => { }, }; const wrapper = shallow(); - const modal = shallow(wrapper.instance().renderModalBody()); + const modal = wrapper.find(Modal); + expect(modal.find(Button)).toHaveLength(3); modal.find(Button).at(0).simulate('click'); expect(updateSpy.calledOnce).toBe(true); diff --git a/superset-frontend/src/SqlLab/components/SaveQuery.jsx b/superset-frontend/src/SqlLab/components/SaveQuery.jsx deleted file mode 100644 index 9dfb7fd20c9bc..0000000000000 --- a/superset-frontend/src/SqlLab/components/SaveQuery.jsx +++ /dev/null @@ -1,198 +0,0 @@ -/** - * 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 React from 'react'; -import PropTypes from 'prop-types'; -import { FormControl, FormGroup, Row, Col } from 'react-bootstrap'; -import { t } from '@superset-ui/core'; - -import Button from 'src/components/Button'; -import FormLabel from 'src/components/FormLabel'; -import ModalTrigger from 'src/components/ModalTrigger'; - -const propTypes = { - query: PropTypes.object, - defaultLabel: PropTypes.string, - animation: PropTypes.bool, - onSave: PropTypes.func, - onUpdate: PropTypes.func, - saveQueryWarning: PropTypes.string, -}; -const defaultProps = { - defaultLabel: t('Undefined'), - animation: true, - onSave: () => {}, - saveQueryWarning: null, -}; - -class SaveQuery extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - description: '', - label: props.defaultLabel, - showSave: false, - }; - this.toggleSave = this.toggleSave.bind(this); - this.onSave = this.onSave.bind(this); - this.onUpdate = this.onUpdate.bind(this); - this.onCancel = this.onCancel.bind(this); - this.onLabelChange = this.onLabelChange.bind(this); - this.onDescriptionChange = this.onDescriptionChange.bind(this); - } - - onSave() { - this.props.onSave(this.queryPayload()); - this.close(); - } - - onUpdate() { - this.props.onUpdate(this.queryPayload()); - this.close(); - } - - onCancel() { - this.close(); - } - - onLabelChange(e) { - this.setState({ label: e.target.value }); - } - - onDescriptionChange(e) { - this.setState({ description: e.target.value }); - } - - queryPayload() { - return { - ...this.props.query, - title: this.state.label, - description: this.state.description, - }; - } - - close() { - if (this.saveModal) this.saveModal.close(); - } - - toggleSave() { - this.setState(prevState => ({ showSave: !prevState.showSave })); - } - - renderModalBody() { - const isSaved = !!this.props.query.remoteId; - return ( - - - - - - {t('Label')} - - - - - - - - - - - {t('Description')} - - - - - - - {this.props.saveQueryWarning && ( - - - - {this.props.saveQueryWarning} - - - - - )} - - - {isSaved && ( - - {t('Update')} - - )} - - {isSaved ? t('Save New') : t('Save')} - - - {t('Cancel')} - - - - - ); - } - - render() { - return ( - - { - this.saveModal = ref; - }} - modalTitle={t('Save Query')} - modalBody={this.renderModalBody()} - backdrop="static" - triggerNode={ - - {t('Save')} - - } - bsSize="small" - /> - - ); - } -} -SaveQuery.propTypes = propTypes; -SaveQuery.defaultProps = defaultProps; - -export default SaveQuery; diff --git a/superset-frontend/src/SqlLab/components/SaveQuery.tsx b/superset-frontend/src/SqlLab/components/SaveQuery.tsx new file mode 100644 index 0000000000000..26acbf059cd14 --- /dev/null +++ b/superset-frontend/src/SqlLab/components/SaveQuery.tsx @@ -0,0 +1,215 @@ +/** + * 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 React, { useState } from 'react'; +import { FormControl, FormGroup, Row, Col } from 'react-bootstrap'; +import { t, styled } from '@superset-ui/core'; + +import Button from 'src/components/Button'; +import FormLabel from 'src/components/FormLabel'; +import Modal from 'src/common/components/Modal'; + +interface SaveQueryProps { + query: any; + defaultLabel: string; + onSave: (arg0: QueryPayload) => void; + onUpdate: (arg0: QueryPayload) => void; + saveQueryWarning: string | null; +} + +type QueryPayload = { + autorun: boolean; + dbId: number; + description?: string; + id?: string; + latestQueryId: string; + queryLimit: number; + remoteId: number; + schema: string; + schemaOptions: Array<{ + label: string; + title: string; + value: string; + }>; + selectedText: string | null; + sql: string; + tableOptions: Array<{ + label: string; + schema: string; + title: string; + type: string; + value: string; + }>; + title: string; +}; + +const StyledRow = styled(Row)` + div { + display: flex; + justify-content: flex-start; + } + + button.cta { + margin: 0 7px; + min-width: 105px; + font-size: 12px; + + &:first-of-type { + margin-left: 0; + } + } +`; + +export default function SaveQuery({ + query, + defaultLabel = t('Undefined'), + onSave = () => {}, + onUpdate, + saveQueryWarning = null, +}: SaveQueryProps) { + const [description, setDescription] = useState( + query.description || '', + ); + const [label, setLabel] = useState(defaultLabel); + const [showSave, setShowSave] = useState(false); + const isSaved = !!query.remoteId; + + const queryPayload = () => { + return { + ...query, + title: label, + description, + }; + }; + + const close = () => { + setShowSave(false); + }; + + const onSaveWrapper = () => { + onSave(queryPayload()); + close(); + }; + + const onUpdateWrapper = () => { + onUpdate(queryPayload()); + close(); + }; + + const onLabelChange = (e: React.FormEvent) => { + setLabel((e.target as HTMLInputElement).value); + }; + + const onDescriptionChange = (e: React.FormEvent) => { + setDescription((e.target as HTMLInputElement).value); + }; + + const toggleSave = () => { + setShowSave(!showSave); + }; + + const renderModalBody = () => { + return ( + + + + + {t('Label')} + + + + + + + + + {t('Description')} + + + + + + {saveQueryWarning && ( + + + + {saveQueryWarning} + + + + + )} + + + {isSaved && ( + + {t('Update')} + + )} + + {isSaved ? t('Save New') : t('Save')} + + + {t('Cancel')} + + + + + ); + }; + + return ( + + + {t('Save')} + + {t('Save Query')}} + hideFooter + > + {renderModalBody()} + + + ); +} diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx index aeaa9d0bd26dd..f51def0c78da1 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx @@ -529,9 +529,7 @@ class SqlEditor extends React.PureComponent { } - footer={modalFooter} + footer={!hideFooter ? modalFooter : null} {...rest} > {children}