diff --git a/superset/assets/package.json b/superset/assets/package.json
index fb96f73473742..ad1ca18c9b0d1 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -9,6 +9,7 @@
},
"scripts": {
"test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js 'spec/**/*_spec.*'",
+ "test:one": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js",
"cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --require ignore-styles spec/helpers/browser.js 'spec/**/*_spec.*'",
"dev": "NODE_ENV=dev webpack --watch --colors --progress --debug --output-pathinfo --devtool eval-cheap-source-map",
"dev-slow": "NODE_ENV=dev webpack --watch --colors --progress --debug --output-pathinfo --devtool inline-source-map",
@@ -89,7 +90,6 @@
"react-ace": "^5.10.0",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-shallow-compare": "^15.4.2",
- "react-alert": "^2.3.0",
"react-bootstrap": "^0.31.5",
"react-bootstrap-slider": "2.0.1",
"react-bootstrap-table": "^4.0.2",
diff --git a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
index 4b2848085c0f2..e58bb11a48bff 100644
--- a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
@@ -23,7 +23,7 @@ import {
} from '../../../../src/dashboard/actions/dashboardLayout';
import { setUnsavedChanges } from '../../../../src/dashboard/actions/dashboardState';
-import { addInfoToast } from '../../../../src/dashboard/actions/messageToasts';
+import { addInfoToast } from '../../../../src/messageToasts/actions';
import {
DASHBOARD_GRID_TYPE,
diff --git a/superset/assets/spec/javascripts/dashboard/fixtures/mockState.js b/superset/assets/spec/javascripts/dashboard/fixtures/mockState.js
index 655f0bf7b835d..514442fc00ac8 100644
--- a/superset/assets/spec/javascripts/dashboard/fixtures/mockState.js
+++ b/superset/assets/spec/javascripts/dashboard/fixtures/mockState.js
@@ -2,7 +2,7 @@ import chartQueries from './mockChartQueries';
import { dashboardLayout } from './mockDashboardLayout';
import dashboardInfo from './mockDashboardInfo';
import dashboardState from './mockDashboardState';
-import messageToasts from './mockMessageToasts';
+import messageToasts from '../../messageToasts/mockMessageToasts';
import datasources from './mockDatasource';
import sliceEntities from './mockSliceEntities';
diff --git a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
index c8d139085037b..68dc783c4cfda 100644
--- a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx
@@ -1,7 +1,8 @@
import React from 'react';
import sinon from 'sinon';
+import configureStore from 'redux-mock-store';
import { expect } from 'chai';
-import { describe, it, beforeEach } from 'mocha';
+import { describe, it } from 'mocha';
import { shallow } from 'enzyme';
import { Modal } from 'react-bootstrap';
import DatasourceControl from '../../../../src/explore/components/controls/DatasourceControl';
@@ -26,13 +27,14 @@ const defaultProps = {
};
describe('DatasourceControl', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = shallow();
- });
+ function setup() {
+ const mockStore = configureStore([]);
+ const store = mockStore({});
+ return shallow(, { context: { store } }).dive();
+ }
it('renders a Modal', () => {
+ const wrapper = setup();
expect(wrapper.find(Modal)).to.have.lengthOf(1);
});
});
diff --git a/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx
index 18129666cdced..418e9c5812a2e 100644
--- a/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx
@@ -1,5 +1,5 @@
-/* eslint-disable no-unused-expressions */
import React from 'react';
+import configureStore from 'redux-mock-store';
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { shallow } from 'enzyme';
@@ -10,18 +10,25 @@ import ColumnOption from '../../../../src/components/ColumnOption';
import AggregateOption from '../../../../src/explore/components/AggregateOption';
describe('MetricDefinitionOption', () => {
+ const mockStore = configureStore([]);
+ const store = mockStore({});
+
+ function setup(props) {
+ return shallow(, { context: { store } }).dive();
+ }
+
it('renders a MetricOption given a saved metric', () => {
- const wrapper = shallow();
+ const wrapper = setup({ option: { metric_name: 'a_saved_metric' } });
expect(wrapper.find(MetricOption)).to.have.lengthOf(1);
});
it('renders a ColumnOption given a column', () => {
- const wrapper = shallow();
+ const wrapper = setup({ option: { column_name: 'a_column' } });
expect(wrapper.find(ColumnOption)).to.have.lengthOf(1);
});
it('renders an AggregateOption given an aggregate metric', () => {
- const wrapper = shallow();
+ const wrapper = setup({ option: { aggregate_name: 'an_aggregate' } });
expect(wrapper.find(AggregateOption)).to.have.lengthOf(1);
});
});
diff --git a/superset/assets/spec/javascripts/messageToasts/.eslintrc b/superset/assets/spec/javascripts/messageToasts/.eslintrc
new file mode 100644
index 0000000000000..a3f86e3a17a0c
--- /dev/null
+++ b/superset/assets/spec/javascripts/messageToasts/.eslintrc
@@ -0,0 +1,33 @@
+{
+ "extends": "prettier",
+ "plugins": ["prettier"],
+ "rules": {
+ "prefer-template": 2,
+ "new-cap": 2,
+ "no-restricted-syntax": 2,
+ "guard-for-in": 2,
+ "prefer-arrow-callback": 2,
+ "func-names": 2,
+ "react/jsx-no-bind": 2,
+ "no-confusing-arrow": 2,
+ "jsx-a11y/no-static-element-interactions": 2,
+ "jsx-a11y/anchor-has-content": 2,
+ "react/require-default-props": 2,
+ "no-plusplus": 2,
+ "no-mixed-operators": 0,
+ "no-continue": 2,
+ "no-bitwise": 2,
+ "no-undef": 2,
+ "no-multi-assign": 2,
+ "no-restricted-properties": 2,
+ "no-prototype-builtins": 2,
+ "jsx-a11y/href-no-hash": 2,
+ "class-methods-use-this": 2,
+ "import/no-named-as-default": 2,
+ "import/prefer-default-export": 2,
+ "react/no-unescaped-entities": 2,
+ "react/no-string-refs": 2,
+ "react/jsx-indent": 0,
+ "prettier/prettier": "error"
+ }
+}
diff --git a/superset/assets/spec/javascripts/messageToasts/.prettierrc b/superset/assets/spec/javascripts/messageToasts/.prettierrc
new file mode 100644
index 0000000000000..a20502b7f06d8
--- /dev/null
+++ b/superset/assets/spec/javascripts/messageToasts/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/superset/assets/spec/javascripts/dashboard/components/ToastPresenter_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx
similarity index 82%
rename from superset/assets/spec/javascripts/dashboard/components/ToastPresenter_spec.jsx
rename to superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx
index 7545ad6b0986a..aa04adcb3cda7 100644
--- a/superset/assets/spec/javascripts/dashboard/components/ToastPresenter_spec.jsx
+++ b/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx
@@ -3,9 +3,9 @@ import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
-import mockMessageToasts from '../fixtures/mockMessageToasts';
-import Toast from '../../../../src/dashboard/components/Toast';
-import ToastPresenter from '../../../../src/dashboard/components/ToastPresenter';
+import mockMessageToasts from '../mockMessageToasts';
+import Toast from '../../../../src/messageToasts/components/Toast';
+import ToastPresenter from '../../../../src/messageToasts/components/ToastPresenter';
describe('ToastPresenter', () => {
const props = {
diff --git a/superset/assets/spec/javascripts/dashboard/components/Toast_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx
similarity index 90%
rename from superset/assets/spec/javascripts/dashboard/components/Toast_spec.jsx
rename to superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx
index 6ed0bc5adf517..ce3396cd62391 100644
--- a/superset/assets/spec/javascripts/dashboard/components/Toast_spec.jsx
+++ b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx
@@ -4,8 +4,8 @@ import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
-import mockMessageToasts from '../fixtures/mockMessageToasts';
-import Toast from '../../../../src/dashboard/components/Toast';
+import mockMessageToasts from '../mockMessageToasts';
+import Toast from '../../../../src/messageToasts/components/Toast';
describe('Toast', () => {
const props = {
diff --git a/superset/assets/spec/javascripts/dashboard/fixtures/mockMessageToasts.js b/superset/assets/spec/javascripts/messageToasts/mockMessageToasts.js
similarity index 63%
rename from superset/assets/spec/javascripts/dashboard/fixtures/mockMessageToasts.js
rename to superset/assets/spec/javascripts/messageToasts/mockMessageToasts.js
index 07726a8c73c93..087374c91b6f5 100644
--- a/superset/assets/spec/javascripts/dashboard/fixtures/mockMessageToasts.js
+++ b/superset/assets/spec/javascripts/messageToasts/mockMessageToasts.js
@@ -1,7 +1,4 @@
-import {
- INFO_TOAST,
- DANGER_TOAST,
-} from '../../../../src/dashboard/util/constants';
+import { INFO_TOAST, DANGER_TOAST } from '../../../src/messageToasts/constants';
export default [
{ id: 'info_id', toastType: INFO_TOAST, text: 'info toast' },
diff --git a/superset/assets/spec/javascripts/dashboard/reducers/messageToasts_spec.js b/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js
similarity index 79%
rename from superset/assets/spec/javascripts/dashboard/reducers/messageToasts_spec.js
rename to superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js
index 5280312bb6971..8d7127087e7a0 100644
--- a/superset/assets/spec/javascripts/dashboard/reducers/messageToasts_spec.js
+++ b/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js
@@ -1,11 +1,8 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
-import {
- ADD_TOAST,
- REMOVE_TOAST,
-} from '../../../../src/dashboard/actions/messageToasts';
-import messageToastsReducer from '../../../../src/dashboard/reducers/messageToasts';
+import { ADD_TOAST, REMOVE_TOAST } from '../../../../src/messageToasts/actions';
+import messageToastsReducer from '../../../../src/messageToasts/reducers';
describe('messageToasts reducer', () => {
it('should return initial state', () => {
diff --git a/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js b/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
new file mode 100644
index 0000000000000..a3c7ce906809d
--- /dev/null
+++ b/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js
@@ -0,0 +1,35 @@
+import { describe, it } from 'mocha';
+import { expect } from 'chai';
+
+import {
+ DANGER_TOAST,
+ INFO_TOAST,
+ SUCCESS_TOAST,
+} from '../../../../src/messageToasts/constants';
+
+import getToastsFromPyFlashMessages from '../../../../src/messageToasts/utils/getToastsFromPyFlashMessages';
+
+describe('getToastsFromPyFlashMessages', () => {
+ it('should return an info toast', () => {
+ const toast = getToastsFromPyFlashMessages([['info', 'info test']])[0];
+ expect(toast).to.deep.include({ toastType: INFO_TOAST, text: 'info test' });
+ });
+
+ it('should return a success toast', () => {
+ const toast = getToastsFromPyFlashMessages([
+ ['success', 'success test'],
+ ])[0];
+ expect(toast).to.deep.include({
+ toastType: SUCCESS_TOAST,
+ text: 'success test',
+ });
+ });
+
+ it('should return a danger toast', () => {
+ const toast = getToastsFromPyFlashMessages([['danger', 'danger test']])[0];
+ expect(toast).to.deep.include({
+ toastType: DANGER_TOAST,
+ text: 'danger test',
+ });
+ });
+});
diff --git a/superset/assets/spec/javascripts/sqllab/AlertsWrapper_spec.jsx b/superset/assets/spec/javascripts/sqllab/AlertsWrapper_spec.jsx
deleted file mode 100644
index 9adec4fbbb99f..0000000000000
--- a/superset/assets/spec/javascripts/sqllab/AlertsWrapper_spec.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { describe, it } from 'mocha';
-import { expect } from 'chai';
-import { shallow } from 'enzyme';
-import AlertContainer from 'react-alert';
-import AlertsWrapper from '../../../src/components/AlertsWrapper';
-
-describe('AlertsWrapper', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = shallow();
- });
-
- it('is valid', () => {
- expect(React.isValidElement()).to.equal(true);
- });
-
- it('renders AlertContainer', () => {
- expect(wrapper.find(AlertContainer)).to.have.length(1);
- });
-
- it('expects AlertContainer to have correct props', () => {
- const alertContainerProps = wrapper.find(AlertContainer).props();
- expect(alertContainerProps.offset).to.be.equal(14);
- expect(alertContainerProps.position).to.be.equal('top right');
- expect(alertContainerProps.theme).to.be.equal('dark');
- expect(alertContainerProps.time).to.be.equal(5000);
- expect(alertContainerProps.transition).to.be.equal('fade');
- });
-});
diff --git a/superset/assets/spec/javascripts/sqllab/App_spec.jsx b/superset/assets/spec/javascripts/sqllab/App_spec.jsx
index 8d9facb0c2fc4..ce76e309f40f0 100644
--- a/superset/assets/spec/javascripts/sqllab/App_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/App_spec.jsx
@@ -14,23 +14,24 @@ import { sqlLabReducer } from '../../../src/SqlLab/reducers';
describe('App', () => {
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
- const store = mockStore(sqlLabReducer(undefined, {}));
+ const store = mockStore({ sqlLab: sqlLabReducer(undefined, {}), messageToasts: [] });
let wrapper;
beforeEach(() => {
- wrapper = shallow(, {
- context: { store },
- }).dive();
+ wrapper = shallow(, { context: { store } }).dive();
});
+
it('is valid', () => {
expect(React.isValidElement()).to.equal(true);
});
+
it('should handler resize', () => {
sinon.spy(wrapper.instance(), 'getHeight');
wrapper.instance().handleResize();
expect(wrapper.instance().getHeight.callCount).to.equal(1);
wrapper.instance().getHeight.restore();
});
+
it('should render', () => {
expect(wrapper.find('.SqlLab')).to.have.length(1);
expect(wrapper.find(TabbedSqlEditors)).to.have.length(1);
diff --git a/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx b/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx
index dcbb64e1a15cf..662cb352f39e3 100644
--- a/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx
@@ -7,7 +7,7 @@ import CopyQueryTabUrl from '../../../src/SqlLab/components/CopyQueryTabUrl';
describe('CopyQueryTabUrl', () => {
const mockedProps = {
- queryEditor: initialState.queryEditors[0],
+ queryEditor: initialState.sqlLab.queryEditors[0],
};
it('is valid with props', () => {
expect(
diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
index df7035a96d829..16f1f8bf36404 100644
--- a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
@@ -20,6 +20,7 @@ describe('SqlEditorLeftBar', () => {
queryEditorSetDb: sinon.stub(),
setDatabases: sinon.stub(),
addTable: sinon.stub(),
+ addDangerToast: sinon.stub(),
},
tables: [table],
queryEditor: defaultQueryEditor,
diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx
index d1b58d32ed84e..b0689650da306 100644
--- a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx
@@ -11,7 +11,7 @@ describe('SqlEditor', () => {
const mockedProps = {
actions: {},
database: {},
- queryEditor: initialState.queryEditors[0],
+ queryEditor: initialState.sqlLab.queryEditors[0],
latestQuery: queries[0],
tables: [table],
queries,
diff --git a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
index c898662f573dd..0846af81667e7 100644
--- a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx
@@ -22,10 +22,12 @@ describe('TabbedSqlEditors', () => {
'dfsadfs',
'newEditorId',
];
+
const tables = [Object.assign({}, table[0], {
dataPreviewQueryId: 'B1-VQU1zW',
queryEditorId: 'newEditorId',
})];
+
const queryEditors = [{
autorun: false,
dbId: 1,
@@ -47,8 +49,8 @@ describe('TabbedSqlEditors', () => {
databases: {},
tables: [],
queries: {},
- queryEditors: initialState.queryEditors,
- tabHistory: initialState.tabHistory,
+ queryEditors: initialState.sqlLab.queryEditors,
+ tabHistory: initialState.sqlLab.tabHistory,
editorHeight: '',
getHeight: () => ('100px'),
database: {},
@@ -163,7 +165,7 @@ describe('TabbedSqlEditors', () => {
wrapper.setState({ hideLeftBar: true });
const firstTab = wrapper.find(Tab).first();
- expect(firstTab.props().eventKey).to.contain(initialState.queryEditors[0].id);
+ expect(firstTab.props().eventKey).to.contain(initialState.sqlLab.queryEditors[0].id);
expect(firstTab.find(SqlEditor)).to.have.length(1);
const lastTab = wrapper.find(Tab).last();
diff --git a/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx b/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx
index b80a9626067d0..5eb4802a7de2f 100644
--- a/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx
@@ -17,17 +17,16 @@ import { VISUALIZE_VALIDATION_ERRORS } from '../../../src/SqlLab/constants';
import VisualizeModal from '../../../src/SqlLab/components/VisualizeModal';
import * as exploreUtils from '../../../src/explore/exploreUtils';
-global.notify = {
- info: () => {},
- error: () => {},
-};
-
describe('VisualizeModal', () => {
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
- const initialState = sqlLabReducer({}, {});
- initialState.common = {
- conf: { SUPERSET_WEBSERVER_TIMEOUT: 45 },
+ const initialState = {
+ sqlLab: {
+ ...sqlLabReducer(undefined, {}),
+ common: {
+ conf: { SUPERSET_WEBSERVER_TIMEOUT: 45 },
+ },
+ },
};
const store = mockStore(initialState);
const mockedProps = {
@@ -277,7 +276,7 @@ describe('VisualizeModal', () => {
});
it('should build visualize advise for long query', () => {
- const longQuery = Object.assign({}, queries[0], { endDttm: 1476910666798 });
+ const longQuery = { ...queries[0], endDttm: 1476910666798 };
const props = {
show: true,
query: longQuery,
@@ -334,29 +333,46 @@ describe('VisualizeModal', () => {
expect(spyCall.args[0].data.data).to.equal(JSON.stringify(mockOptions));
});
it('should open new window', () => {
+ const infoToastSpy = sinon.spy();
+
datasourceSpy.callsFake(() => {
const d = $.Deferred();
d.resolve('done');
return d.promise();
});
- wrapper.setProps({ actions: { createDatasource: datasourceSpy } });
+
+ wrapper.setProps({
+ actions: {
+ createDatasource: datasourceSpy,
+ addInfoToast: infoToastSpy,
+ },
+ });
wrapper.instance().visualize();
expect(exploreUtils.exportChart.callCount).to.equal(1);
expect(exploreUtils.exportChart.getCall(0).args[0].datasource).to.equal('107__table');
+ expect(infoToastSpy.callCount).to.equal(1);
});
- it('should notify error', () => {
+ it('should add error toast', () => {
+ const dangerToastSpy = sinon.spy();
+
datasourceSpy.callsFake(() => {
const d = $.Deferred();
d.reject('error message');
return d.promise();
});
- wrapper.setProps({ actions: { createDatasource: datasourceSpy } });
- sinon.spy(notify, 'error');
+
+
+ wrapper.setProps({
+ actions: {
+ createDatasource: datasourceSpy,
+ addDangerToast: dangerToastSpy,
+ },
+ });
wrapper.instance().visualize();
expect(exploreUtils.exportChart.callCount).to.equal(0);
- expect(notify.error.callCount).to.equal(1);
+ expect(dangerToastSpy.callCount).to.equal(1);
});
});
});
diff --git a/superset/assets/spec/javascripts/sqllab/actions_spec.js b/superset/assets/spec/javascripts/sqllab/actions_spec.js
index 34c32a23a0d1a..5909ca8241c25 100644
--- a/superset/assets/spec/javascripts/sqllab/actions_spec.js
+++ b/superset/assets/spec/javascripts/sqllab/actions_spec.js
@@ -20,13 +20,15 @@ describe('async actions', () => {
describe('saveQuery', () => {
it('makes the ajax request', () => {
- actions.saveQuery(query);
+ const thunk = actions.saveQuery(query);
+ thunk((/* mockDispatch */) => {});
expect(ajaxStub.calledOnce).to.be.true;
});
it('calls correct url', () => {
const url = '/savedqueryviewapi/api/create';
- actions.saveQuery(query);
+ const thunk = actions.saveQuery(query);
+ thunk((/* mockDispatch */) => {});
expect(ajaxStub.getCall(0).args[0].url).to.equal(url);
});
});
diff --git a/superset/assets/spec/javascripts/sqllab/fixtures.js b/superset/assets/spec/javascripts/sqllab/fixtures.js
index c05a745884db7..510b5d5bda9d1 100644
--- a/superset/assets/spec/javascripts/sqllab/fixtures.js
+++ b/superset/assets/spec/javascripts/sqllab/fixtures.js
@@ -319,15 +319,18 @@ export const runningQuery = {
export const cachedQuery = Object.assign({}, queries[0], { cached: true });
export const initialState = {
- alerts: [],
- queries: {},
- databases: {},
- queryEditors: [defaultQueryEditor],
- tabHistory: [defaultQueryEditor.id],
- tables: [],
- workspaceQueries: [],
- queriesLastUpdate: 0,
- activeSouthPaneTab: 'Results',
+ sqlLab: {
+ alerts: [],
+ queries: {},
+ databases: {},
+ queryEditors: [defaultQueryEditor],
+ tabHistory: [defaultQueryEditor.id],
+ tables: [],
+ workspaceQueries: [],
+ queriesLastUpdate: 0,
+ activeSouthPaneTab: 'Results',
+ },
+ messageToasts: [],
};
export const query = {
diff --git a/superset/assets/spec/javascripts/sqllab/reducers_spec.js b/superset/assets/spec/javascripts/sqllab/reducers_spec.js
index a23ceb5d57b8f..2931d13b46a92 100644
--- a/superset/assets/spec/javascripts/sqllab/reducers_spec.js
+++ b/superset/assets/spec/javascripts/sqllab/reducers_spec.js
@@ -3,12 +3,17 @@ import { expect } from 'chai';
import * as r from '../../../src/SqlLab/reducers';
import * as actions from '../../../src/SqlLab/actions';
-import { alert, table, initialState } from './fixtures';
+import { table, initialState as mockState } from './fixtures';
+
+const initialState = mockState.sqlLab;
describe('sqlLabReducer', () => {
describe('CLONE_QUERY_TO_NEW_TAB', () => {
const testQuery = { sql: 'SELECT * FROM...', dbId: 1, id: 'flasj233' };
- let newState = Object.assign({}, initialState, { queries: { [testQuery.id]: testQuery } });
+ let newState = {
+ ...initialState,
+ queries: { [testQuery.id]: testQuery },
+ };
beforeEach(() => {
newState = r.sqlLabReducer(newState, actions.cloneQueryToNewTab(testQuery));
});
@@ -29,24 +34,12 @@ describe('sqlLabReducer', () => {
expect(newState.tabHistory[1]).to.eq(newState.queryEditors[1].id);
});
});
- describe('Alerts', () => {
- const state = Object.assign({}, initialState);
- let newState;
- it('should add one alert', () => {
- newState = r.sqlLabReducer(state, actions.addAlert(alert));
- expect(newState.alerts).to.have.lengthOf(1);
- });
- it('should remove one alert', () => {
- newState = r.sqlLabReducer(newState, actions.removeAlert(newState.alerts[0]));
- expect(newState.alerts).to.have.lengthOf(0);
- });
- });
describe('Query editors actions', () => {
let newState;
let defaultQueryEditor;
let qe;
beforeEach(() => {
- newState = Object.assign({}, initialState);
+ newState = { ...initialState };
defaultQueryEditor = newState.queryEditors[0];
qe = Object.assign({}, defaultQueryEditor);
newState = r.sqlLabReducer(newState, actions.addQueryEditor(qe));
@@ -134,8 +127,8 @@ describe('sqlLabReducer', () => {
let query;
let newQuery;
beforeEach(() => {
- newState = Object.assign({}, initialState);
- newQuery = Object.assign({}, query);
+ newState = { ...initialState };
+ newQuery = { ...query };
});
it('should start a query', () => {
newState = r.sqlLabReducer(newState, actions.startQuery(newQuery));
diff --git a/superset/assets/src/SqlLab/actions.js b/superset/assets/src/SqlLab/actions.js
index 644947023bcb8..a71f4955d4c92 100644
--- a/superset/assets/src/SqlLab/actions.js
+++ b/superset/assets/src/SqlLab/actions.js
@@ -1,11 +1,16 @@
-/* global notify */
+/* global window */
+/* eslint no-undef: 2 */
+import $ from 'jquery';
import shortid from 'shortid';
import { now } from '../modules/dates';
import { t } from '../locales';
+import {
+ addSuccessToast as addSuccessToastAction,
+ addDangerToast as addDangerToastAction,
+ addInfoToast as addInfoToastAction,
+} from '../messageToasts/actions';
import { COMMON_ERR_MESSAGES } from '../common';
-const $ = require('jquery');
-
export const RESET_STATE = 'RESET_STATE';
export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR';
export const CLONE_QUERY_TO_NEW_TAB = 'CLONE_QUERY_TO_NEW_TAB';
@@ -28,8 +33,6 @@ export const QUERY_EDITOR_PERSIST_HEIGHT = 'QUERY_EDITOR_PERSIST_HEIGHT';
export const SET_DATABASES = 'SET_DATABASES';
export const SET_ACTIVE_QUERY_EDITOR = 'SET_ACTIVE_QUERY_EDITOR';
export const SET_ACTIVE_SOUTHPANE_TAB = 'SET_ACTIVE_SOUTHPANE_TAB';
-export const ADD_ALERT = 'ADD_ALERT';
-export const REMOVE_ALERT = 'REMOVE_ALERT';
export const REFRESH_QUERIES = 'REFRESH_QUERIES';
export const RUN_QUERY = 'RUN_QUERY';
export const START_QUERY = 'START_QUERY';
@@ -46,21 +49,31 @@ export const CREATE_DATASOURCE_STARTED = 'CREATE_DATASOURCE_STARTED';
export const CREATE_DATASOURCE_SUCCESS = 'CREATE_DATASOURCE_SUCCESS';
export const CREATE_DATASOURCE_FAILED = 'CREATE_DATASOURCE_FAILED';
+export const addInfoToast = addInfoToastAction;
+export const addSuccessToast = addSuccessToastAction;
+export const addDangerToast = addDangerToastAction;
+
export function resetState() {
return { type: RESET_STATE };
}
export function saveQuery(query) {
- const url = '/savedqueryviewapi/api/create';
- $.ajax({
- type: 'POST',
- url,
- data: query,
- success: () => notify.success(t('Your query was saved')),
- error: () => notify.error(t('Your query could not be saved')),
- dataType: 'json',
- });
- return { type: SAVE_QUERY };
+ return (dispatch) => {
+ const url = '/savedqueryviewapi/api/create';
+ $.ajax({
+ type: 'POST',
+ url,
+ data: query,
+ success: () => {
+ dispatch(addSuccessToast(t('Your query was saved')));
+ },
+ error: () => {
+ dispatch(addDangerToast(t('Your query could not be saved')));
+ },
+ dataType: 'json',
+ });
+ return { type: SAVE_QUERY };
+ };
}
export function startQuery(query) {
@@ -136,7 +149,7 @@ export function runQuery(query) {
select_as_cta: query.ctas,
templateParams: query.templateParams,
};
- const sqlJsonUrl = '/superset/sql_json/' + location.search;
+ const sqlJsonUrl = '/superset/sql_json/' + window.location.search;
$.ajax({
type: 'POST',
dataType: 'json',
@@ -183,10 +196,10 @@ export function postStopQuery(query) {
url: stopQueryUrl,
data: stopQueryRequestData,
success() {
- notify.success(t('Query was stopped.'));
+ dispatch(addSuccessToast(t('Query was stopped.')));
},
error() {
- notify.error(t('Failed at stopping query.'));
+ dispatch(addDangerToast(t('Failed at stopping query.')));
},
});
};
@@ -208,16 +221,6 @@ export function cloneQueryToNewTab(query) {
return { type: CLONE_QUERY_TO_NEW_TAB, query };
}
-export function addAlert(alert) {
- const o = Object.assign({}, alert);
- o.id = shortid.generate();
- return { type: ADD_ALERT, alert: o };
-}
-
-export function removeAlert(alert) {
- return { type: REMOVE_ALERT, alert };
-}
-
export function setActiveQueryEditor(queryEditor) {
return { type: SET_ACTIVE_QUERY_EDITOR, queryEditor };
}
@@ -306,7 +309,7 @@ export function addTable(query, tableName, schemaName) {
isMetadataLoading: false,
});
dispatch(mergeTable(newTable));
- notify.error(t('Error occurred while fetching table metadata'));
+ dispatch(addDangerToast(t('Error occurred while fetching table metadata')));
});
url = `/superset/extra_table_metadata/${query.dbId}/${tableName}/${schemaName}/`;
@@ -319,7 +322,7 @@ export function addTable(query, tableName, schemaName) {
isExtraMetadataLoading: false,
});
dispatch(mergeTable(newTable));
- notify.error(t('Error occurred while fetching table metadata'));
+ dispatch(addDangerToast(t('Error occurred while fetching table metadata')));
});
};
}
@@ -381,7 +384,9 @@ export function popStoredQuery(urlId) {
};
dispatch(addQueryEditor(queryEditorProps));
},
- error: () => notify.error(t('The query couldn\'t be loaded')),
+ error: () => {
+ dispatch(addDangerToast(t('The query couldn\'t be loaded')));
+ },
});
};
}
@@ -401,7 +406,9 @@ export function popSavedQuery(saveQueryId) {
};
dispatch(addQueryEditor(queryEditorProps));
},
- error: () => notify.error(t('The query couldn\'t be loaded')),
+ error: () => {
+ dispatch(addDangerToast(t('The query couldn\'t be loaded')));
+ },
});
};
}
diff --git a/superset/assets/src/SqlLab/components/App.jsx b/superset/assets/src/SqlLab/components/App.jsx
index 3698a2a258784..8a0c084373894 100644
--- a/superset/assets/src/SqlLab/components/App.jsx
+++ b/superset/assets/src/SqlLab/components/App.jsx
@@ -2,15 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
+import $ from 'jquery';
import TabbedSqlEditors from './TabbedSqlEditors';
import QueryAutoRefresh from './QueryAutoRefresh';
import QuerySearch from './QuerySearch';
-import AlertsWrapper from '../../components/AlertsWrapper';
+import ToastPresenter from '../../messageToasts/containers/ToastPresenter';
import * as Actions from '../actions';
-const $ = window.$ = require('jquery');
-
class App extends React.PureComponent {
constructor(props) {
super(props);
@@ -39,8 +38,10 @@ class App extends React.PureComponent {
const alertEl = $('#sqllab-alerts');
const headerEl = $('header .navbar');
const headerHeight = headerEl.outerHeight() + parseInt(headerEl.css('marginBottom'), 10);
- const searchHeaderHeight = searchHeaderEl.length > 0 ?
- searchHeaderEl.outerHeight() + parseInt(searchHeaderEl.css('marginBottom'), 10) : 0;
+ const searchHeaderHeight =
+ searchHeaderEl.length > 0
+ ? searchHeaderEl.outerHeight() + parseInt(searchHeaderEl.css('marginBottom'), 10)
+ : 0;
const tabsHeight = tabsEl.length > 0 ? tabsEl.outerHeight() : searchHeaderHeight;
const warningHeight = warningEl.length > 0 ? warningEl.outerHeight() : 0;
const alertHeight = alertEl.length > 0 ? alertEl.outerHeight() : 0;
@@ -71,27 +72,17 @@ class App extends React.PureComponent {
}
return (
-
-
- {content}
-
+
{content}
+
);
}
}
App.propTypes = {
- alerts: PropTypes.array,
actions: PropTypes.object,
- initMessages: PropTypes.array,
};
-function mapStateToProps(state) {
- return {
- alerts: state.alerts,
- initMessages: state.flash_messages,
- };
-}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
@@ -99,4 +90,7 @@ function mapDispatchToProps(dispatch) {
}
export { App };
-export default connect(mapStateToProps, mapDispatchToProps)(App);
+export default connect(
+ null,
+ mapDispatchToProps,
+)(App);
diff --git a/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx b/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx
index 55e06cc1467b2..4bd034e712a79 100644
--- a/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx
+++ b/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx
@@ -57,10 +57,10 @@ QueryAutoRefresh.propTypes = {
queriesLastUpdate: PropTypes.number.isRequired,
};
-function mapStateToProps(state) {
+function mapStateToProps({ sqlLab }) {
return {
- queries: state.queries,
- queriesLastUpdate: state.queriesLastUpdate,
+ queries: sqlLab.queries,
+ queriesLastUpdate: sqlLab.queriesLastUpdate,
};
}
diff --git a/superset/assets/src/SqlLab/components/QuerySearch.jsx b/superset/assets/src/SqlLab/components/QuerySearch.jsx
index e6a19d911fd6f..d13d99376bcbb 100644
--- a/superset/assets/src/SqlLab/components/QuerySearch.jsx
+++ b/superset/assets/src/SqlLab/components/QuerySearch.jsx
@@ -1,3 +1,4 @@
+/* eslint no-undef: 2 */
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
@@ -14,7 +15,7 @@ import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
import AsyncSelect from '../../components/AsyncSelect';
import { t } from '../../locales';
-const $ = (window.$ = require('jquery'));
+const $ = require('jquery');
const propTypes = {
actions: PropTypes.object.isRequired,
@@ -127,10 +128,7 @@ class QuerySearch extends React.PureComponent {
const options = data.result.map(db => ({ value: db.id, label: db.database_name }));
this.props.actions.setDatabases(data.result);
if (data.result.length === 0) {
- this.props.actions.addAlert({
- bsStyle: 'danger',
- msg: t("It seems you don't have access to any database"),
- });
+ this.props.actions.addDangerToast(t("It seems you don't have access to any database"));
}
return options;
}
diff --git a/superset/assets/src/SqlLab/components/SouthPane.jsx b/superset/assets/src/SqlLab/components/SouthPane.jsx
index 1ea8e27e8a13c..7c801149cb908 100644
--- a/superset/assets/src/SqlLab/components/SouthPane.jsx
+++ b/superset/assets/src/SqlLab/components/SouthPane.jsx
@@ -98,9 +98,9 @@ class SouthPane extends React.PureComponent {
}
}
-function mapStateToProps(state) {
+function mapStateToProps({ sqlLab }) {
return {
- activeSouthPaneTab: state.activeSouthPaneTab,
+ activeSouthPaneTab: sqlLab.activeSouthPaneTab,
};
}
diff --git a/superset/assets/src/SqlLab/components/SqlEditor.jsx b/superset/assets/src/SqlLab/components/SqlEditor.jsx
index 37626a83bc280..a4cb4eb0557ec 100644
--- a/superset/assets/src/SqlLab/components/SqlEditor.jsx
+++ b/superset/assets/src/SqlLab/components/SqlEditor.jsx
@@ -1,3 +1,5 @@
+/* global window */
+/* eslint no-undef: 2 */
import React from 'react';
import PropTypes from 'prop-types';
import throttle from 'lodash.throttle';
@@ -126,7 +128,7 @@ class SqlEditor extends React.PureComponent {
this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
}
runQuery() {
- this.startQuery(!this.props.database.allow_run_sync);
+ this.startQuery(!(this.props.database || {}).allow_run_sync);
}
startQuery(runAsync = false, ctas = false) {
const qe = this.props.queryEditor;
diff --git a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
index 08c0e9cdc68d8..a255ca631b396 100644
--- a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
+++ b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
@@ -1,4 +1,5 @@
-/* global notify */
+/* global window */
+/* eslint no-undef: 2 */
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
@@ -9,7 +10,7 @@ import TableElement from './TableElement';
import AsyncSelect from '../../components/AsyncSelect';
import { t } from '../../locales';
-const $ = window.$ = require('jquery');
+const $ = require('jquery');
const propTypes = {
queryEditor: PropTypes.object.isRequired,
@@ -62,10 +63,7 @@ class SqlEditorLeftBar extends React.PureComponent {
const options = data.result.map(db => ({ value: db.id, label: db.database_name }));
this.props.actions.setDatabases(data.result);
if (data.result.length === 0) {
- this.props.actions.addAlert({
- bsStyle: 'danger',
- msg: t('It seems you don\'t have access to any database'),
- });
+ this.props.actions.addDangerToast(t('It seems you don\'t have access to any database'));
}
return options;
}
@@ -88,7 +86,7 @@ class SqlEditorLeftBar extends React.PureComponent {
})
.fail(() => {
this.setState({ tableLoading: false, tableOptions: [], tableLength: 0 });
- notify.error(t('Error while fetching table list'));
+ this.props.actions.addDangerToast(t('Error while fetching table list'));
});
} else {
this.setState({ tableLoading: false, tableOptions: [], filterOptions: null });
@@ -129,7 +127,7 @@ class SqlEditorLeftBar extends React.PureComponent {
})
.fail(() => {
this.setState({ schemaLoading: false, schemaOptions: [] });
- notify.error(t('Error while fetching schema list'));
+ this.props.actions.addDangerToast(t('Error while fetching schema list'));
});
}
}
@@ -159,7 +157,9 @@ class SqlEditorLeftBar extends React.PureComponent {
'_od_DatabaseAsync=asc'
}
onChange={this.onDatabaseChange.bind(this)}
- onAsyncError={() => notify.error(t('Error while fetching database list'))}
+ onAsyncError={() => {
+ this.props.actions.addDangerToast(t('Error while fetching database list'));
+ }}
value={this.props.queryEditor.dbId}
databaseId={this.props.queryEditor.dbId}
actions={this.props.actions}
diff --git a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
index b9acc160d5330..0c5b2729960eb 100644
--- a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
+++ b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
@@ -231,14 +231,14 @@ class TabbedSqlEditors extends React.PureComponent {
TabbedSqlEditors.propTypes = propTypes;
TabbedSqlEditors.defaultProps = defaultProps;
-function mapStateToProps(state) {
+function mapStateToProps({ sqlLab }) {
return {
- databases: state.databases,
- queryEditors: state.queryEditors,
- queries: state.queries,
- tabHistory: state.tabHistory,
- tables: state.tables,
- defaultDbId: state.defaultDbId,
+ databases: sqlLab.databases,
+ queryEditors: sqlLab.queryEditors,
+ queries: sqlLab.queries,
+ tabHistory: sqlLab.tabHistory,
+ tables: sqlLab.tables,
+ defaultDbId: sqlLab.defaultDbId,
};
}
function mapDispatchToProps(dispatch) {
diff --git a/superset/assets/src/SqlLab/components/VisualizeModal.jsx b/superset/assets/src/SqlLab/components/VisualizeModal.jsx
index 14ba0c0e0b51d..c02656e08888c 100644
--- a/superset/assets/src/SqlLab/components/VisualizeModal.jsx
+++ b/superset/assets/src/SqlLab/components/VisualizeModal.jsx
@@ -1,4 +1,4 @@
-/* global notify */
+/* eslint no-undef: 2 */
import moment from 'moment';
import React from 'react';
import PropTypes from 'prop-types';
@@ -168,13 +168,13 @@ class VisualizeModal extends React.PureComponent {
if (mainGroupBy) {
formData.groupby = [mainGroupBy.name];
}
- notify.info(t('Creating a data source and popping a new tab'));
+ this.props.actions.addInfoToast(t('Creating a data source and creating a new tab'));
// open new window for data visualization
exportChart(formData);
})
.fail(() => {
- notify.error(this.props.errorMessage);
+ this.props.actions.addDangerToast(this.props.errorMessage);
});
}
changeDatasourceName(event) {
@@ -295,11 +295,11 @@ class VisualizeModal extends React.PureComponent {
VisualizeModal.propTypes = propTypes;
VisualizeModal.defaultProps = defaultProps;
-function mapStateToProps(state) {
+function mapStateToProps({ sqlLab }) {
return {
- datasource: state.datasource,
- errorMessage: state.errorMessage,
- timeout: state.common ? state.common.conf.SUPERSET_WEBSERVER_TIMEOUT : null,
+ datasource: sqlLab.datasource,
+ errorMessage: sqlLab.errorMessage,
+ timeout: sqlLab.common ? sqlLab.common.conf.SUPERSET_WEBSERVER_TIMEOUT : null,
};
}
diff --git a/superset/assets/src/SqlLab/getInitialState.js b/superset/assets/src/SqlLab/getInitialState.js
new file mode 100644
index 0000000000000..9452ac570c736
--- /dev/null
+++ b/superset/assets/src/SqlLab/getInitialState.js
@@ -0,0 +1,33 @@
+/* eslint no-undef: 2 */
+import shortid from 'shortid';
+import { t } from '../locales';
+import getToastsFromPyFlashMessages from '../messageToasts/utils/getToastsFromPyFlashMessages';
+
+export default function getInitialState({ defaultDbId, ...restBootstrapData }) {
+ const defaultQueryEditor = {
+ id: shortid.generate(),
+ title: t('Untitled Query'),
+ sql: 'SELECT *\nFROM\nWHERE',
+ selectedText: null,
+ latestQueryId: null,
+ autorun: false,
+ dbId: defaultDbId,
+ };
+
+ return {
+ sqlLab: {
+ alerts: [],
+ queries: {},
+ databases: {},
+ queryEditors: [defaultQueryEditor],
+ tabHistory: [defaultQueryEditor.id],
+ tables: [],
+ queriesLastUpdate: 0,
+ activeSouthPaneTab: 'Results',
+ ...restBootstrapData,
+ },
+ messageToasts: getToastsFromPyFlashMessages(
+ (restBootstrapData.common || {}).flash_messages || [],
+ ),
+ };
+}
diff --git a/superset/assets/src/SqlLab/index.jsx b/superset/assets/src/SqlLab/index.jsx
index 4e2eae898c6db..24983de9bc47c 100644
--- a/superset/assets/src/SqlLab/index.jsx
+++ b/superset/assets/src/SqlLab/index.jsx
@@ -4,7 +4,8 @@ import { createStore, compose, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk';
-import { getInitialState, sqlLabReducer } from './reducers';
+import getInitialState from './getInitialState';
+import rootReducer from './reducers';
import { initEnhancer } from '../reduxUtils';
import { initJQueryAjax } from '../modules/utils';
import App from './components/App';
@@ -19,13 +20,21 @@ initJQueryAjax();
const appContainer = document.getElementById('app');
const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
-const state = Object.assign({}, getInitialState(bootstrapData.defaultDbId), bootstrapData);
+const state = getInitialState(bootstrapData);
const store = createStore(
- sqlLabReducer, state, compose(applyMiddleware(thunkMiddleware), initEnhancer()));
+ rootReducer,
+ state,
+ compose(
+ applyMiddleware(thunkMiddleware),
+ initEnhancer(),
+ ),
+);
// jquery hack to highlight the navbar menu
-$('a:contains("SQL Lab")').parent().addClass('active');
+$('a:contains("SQL Lab")')
+ .parent()
+ .addClass('active');
render(
diff --git a/superset/assets/src/SqlLab/reducers.js b/superset/assets/src/SqlLab/reducers.js
index 9c67f3822cf3f..5b044e5b4723a 100644
--- a/superset/assets/src/SqlLab/reducers.js
+++ b/superset/assets/src/SqlLab/reducers.js
@@ -1,34 +1,20 @@
+import { combineReducers } from 'redux';
import shortid from 'shortid';
+import messageToasts from '../messageToasts/reducers';
+
import * as actions from './actions';
import { now } from '../modules/dates';
-import { addToObject, alterInObject, alterInArr, removeFromArr, getFromArr, addToArr }
- from '../reduxUtils';
+import {
+ addToObject,
+ alterInObject,
+ alterInArr,
+ removeFromArr,
+ getFromArr,
+ addToArr,
+} from '../reduxUtils';
import { t } from '../locales';
-export function getInitialState(defaultDbId) {
- const defaultQueryEditor = {
- id: shortid.generate(),
- title: t('Untitled Query'),
- sql: 'SELECT *\nFROM\nWHERE',
- selectedText: null,
- latestQueryId: null,
- autorun: false,
- dbId: defaultDbId,
- };
-
- return {
- alerts: [],
- queries: {},
- databases: {},
- queryEditors: [defaultQueryEditor],
- tabHistory: [defaultQueryEditor.id],
- tables: [],
- queriesLastUpdate: 0,
- activeSouthPaneTab: 'Results',
- };
-}
-
-export const sqlLabReducer = function (state, action) {
+export const sqlLabReducer = function (state = {}, action) {
const actionHandlers = {
[actions.ADD_QUERY_EDITOR]() {
const tabHistory = state.tabHistory.slice();
@@ -220,9 +206,6 @@ export const sqlLabReducer = function (state, action) {
[actions.QUERY_EDITOR_PERSIST_HEIGHT]() {
return alterInArr(state, 'queryEditors', action.queryEditor, { height: action.currentHeight });
},
- [actions.ADD_ALERT]() {
- return addToArr(state, 'alerts', action.alert);
- },
[actions.SET_DATABASES]() {
const databases = {};
action.databases.forEach((db) => {
@@ -230,9 +213,6 @@ export const sqlLabReducer = function (state, action) {
});
return Object.assign({}, state, { databases });
},
- [actions.REMOVE_ALERT]() {
- return removeFromArr(state, 'alerts', action.alert);
- },
[actions.REFRESH_QUERIES]() {
let newQueries = Object.assign({}, state.queries);
// Fetch the updates to the queries present in the store.
@@ -279,3 +259,8 @@ export const sqlLabReducer = function (state, action) {
}
return state;
};
+
+export default combineReducers({
+ sqlLab: sqlLabReducer,
+ messageToasts,
+});
diff --git a/superset/assets/src/chart/Chart.jsx b/superset/assets/src/chart/Chart.jsx
index 1718fc78f5236..fa6d9e6364278 100644
--- a/superset/assets/src/chart/Chart.jsx
+++ b/superset/assets/src/chart/Chart.jsx
@@ -238,7 +238,9 @@ class Chart extends React.PureComponent {
vizType={this.props.vizType}
height={this.height}
width={this.width}
- faded={this.props.refreshOverlayVisible && !this.props.errorMessage}
+ faded={
+ this.props.refreshOverlayVisible && !this.props.errorMessage
+ }
ref={(inner) => {
this.container = inner;
}}
diff --git a/superset/assets/src/components/AlertsWrapper.jsx b/superset/assets/src/components/AlertsWrapper.jsx
deleted file mode 100644
index 672c56d59d830..0000000000000
--- a/superset/assets/src/components/AlertsWrapper.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-/* global notify */
-import React from 'react';
-import AlertContainer from 'react-alert';
-import PropTypes from 'prop-types';
-
-const propTypes = {
- initMessages: PropTypes.array,
-};
-const defaultProps = {
- initMessages: [],
-};
-
-export default class AlertsWrapper extends React.PureComponent {
- componentDidMount() {
- this.props.initMessages.forEach((msg) => {
- if (['info', 'error', 'success'].indexOf(msg[0]) >= 0) {
- notify[msg[0]](msg[1]);
- } else {
- notify.show(msg[1]);
- }
- });
- }
- render() {
- return (
- {
- global.notify = ref;
- }}
- offset={14}
- position="top right"
- theme="dark"
- time={5000}
- transition="fade"
- />);
- }
-}
-AlertsWrapper.propTypes = propTypes;
-AlertsWrapper.defaultProps = defaultProps;
diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js
index bd01146143487..149ead7fc9a72 100644
--- a/superset/assets/src/dashboard/actions/dashboardLayout.js
+++ b/superset/assets/src/dashboard/actions/dashboardLayout.js
@@ -1,6 +1,6 @@
import { ActionCreators as UndoActionCreators } from 'redux-undo';
-import { addInfoToast } from './messageToasts';
+import { addInfoToast } from '../../messageToasts/actions';
import { setUnsavedChanges } from './dashboardState';
import { TABS_TYPE, ROW_TYPE } from '../util/componentTypes';
import {
diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js
index 5c92ff26f0f14..17f6d46da0172 100644
--- a/superset/assets/src/dashboard/actions/dashboardState.js
+++ b/superset/assets/src/dashboard/actions/dashboardState.js
@@ -19,7 +19,7 @@ import {
addSuccessToast,
addWarningToast,
addDangerToast,
-} from './messageToasts';
+} from '../../messageToasts/actions';
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
export function setUnsavedChanges(hasUnsavedChanges) {
diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx b/superset/assets/src/dashboard/components/Dashboard.jsx
index 5f5479e81ce66..80d4bdf1e1f06 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -2,7 +2,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import AlertsWrapper from '../../components/AlertsWrapper';
import getChartIdsFromLayout from '../util/getChartIdsFromLayout';
import DashboardBuilder from '../containers/DashboardBuilder';
import {
@@ -220,12 +219,7 @@ class Dashboard extends React.PureComponent {
}
render() {
- return (
-
- );
+ return ;
}
}
diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
index 9621a4972aef0..ecb528dc893e6 100644
--- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
@@ -14,7 +14,7 @@ import DashboardGrid from '../containers/DashboardGrid';
import IconButton from './IconButton';
import DragDroppable from './dnd/DragDroppable';
import DashboardComponent from '../containers/DashboardComponent';
-import ToastPresenter from '../containers/ToastPresenter';
+import ToastPresenter from '../../messageToasts/containers/ToastPresenter';
import WithPopoverMenu from './menu/WithPopoverMenu';
import getDragDropManager from '../util/getDragDropManager';
diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
index dec97b7cb6cae..3740404da12bf 100644
--- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
@@ -23,7 +23,7 @@ import {
updateDashboardTitle,
} from '../actions/dashboardLayout';
-import { addSuccessToast, addDangerToast } from '../actions/messageToasts';
+import { addSuccessToast, addDangerToast } from '../../messageToasts/actions';
import { DASHBOARD_HEADER_ID } from '../util/constants';
diff --git a/superset/assets/src/dashboard/deprecated/v1/actions.js b/superset/assets/src/dashboard/deprecated/v1/actions.js
index 7381486f24c76..a8701207cbef3 100644
--- a/superset/assets/src/dashboard/deprecated/v1/actions.js
+++ b/superset/assets/src/dashboard/deprecated/v1/actions.js
@@ -1,6 +1,7 @@
-/* global notify */
+/* global window */
import $ from 'jquery';
import { getExploreUrlAndPayload } from '../../../explore/exploreUtils';
+import { addSuccessToast, addDangerToast } from '../../../messageToasts/actions';
export const ADD_FILTER = 'ADD_FILTER';
export function addFilter(sliceId, col, vals, merge = true, refresh = true) {
@@ -36,10 +37,10 @@ export function addSlicesToDashboard(dashboardId, sliceIds) {
data: JSON.stringify({ slice_ids: sliceIds }),
},
})
- .done(() => {
- // Refresh page to allow for slices to re-render
- window.location.reload();
- })
+ .done(() => {
+ // Refresh page to allow for slices to re-render
+ window.location.reload();
+ })
);
}
@@ -75,13 +76,13 @@ export function saveSlice(slice, sliceName) {
},
success: () => {
dispatch(updateSliceName(slice, sliceName));
- notify.success('This slice name was saved successfully.');
+ dispatch(addSuccessToast('This slice name was saved successfully.'));
},
error: () => {
// if server-side reject the overwrite action,
// revert to old state
dispatch(updateSliceName(slice, oldName));
- notify.error("You don't have the rights to alter this slice");
+ dispatch(addDangerToast("You don't have the rights to alter this slice"));
},
});
};
diff --git a/superset/assets/src/dashboard/deprecated/v1/components/Dashboard.jsx b/superset/assets/src/dashboard/deprecated/v1/components/Dashboard.jsx
index ec831fa567bd6..b9e62409566dc 100644
--- a/superset/assets/src/dashboard/deprecated/v1/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/deprecated/v1/components/Dashboard.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import AlertsWrapper from '../../../../components/AlertsWrapper';
+import ToastsPresenter from '../../../../messageToasts/containers/ToastPresenter';
import GridLayout from './GridLayout';
import Header from './Header';
import { exportChart } from '../../../../explore/exploreUtils';
@@ -385,7 +385,7 @@ class Dashboard extends React.PureComponent {
return (