diff --git a/superset/assets/cypress/integration/sqllab/tabs.js 
b/superset/assets/cypress/integration/sqllab/tabs.js
index cf49c184ca..1862974390 100644
--- a/superset/assets/cypress/integration/sqllab/tabs.js
+++ b/superset/assets/cypress/integration/sqllab/tabs.js
@@ -10,9 +10,9 @@ export default () => {
       cy.get('#a11y-query-editor-tabs > ul > li').then((tabList) => {
         const initialTabCount = tabList.length;
 
-        // add tab
+        // add tab (second to last tab)
         cy.get('#a11y-query-editor-tabs > ul > li')
-          .last()
+          .eq(initialTabCount - 2)
           .click();
 
         cy.get('#a11y-query-editor-tabs > ul > li').should('have.length', 
initialTabCount + 1);
diff --git a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx 
b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
index 111aba9499..670a914a74 100644
--- a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
@@ -147,11 +147,16 @@ describe('TabbedSqlEditors', () => {
   it('should handle select', () => {
     wrapper = getWrapper();
     sinon.spy(wrapper.instance(), 'newQueryEditor');
+    sinon.spy(wrapper.instance(), 'removeQueryEditor');
     sinon.stub(wrapper.instance().props.actions, 'setActiveQueryEditor');
+    sinon.stub(window, 'confirm').returns(true);
 
     wrapper.instance().handleSelect('add_tab');
     expect(wrapper.instance().newQueryEditor.callCount).toBe(1);
 
+    wrapper.instance().onConfirmClose();
+    expect(wrapper.instance().removeQueryEditor.callCount).toBe(1);
+
     wrapper.instance().handleSelect('123');
     
expect(wrapper.instance().props.actions.setActiveQueryEditor.getCall(0).args[0].id)
         .toContain(123);
@@ -166,7 +171,7 @@ describe('TabbedSqlEditors', () => {
     expect(firstTab.find(SqlEditor)).toHaveLength(1);
 
     const lastTab = wrapper.find(Tab).last();
-    expect(lastTab.props().eventKey).toContain('add_tab');
+    expect(lastTab.props().eventKey).toContain('close_all_tabs');
   });
   it('should disable new tab when offline', () => {
     wrapper = getWrapper();
diff --git a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx 
b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
index da0d9d127c..847d52bb0e 100644
--- a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
+++ b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { DropdownButton, MenuItem, Tab, Tabs } from 'react-bootstrap';
+import { Alert, DropdownButton, MenuItem, Tab, Tabs } from 'react-bootstrap';
+import Dialog from 'react-bootstrap-dialog';
 import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
 import URI from 'urijs';
@@ -41,6 +42,8 @@ class TabbedSqlEditors extends React.PureComponent {
       dataPreviewQueries: [],
       hideLeftBar: false,
     };
+    this.onConfirmClose = this.onConfirmClose.bind(this);
+    this.setDialogRef = this.setDialogRef.bind(this);
   }
   componentDidMount() {
     const query = URI(window.location).search(true);
@@ -145,16 +148,50 @@ class TabbedSqlEditors extends React.PureComponent {
   handleSelect(key) {
     if (key === 'add_tab') {
       this.newQueryEditor();
+    } else if (key === 'close_all_tabs') {
+      this.dialog.show({
+        title: t('Confirm closing all query editor tabs'),
+        bsSize: 'medium',
+        actions: [
+          Dialog.CancelAction(),
+          Dialog.OKAction(this.onConfirmClose),
+        ],
+        body: this.renderSaveDialog(),
+      });
     } else {
       this.props.actions.setActiveQueryEditor({ id: key });
     }
   }
+  onConfirmClose() {
+    for (let i = 0; i < this.props.queryEditors.length; i++) {
+      const qe = this.props.queryEditors[i];
+      this.removeQueryEditor(qe);
+    }
+    queryCount = 0;
+  }
   removeQueryEditor(qe) {
     this.props.actions.removeQueryEditor(qe);
   }
+  setDialogRef(ref) {
+    this.dialog = ref;
+  }
   toggleLeftBar() {
     this.setState({ hideLeftBar: !this.state.hideLeftBar });
   }
+  renderSaveDialog() {
+    return (
+      <div>
+        <Alert bsStyle="warning" className="pointer" onClick={this.hideAlert}>
+          <div>
+            <i className="fa fa-exclamation-triangle" />{' '}
+            {t(`Once all query editor tabs are closed, they cannot
+                be reopened.`)}
+          </div>
+        </Alert>
+        {t('Are you sure you want to close all query editor tabs?')}
+      </div>
+    );
+  }
   render() {
     const editors = this.props.queryEditors.map((qe, i) => {
       const isSelected = qe.id === this.activeQueryEditor().id;
@@ -241,10 +278,21 @@ class TabbedSqlEditors extends React.PureComponent {
           eventKey="add_tab"
           disabled={this.props.offline}
         />
+        <Tab
+          title={
+            <div>
+              <i className="fa fa-times-circle" />&nbsp;
+            </div>
+          }
+          eventKey="close_all_tabs"
+          disabled={this.props.offline}
+        />
+        <Dialog ref={this.setDialogRef} />
       </Tabs>
     );
   }
 }
+
 TabbedSqlEditors.propTypes = propTypes;
 TabbedSqlEditors.defaultProps = defaultProps;
 


With regards,
Apache Git Services

Reply via email to