superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From maximebeauche...@apache.org
Subject [incubator-superset] branch master updated: Explore view save modal spec (#3110)
Date Fri, 11 Aug 2017 00:04:47 GMT
This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin 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 b68084b  Explore view save modal spec (#3110)
b68084b is described below

commit b68084b9ac76c943e55f44b1b5ae22d79fc19d6f
Author: Grace Guo <grace.guo@airbnb.com>
AuthorDate: Thu Aug 10 17:04:44 2017 -0700

    Explore view save modal spec (#3110)
    
    * split reducer logic for ExploreViewContainer
    
    * fix saveModal component and unit tests
    
    * revert changes in SaveModal_spec.
    will make another commit just to improve test coverage for SaveModal component.
    
    * improve test coverage for explore view components:
    - SaveModal component
    - URLShortLinkButton
    
    * remove comment-out code
    
    * [bugfix] wrong 'Cant have overlap between Series and Breakdowns' (#3254)
    
    * [explore] make edit datasource a basic link (#3244)
    
    * Relying on FAB for font-awesome.min.css (#3261)
    
    * Modernize SQLA pessimistic handling (#3256)
    
    Looks like SQLAlchemy has redefined the best practice around
    pessimistic connection handling.
    
    * [webpack] break CSS and JS files while webpackin' (#3262)
    
    * [webpack] break CSS and JS files while webpackin'
    
    * cleaning up some templates
    
    * Fix pylint issue
    
    * import logging (#3264)
    
    * [bugfix] preserve order in groupby (#3268)
    
    Recently in
    https://github.com/apache/incubator-superset/commit/4c3313b01cb508ced8519a68f6479db423974929
    I introduced an issue where the order of groupby fields might change.
    
    This addresses this issue and will preserve ordering.
    
    * Explicitly add Flask as dependancy (#3252)
    
    * Use sane Celery defaults to prevent tasks from being delayed (#3267)
    
    * Improve the chart type of Visualize in sqllab (#3241)
    
    * Improve the chart type of Visualize in sqllab & Add some css & Fix the link
address in the navbar
    
    * add vizTypes filter
    
    * Set default ports Druid (#3266)
    
    For Druid set the default port for the broker and coordinator.
    
    * [explore] Split large reducer logic in ExploreViewContainer (#3088)
    
    * split reducer logic for ExploreViewContainer
    
    * fix saveModal component and unit tests
    
    * revert changes in SaveModal_spec.
    will make another commit just to improve test coverage for SaveModal component.
    
    * remove comment-out code
    
    * fix merge confilicts
---
 .../javascripts/explore/actions/exploreActions.js  |  38 ++++
 .../explore/components/CheckboxControl_spec.jsx    |  12 +-
 .../explore/components/SaveModal_spec.jsx          | 221 +++++++++++++++++----
 .../explore/components/URLShortLinkButton_spec.jsx |   6 +
 4 files changed, 238 insertions(+), 39 deletions(-)

diff --git a/superset/assets/javascripts/explore/actions/exploreActions.js b/superset/assets/javascripts/explore/actions/exploreActions.js
index 32fa3c5..f539aa1 100644
--- a/superset/assets/javascripts/explore/actions/exploreActions.js
+++ b/superset/assets/javascripts/explore/actions/exploreActions.js
@@ -14,6 +14,11 @@ export function setDatasource(datasource) {
   return { type: SET_DATASOURCE, datasource };
 }
 
+export const SET_DATASOURCES = 'SET_DATASOURCES';
+export function setDatasources(datasources) {
+  return { type: SET_DATASOURCES, datasources };
+}
+
 export const FETCH_DATASOURCE_STARTED = 'FETCH_DATASOURCE_STARTED';
 export function fetchDatasourceStarted() {
   return { type: FETCH_DATASOURCE_STARTED };
@@ -29,6 +34,21 @@ export function fetchDatasourceFailed(error) {
   return { type: FETCH_DATASOURCE_FAILED, error };
 }
 
+export const FETCH_DATASOURCES_STARTED = 'FETCH_DATASOURCES_STARTED';
+export function fetchDatasourcesStarted() {
+  return { type: FETCH_DATASOURCES_STARTED };
+}
+
+export const FETCH_DATASOURCES_SUCCEEDED = 'FETCH_DATASOURCES_SUCCEEDED';
+export function fetchDatasourcesSucceeded() {
+  return { type: FETCH_DATASOURCES_SUCCEEDED };
+}
+
+export const FETCH_DATASOURCES_FAILED = 'FETCH_DATASOURCES_FAILED';
+export function fetchDatasourcesFailed(error) {
+  return { type: FETCH_DATASOURCES_FAILED, error };
+}
+
 export const RESET_FIELDS = 'RESET_FIELDS';
 export function resetControls() {
   return { type: RESET_FIELDS };
@@ -61,6 +81,24 @@ export function fetchDatasourceMetadata(datasourceKey, alsoTriggerQuery
= false)
   };
 }
 
+export function fetchDatasources() {
+  return function (dispatch) {
+    dispatch(fetchDatasourcesStarted());
+    const url = '/superset/datasources/';
+    $.ajax({
+      type: 'GET',
+      url,
+      success: (data) => {
+        dispatch(setDatasources(data));
+        dispatch(fetchDatasourcesSucceeded());
+      },
+      error(error) {
+        dispatch(fetchDatasourcesFailed(error.responseJSON.error));
+      },
+    });
+  };
+}
+
 export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
 export function toggleFaveStar(isStarred) {
   return { type: TOGGLE_FAVE_STAR, isStarred };
diff --git a/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
b/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
index 5512b96..0b08397 100644
--- a/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx
@@ -4,24 +4,30 @@ import { Checkbox } from 'react-bootstrap';
 import sinon from 'sinon';
 import { expect } from 'chai';
 import { describe, it, beforeEach } from 'mocha';
-import { mount } from 'enzyme';
+import { shallow } from 'enzyme';
 
 import CheckboxControl from '../../../../javascripts/explore/components/controls/CheckboxControl';
+import ControlHeader from '../../../../javascripts/explore/components/ControlHeader';
 
 const defaultProps = {
   name: 'show_legend',
   onChange: sinon.spy(),
   value: false,
+  label: 'checkbox label',
 };
 
 describe('CheckboxControl', () => {
   let wrapper;
 
   beforeEach(() => {
-    wrapper = mount(<CheckboxControl {...defaultProps} />);
+    wrapper = shallow(<CheckboxControl {...defaultProps} />);
   });
 
   it('renders a Checkbox', () => {
-    expect(wrapper.find(Checkbox)).to.have.lengthOf(1);
+    const controlHeader = wrapper.find(ControlHeader);
+    expect(controlHeader).to.have.lengthOf(1);
+
+    const headerWrapper = controlHeader.shallow();
+    expect(headerWrapper.find(Checkbox)).to.have.length(1);
   });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx b/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
index e0a1a84..e548d21 100644
--- a/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
@@ -1,33 +1,57 @@
 import React from 'react';
+import configureStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+
 import { expect } from 'chai';
 import { describe, it, beforeEach } from 'mocha';
-import { shallow } from 'enzyme';
+import { shallow, mount } from 'enzyme';
 import { Modal, Button, Radio } from 'react-bootstrap';
 import sinon from 'sinon';
 
-import { defaultFormData } from '../../../../javascripts/explore/stores/store';
-import { SaveModal } from '../../../../javascripts/explore/components/SaveModal';
-
-const defaultProps = {
-  can_edit: true,
-  onHide: () => ({}),
-  actions: {
-    saveSlice: sinon.spy(),
-  },
-  form_data: defaultFormData,
-  user_id: '1',
-  dashboards: [],
-  slice: {},
-};
+import * as exploreUtils from '../../../../javascripts/explore/exploreUtils';
+import * as saveModalActions from '../../../../javascripts/explore/actions/saveModalActions';
+import SaveModal from '../../../../javascripts/explore/components/SaveModal';
+
+const $ = window.$ = require('jquery');
 
 describe('SaveModal', () => {
-  let wrapper;
+  const middlewares = [thunk];
+  const mockStore = configureStore(middlewares);
+  const initialState = {
+    chart: {},
+    saveModal: {
+      dashboards: [],
+    },
+    explore: {
+      can_overwrite: true,
+      user_id: '1',
+      datasource: {},
+      slice: {
+        slice_id: 1,
+        slice_name: 'title',
+      },
+      alert: null,
+    },
+  };
+  const store = mockStore(initialState);
 
-  beforeEach(() => {
-    wrapper = shallow(<SaveModal {...defaultProps} />);
-  });
+  const defaultProps = {
+    onHide: () => ({}),
+    actions: saveModalActions,
+    form_data: {},
+  };
+  const mockEvent = {
+    target: {
+      value: 'mock event target',
+    },
+    value: 'mock value',
+  };
+  const getWrapper = () => (shallow(<SaveModal {...defaultProps} />, {
+    context: { store },
+  }).dive());
 
   it('renders a Modal with 7 inputs and 2 buttons', () => {
+    const wrapper = getWrapper();
     expect(wrapper.find(Modal)).to.have.lengthOf(1);
     expect(wrapper.find('input')).to.have.lengthOf(2);
     expect(wrapper.find(Button)).to.have.lengthOf(2);
@@ -35,42 +59,167 @@ describe('SaveModal', () => {
   });
 
   it('does not show overwrite option for new slice', () => {
-    defaultProps.slice = null;
-    const wrapperNewSlice = shallow(<SaveModal {...defaultProps} />);
+    const wrapperNewSlice = getWrapper();
+    wrapperNewSlice.setProps({ slice: null });
     expect(wrapperNewSlice.find('#overwrite-radio')).to.have.lengthOf(0);
     expect(wrapperNewSlice.find('#saveas-radio')).to.have.lengthOf(1);
   });
 
   it('disable overwrite option for non-owner', () => {
-    defaultProps.slice = {};
-    defaultProps.can_overwrite = false;
-    const wrapperForNonOwner = shallow(<SaveModal {...defaultProps} />);
+    const wrapperForNonOwner = getWrapper();
+    wrapperForNonOwner.setProps({ can_overwrite: false });
     const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio');
     expect(overwriteRadio).to.have.lengthOf(1);
     expect(overwriteRadio.prop('disabled')).to.equal(true);
   });
 
   it('saves a new slice', () => {
-    defaultProps.slice = {
-      slice_id: 1,
-      slice_name: 'title',
-    };
-    defaultProps.can_overwrite = false;
-    const wrapperForNewSlice = shallow(<SaveModal {...defaultProps} />);
+    const wrapperForNewSlice = getWrapper();
+    wrapperForNewSlice.setProps({ can_overwrite: false });
+    wrapperForNewSlice.instance().changeAction('saveas');
     const saveasRadio = wrapperForNewSlice.find('#saveas-radio');
     saveasRadio.simulate('click');
     expect(wrapperForNewSlice.state().action).to.equal('saveas');
   });
 
   it('overwrite a slice', () => {
-    defaultProps.slice = {
-      slice_id: 1,
-      slice_name: 'title',
-    };
-    defaultProps.can_overwrite = true;
-    const wrapperForOverwrite = shallow(<SaveModal {...defaultProps} />);
+    const wrapperForOverwrite = getWrapper();
     const overwriteRadio = wrapperForOverwrite.find('#overwrite-radio');
     overwriteRadio.simulate('click');
     expect(wrapperForOverwrite.state().action).to.equal('overwrite');
   });
+
+  it('componentDidMount', () => {
+    sinon.spy(SaveModal.prototype, 'componentDidMount');
+    sinon.spy(saveModalActions, 'fetchDashboards');
+    mount(<SaveModal {...defaultProps} />, {
+      context: { store },
+    });
+    expect(SaveModal.prototype.componentDidMount.calledOnce).to.equal(true);
+    expect(saveModalActions.fetchDashboards.calledOnce).to.equal(true);
+
+    SaveModal.prototype.componentDidMount.restore();
+    saveModalActions.fetchDashboards.restore();
+  });
+
+  it('onChange', () => {
+    const wrapper = getWrapper();
+
+    wrapper.instance().onChange('newSliceName', mockEvent);
+    expect(wrapper.state().newSliceName).to.equal(mockEvent.target.value);
+
+    wrapper.instance().onChange('saveToDashboardId', mockEvent);
+    expect(wrapper.state().saveToDashboardId).to.equal(mockEvent.value);
+
+    wrapper.instance().onChange('newDashboardName', mockEvent);
+    expect(wrapper.state().newDashboardName).to.equal(mockEvent.target.value);
+  });
+
+  describe('saveOrOverwrite', () => {
+    beforeEach(() => {
+      sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => ('mockURL'));
+      sinon.stub(saveModalActions, 'saveSlice').callsFake(() => {
+        const d = $.Deferred();
+        d.resolve('done');
+        return d.promise();
+      });
+    });
+    afterEach(() => {
+      exploreUtils.getExploreUrl.restore();
+      saveModalActions.saveSlice.restore();
+    });
+
+    it('should save slice', () => {
+      const wrapper = getWrapper();
+      wrapper.instance().saveOrOverwrite(true);
+      expect(saveModalActions.saveSlice.getCall(0).args[0]).to.equal('mockURL');
+    });
+    it('existing dashboard', () => {
+      const wrapper = getWrapper();
+      const saveToDashboardId = 100;
+
+      wrapper.setState({ addToDash: 'existing' });
+      wrapper.instance().saveOrOverwrite(true);
+      expect(wrapper.state().alert).to.equal('Please select a dashboard');
+
+      wrapper.setState({ saveToDashboardId });
+      wrapper.instance().saveOrOverwrite(true);
+      const args = exploreUtils.getExploreUrl.getCall(0).args;
+      expect(args[4].save_to_dashboard_id).to.equal(saveToDashboardId);
+    });
+    it('new dashboard', () => {
+      const wrapper = getWrapper();
+      const newDashboardName = 'new dashboard name';
+
+      wrapper.setState({ addToDash: 'new' });
+      wrapper.instance().saveOrOverwrite(true);
+      expect(wrapper.state().alert).to.equal('Please enter a dashboard name');
+
+      wrapper.setState({ newDashboardName });
+      wrapper.instance().saveOrOverwrite(true);
+      const args = exploreUtils.getExploreUrl.getCall(0).args;
+      expect(args[4].new_dashboard_name).to.equal(newDashboardName);
+    });
+  });
+
+  describe('should fetchDashboards', () => {
+    let dispatch;
+    let request;
+    let ajaxStub;
+    const userID = 1;
+    beforeEach(() => {
+      dispatch = sinon.spy();
+      ajaxStub = sinon.stub($, 'ajax');
+    });
+    afterEach(() => {
+      ajaxStub.restore();
+    });
+    const mockDashboardData = {
+      pks: ['value'],
+      result: [
+        { dashboard_title: 'dashboard title' },
+      ],
+    };
+    const makeRequest = () => {
+      request = saveModalActions.fetchDashboards(userID);
+      request(dispatch);
+    };
+
+    it('makes the ajax request', () => {
+      makeRequest();
+      expect(ajaxStub.callCount).to.equal(1);
+    });
+
+    it('calls correct url', () => {
+      const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userID;
+      makeRequest();
+      expect(ajaxStub.getCall(0).args[0].url).to.be.equal(url);
+    });
+
+    it('calls correct actions on error', () => {
+      ajaxStub.yieldsTo('error', { responseJSON: { error: 'error text' } });
+      makeRequest();
+      expect(dispatch.callCount).to.equal(1);
+      expect(dispatch.getCall(0).args[0].type).to.equal(saveModalActions.FETCH_DASHBOARDS_FAILED);
+    });
+
+    it('calls correct actions on success', () => {
+      ajaxStub.yieldsTo('success', mockDashboardData);
+      makeRequest();
+      expect(dispatch.callCount).to.equal(1);
+      expect(dispatch.getCall(0).args[0].type)
+        .to.equal(saveModalActions.FETCH_DASHBOARDS_SUCCEEDED);
+    });
+  });
+
+  it('removeAlert', () => {
+    sinon.spy(saveModalActions, 'removeSaveModalAlert');
+    const wrapper = getWrapper();
+    wrapper.setProps({ alert: 'old alert' });
+
+    wrapper.instance().removeAlert();
+    expect(saveModalActions.removeSaveModalAlert.callCount).to.equal(1);
+    expect(wrapper.state().alert).to.be.a('null');
+    saveModalActions.removeSaveModalAlert.restore();
+  });
 });
diff --git a/superset/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx
b/superset/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx
index f2729db..74d0d04 100644
--- a/superset/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx
@@ -1,7 +1,9 @@
 import React from 'react';
 import { expect } from 'chai';
 import { describe, it } from 'mocha';
+import { shallow } from 'enzyme';
 
+import { OverlayTrigger } from 'react-bootstrap';
 import URLShortLinkButton from '../../../../javascripts/explore/components/URLShortLinkButton';
 
 describe('URLShortLinkButton', () => {
@@ -14,4 +16,8 @@ describe('URLShortLinkButton', () => {
   it('renders', () => {
     expect(React.isValidElement(<URLShortLinkButton {...defaultProps} />)).to.equal(true);
   });
+  it('renders OverlayTrigger', () => {
+    const wrapper = shallow(<URLShortLinkButton {...defaultProps} />);
+    expect(wrapper.find(OverlayTrigger)).have.length(1);
+  });
 });

-- 
To stop receiving notification emails like this one, please contact
['"commits@superset.apache.org" <commits@superset.apache.org>'].

Mime
View raw message