superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ccwilli...@apache.org
Subject [incubator-superset] branch master updated: [superset-client][datasource editor] replace ajax with SupersetClient (#6134)
Date Fri, 19 Oct 2018 18:41:17 GMT
This is an automated email from the ASF dual-hosted git repository.

ccwilliams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 96228ad  [superset-client][datasource editor] replace ajax with SupersetClient (#6134)
96228ad is described below

commit 96228adda975f2b27a6af228adc2ec7064d5b75b
Author: Chris Williams <williaster@users.noreply.github.com>
AuthorDate: Fri Oct 19 11:41:11 2018 -0700

    [superset-client][datasource editor] replace ajax with SupersetClient (#6134)
    
    * [superset-client][datasource editor] replace ajax with SupersetClient
    
    * [superset-client][datasource control] replace ajax with SupersetClient
    
    * [superset-client][datasource editor] remove unused funcs in DatasourceControl
    
    * [superset-client][data source control] lint, remove toasts
    
    * [superset-client] fix DatasourceControl_spec
    
    * [superset-client] remove unneeded functional setState calls
---
 .../datasource/DatasourceEditor_spec.jsx           | 28 +++++-----
 .../datasource/DatasourceModal_spec.jsx            | 27 +++++----
 .../explore/components/DatasourceControl_spec.jsx  |  2 +-
 .../assets/src/datasource/DatasourceEditor.jsx     | 48 ++++++++++------
 superset/assets/src/datasource/DatasourceModal.jsx | 42 +++++++-------
 .../components/controls/DatasourceControl.jsx      | 65 ++++------------------
 6 files changed, 96 insertions(+), 116 deletions(-)

diff --git a/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx b/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx
index 7c8984e..4808c48 100644
--- a/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx
+++ b/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx
@@ -2,8 +2,8 @@ import React from 'react';
 import { Tabs } from 'react-bootstrap';
 import { shallow } from 'enzyme';
 import configureStore from 'redux-mock-store';
-import $ from 'jquery';
-import sinon from 'sinon';
+import fetchMock from 'fetch-mock';
+import thunk from 'redux-thunk';
 
 import DatasourceEditor from '../../../src/datasource/DatasourceEditor';
 import mockDatasource from '../../fixtures/mockDatasource';
@@ -12,8 +12,9 @@ const props = {
   datasource: mockDatasource['7__table'],
   addSuccessToast: () => {},
   addDangerToast: () => {},
-  onChange: sinon.spy(),
+  onChange: () => {},
 };
+
 const extraColumn = {
   column_name: 'new_column',
   type: 'VARCHAR(10)',
@@ -25,26 +26,23 @@ const extraColumn = {
   groupby: true,
 };
 
+const DATASOURCE_ENDPOINT = 'glob:*/datasource/external_metadata/*';
+
 describe('DatasourceEditor', () => {
-  const mockStore = configureStore([]);
+  const mockStore = configureStore([thunk]);
   const store = mockStore({});
+  fetchMock.get(DATASOURCE_ENDPOINT, []);
 
   let wrapper;
   let el;
-  let ajaxStub;
   let inst;
 
   beforeEach(() => {
-    ajaxStub = sinon.stub($, 'ajax');
     el = <DatasourceEditor {...props} />;
     wrapper = shallow(el, { context: { store } }).dive();
     inst = wrapper.instance();
   });
 
-  afterEach(() => {
-    ajaxStub.restore();
-  });
-
   it('is valid', () => {
     expect(React.isValidElement(el)).toBe(true);
   });
@@ -53,12 +51,17 @@ describe('DatasourceEditor', () => {
     expect(wrapper.find(Tabs)).toHaveLength(1);
   });
 
-  it('makes an async request', () => {
+  it('makes an async request', (done) => {
     wrapper.setState({ activeTabKey: 2 });
     const syncButton = wrapper.find('.sync-from-source');
     expect(syncButton).toHaveLength(1);
     syncButton.simulate('click');
-    expect(ajaxStub.calledOnce).toBe(true);
+
+    setTimeout(() => {
+      expect(fetchMock.calls(DATASOURCE_ENDPOINT)).toHaveLength(1);
+      fetchMock.reset();
+      done();
+    }, 0);
   });
 
   it('merges columns', () => {
@@ -67,5 +70,4 @@ describe('DatasourceEditor', () => {
     inst.mergeColumns([extraColumn]);
     expect(inst.state.databaseColumns).toHaveLength(numCols + 1);
   });
-
 });
diff --git a/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx b/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx
index 7c9ed6d..0cc1829 100644
--- a/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx
+++ b/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx
@@ -2,7 +2,8 @@ import React from 'react';
 import { Modal } from 'react-bootstrap';
 import configureStore from 'redux-mock-store';
 import { shallow } from 'enzyme';
-import $ from 'jquery';
+import fetchMock from 'fetch-mock';
+import thunk from 'redux-thunk';
 import sinon from 'sinon';
 
 import DatasourceModal from '../../../src/datasource/DatasourceModal';
@@ -13,31 +14,30 @@ const props = {
   datasource: mockDatasource['7__table'],
   addSuccessToast: () => {},
   addDangerToast: () => {},
-  onChange: sinon.spy(),
+  onChange: () => {},
   show: true,
   onHide: () => {},
+  onDatasourceSave: sinon.spy(),
 };
 
+const SAVE_ENDPOINT = 'glob:*/datasource/save/';
+const SAVE_PAYLOAD = { new: 'data' };
+
 describe('DatasourceModal', () => {
-  const mockStore = configureStore([]);
+  const mockStore = configureStore([thunk]);
   const store = mockStore({});
+  fetchMock.post(SAVE_ENDPOINT, SAVE_PAYLOAD);
 
   let wrapper;
   let el;
-  let ajaxStub;
   let inst;
 
   beforeEach(() => {
-    ajaxStub = sinon.stub($, 'ajax');
     el = <DatasourceModal {...props} />;
     wrapper = shallow(el, { context: { store } }).dive();
     inst = wrapper.instance();
   });
 
-  afterEach(() => {
-    ajaxStub.restore();
-  });
-
   it('is valid', () => {
     expect(React.isValidElement(el)).toBe(true);
   });
@@ -50,8 +50,13 @@ describe('DatasourceModal', () => {
     expect(wrapper.find(DatasourceEditor)).toHaveLength(1);
   });
 
-  it('saves on confirm', () => {
+  it('saves on confirm', (done) => {
     inst.onConfirmSave();
-    expect(ajaxStub.calledOnce).toBe(true);
+    setTimeout(() => {
+      expect(fetchMock.calls(SAVE_ENDPOINT)).toHaveLength(1);
+      expect(props.onDatasourceSave.getCall(0).args[0]).toEqual(SAVE_PAYLOAD);
+      fetchMock.reset();
+      done();
+    }, 0);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
index ab70ea8..9919d36 100644
--- a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
@@ -28,7 +28,7 @@ describe('DatasourceControl', () => {
   function setup() {
     const mockStore = configureStore([]);
     const store = mockStore({});
-    return shallow(<DatasourceControl {...defaultProps} />, { context: { store } }).dive();
+    return shallow(<DatasourceControl {...defaultProps} />, { context: { store } });
   }
 
   it('renders a Modal', () => {
diff --git a/superset/assets/src/datasource/DatasourceEditor.jsx b/superset/assets/src/datasource/DatasourceEditor.jsx
index 5ffb7a6..71a2971 100644
--- a/superset/assets/src/datasource/DatasourceEditor.jsx
+++ b/superset/assets/src/datasource/DatasourceEditor.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Alert, Badge, Col, Label, Tabs, Tab, Well } from 'react-bootstrap';
 import shortid from 'shortid';
-import $ from 'jquery';
+import { SupersetClient } from '@superset-ui/core';
 
 import { t } from '../locales';
 
@@ -34,6 +34,7 @@ function CollectionTabTitle({ title, collection }) {
     </div>
   );
 }
+
 CollectionTabTitle.propTypes = {
   title: PropTypes.string,
   collection: PropTypes.array,
@@ -159,6 +160,7 @@ function StackedField({ label, formElement }) {
     </div>
   );
 }
+
 StackedField.propTypes = {
   label: PropTypes.string,
   formElement: PropTypes.node,
@@ -171,6 +173,7 @@ function FormContainer({ children }) {
     </Well>
   );
 }
+
 FormContainer.propTypes = {
   children: PropTypes.node,
 };
@@ -181,9 +184,11 @@ const propTypes = {
   addSuccessToast: PropTypes.func.isRequired,
   addDangerToast: PropTypes.func.isRequired,
 };
+
 const defaultProps = {
   onChange: () => {},
 };
+
 export class DatasourceEditor extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -206,6 +211,7 @@ export class DatasourceEditor extends React.PureComponent {
     this.validateAndChange = this.validateAndChange.bind(this);
     this.handleTabSelect = this.handleTabSelect.bind(this);
   }
+
   onChange() {
     const datasource = {
       ...this.state.datasource,
@@ -213,19 +219,24 @@ export class DatasourceEditor extends React.PureComponent {
     };
     this.props.onChange(datasource, this.state.errors);
   }
+
   onDatasourceChange(newDatasource) {
     this.setState({ datasource: newDatasource }, this.validateAndChange);
   }
+
   onDatasourcePropChange(attr, value) {
     const datasource = { ...this.state.datasource, [attr]: value };
     this.setState({ datasource }, this.onDatasourceChange(datasource));
   }
+
   setColumns(obj) {
     this.setState(obj, this.validateAndChange);
   }
+
   validateAndChange() {
     this.validate(this.onChange);
   }
+
   mergeColumns(cols) {
     let { databaseColumns } = this.state;
     let hasChanged;
@@ -248,29 +259,22 @@ export class DatasourceEditor extends React.PureComponent {
     }
   }
   syncMetadata() {
-    const datasource = this.state.datasource;
-    const url = `/datasource/external_metadata/${datasource.type}/${datasource.id}/`;
+    const { datasource } = this.state;
     this.setState({ metadataLoading: true });
-    const success = (data) => {
-      this.mergeColumns(data);
+
+    SupersetClient.get({
+      endpoint: `/datasource/external_metadata/${datasource.type}/${datasource.id}/`,
+    }).then(({ json }) => {
+      this.mergeColumns(json);
       this.props.addSuccessToast(t('Metadata has been synced'));
       this.setState({ metadataLoading: false });
-    };
-    const error = (err) => {
-      let msg = t('An error has occurred');
-      if (err.responseJSON && err.responseJSON.error) {
-        msg = err.responseJSON.error;
-      }
+    }).catch((error) => {
+      const msg = error.error || error.statusText || t('An error has occurred');
       this.props.addDangerToast(msg);
       this.setState({ metadataLoading: false });
-    };
-    $.ajax({
-      url,
-      type: 'GET',
-      success,
-      error,
     });
   }
+
   findDuplicates(arr, accessor) {
     const seen = {};
     const dups = [];
@@ -284,6 +288,7 @@ export class DatasourceEditor extends React.PureComponent {
     });
     return dups;
   }
+
   validate(callback) {
     let errors = [];
     let dups;
@@ -305,9 +310,11 @@ export class DatasourceEditor extends React.PureComponent {
 
     this.setState({ errors }, callback);
   }
+
   handleTabSelect(activeTabKey) {
     this.setState({ activeTabKey });
   }
+
   renderSettingsFieldset() {
     const datasource = this.state.datasource;
     return (
@@ -348,6 +355,7 @@ export class DatasourceEditor extends React.PureComponent {
       </Fieldset>
     );
   }
+
   renderAdvancedFieldset() {
     const datasource = this.state.datasource;
     return (
@@ -388,6 +396,7 @@ export class DatasourceEditor extends React.PureComponent {
         />
       </Fieldset>);
   }
+
   renderSpatialTab() {
     const { datasource } = this.state;
     const { spatials, all_cols: allCols } = datasource;
@@ -416,6 +425,7 @@ export class DatasourceEditor extends React.PureComponent {
         />
       </Tab>);
   }
+
   renderErrors() {
     if (this.state.errors.length > 0) {
       return (
@@ -425,6 +435,7 @@ export class DatasourceEditor extends React.PureComponent {
     }
     return null;
   }
+
   renderMetricCollection() {
     return (
       <CollectionTable
@@ -490,6 +501,7 @@ export class DatasourceEditor extends React.PureComponent {
         allowDeletes
       />);
   }
+
   render() {
     const datasource = this.state.datasource;
     return (
@@ -578,6 +590,8 @@ export class DatasourceEditor extends React.PureComponent {
     );
   }
 }
+
 DatasourceEditor.defaultProps = defaultProps;
 DatasourceEditor.propTypes = propTypes;
+
 export default withToasts(DatasourceEditor);
diff --git a/superset/assets/src/datasource/DatasourceModal.jsx b/superset/assets/src/datasource/DatasourceModal.jsx
index 915010b..0d4d8e2 100644
--- a/superset/assets/src/datasource/DatasourceModal.jsx
+++ b/superset/assets/src/datasource/DatasourceModal.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Alert, Button, Modal } from 'react-bootstrap';
 import Dialog from 'react-bootstrap-dialog';
-import $ from 'jquery';
+import { SupersetClient } from '@superset-ui/core';
 
 import { t } from '../locales';
 import DatasourceEditor from '../datasource/DatasourceEditor';
@@ -40,6 +40,7 @@ class DatasourceModal extends React.PureComponent {
     this.onConfirmSave = this.onConfirmSave.bind(this);
     this.setDialogRef = this.setDialogRef.bind(this);
   }
+
   onClickSave() {
     this.dialog.show({
       title: t('Confirm save'),
@@ -51,49 +52,48 @@ class DatasourceModal extends React.PureComponent {
       body: this.renderSaveDialog(),
     });
   }
+
   onConfirmSave() {
-    const url = '/datasource/save/';
-    const that = this;
-    $.ajax({
-      url,
-      type: 'POST',
-      data: {
-        data: JSON.stringify(this.state.datasource),
+    SupersetClient.post({
+      endpoint: '/datasource/save/',
+      postPayload: {
+        data: this.state.datasource,
       },
-      success: (data) => {
+    })
+      .then(({ json }) => {
         this.props.addSuccessToast(t('The datasource has been saved'));
-        this.props.onDatasourceSave(data);
+        this.props.onDatasourceSave(json);
         this.props.onHide();
-      },
-      error(err) {
-        let msg = t('An error has occurred');
-        if (err.responseJSON && err.responseJSON.error) {
-          msg = err.responseJSON.error;
-        }
-        that.dialog.show({
+      })
+      .catch((error) => {
+        this.dialog.show({
           title: 'Error',
           bsSize: 'medium',
           bsStyle: 'danger',
           actions: [
             Dialog.DefaultAction('Ok', () => {}, 'btn-danger'),
           ],
-          body: msg,
+          body: error.error || error.statusText || t('An error has occurred'),
         });
-      },
-    });
+      });
   }
+
   onDatasourceChange(datasource, errors) {
     this.setState({ datasource, errors });
   }
+
   setSearchRef(searchRef) {
     this.searchRef = searchRef;
   }
+
   setDialogRef(ref) {
     this.dialog = ref;
   }
+
   toggleShowDatasource() {
     this.setState({ showDatasource: !this.state.showDatasource });
   }
+
   renderSaveDialog() {
     return (
       <div>
@@ -111,6 +111,7 @@ class DatasourceModal extends React.PureComponent {
       </div>
     );
   }
+
   render() {
     return (
       <Modal
@@ -156,4 +157,5 @@ class DatasourceModal extends React.PureComponent {
 
 DatasourceModal.propTypes = propTypes;
 DatasourceModal.defaultProps = defaultProps;
+
 export default withToasts(DatasourceModal);
diff --git a/superset/assets/src/explore/components/controls/DatasourceControl.jsx b/superset/assets/src/explore/components/controls/DatasourceControl.jsx
index 69f398d..f200d67 100644
--- a/superset/assets/src/explore/components/controls/DatasourceControl.jsx
+++ b/superset/assets/src/explore/components/controls/DatasourceControl.jsx
@@ -9,20 +9,16 @@ import {
   Tooltip,
   Well,
 } from 'react-bootstrap';
-import $ from 'jquery';
 
 import ControlHeader from '../ControlHeader';
 import { t } from '../../../locales';
 import DatasourceModal from '../../../datasource/DatasourceModal';
 import ColumnOption from '../../../components/ColumnOption';
 import MetricOption from '../../../components/MetricOption';
-import withToasts from '../../../messageToasts/enhancers/withToasts';
-
 
 const propTypes = {
   onChange: PropTypes.func,
   value: PropTypes.string.isRequired,
-  addDangerToast: PropTypes.func.isRequired,
   datasource: PropTypes.object.isRequired,
   onDatasourceSave: PropTypes.func,
 };
@@ -39,70 +35,30 @@ class DatasourceControl extends React.PureComponent {
       showEditDatasourceModal: false,
       loading: true,
       showDatasource: false,
+      datasources: null,
     };
     this.toggleShowDatasource = this.toggleShowDatasource.bind(this);
     this.toggleEditDatasourceModal = this.toggleEditDatasourceModal.bind(this);
-    this.setSearchRef = this.setSearchRef.bind(this);
-    this.selectDatasource = this.selectDatasource.bind(this);
   }
+
   onChange(vizType) {
     this.props.onChange(vizType);
     this.setState({ showModal: false });
   }
-  onEnterModal() {
-    if (this.searchRef) {
-      this.searchRef.focus();
-    }
-    const url = '/superset/datasources/';
-    const that = this;
-    if (!this.state.datasources) {
-      $.ajax({
-        type: 'GET',
-        url,
-        success: (data) => {
-          const datasources = data.map(ds => ({
-            rawName: ds.name,
-            connection: ds.connection,
-            schema: ds.schema,
-            name: (
-              <a
-                href="#"
-                onClick={this.selectDatasource.bind(this, ds.uid)}
-                className="datasource-link"
-              >
-                {ds.name}
-              </a>
-            ),
-            type: ds.type,
-          }));
 
-          that.setState({ loading: false, datasources });
-        },
-        error() {
-          that.setState({ loading: false });
-          this.props.addDangerToast(t('Something went wrong while fetching the datasource
list'));
-        },
-      });
-    }
-  }
-  setSearchRef(searchRef) {
-    this.searchRef = searchRef;
-  }
   toggleShowDatasource() {
-    this.setState({ showDatasource: !this.state.showDatasource });
+    this.setState(({ showDatasource }) => ({ showDatasource: !showDatasource }));
   }
+
   toggleModal() {
-    this.setState({ showModal: !this.state.showModal });
-  }
-  selectDatasource(datasourceId) {
-    this.setState({ showModal: false });
-    this.props.onChange(datasourceId);
+    this.setState(({ showModal }) => ({ showModal: !showModal }));
   }
   toggleEditDatasourceModal() {
-    this.setState({ showEditDatasourceModal: !this.state.showEditDatasourceModal });
-  }
-  renderModal() {
+    this.setState(({ showEditDatasourceModal }) => ({
+      showEditDatasourceModal: !showEditDatasourceModal,
+    }));
   }
+
   renderDatasource() {
     const datasource = this.props.datasource;
     return (
@@ -136,6 +92,7 @@ class DatasourceControl extends React.PureComponent {
       </div>
     );
   }
+
   render() {
     return (
       <div>
@@ -193,4 +150,4 @@ class DatasourceControl extends React.PureComponent {
 DatasourceControl.propTypes = propTypes;
 DatasourceControl.defaultProps = defaultProps;
 
-export default withToasts(DatasourceControl);
+export default DatasourceControl;


Mime
View raw message