From 4d7112185bcbf17c5d889b5954aec8f316997315 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 5 Jul 2018 14:24:38 -0700 Subject: [PATCH 1/8] [toasts] get rid of notify globals, refactor messageToasts for use by entire app --- superset/assets/package.json | 1 - .../dashboard/fixtures/mockFilters.js | 75 +++++++++++++++++++ .../spec/javascripts/messageToasts/.eslintrc | 33 ++++++++ .../javascripts/messageToasts/.prettierrc | 4 + .../components/ToastPresenter_spec.jsx | 0 .../components/Toast_spec.jsx | 2 +- .../mockMessageToasts.js | 5 +- superset/assets/src/SqlLab/actions.js | 68 +++++++++-------- superset/assets/src/SqlLab/components/App.jsx | 15 ++-- .../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 | 29 +++++++ 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 | 27 ++++--- .../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 | 38 +++++----- superset/assets/src/explore/reducers/index.js | 4 +- superset/assets/src/messageToasts/.eslintrc | 33 ++++++++ superset/assets/src/messageToasts/.prettierrc | 4 + .../actions/index.js} | 6 +- .../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 +- superset/assets/src/utils/common.js | 13 ++-- 53 files changed, 462 insertions(+), 256 deletions(-) create mode 100644 superset/assets/spec/javascripts/dashboard/fixtures/mockFilters.js 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 (100%) rename superset/assets/spec/javascripts/{dashboard => messageToasts}/components/Toast_spec.jsx (94%) rename superset/assets/spec/javascripts/{dashboard/fixtures => messageToasts}/mockMessageToasts.js (63%) 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} (95%) 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%) diff --git a/superset/assets/package.json b/superset/assets/package.json index 6a3935422a40d..bd6ff7fb58ceb 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -89,7 +89,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/dashboard/fixtures/mockFilters.js b/superset/assets/spec/javascripts/dashboard/fixtures/mockFilters.js new file mode 100644 index 0000000000000..4dfb60fe948e6 --- /dev/null +++ b/superset/assets/spec/javascripts/dashboard/fixtures/mockFilters.js @@ -0,0 +1,75 @@ +export const regionFilterSliceId = 256; +export const countryFilterSliceId = 257; + +export const defaultFilters = { + [regionFilterSliceId]: { region: [] }, + [countryFilterSliceId]: { country_name: ['United States'] }, +}; + +export const regionFilterSlice = { + datasource: null, + description: null, + description_markeddown: '', + edit_url: '/slicemodelview/edit/regionFilterSliceId', + form_data: { + datasource: '2__table', + date_filter: false, + filters: [ + { + col: 'country_name', + op: 'in', + val: ['United States', 'France', 'Japan'], + }, + ], + granularity_sqla: null, + groupby: ['region', 'country_name'], + having: '', + instant_filtering: true, + metric: 'sum__SP_POP_TOTL', + show_druid_time_granularity: false, + show_druid_time_origin: false, + show_sqla_time_column: false, + show_sqla_time_granularity: false, + since: '100 years ago', + slice_id: regionFilterSliceId, + time_grain_sqla: null, + until: 'now', + viz_type: 'filter_box', + where: '', + }, + slice_id: regionFilterSliceId, + slice_name: 'Region Filters', + slice_url: `/superset/explore/table/2/?form_data=%7B%22slice_id%22%3A%20${regionFilterSliceId}%7D`, + viz_type: 'filter_box', +}; + +export const countryFilterSlice = { + datasource: null, + description: null, + description_markeddown: '', + edit_url: '/slicemodelview/edit/countryFilterSliceId', + form_data: { + datasource: '2__table', + date_filter: false, + filters: [], + granularity_sqla: null, + groupby: ['country_name'], + having: '', + instant_filtering: true, + metric: 'sum__SP_POP_TOTL', + show_druid_time_granularity: false, + show_druid_time_origin: false, + show_sqla_time_column: false, + show_sqla_time_granularity: false, + since: '100 years ago', + slice_id: countryFilterSliceId, + time_grain_sqla: null, + until: 'now', + viz_type: 'filter_box', + where: '', + }, + slice_id: countryFilterSliceId, + slice_name: 'Country Filters', + slice_url: `/superset/explore/table/2/?form_data=%7B%22slice_id%22%3A%20${countryFilterSliceId}%7D`, + viz_type: 'filter_box', +}; 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 100% rename from superset/assets/spec/javascripts/dashboard/components/ToastPresenter_spec.jsx rename to superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx diff --git a/superset/assets/spec/javascripts/dashboard/components/Toast_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx similarity index 94% rename from superset/assets/spec/javascripts/dashboard/components/Toast_spec.jsx rename to superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx index 6ed0bc5adf517..6f8d6754e7162 100644 --- a/superset/assets/spec/javascripts/dashboard/components/Toast_spec.jsx +++ b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx @@ -5,7 +5,7 @@ import { describe, it } from 'mocha'; import { expect } from 'chai'; import mockMessageToasts from '../fixtures/mockMessageToasts'; -import Toast from '../../../../src/dashboard/components/Toast'; +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/src/SqlLab/actions.js b/superset/assets/src/SqlLab/actions.js index 540bfe7b035ef..7e5381f9ad049 100644 --- a/superset/assets/src/SqlLab/actions.js +++ b/superset/assets/src/SqlLab/actions.js @@ -1,7 +1,13 @@ -/* global notify */ +/* global window */ +/* eslint no-undef: 2 */ 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'); @@ -28,8 +34,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 +50,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 +158,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 +205,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 +230,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 +318,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 +331,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 +393,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 +415,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..fa48e48994f41 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); @@ -71,10 +70,10 @@ class App extends React.PureComponent { } return (
-
{content}
+
); } @@ -83,13 +82,13 @@ class App extends React.PureComponent { App.propTypes = { alerts: PropTypes.array, actions: PropTypes.object, - initMessages: PropTypes.array, + // initMessages: PropTypes.array, }; -function mapStateToProps(state) { +function mapStateToProps({ sqlLab }) { return { - alerts: state.alerts, - initMessages: state.flash_messages, + alerts: sqlLab.alerts, + // initMessages: state.flash_messages, }; } function mapDispatchToProps(dispatch) { 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..cf0bac5297ed9 --- /dev/null +++ b/superset/assets/src/SqlLab/getInitialState.js @@ -0,0 +1,29 @@ +import shortid from 'shortid'; +import { t } from '../locales'; + +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, // @TODO do something with flash_messages? + }, + messageToasts: [], + }; +} 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..a03eeb6899e8c 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) { +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 6ba4159388cd2..f1b88e46462a4 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'; @@ -368,7 +368,7 @@ class Dashboard extends React.PureComponent { return (
- +
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) => {