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
This commit is contained in:
Chris Williams 2018-07-12 11:50:25 -07:00 committed by GitHub
parent f9352af80e
commit 19ac6e1231
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 657 additions and 524 deletions

View File

@ -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",

View File

@ -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(<URLShortLinkButton {...defaultProps} />)).to.equal(true);
});
function setup() {
const mockStore = configureStore([]);
const store = mockStore({});
return shallow(<URLShortLinkButton {...defaultProps} />, { context: { store } }).dive();
}
it('renders OverlayTrigger', () => {
const wrapper = shallow(<URLShortLinkButton {...defaultProps} />);
const wrapper = setup();
expect(wrapper.find(OverlayTrigger)).have.length(1);
});
});

View File

@ -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,

View File

@ -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';

View File

@ -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(<DatasourceControl {...defaultProps} />);
});
function setup() {
const mockStore = configureStore([]);
const store = mockStore({});
return shallow(<DatasourceControl {...defaultProps} />, { context: { store } }).dive();
}
it('renders a Modal', () => {
const wrapper = setup();
expect(wrapper.find(Modal)).to.have.lengthOf(1);
});
});

View File

@ -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(<MetricDefinitionOption {...props} />, { context: { store } }).dive();
}
it('renders a MetricOption given a saved metric', () => {
const wrapper = shallow(<MetricDefinitionOption option={{ metric_name: 'a_saved_metric' }} />);
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(<MetricDefinitionOption option={{ column_name: 'a_column' }} />);
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(<MetricDefinitionOption option={{ aggregate_name: 'an_aggregate' }} />);
const wrapper = setup({ option: { aggregate_name: 'an_aggregate' } });
expect(wrapper.find(AggregateOption)).to.have.lengthOf(1);
});
});

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@ -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 = {

View File

@ -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 = {

View File

@ -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' },

View File

@ -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', () => {

View File

@ -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',
});
});
});

View File

@ -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(<AlertsWrapper />);
});
it('is valid', () => {
expect(React.isValidElement(<AlertsWrapper />)).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');
});
});

View File

@ -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(<App />, {
context: { store },
}).dive();
wrapper = shallow(<App />, { context: { store } }).dive();
});
it('is valid', () => {
expect(React.isValidElement(<App />)).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);

View File

@ -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(

View File

@ -20,6 +20,7 @@ describe('SqlEditorLeftBar', () => {
queryEditorSetDb: sinon.stub(),
setDatabases: sinon.stub(),
addTable: sinon.stub(),
addDangerToast: sinon.stub(),
},
tables: [table],
queryEditor: defaultQueryEditor,

View File

@ -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,

View File

@ -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();

View File

@ -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);
});
});
});

View File

@ -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);
});
});

View File

@ -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 = {

View File

@ -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));

View File

@ -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')));
},
});
};
}

View File

@ -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 (
<div className="App SqlLab">
<AlertsWrapper initMessages={this.props.initMessages} />
<div className="container-fluid">
{content}
</div>
<div className="container-fluid">{content}</div>
<ToastPresenter />
</div>
);
}
}
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);

View File

@ -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,
};
}

View File

@ -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;
}

View File

@ -97,9 +97,9 @@ class SouthPane extends React.PureComponent {
}
}
function mapStateToProps(state) {
function mapStateToProps({ sqlLab }) {
return {
activeSouthPaneTab: state.activeSouthPaneTab,
activeSouthPaneTab: sqlLab.activeSouthPaneTab,
};
}

View File

@ -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;

View File

@ -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}

View File

@ -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) {

View File

@ -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,
};
}

View File

@ -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 || [],
),
};
}

View File

@ -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(
<Provider store={store}>

View File

@ -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,
});

View File

@ -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;
}}

View File

@ -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 (
<AlertContainer
ref={(ref) => {
global.notify = ref;
}}
offset={14}
position="top right"
theme="dark"
time={5000}
transition="fade"
/>);
}
}
AlertsWrapper.propTypes = propTypes;
AlertsWrapper.defaultProps = defaultProps;

View File

@ -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);

View File

@ -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 {

View File

@ -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) {

View File

@ -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 (
<div>
<AlertsWrapper initMessages={this.props.initMessages} />
<DashboardBuilder />
</div>
);
return <DashboardBuilder />;
}
}

View File

@ -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';

View File

@ -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';

View File

@ -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"));
},
});
};

View File

@ -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 (
<div id="dashboard-container">
<div id="dashboard-header">
<AlertsWrapper initMessages={this.props.initMessages} />
<ToastsPresenter />
<Header
dashboard={this.props.dashboard}
unsavedChanges={this.state.unsavedChanges}

View File

@ -1,13 +1,14 @@
/* global notify */
/* global window */
import React from 'react';
import PropTypes from 'prop-types';
import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap';
import $ from 'jquery';
import { getAjaxErrorMsg } from '../../../../modules/utils';
import ModalTrigger from '../../../../components/ModalTrigger';
import { t } from '../../../../locales';
import Checkbox from '../../../../components/Checkbox';
const $ = window.$ = require('jquery');
import withToasts from '../../../../messageToasts/enhancers/withToasts';
const propTypes = {
css: PropTypes.string,
@ -16,6 +17,8 @@ const propTypes = {
filters: PropTypes.object.isRequired,
serialize: PropTypes.func,
onSave: PropTypes.func,
addSuccessToast: PropTypes.func.isRequired,
addDangerToast: PropTypes.func.isRequired,
};
class SaveModal extends React.PureComponent {
@ -57,19 +60,23 @@ class SaveModal extends React.PureComponent {
data: {
data: JSON.stringify(data),
},
success(resp) {
success: (resp) => {
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);

View File

@ -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(
<Provider store={store}>

View File

@ -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,
});

View File

@ -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;

View File

@ -10,4 +10,3 @@
@import './popover-menu.less';
@import './resizable.less';
@import './components/index.less';
@import './toast.less';

View File

@ -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;

View File

@ -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,

View File

@ -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 (
<MetricOption metric={option} showType />
@ -30,7 +32,10 @@ export default function MetricDefinitionOption({ option }) {
<AggregateOption aggregate={option} showType />
);
}
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);

View File

@ -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);

View File

@ -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) => {
<Select
dataEndpoint={dataEndpoint}
onChange={onSelectionChange}
onAsyncError={errorMsg => notify.error(onAsyncErrorMessage + ': ' + errorMsg)}
onAsyncError={errorMsg => this.props.addDangerToast(onAsyncErrorMessage + ': ' + errorMsg)}
mutator={mutator}
multi={multi}
value={value}
@ -54,4 +57,4 @@ const SelectAsyncControl = (props) => {
SelectAsyncControl.propTypes = propTypes;
SelectAsyncControl.defaultProps = defaultProps;
export default SelectAsyncControl;
export default withToasts(SelectAsyncControl);

View File

@ -1,4 +1,4 @@
/* eslint camelcase: 0 */
/* eslint no-undef: 2 */
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
@ -9,11 +9,12 @@ import shortid from 'shortid';
import { now } from '../modules/dates';
import { initEnhancer } from '../reduxUtils';
import { getChartKey } from './exploreUtils';
import AlertsWrapper from '../components/AlertsWrapper';
import ToastPresenter from '../messageToasts/containers/ToastPresenter';
import { getControlsState, getFormDataFromControls } from './store';
import { initJQueryAjax } from '../modules/utils';
import ExploreViewContainer from './components/ExploreViewContainer';
import rootReducer from './reducers/index';
import getToastsFromPyFlashMessages from '../messageToasts/utils/getToastsFromPyFlashMessages';
import { appSetup } from '../common';
import './main.css';
@ -26,25 +27,24 @@ const exploreViewContainer = document.getElementById('app');
const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
const controls = getControlsState(bootstrapData, bootstrapData.form_data);
const rawFormData = { ...bootstrapData.form_data };
delete bootstrapData.form_data;
delete bootstrapData.common.locale;
delete bootstrapData.common.language_pack;
// Initial state
const bootstrappedState = Object.assign(
bootstrapData, {
rawFormData,
controls,
filterColumnOpts: [],
isDatasourceMetaLoading: false,
isStarred: false,
},
);
const bootstrappedState = {
...bootstrapData,
rawFormData,
controls,
filterColumnOpts: [],
isDatasourceMetaLoading: false,
isStarred: false,
};
const slice = bootstrappedState.slice;
const sliceFormData = slice ?
getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
:
null;
const sliceFormData = slice
? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
: null;
const chartKey = getChartKey(bootstrappedState);
const initState = {
charts: {
@ -68,16 +68,23 @@ const initState = {
},
explore: bootstrappedState,
impressionId: shortid.generate(),
messageToasts: getToastsFromPyFlashMessages((bootstrapData.common || {}).flash_messages || []),
};
const store = createStore(rootReducer, initState,
compose(applyMiddleware(thunk), initEnhancer(false)),
const store = createStore(
rootReducer,
initState,
compose(
applyMiddleware(thunk),
initEnhancer(false),
),
);
ReactDOM.render(
<Provider store={store}>
<div>
<ExploreViewContainer />
<AlertsWrapper initMessages={bootstrappedState.common.flash_messages} />
<ToastPresenter />
</div>
</Provider>,
exploreViewContainer,

View File

@ -3,12 +3,14 @@ import { combineReducers } from 'redux';
import charts from '../../chart/chartReducer';
import saveModal from './saveModalReducer';
import explore from './exploreReducer';
import messageToasts from '../../messageToasts/reducers';
const impressionId = (state = '') => (state);
const impressionId = (state = '') => state;
export default combineReducers({
charts,
saveModal,
explore,
impressionId,
messageToasts,
});

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@ -5,14 +5,14 @@ import {
SUCCESS_TOAST,
WARNING_TOAST,
DANGER_TOAST,
} from '../util/constants';
} from '../constants';
function getToastUuid(type) {
export function getToastUuid(type) {
return `${type}-${shortid.generate()}`;
}
export const ADD_TOAST = 'ADD_TOAST';
export function addToast({ toastType, text, duration }) {
export function addToast({ toastType, text, duration = 8000 }) {
return {
type: ADD_TOAST,
payload: {
@ -50,10 +50,11 @@ export function addSuccessToast(text) {
export const ADD_WARNING_TOAST = 'ADD_WARNING_TOAST';
export function addWarningToast(text) {
return dispatch =>
dispatch(addToast({ text, toastType: WARNING_TOAST, duration: 4000 }));
dispatch(addToast({ text, toastType: WARNING_TOAST, duration: 6000 }));
}
export const ADD_DANGER_TOAST = 'ADD_DANGER_TOAST';
export function addDangerToast(text) {
return dispatch => dispatch(addToast({ text, toastType: DANGER_TOAST }));
return dispatch =>
dispatch(addToast({ text, toastType: DANGER_TOAST, duration: 8000 }));
}

View File

@ -3,13 +3,14 @@ import cx from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { toastShape } from '../util/propShapes';
import { toastShape } from '../propShapes';
import {
INFO_TOAST,
SUCCESS_TOAST,
WARNING_TOAST,
DANGER_TOAST,
} from '../util/constants';
} from '../constants';
const propTypes = {
toast: toastShape.isRequired,

View File

@ -2,7 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import Toast from './Toast';
import { toastShape } from '../util/propShapes';
import { toastShape } from '../propShapes';
import '../stylesheets/toast.less';
const propTypes = {
toasts: PropTypes.arrayOf(toastShape),

View File

@ -0,0 +1,5 @@
// 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';

View File

@ -2,7 +2,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import ToastPresenter from '../components/ToastPresenter';
import { removeToast } from '../actions/messageToasts';
import { removeToast } from '../actions';
export default connect(
({ messageToasts: toasts }) => ({ toasts }),

View File

@ -0,0 +1,26 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {
addDangerToast,
addInfoToast,
addSuccessToast,
addWarningToast,
} from '../actions';
// To work properly the redux state must have a `messageToasts` subtree
export default function withToasts(BaseComponent) {
return connect(
null,
dispatch =>
bindActionCreators(
{
addInfoToast,
addSuccessToast,
addWarningToast,
addDangerToast,
},
dispatch,
),
)(BaseComponent);
}

View File

@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import {
INFO_TOAST,
SUCCESS_TOAST,
WARNING_TOAST,
DANGER_TOAST,
} from './constants';
// eslint-disable-next-line import/prefer-default-export
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,
});

View File

@ -1,4 +1,4 @@
import { ADD_TOAST, REMOVE_TOAST } from '../actions/messageToasts';
import { ADD_TOAST, REMOVE_TOAST } from '../actions';
export default function messageToastsReducer(toasts = [], action) {
switch (action.type) {

View File

@ -1,3 +1,5 @@
@import '../../dashboard/stylesheets/variables.less';
.toast-presenter {
position: fixed;
bottom: 16px;
@ -16,7 +18,7 @@
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.35);
will-change: transform, opacity;
transform: translateY(-100%);
transition: transform .3s, opacity .3s;
transition: transform 0.3s, opacity 0.3s;
}
.toast > button {
@ -33,7 +35,7 @@
}
.toast:after {
content: "";
content: '';
position: absolute;
top: 0;
left: 0;

View File

@ -0,0 +1,22 @@
import { addToast } from '../actions';
import { INFO_TOAST, SUCCESS_TOAST, DANGER_TOAST } from '../constants';
export default function toastsFromPyFlashMessages(flashMessages = []) {
const toasts = [];
flashMessages.forEach(([messageType, message]) => {
const toastType =
messageType === 'danger'
? DANGER_TOAST
: (messageType === 'success' && SUCCESS_TOAST) || INFO_TOAST;
const toast = addToast({
text: message,
toastType,
}).payload;
toasts.push(toast);
});
return toasts;
}

View File

@ -67,9 +67,8 @@ export function addToArr(state, arrKey, obj) {
export function initEnhancer(persist = true) {
let enhancer = persist ? compose(persistState()) : compose();
if (process.env.NODE_ENV === 'dev') {
/* eslint-disable no-underscore-dangle */
/* eslint-disable-next-line no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
/* eslint-enable */
enhancer = persist ? composeEnhancers(persistState()) : composeEnhancers();
}
return enhancer;

View File

@ -1,8 +1,7 @@
/* global notify */
/* eslint global-require: 0 */
import $ from 'jquery';
const d3 = window.d3 || require('d3');
const d3 = require('d3');
export const EARTH_CIRCUMFERENCE_KM = 40075.16;
export const LUMINANCE_RED_WEIGHT = 0.2126;
@ -72,7 +71,7 @@ export function getParamsFromUrl() {
return newParams;
}
export function getShortUrl(longUrl, callback) {
export function getShortUrl(longUrl, callback, onError) {
$.ajax({
type: 'POST',
url: '/r/shortner/',
@ -80,11 +79,11 @@ export function getShortUrl(longUrl, callback) {
data: {
data: '/' + longUrl,
},
success: (data) => {
callback(data);
},
success: callback,
error: () => {
notify.error('Error getting the short URL');
if (onError) {
onError('Error getting the short URL');
}
callback(longUrl);
},
});

View File

@ -391,8 +391,8 @@ acorn@^5.0.0, acorn@^5.5.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
dependencies:
es6-promisify "^5.0.0"
@ -427,8 +427,8 @@ ajv@^5.0.0, ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
json-schema-traverse "^0.3.0"
ajv@^6.1.0:
version "6.5.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.1.tgz#88ebc1263c7133937d108b80c5572e64e1d9322d"
version "6.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360"
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
@ -938,8 +938,8 @@ babel-istanbul@^0.12.2:
wordwrap "1.0.x"
babel-loader@^7.0.0:
version "7.1.4"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.4.tgz#e3463938bd4e6d55d1c174c5485d406a188ed015"
version "7.1.5"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68"
dependencies:
find-cache-dir "^1.0.0"
loader-utils "^1.0.2"
@ -1418,8 +1418,8 @@ base@^0.11.1:
pascalcase "^0.1.1"
bcrypt-pbkdf@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
dependencies:
tweetnacl "^0.14.3"
@ -1507,8 +1507,8 @@ bops@0.0.6:
to-utf8 "0.0.1"
bowser@^1.2.0, bowser@^1.7.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.3.tgz#6643ae4d783f31683f6d23156976b74183862162"
version "1.9.4"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
boxen@^1.2.1:
version "1.3.0"
@ -1556,10 +1556,6 @@ braces@^2.3.0, braces@^2.3.1:
split-string "^3.0.2"
to-regex "^3.0.1"
brcast@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/brcast/-/brcast-2.0.2.tgz#2db16de44140e418dc37fab10beec0369e78dcef"
brfs@^1.3.0, brfs@^1.4.3, brfs@^1.4.4:
version "1.6.1"
resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3"
@ -1778,12 +1774,12 @@ caniuse-api@^1.5.2:
lodash.uniq "^4.5.0"
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000856"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000856.tgz#fbebb99abe15a5654fc7747ebb5315bdfde3358f"
version "1.0.30000864"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000864.tgz#35a4b2325a8d4553a46b516dbc233bf391d75555"
caniuse-lite@^1.0.30000844:
version "1.0.30000856"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000856.tgz#ecc16978135a6f219b138991eb62009d25ee8daa"
version "1.0.30000864"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000864.tgz#7a08c78da670f23c06f11aa918831b8f2dd60ddc"
capture-stack-trace@^1.0.0:
version "1.0.0"
@ -2181,8 +2177,8 @@ commander@2.9.0:
graceful-readlink ">= 1.0.0"
commander@^2.11.0, commander@^2.9.0:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
version "2.16.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
commander@~2.13.0:
version "2.13.0"
@ -2505,8 +2501,8 @@ csso@~2.3.1:
source-map "^0.5.3"
cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
version "0.3.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797"
"cssstyle@>= 0.2.37 < 0.3.0":
version "0.2.37"
@ -2696,15 +2692,15 @@ datamaps@^0.5.8:
topojson "^1.6.19"
datatables.net-bs@^1.10.15:
version "1.10.18"
resolved "https://registry.yarnpkg.com/datatables.net-bs/-/datatables.net-bs-1.10.18.tgz#72c9ebe926f9189f891d4c474a629defc99753e6"
version "1.10.19"
resolved "https://registry.yarnpkg.com/datatables.net-bs/-/datatables.net-bs-1.10.19.tgz#08763b4e4d0cef1a427d019dc15e717c7ed67a4d"
dependencies:
datatables.net "1.10.18"
datatables.net "1.10.19"
jquery ">=1.7"
datatables.net@1.10.18:
version "1.10.18"
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.18.tgz#b6f045aa533101bcd33714e5338da8f905d3ef09"
datatables.net@1.10.19:
version "1.10.19"
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.19.tgz#97a1ed41c85e62d61040603481b59790a172dd1f"
dependencies:
jquery ">=1.7"
@ -3023,8 +3019,8 @@ editor@~1.0.0:
resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742"
electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47:
version "1.3.50"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.50.tgz#7438b76f92b41b919f3fbdd350fbd0757dacddf7"
version "1.3.51"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.51.tgz#6a42b49daaf7f22a5b37b991daf949f34dbdb9b5"
elliptic@^6.0.0:
version "6.4.0"
@ -3270,8 +3266,8 @@ eslint-module-utils@^2.2.0:
pkg-dir "^1.0.0"
eslint-plugin-import@^2.2.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz#dad31781292d6664b25317fd049d2e2b2f02205d"
version "2.13.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.13.0.tgz#df24f241175e312d91662dc91ca84064caec14ed"
dependencies:
contains-path "^0.1.0"
debug "^2.6.8"
@ -3297,20 +3293,20 @@ eslint-plugin-jsx-a11y@^5.1.1:
jsx-ast-utils "^1.4.0"
eslint-plugin-prettier@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz#33e4e228bdb06142d03c560ce04ec23f6c767dd7"
version "2.6.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.1.tgz#de902b4a66b7bca24296429a59a1cc04020ccbbd"
dependencies:
fast-diff "^1.1.1"
jest-docblock "^21.0.0"
eslint-plugin-react@^7.0.1:
version "7.9.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.9.1.tgz#101aadd15e7c7b431ed025303ac7b421a8e3dc15"
version "7.10.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.10.0.tgz#af5c1fef31c4704db02098f9be18202993828b50"
dependencies:
doctrine "^2.1.0"
has "^1.0.2"
has "^1.0.3"
jsx-ast-utils "^2.0.1"
prop-types "^15.6.1"
prop-types "^15.6.2"
eslint-restricted-globals@^0.1.1:
version "0.1.1"
@ -3580,10 +3576,6 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
fast-memoize@^2.2.7:
version "2.4.0"
resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.4.0.tgz#2f79eca41c41112b0b70cf53ac3940e206574648"
fastdom@^1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/fastdom/-/fastdom-1.0.8.tgz#10f9d36998fd6efae30e529597d788e750c9febb"
@ -3600,7 +3592,7 @@ fault@^1.0.2:
dependencies:
format "^0.2.2"
fbjs@^0.8.1, fbjs@^0.8.12, fbjs@^0.8.4, fbjs@^0.8.9:
fbjs@^0.8.1, fbjs@^0.8.4, fbjs@^0.8.9:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
dependencies:
@ -4087,26 +4079,6 @@ gl-vec4@^1.0.0, gl-vec4@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gl-vec4/-/gl-vec4-1.0.1.tgz#97d96878281b14b532cbce101785dfd1cb340964"
glamor@^2.20.24:
version "2.20.40"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.40.tgz#f606660357b7cf18dface731ad1a2cfa93817f05"
dependencies:
fbjs "^0.8.12"
inline-style-prefixer "^3.0.6"
object-assign "^4.1.1"
prop-types "^15.5.10"
through "^2.3.8"
glamorous@^3.13.1:
version "3.25.0"
resolved "https://registry.yarnpkg.com/glamorous/-/glamorous-3.25.0.tgz#d6d66c3dfffdc194761469adcbd996d4b70222e1"
dependencies:
brcast "^2.0.0"
fast-memoize "^2.2.7"
html-tag-names "^1.1.1"
react-html-attributes "^1.3.0"
svg-tag-names "^1.1.0"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@ -4344,7 +4316,7 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
has@^1.0.1, has@^1.0.2:
has@^1.0.1, has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
dependencies:
@ -4440,8 +4412,8 @@ home-or-tmp@^2.0.0:
os-tmpdir "^1.0.1"
hosted-git-info@^2.1.4, hosted-git-info@^2.1.5, hosted-git-info@^2.4.2, hosted-git-info@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222"
version "2.6.1"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.1.tgz#6e4cee78b01bb849dcf93527708c69fdbee410df"
hosted-git-info@~2.1.5:
version "2.1.5"
@ -4451,20 +4423,12 @@ html-comment-regex@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
html-element-attributes@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.3.1.tgz#9fa6a2e37e6b61790a303e87ddbbb9746e8c035f"
html-encoding-sniffer@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
dependencies:
whatwg-encoding "^1.0.1"
html-tag-names@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.3.tgz#f81f75e59d626cb8a958a19e58f90c1d69707b82"
htmlparser2@^3.9.1:
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
@ -4767,8 +4731,8 @@ is-builtin-module@^1.0.0:
builtin-modules "^1.0.0"
is-callable@^1.1.1, is-callable@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
is-ci@^1.0.10:
version "1.1.0"
@ -4933,12 +4897,6 @@ is-object@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7"
is-odd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24"
dependencies:
is-number "^4.0.0"
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@ -5812,7 +5770,7 @@ math-random@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
math.gl@^1.1.0, math.gl@^1.1.3, math.gl@^1.2.1:
math.gl@^1.1.0, math.gl@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/math.gl/-/math.gl-1.2.1.tgz#3c7da0ae4f3383116c24bc183533cc4d7b8065a9"
dependencies:
@ -5823,6 +5781,17 @@ math.gl@^1.1.0, math.gl@^1.1.3, math.gl@^1.2.1:
gl-vec3 "^1.0.3"
gl-vec4 "^1.0.1"
math.gl@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/math.gl/-/math.gl-2.0.0.tgz#c41cf8f5cfce820161511c435d3f706eebd7deb0"
dependencies:
gl-mat3 "^1.0.0"
gl-mat4 "^1.1.4"
gl-quat "^1.0.0"
gl-vec2 "^1.0.0"
gl-vec3 "^1.0.3"
gl-vec4 "^1.0.1"
mathfn@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mathfn/-/mathfn-1.0.1.tgz#650a0b183b0102debe94e42a807dc6b4d2cc57bd"
@ -6105,20 +6074,15 @@ nan@^2.9.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
nanoid@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-0.2.2.tgz#e2ebc6ad3db5e0454fd8124d30ca39b06555fe56"
nanomatch@^1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
dependencies:
arr-diff "^4.0.0"
array-unique "^0.3.2"
define-property "^2.0.2"
extend-shallow "^3.0.2"
fragment-cache "^0.2.1"
is-odd "^2.0.0"
is-windows "^1.0.2"
kind-of "^6.0.2"
object.pick "^1.3.0"
@ -6243,8 +6207,8 @@ node-libs-browser@^2.0.0:
vm-browserify "0.0.4"
node-pre-gyp@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46"
version "0.10.2"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.2.tgz#e8945c20ef6795a20aac2b44f036eb13cf5146e3"
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
@ -6252,7 +6216,7 @@ node-pre-gyp@^0.10.0:
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.1.7"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4"
@ -7444,8 +7408,8 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.12.1:
version "1.13.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.5.tgz#7ae2076998c8edce79d63834e9b7b09fead6bfd0"
version "1.13.7"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
prismjs@^1.8.4:
version "1.15.0"
@ -7522,7 +7486,7 @@ prop-types@15.5.10:
fbjs "^0.8.9"
loose-envify "^1.3.1"
prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1:
prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
@ -7704,7 +7668,7 @@ rc-config-loader@^2.0.1:
path-exists "^2.1.0"
require-from-string "^2.0.1"
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
dependencies:
@ -7714,8 +7678,8 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
strip-json-comments "~2.0.1"
re-resizable@^4.3.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-4.5.1.tgz#4688e8311ea4b70d558f7aebb3168a25926e62d9"
version "4.7.1"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-4.7.1.tgz#3eca5bb94a6059d14311786cfd2d430bc9f7fba0"
react-ace@^5.10.0:
version "5.10.0"
@ -7743,15 +7707,6 @@ react-addons-test-utils@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz#c12b6efdc2247c10da7b8770d185080a7b047156"
react-alert@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-alert/-/react-alert-2.4.0.tgz#b4c9ce74919b46be330a7addbf46e7561c295bc7"
dependencies:
glamor "^2.20.24"
glamorous "^3.13.1"
nanoid "^0.2.2"
react-transition-group "^1.1.2"
react-bootstrap-slider@2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/react-bootstrap-slider/-/react-bootstrap-slider-2.1.5.tgz#2f79e57b69ddf2b5bd23310bddbd2de0c6bdfef3"
@ -7853,12 +7808,6 @@ react-grid-layout@0.16.6:
react-draggable "3.x"
react-resizable "1.x"
react-html-attributes@^1.3.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/react-html-attributes/-/react-html-attributes-1.4.2.tgz#0d2ccf134fc79b2d3543837dc1591d32b7b903f9"
dependencies:
html-element-attributes "^1.0.0"
react-input-autosize@^2.1.2:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
@ -7874,14 +7823,14 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
react-map-gl@^3.0.4:
version "3.2.10"
resolved "https://registry.yarnpkg.com/react-map-gl/-/react-map-gl-3.2.10.tgz#30ce9a26be4aea659c4fef981962ceb6feebe28a"
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-map-gl/-/react-map-gl-3.3.0.tgz#dad906ec4c56a51873884325c44025d49516a9ea"
dependencies:
babel-runtime "^6.23.0"
bowser "^1.2.0"
immutable "^3.8.2"
mapbox-gl "0.45"
math.gl "^1.1.3"
math.gl "^1.1.0"
mjolnir.js "^1.2.1"
prop-types "^15.5.7"
viewport-mercator-project "^5.1.0"
@ -7897,8 +7846,8 @@ react-markdown@^3.3.0:
xtend "^4.0.1"
react-modal@^3.1.7:
version "3.4.5"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.4.5.tgz#75a7eefb8f4c8247278d5ce1c41249d7785d9f69"
version "3.5.1"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.5.1.tgz#33d38527def90ea324848f7d63e53acc4468a451"
dependencies:
exenv "^1.2.0"
prop-types "^15.5.10"
@ -7973,16 +7922,17 @@ react-sortable-hoc@^0.8.3:
prop-types "^15.5.7"
react-split-pane@^0.1.63, react-split-pane@^0.1.66:
version "0.1.77"
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.77.tgz#f0c8cd18d076bbac900248dcf6dbcec02d5340db"
version "0.1.81"
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.81.tgz#b1e8b82e0a6edd10f18fd639a5f512db3cbbb4e6"
dependencies:
inline-style-prefixer "^3.0.6"
prop-types "^15.5.10"
react-lifecycles-compat "^3.0.4"
react-style-proptype "^3.0.0"
react-sticky@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/react-sticky/-/react-sticky-6.0.2.tgz#d301c1b5307649220dbc045fcbacd077885c5ede"
version "6.0.3"
resolved "https://registry.yarnpkg.com/react-sticky/-/react-sticky-6.0.3.tgz#7a18b643e1863da113d7f7036118d2a75d9ecde4"
dependencies:
prop-types "^15.5.8"
raf "^3.3.0"
@ -8010,7 +7960,7 @@ react-test-renderer@^15.6.2:
fbjs "^0.8.9"
object-assign "^4.1.0"
react-transition-group@^1.1.2, react-transition-group@^1.2.0:
react-transition-group@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6"
dependencies:
@ -8901,8 +8851,8 @@ socks@^1.1.10:
smart-buffer "^1.0.13"
socks@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.0.tgz#144985b3331ced3ab5ccbee640ab7cb7d43fdd1f"
version "2.2.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.1.tgz#68ad678b3642fbc5d99c64c165bc561eab0215f9"
dependencies:
ip "^1.1.5"
smart-buffer "^4.0.1"
@ -9269,10 +9219,6 @@ supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
svg-tag-names@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/svg-tag-names/-/svg-tag-names-1.1.1.tgz#9641b29ef71025ee094c7043f7cdde7d99fbd50a"
svgo@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
@ -9360,7 +9306,7 @@ through2@~0.6.3:
readable-stream ">=1.0.33-1 <1.1.0-0"
xtend ">=4.0.0 <4.1.0-0"
through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3.4:
through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3.4:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@ -9442,8 +9388,8 @@ topojson@^1.6.19:
shapefile "0.3"
tough-cookie@^2.3.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.2.tgz#aa9133154518b494efab98a58247bfc38818c00c"
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
@ -9557,8 +9503,8 @@ uglifyjs-webpack-plugin@^0.4.6:
webpack-sources "^1.0.1"
uglifyjs-webpack-plugin@^1.1.0:
version "1.2.6"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.6.tgz#f4bb44f02431e82b301d8d4624330a6a35729381"
version "1.2.7"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz#57638dd99c853a1ebfe9d97b42160a8a507f9d00"
dependencies:
cacache "^10.0.4"
find-cache-dir "^1.0.0"
@ -9768,8 +9714,8 @@ util@^0.10.3:
inherits "2.0.3"
uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
v8flags@^2.1.1:
version "2.1.1"
@ -9828,10 +9774,10 @@ vfile@^2.0.0:
vfile-message "^1.0.0"
viewport-mercator-project@^5.0.0, viewport-mercator-project@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/viewport-mercator-project/-/viewport-mercator-project-5.1.0.tgz#68bc5586988c2808d1456e1eff950d424eccec16"
version "5.2.0"
resolved "https://registry.yarnpkg.com/viewport-mercator-project/-/viewport-mercator-project-5.2.0.tgz#2c50bc624a085d01a3a486f22ec329c0511fe4bd"
dependencies:
math.gl "^1.1.0"
math.gl "^2.0.0"
vlq@^0.2.2:
version "0.2.3"
@ -10089,8 +10035,8 @@ yargs-parser@^9.0.2:
camelcase "^4.1.0"
yargs@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b"
version "11.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
dependencies:
cliui "^4.0.0"
decamelize "^1.1.1"