From 19ac6e1231c6ecedb651a83b60a151672e6abbe0 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 12 Jul 2018 11:50:25 -0700 Subject: [PATCH] get rid of global `notify` (#5355) * [toasts] get rid of notify globals, refactor messageToasts for use by entire app * [remove notify] use arrow func in ajax call * fix lint + tests * actually fix tests from messageToast refactor * add 'test:one' npm script * debugger * [toasts] convert bootstrap flash messages to toasts in explore + sqllab * [toasts][tests] import from right file --- superset/assets/package.json | 2 +- .../components/URLShortLinkButton_spec.jsx | 12 +- .../dashboard/actions/dashboardLayout_spec.js | 2 +- .../dashboard/fixtures/mockState.js | 2 +- .../components/DatasourceControl_spec.jsx | 14 +- .../MetricDefinitionOption_spec.jsx | 15 +- .../spec/javascripts/messageToasts/.eslintrc | 33 +++ .../javascripts/messageToasts/.prettierrc | 4 + .../components/ToastPresenter_spec.jsx | 6 +- .../components/Toast_spec.jsx | 4 +- .../mockMessageToasts.js | 5 +- .../reducers/messageToasts_spec.js | 7 +- .../getToastsFromPyFlashMessages_spec.js | 35 +++ .../javascripts/sqllab/AlertsWrapper_spec.jsx | 31 --- .../spec/javascripts/sqllab/App_spec.jsx | 9 +- .../sqllab/CopyQueryTabUrl_spec.jsx | 2 +- .../sqllab/SqlEditorLeftBar_spec.jsx | 1 + .../javascripts/sqllab/SqlEditor_spec.jsx | 2 +- .../sqllab/TabbedSqlEditors_spec.jsx | 8 +- .../sqllab/VisualizeModal_spec.jsx | 44 ++-- .../spec/javascripts/sqllab/actions_spec.js | 6 +- .../spec/javascripts/sqllab/fixtures.js | 21 +- .../spec/javascripts/sqllab/reducers_spec.js | 27 +- superset/assets/src/SqlLab/actions.js | 71 +++--- superset/assets/src/SqlLab/components/App.jsx | 30 +-- .../SqlLab/components/QueryAutoRefresh.jsx | 6 +- .../src/SqlLab/components/QuerySearch.jsx | 8 +- .../src/SqlLab/components/SouthPane.jsx | 4 +- .../src/SqlLab/components/SqlEditor.jsx | 4 +- .../SqlLab/components/SqlEditorLeftBar.jsx | 18 +- .../SqlLab/components/TabbedSqlEditors.jsx | 14 +- .../src/SqlLab/components/VisualizeModal.jsx | 14 +- superset/assets/src/SqlLab/getInitialState.js | 33 +++ superset/assets/src/SqlLab/index.jsx | 17 +- superset/assets/src/SqlLab/reducers.js | 49 ++-- superset/assets/src/chart/Chart.jsx | 4 +- .../assets/src/components/AlertsWrapper.jsx | 38 --- .../src/components/URLShortLinkButton.jsx | 9 +- .../src/dashboard/actions/dashboardLayout.js | 2 +- .../src/dashboard/actions/dashboardState.js | 2 +- .../src/dashboard/components/Dashboard.jsx | 8 +- .../dashboard/components/DashboardBuilder.jsx | 2 +- .../dashboard/containers/DashboardHeader.jsx | 2 +- .../src/dashboard/deprecated/v1/actions.js | 15 +- .../deprecated/v1/components/Dashboard.jsx | 4 +- .../deprecated/v1/components/SaveModal.jsx | 31 ++- .../src/dashboard/deprecated/v1/index.jsx | 10 +- .../src/dashboard/deprecated/v1/reducers.js | 3 + .../assets/src/dashboard/reducers/index.js | 2 +- .../src/dashboard/stylesheets/index.less | 1 - .../assets/src/dashboard/util/constants.js | 6 - .../assets/src/dashboard/util/propShapes.jsx | 18 -- .../components/MetricDefinitionOption.jsx | 9 +- .../components/controls/DatasourceControl.jsx | 12 +- .../controls/SelectAsyncControl.jsx | 9 +- superset/assets/src/explore/index.jsx | 43 ++-- superset/assets/src/explore/reducers/index.js | 4 +- superset/assets/src/messageToasts/.eslintrc | 33 +++ superset/assets/src/messageToasts/.prettierrc | 4 + .../actions/index.js} | 11 +- .../components/Toast.jsx | 5 +- .../components/ToastPresenter.jsx | 4 +- .../assets/src/messageToasts/constants.js | 5 + .../containers/ToastPresenter.jsx | 2 +- .../messageToasts/enhancers/withToasts.jsx | 26 ++ .../assets/src/messageToasts/propShapes.js | 21 ++ .../reducers/index.js} | 2 +- .../stylesheets/toast.less | 6 +- .../utils/getToastsFromPyFlashMessages.js | 22 ++ superset/assets/src/reduxUtils.js | 3 +- superset/assets/src/utils/common.js | 13 +- superset/assets/yarn.lock | 230 +++++++----------- 72 files changed, 657 insertions(+), 524 deletions(-) create mode 100644 superset/assets/spec/javascripts/messageToasts/.eslintrc create mode 100644 superset/assets/spec/javascripts/messageToasts/.prettierrc rename superset/assets/spec/javascripts/{dashboard => messageToasts}/components/ToastPresenter_spec.jsx (82%) rename superset/assets/spec/javascripts/{dashboard => messageToasts}/components/Toast_spec.jsx (90%) rename superset/assets/spec/javascripts/{dashboard/fixtures => messageToasts}/mockMessageToasts.js (63%) rename superset/assets/spec/javascripts/{dashboard => messageToasts}/reducers/messageToasts_spec.js (79%) create mode 100644 superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js delete mode 100644 superset/assets/spec/javascripts/sqllab/AlertsWrapper_spec.jsx create mode 100644 superset/assets/src/SqlLab/getInitialState.js delete mode 100644 superset/assets/src/components/AlertsWrapper.jsx create mode 100644 superset/assets/src/messageToasts/.eslintrc create mode 100644 superset/assets/src/messageToasts/.prettierrc rename superset/assets/src/{dashboard/actions/messageToasts.js => messageToasts/actions/index.js} (83%) rename superset/assets/src/{dashboard => messageToasts}/components/Toast.jsx (95%) rename superset/assets/src/{dashboard => messageToasts}/components/ToastPresenter.jsx (90%) create mode 100644 superset/assets/src/messageToasts/constants.js rename superset/assets/src/{dashboard => messageToasts}/containers/ToastPresenter.jsx (84%) create mode 100644 superset/assets/src/messageToasts/enhancers/withToasts.jsx create mode 100644 superset/assets/src/messageToasts/propShapes.js rename superset/assets/src/{dashboard/reducers/messageToasts.js => messageToasts/reducers/index.js} (84%) rename superset/assets/src/{dashboard => messageToasts}/stylesheets/toast.less (88%) create mode 100644 superset/assets/src/messageToasts/utils/getToastsFromPyFlashMessages.js diff --git a/superset/assets/package.json b/superset/assets/package.json index 6a3935422a40d..9bcec4d0b967c 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.1.5", "react-bootstrap-table": "^4.3.1", diff --git a/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx b/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx index 98e6e2cf687fc..1aa0074ccf951 100644 --- a/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx +++ b/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import configureStore from 'redux-mock-store'; import { expect } from 'chai'; import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; @@ -13,11 +14,14 @@ describe('URLShortLinkButton', () => { emailContent: 'mock content', }; - it('renders', () => { - expect(React.isValidElement()).to.equal(true); - }); + function setup() { + const mockStore = configureStore([]); + const store = mockStore({}); + return shallow(, { context: { store } }).dive(); + } + it('renders OverlayTrigger', () => { - const wrapper = shallow(); + const wrapper = setup(); expect(wrapper.find(OverlayTrigger)).have.length(1); }); }); 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 540bfe7b035ef..58db1a75c33f7 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) { @@ -144,7 +157,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', @@ -191,10 +204,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.'))); }, }); }; @@ -216,16 +229,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 }; } @@ -314,7 +317,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}/`; @@ -327,7 +330,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'))); }); }; } @@ -389,7 +392,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'))); + }, }); }; } @@ -409,7 +414,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 7102880e99dcf..73ba0697619ca 100644 --- a/superset/assets/src/SqlLab/components/SouthPane.jsx +++ b/superset/assets/src/SqlLab/components/SouthPane.jsx @@ -97,9 +97,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 690126d2f0d78..a55a022721091 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(); @@ -225,9 +211,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) => { @@ -235,9 +218,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. @@ -284,3 +264,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/components/URLShortLinkButton.jsx b/superset/assets/src/components/URLShortLinkButton.jsx index aa9ae96ef5ac9..1efd4f7122d3b 100644 --- a/superset/assets/src/components/URLShortLinkButton.jsx +++ b/superset/assets/src/components/URLShortLinkButton.jsx @@ -4,19 +4,22 @@ import { Popover, OverlayTrigger } from 'react-bootstrap'; import CopyToClipboard from './CopyToClipboard'; import { getShortUrl } from '../utils/common'; import { t } from '../locales'; +import withToasts from '../messageToasts/enhancers/withToasts'; const propTypes = { url: PropTypes.string, emailSubject: PropTypes.string, emailContent: PropTypes.string, + addDangerToast: PropTypes.func.isRequired, }; -export default class URLShortLinkButton extends React.Component { +class URLShortLinkButton extends React.Component { constructor(props) { super(props); this.state = { shortUrl: '', }; + this.onShortUrlSuccess = this.onShortUrlSuccess.bind(this); } onShortUrlSuccess(data) { @@ -26,7 +29,7 @@ export default class URLShortLinkButton extends React.Component { } getCopyUrl() { - getShortUrl(this.props.url, this.onShortUrlSuccess.bind(this)); + getShortUrl(this.props.url, this.onShortUrlSuccess, this.props.addDangerToast); } renderPopover() { @@ -69,3 +72,5 @@ URLShortLinkButton.defaultProps = { }; URLShortLinkButton.propTypes = propTypes; + +export default withToasts(URLShortLinkButton); 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 (
- +
{ saveModal.close(); onSaveDashboard(); if (saveType === 'newDashboard') { window.location = `/superset/dashboard/${resp.id}/`; } else { - notify.success(t('This dashboard was saved successfully.')); + this.props.addSuccessToast( + t('This dashboard was saved successfully.'), + ); } }, - error(error) { + error: (error) => { saveModal.close(); const errorMsg = getAjaxErrorMsg(error); - notify.error(t('Sorry, there was an error saving this dashboard: ') + errorMsg); + this.props.addDangerToast( + t('Sorry, there was an error saving this dashboard: ') + errorMsg, + ); }, }); } @@ -91,10 +98,9 @@ class SaveModal extends React.PureComponent { } else if (saveType === 'newDashboard') { if (!newDashboardTitle) { this.modal.close(); - showModal({ - title: t('Error'), - body: t('You must pick a name for the new dashboard'), - }); + this.props.addDangerToast( + t('You must pick a name for the new dashboard'), + ); } else { data.dashboard_title = newDashboardTitle; url = `/superset/copy_dash/${dashboard.id}/`; @@ -156,6 +162,7 @@ class SaveModal extends React.PureComponent { ); } } + SaveModal.propTypes = propTypes; -export default SaveModal; +export default withToasts(SaveModal); diff --git a/superset/assets/src/dashboard/deprecated/v1/index.jsx b/superset/assets/src/dashboard/deprecated/v1/index.jsx index d7e898e901f9e..47d1e2416f7f2 100644 --- a/superset/assets/src/dashboard/deprecated/v1/index.jsx +++ b/superset/assets/src/dashboard/deprecated/v1/index.jsx @@ -15,10 +15,16 @@ initJQueryAjax(); const appContainer = document.getElementById('app'); const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap')); -const initState = Object.assign({}, getInitialState(bootstrapData)); +const initState = getInitialState(bootstrapData); const store = createStore( - rootReducer, initState, compose(applyMiddleware(thunk), initEnhancer(false))); + rootReducer, + initState, + compose( + applyMiddleware(thunk), + initEnhancer(false), + ), +); ReactDOM.render( diff --git a/superset/assets/src/dashboard/deprecated/v1/reducers.js b/superset/assets/src/dashboard/deprecated/v1/reducers.js index 00bf2bfec19e4..b519e2eed7fd9 100644 --- a/superset/assets/src/dashboard/deprecated/v1/reducers.js +++ b/superset/assets/src/dashboard/deprecated/v1/reducers.js @@ -9,6 +9,7 @@ import { getParam } from '../../../modules/utils'; import { alterInArr, removeFromArr } from '../../../reduxUtils'; import { applyDefaultFormData } from '../../../explore/store'; import { getColorFromScheme } from '../../../modules/colors'; +import messageToasts from '../../../messageToasts/reducers'; export function getInitialState(bootstrapData) { const { @@ -121,6 +122,7 @@ export function getInitialState(bootstrapData) { common, editMode, }, + messageToasts: [], }; } @@ -269,4 +271,5 @@ export default combineReducers({ charts, dashboard, impressionId: () => shortid.generate(), + messageToasts, }); diff --git a/superset/assets/src/dashboard/reducers/index.js b/superset/assets/src/dashboard/reducers/index.js index 787cd5f0bf9a8..a5be96fdf4db5 100644 --- a/superset/assets/src/dashboard/reducers/index.js +++ b/superset/assets/src/dashboard/reducers/index.js @@ -5,7 +5,7 @@ import dashboardState from './dashboardState'; import datasources from './datasources'; import sliceEntities from './sliceEntities'; import dashboardLayout from '../reducers/undoableDashboardLayout'; -import messageToasts from '../reducers/messageToasts'; +import messageToasts from '../../messageToasts/reducers'; const dashboardInfo = (state = {}) => state; const impressionId = (state = '') => state; diff --git a/superset/assets/src/dashboard/stylesheets/index.less b/superset/assets/src/dashboard/stylesheets/index.less index b69c7b05301b5..08a095673e797 100644 --- a/superset/assets/src/dashboard/stylesheets/index.less +++ b/superset/assets/src/dashboard/stylesheets/index.less @@ -10,4 +10,3 @@ @import './popover-menu.less'; @import './resizable.less'; @import './components/index.less'; -@import './toast.less'; diff --git a/superset/assets/src/dashboard/util/constants.js b/superset/assets/src/dashboard/util/constants.js index bfe24cc378664..09d72448d036d 100644 --- a/superset/assets/src/dashboard/util/constants.js +++ b/superset/assets/src/dashboard/util/constants.js @@ -33,12 +33,6 @@ export const LARGE_HEADER = 'LARGE_HEADER'; export const BACKGROUND_WHITE = 'BACKGROUND_WHITE'; export const BACKGROUND_TRANSPARENT = 'BACKGROUND_TRANSPARENT'; -// Toast types -export const INFO_TOAST = 'INFO_TOAST'; -export const SUCCESS_TOAST = 'SUCCESS_TOAST'; -export const WARNING_TOAST = 'WARNING_TOAST'; -export const DANGER_TOAST = 'DANGER_TOAST'; - // undo-redo export const UNDO_LIMIT = 50; diff --git a/superset/assets/src/dashboard/util/propShapes.jsx b/superset/assets/src/dashboard/util/propShapes.jsx index f15519094b893..3c06e14cf36d6 100644 --- a/superset/assets/src/dashboard/util/propShapes.jsx +++ b/superset/assets/src/dashboard/util/propShapes.jsx @@ -2,12 +2,6 @@ import PropTypes from 'prop-types'; import componentTypes from './componentTypes'; import backgroundStyleOptions from './backgroundStyleOptions'; import headerStyleOptions from './headerStyleOptions'; -import { - INFO_TOAST, - SUCCESS_TOAST, - WARNING_TOAST, - DANGER_TOAST, -} from './constants'; export const componentShape = PropTypes.shape({ id: PropTypes.string.isRequired, @@ -26,18 +20,6 @@ export const componentShape = PropTypes.shape({ }), }); -export const toastShape = PropTypes.shape({ - id: PropTypes.string.isRequired, - toastType: PropTypes.oneOf([ - INFO_TOAST, - SUCCESS_TOAST, - WARNING_TOAST, - DANGER_TOAST, - ]).isRequired, - text: PropTypes.string.isRequired, - duration: PropTypes.number, -}); - export const chartPropShape = PropTypes.shape({ id: PropTypes.number.isRequired, chartAlert: PropTypes.string, diff --git a/superset/assets/src/explore/components/MetricDefinitionOption.jsx b/superset/assets/src/explore/components/MetricDefinitionOption.jsx index c275b9c4d571e..d64d3fe81dac7 100644 --- a/superset/assets/src/explore/components/MetricDefinitionOption.jsx +++ b/superset/assets/src/explore/components/MetricDefinitionOption.jsx @@ -7,6 +7,7 @@ import AggregateOption from './AggregateOption'; import columnType from '../propTypes/columnType'; import savedMetricType from '../propTypes/savedMetricType'; import aggregateOptionType from '../propTypes/aggregateOptionType'; +import withToasts from '../../messageToasts/enhancers/withToasts'; const propTypes = { option: PropTypes.oneOfType([ @@ -14,9 +15,10 @@ const propTypes = { savedMetricType, aggregateOptionType, ]).isRequired, + addWarningToast: PropTypes.func.isRequired, }; -export default function MetricDefinitionOption({ option }) { +function MetricDefinitionOption({ option, addWarningToast }) { if (option.metric_name) { return ( @@ -30,7 +32,10 @@ export default function MetricDefinitionOption({ option }) { ); } - notify.error('You must supply either a saved metric, column or aggregate to MetricDefinitionOption'); + addWarningToast('You must supply either a saved metric, column or aggregate to MetricDefinitionOption'); return null; } + MetricDefinitionOption.propTypes = propTypes; + +export default withToasts(MetricDefinitionOption); diff --git a/superset/assets/src/explore/components/controls/DatasourceControl.jsx b/superset/assets/src/explore/components/controls/DatasourceControl.jsx index d63d6fe806d94..545ad1055a993 100644 --- a/superset/assets/src/explore/components/controls/DatasourceControl.jsx +++ b/superset/assets/src/explore/components/controls/DatasourceControl.jsx @@ -1,4 +1,4 @@ -/* global notify */ +/* eslint no-undef: 2 */ import React from 'react'; import PropTypes from 'prop-types'; import { Table } from 'reactable'; @@ -13,12 +13,15 @@ import { Tooltip, Well, } from 'react-bootstrap'; +import $ from 'jquery'; import ControlHeader from '../ControlHeader'; import Loading from '../../../components/Loading'; import { t } from '../../../locales'; import ColumnOption from '../../../components/ColumnOption'; import MetricOption from '../../../components/MetricOption'; +import withToasts from '../../../messageToasts/enhancers/withToasts'; + const propTypes = { description: PropTypes.string, @@ -27,13 +30,14 @@ const propTypes = { onChange: PropTypes.func, value: PropTypes.string.isRequired, datasource: PropTypes.object, + addDangerToast: PropTypes.func.isRequired, }; const defaultProps = { onChange: () => {}, }; -export default class DatasourceControl extends React.PureComponent { +class DatasourceControl extends React.PureComponent { constructor(props) { super(props); this.state = { @@ -85,7 +89,7 @@ export default class DatasourceControl extends React.PureComponent { }, error() { that.setState({ loading: false }); - notify.error(t('Something went wrong while fetching the datasource list')); + this.props.addDangerToast(t('Something went wrong while fetching the datasource list')); }, }); } @@ -229,3 +233,5 @@ export default class DatasourceControl extends React.PureComponent { DatasourceControl.propTypes = propTypes; DatasourceControl.defaultProps = defaultProps; + +export default withToasts(DatasourceControl); diff --git a/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx b/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx index ec5a365322f81..ef8c4172b7c27 100644 --- a/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx +++ b/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx @@ -1,10 +1,12 @@ -/* global notify */ +/* eslint no-undef: 2 */ import React from 'react'; import PropTypes from 'prop-types'; import Select from '../../../components/AsyncSelect'; import ControlHeader from '../ControlHeader'; import { t } from '../../../locales'; +import withToasts from '../../../messageToasts/enhancers/withToasts'; + const propTypes = { dataEndpoint: PropTypes.string.isRequired, multi: PropTypes.bool, @@ -18,6 +20,7 @@ const propTypes = { PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number), ]), + addDangerToast: PropTypes.func.isRequired, }; const defaultProps = { @@ -40,7 +43,7 @@ const SelectAsyncControl = (props) => {