diff --git a/superset/assets/.babelrc b/superset/assets/.babelrc
index bb026bc1b36c2..0c426be741d9a 100644
--- a/superset/assets/.babelrc
+++ b/superset/assets/.babelrc
@@ -1,4 +1,4 @@
{
"presets" : ["airbnb", "react", "env"],
- "plugins": ["syntax-dynamic-import"],
+ "plugins": ["syntax-dynamic-import", "react-hot-loader/babel"]
}
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 3927b2c500c31..eb5aa56d4863b 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -100,6 +100,7 @@
"react-dnd-html5-backend": "^2.5.4",
"react-dom": "^15.6.2",
"react-gravatar": "^2.6.1",
+ "react-hot-loader": "^4.3.6",
"react-map-gl": "^3.0.4",
"react-markdown": "^3.3.0",
"react-redux": "^5.0.2",
@@ -113,7 +114,7 @@
"react-syntax-highlighter": "^5.7.0",
"react-virtualized": "9.3.0",
"react-virtualized-select": "2.4.0",
- "reactable": "^0.14.1",
+ "reactable": "^1.1.0",
"redux": "^3.5.2",
"redux-localstorage": "^0.4.1",
"redux-thunk": "^2.1.0",
diff --git a/superset/assets/spec/javascripts/welcome/App_spec.jsx b/superset/assets/spec/javascripts/welcome/Welcome_spec.jsx
similarity index 71%
rename from superset/assets/spec/javascripts/welcome/App_spec.jsx
rename to superset/assets/spec/javascripts/welcome/Welcome_spec.jsx
index 46c6fdb90600f..a0a3982608237 100644
--- a/superset/assets/spec/javascripts/welcome/App_spec.jsx
+++ b/superset/assets/spec/javascripts/welcome/Welcome_spec.jsx
@@ -4,17 +4,17 @@ import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
-import App from '../../../src/welcome/App';
+import Welcome from '../../../src/welcome/Welcome';
-describe('App', () => {
+describe('Welcome', () => {
const mockedProps = {};
it('is valid', () => {
expect(
- React.isValidElement(),
+ React.isValidElement(),
).to.equal(true);
});
it('renders 4 Tab, Panel, and Row components', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper.find(Tab)).to.have.length(3);
expect(wrapper.find(Panel)).to.have.length(3);
expect(wrapper.find(Row)).to.have.length(3);
diff --git a/superset/assets/src/SqlLab/App.jsx b/superset/assets/src/SqlLab/App.jsx
new file mode 100644
index 0000000000000..36d8ddd1f9d50
--- /dev/null
+++ b/superset/assets/src/SqlLab/App.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { createStore, compose, applyMiddleware } from 'redux';
+import { Provider } from 'react-redux';
+import thunkMiddleware from 'redux-thunk';
+import { hot } from 'react-hot-loader';
+
+import getInitialState from './getInitialState';
+import rootReducer from './reducers';
+import { initEnhancer } from '../reduxUtils';
+import { initJQueryAjax } from '../modules/utils';
+import App from './components/App';
+import { appSetup } from '../common';
+
+import './main.less';
+import '../../stylesheets/reactable-pagination.css';
+import '../components/FilterableTable/FilterableTableStyles.css';
+
+appSetup();
+initJQueryAjax();
+
+const appContainer = document.getElementById('app');
+const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
+const state = getInitialState(bootstrapData);
+
+const store = createStore(
+ rootReducer,
+ state,
+ compose(
+ applyMiddleware(thunkMiddleware),
+ initEnhancer(),
+ ),
+);
+
+// Highlight the navbar menu
+const menus = document.querySelectorAll('.nav.navbar-nav li.dropdown');
+const sqlLabMenu = Array.prototype.slice.apply(menus)
+ .find(element => element.innerText.trim() === 'SQL Lab');
+if (sqlLabMenu) {
+ const classes = sqlLabMenu.getAttribute('class');
+ if (classes.indexOf('active') === -1) {
+ sqlLabMenu.setAttribute('class', `${classes} active`);
+ }
+}
+
+const Application = () => (
+
+
+
+);
+
+export default hot(module)(Application);
diff --git a/superset/assets/src/SqlLab/index.jsx b/superset/assets/src/SqlLab/index.jsx
index 24983de9bc47c..3088170f013dd 100644
--- a/superset/assets/src/SqlLab/index.jsx
+++ b/superset/assets/src/SqlLab/index.jsx
@@ -1,44 +1,8 @@
import React from 'react';
-import { render } from 'react-dom';
-import { createStore, compose, applyMiddleware } from 'redux';
-import { Provider } from 'react-redux';
-import thunkMiddleware from 'redux-thunk';
+import ReactDOM from 'react-dom';
+import App from './App';
-import getInitialState from './getInitialState';
-import rootReducer from './reducers';
-import { initEnhancer } from '../reduxUtils';
-import { initJQueryAjax } from '../modules/utils';
-import App from './components/App';
-import { appSetup } from '../common';
-
-import './main.less';
-import '../../stylesheets/reactable-pagination.css';
-import '../components/FilterableTable/FilterableTableStyles.css';
-
-appSetup();
-initJQueryAjax();
-
-const appContainer = document.getElementById('app');
-const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
-const state = getInitialState(bootstrapData);
-
-const store = createStore(
- rootReducer,
- state,
- compose(
- applyMiddleware(thunkMiddleware),
- initEnhancer(),
- ),
-);
-
-// jquery hack to highlight the navbar menu
-$('a:contains("SQL Lab")')
- .parent()
- .addClass('active');
-
-render(
-
-
- ,
- appContainer,
+ReactDOM.render(
+ ,
+ document.getElementById('app'),
);
diff --git a/superset/assets/src/addSlice/App.jsx b/superset/assets/src/addSlice/App.jsx
new file mode 100644
index 0000000000000..56469cc1d95eb
--- /dev/null
+++ b/superset/assets/src/addSlice/App.jsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { hot } from 'react-hot-loader';
+import { appSetup } from '../common';
+import AddSliceContainer from './AddSliceContainer';
+
+appSetup();
+
+const addSliceContainer = document.getElementById('js-add-slice-container');
+const bootstrapData = JSON.parse(addSliceContainer.getAttribute('data-bootstrap'));
+
+const App = () => (
+
+);
+
+export default hot(module)(App);
diff --git a/superset/assets/src/addSlice/index.jsx b/superset/assets/src/addSlice/index.jsx
index f83c2d5272a49..6bf2decfe6c34 100644
--- a/superset/assets/src/addSlice/index.jsx
+++ b/superset/assets/src/addSlice/index.jsx
@@ -1,14 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { appSetup } from '../common';
-import AddSliceContainer from './AddSliceContainer';
-
-appSetup();
-
-const addSliceContainer = document.getElementById('js-add-slice-container');
-const bootstrapData = JSON.parse(addSliceContainer.getAttribute('data-bootstrap'));
+import App from './App';
ReactDOM.render(
- ,
- addSliceContainer,
+ ,
+ document.getElementById('js-add-slice-container'),
);
diff --git a/superset/assets/src/dashboard/App.jsx b/superset/assets/src/dashboard/App.jsx
new file mode 100644
index 0000000000000..1167e9bfa6b3b
--- /dev/null
+++ b/superset/assets/src/dashboard/App.jsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import thunk from 'redux-thunk';
+import { createStore, applyMiddleware, compose } from 'redux';
+import { Provider } from 'react-redux';
+import { hot } from 'react-hot-loader';
+
+import { initEnhancer } from '../reduxUtils';
+import { appSetup } from '../common';
+import { initJQueryAjax } from '../modules/utils';
+import DashboardContainer from './containers/Dashboard';
+import getInitialState from './reducers/getInitialState';
+import rootReducer from './reducers/index';
+
+appSetup();
+initJQueryAjax();
+
+const appContainer = document.getElementById('app');
+const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
+const initState = getInitialState(bootstrapData);
+
+const store = createStore(
+ rootReducer,
+ initState,
+ compose(
+ applyMiddleware(thunk),
+ initEnhancer(false),
+ ),
+);
+
+const App = () => (
+
+
+
+);
+
+export default hot(module)(App);
diff --git a/superset/assets/src/dashboard/index.jsx b/superset/assets/src/dashboard/index.jsx
index e0e98301a76a4..a22f17594a2b3 100644
--- a/superset/assets/src/dashboard/index.jsx
+++ b/superset/assets/src/dashboard/index.jsx
@@ -1,35 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { createStore, applyMiddleware, compose } from 'redux';
-import { Provider } from 'react-redux';
-import thunk from 'redux-thunk';
+import App from './App';
-import { initEnhancer } from '../reduxUtils';
-import { appSetup } from '../common';
-import { initJQueryAjax } from '../modules/utils';
-import DashboardContainer from './containers/Dashboard';
-import getInitialState from './reducers/getInitialState';
-import rootReducer from './reducers/index';
-
-appSetup();
-initJQueryAjax();
-
-const appContainer = document.getElementById('app');
-const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
-const initState = getInitialState(bootstrapData);
-
-const store = createStore(
- rootReducer,
- initState,
- compose(
- applyMiddleware(thunk),
- initEnhancer(false),
- ),
-);
-
-ReactDOM.render(
-
-
- ,
- appContainer,
-);
+ReactDOM.render(, document.getElementById('app'));
diff --git a/superset/assets/src/explore/App.jsx b/superset/assets/src/explore/App.jsx
new file mode 100644
index 0000000000000..2f279897754a1
--- /dev/null
+++ b/superset/assets/src/explore/App.jsx
@@ -0,0 +1,92 @@
+/* eslint no-undef: 2 */
+import React from 'react';
+import { hot } from 'react-hot-loader';
+import { createStore, applyMiddleware, compose } from 'redux';
+import { Provider } from 'react-redux';
+import thunk from 'redux-thunk';
+
+import shortid from 'shortid';
+import { now } from '../modules/dates';
+import { initEnhancer } from '../reduxUtils';
+import { getChartKey } from './exploreUtils';
+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';
+import '../../stylesheets/reactable-pagination.css';
+
+appSetup();
+initJQueryAjax();
+
+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 = {
+ ...bootstrapData,
+ rawFormData,
+ controls,
+ filterColumnOpts: [],
+ isDatasourceMetaLoading: false,
+ isStarred: false,
+};
+const slice = bootstrappedState.slice;
+const sliceFormData = slice
+ ? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
+ : null;
+const chartKey = getChartKey(bootstrappedState);
+const initState = {
+ charts: {
+ [chartKey]: {
+ id: chartKey,
+ chartAlert: null,
+ chartStatus: 'loading',
+ chartUpdateEndTime: null,
+ chartUpdateStartTime: now(),
+ latestQueryFormData: getFormDataFromControls(controls),
+ sliceFormData,
+ queryRequest: null,
+ queryResponse: null,
+ triggerQuery: true,
+ lastRendered: 0,
+ },
+ },
+ saveModal: {
+ dashboards: [],
+ saveModalAlert: null,
+ },
+ explore: bootstrappedState,
+ impressionId: shortid.generate(),
+ messageToasts: getToastsFromPyFlashMessages((bootstrapData.common || {}).flash_messages || []),
+};
+
+const store = createStore(
+ rootReducer,
+ initState,
+ compose(
+ applyMiddleware(thunk),
+ initEnhancer(false),
+ ),
+);
+
+const App = () => (
+
+
+
+
+
+
+);
+
+export default hot(module)(App);
diff --git a/superset/assets/src/explore/index.jsx b/superset/assets/src/explore/index.jsx
index 283b5f5297e9b..3088170f013dd 100644
--- a/superset/assets/src/explore/index.jsx
+++ b/superset/assets/src/explore/index.jsx
@@ -1,91 +1,8 @@
-/* eslint no-undef: 2 */
import React from 'react';
import ReactDOM from 'react-dom';
-import { createStore, applyMiddleware, compose } from 'redux';
-import { Provider } from 'react-redux';
-import thunk from 'redux-thunk';
-
-import shortid from 'shortid';
-import { now } from '../modules/dates';
-import { initEnhancer } from '../reduxUtils';
-import { getChartKey } from './exploreUtils';
-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';
-import '../../stylesheets/reactable-pagination.css';
-
-appSetup();
-initJQueryAjax();
-
-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 = {
- ...bootstrapData,
- rawFormData,
- controls,
- filterColumnOpts: [],
- isDatasourceMetaLoading: false,
- isStarred: false,
-};
-const slice = bootstrappedState.slice;
-const sliceFormData = slice
- ? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
- : null;
-const chartKey = getChartKey(bootstrappedState);
-const initState = {
- charts: {
- [chartKey]: {
- id: chartKey,
- chartAlert: null,
- chartStatus: 'loading',
- chartUpdateEndTime: null,
- chartUpdateStartTime: now(),
- latestQueryFormData: getFormDataFromControls(controls),
- sliceFormData,
- queryRequest: null,
- queryResponse: null,
- triggerQuery: true,
- lastRendered: 0,
- },
- },
- saveModal: {
- dashboards: [],
- saveModalAlert: null,
- },
- explore: bootstrappedState,
- impressionId: shortid.generate(),
- messageToasts: getToastsFromPyFlashMessages((bootstrapData.common || {}).flash_messages || []),
-};
-
-const store = createStore(
- rootReducer,
- initState,
- compose(
- applyMiddleware(thunk),
- initEnhancer(false),
- ),
-);
+import App from './App';
ReactDOM.render(
-
-
-
-
-
- ,
- exploreViewContainer,
+ ,
+ document.getElementById('app'),
);
diff --git a/superset/assets/src/explore/visTypes.jsx b/superset/assets/src/explore/visTypes.jsx
index 0b50830c7551c..87a4894aab371 100644
--- a/superset/assets/src/explore/visTypes.jsx
+++ b/superset/assets/src/explore/visTypes.jsx
@@ -1716,6 +1716,7 @@ export const visTypes = {
},
{
label: t('Viewport'),
+ expanded: true,
controlSetRows: [
['viewport_longitude', 'viewport_latitude'],
['viewport_zoom', null],
diff --git a/superset/assets/src/profile/App.jsx b/superset/assets/src/profile/App.jsx
new file mode 100644
index 0000000000000..13146ec225306
--- /dev/null
+++ b/superset/assets/src/profile/App.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { hot } from 'react-hot-loader';
+import App from './components/App';
+import { appSetup } from '../common';
+
+import './main.css';
+
+appSetup();
+
+const profileViewContainer = document.getElementById('app');
+const bootstrap = JSON.parse(profileViewContainer.getAttribute('data-bootstrap'));
+
+const Application = () => (
+
+);
+
+export default hot(module)(Application);
diff --git a/superset/assets/src/profile/index.jsx b/superset/assets/src/profile/index.jsx
index e9ed59bd719b2..3088170f013dd 100644
--- a/superset/assets/src/profile/index.jsx
+++ b/superset/assets/src/profile/index.jsx
@@ -1,19 +1,8 @@
-/* eslint no-unused-vars: 0 */
import React from 'react';
import ReactDOM from 'react-dom';
+import App from './App';
-import App from './components/App';
-import { appSetup } from '../common';
-
-import './main.css';
-
-appSetup();
-
-const profileViewContainer = document.getElementById('app');
-const bootstrap = JSON.parse(profileViewContainer.getAttribute('data-bootstrap'));
-
-const user = bootstrap.user;
ReactDOM.render(
- ,
- profileViewContainer,
+ ,
+ document.getElementById('app'),
);
diff --git a/superset/assets/src/reduxUtils.js b/superset/assets/src/reduxUtils.js
index 4a8d944c4f932..5bd2565c75b38 100644
--- a/superset/assets/src/reduxUtils.js
+++ b/superset/assets/src/reduxUtils.js
@@ -70,7 +70,7 @@ export function addToArr(state, arrKey, obj, prepend = false) {
export function initEnhancer(persist = true) {
let enhancer = persist ? compose(persistState()) : compose();
- if (process.env.NODE_ENV === 'dev') {
+ if (process.env.WEBPACK_MODE === 'development') {
/* eslint-disable-next-line no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
enhancer = persist ? composeEnhancers(persistState()) : composeEnhancers();
diff --git a/superset/assets/src/utils/common.js b/superset/assets/src/utils/common.js
index a00fea329a037..756175d0d0124 100644
--- a/superset/assets/src/utils/common.js
+++ b/superset/assets/src/utils/common.js
@@ -8,9 +8,6 @@ export const LUMINANCE_RED_WEIGHT = 0.2126;
export const LUMINANCE_GREEN_WEIGHT = 0.7152;
export const LUMINANCE_BLUE_WEIGHT = 0.0722;
export const MILES_PER_KM = 1.60934;
-export const DEFAULT_LONGITUDE = -122.405293;
-export const DEFAULT_LATITUDE = 37.772123;
-export const DEFAULT_ZOOM = 11;
export function kmToPixels(kilometers, latitude, zoomLevel) {
// Algorithm from: http://wiki.openstreetmap.org/wiki/Zoom_levels
diff --git a/superset/assets/src/visualizations/MapBox/MapBox.jsx b/superset/assets/src/visualizations/MapBox/MapBox.jsx
index 81f41f074b1d2..e6eb71a51b212 100644
--- a/superset/assets/src/visualizations/MapBox/MapBox.jsx
+++ b/superset/assets/src/visualizations/MapBox/MapBox.jsx
@@ -6,12 +6,6 @@ import Immutable from 'immutable';
import supercluster from 'supercluster';
import ViewportMercator from 'viewport-mercator-project';
import ScatterPlotGlowOverlay from './ScatterPlotGlowOverlay';
-
-import {
- DEFAULT_LONGITUDE,
- DEFAULT_LATITUDE,
- DEFAULT_ZOOM,
-} from '../../utils/common';
import './MapBox.css';
const NOOP = () => {};
@@ -31,9 +25,7 @@ const propTypes = {
pointRadiusUnit: PropTypes.string,
renderWhileDragging: PropTypes.bool,
rgb: PropTypes.array,
- viewportLatitude: PropTypes.number,
- viewportLongitude: PropTypes.number,
- viewportZoom: PropTypes.number,
+ bounds: PropTypes.array,
};
const defaultProps = {
@@ -41,27 +33,31 @@ const defaultProps = {
onViewportChange: NOOP,
pointRadius: DEFAULT_POINT_RADIUS,
pointRadiusUnit: 'Pixels',
- viewportLatitude: DEFAULT_LATITUDE,
- viewportLongitude: DEFAULT_LONGITUDE,
- viewportZoom: DEFAULT_ZOOM,
};
class MapBox extends React.Component {
constructor(props) {
super(props);
- const {
- viewportLatitude: latitude,
- viewportLongitude: longitude,
- viewportZoom: zoom,
- } = this.props;
+ const { width, height, bounds } = this.props;
+ // Get a viewport that fits the given bounds, which all marks to be clustered.
+ // Derive lat, lon and zoom from this viewport. This is only done on initial
+ // render as the bounds don't update as we pan/zoom in the current design.
+ const mercator = new ViewportMercator({
+ width,
+ height,
+ }).fitBounds(bounds);
+ const { latitude, longitude, zoom } = mercator;
+ // Compute the clusters based on the bounds. Again, this is only done once because
+ // we don't update the clusters as we pan/zoom in the current design.
+ const bbox = [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
+ this.clusters = this.props.clusterer.getClusters(bbox, Math.round(zoom));
this.state = {
viewport: {
longitude,
latitude,
zoom,
- startDragLngLat: [longitude, latitude],
},
};
this.onViewportChange = this.onViewportChange.bind(this);
@@ -86,23 +82,11 @@ class MapBox extends React.Component {
rgb,
} = this.props;
const { viewport } = this.state;
- const { latitude, longitude, zoom } = viewport;
- const mercator = new ViewportMercator({
- width,
- height,
- longitude,
- latitude,
- zoom,
- });
- const topLeft = mercator.unproject([0, 0]);
- const bottomRight = mercator.unproject([width, height]);
- const bbox = [topLeft[0], bottomRight[1], bottomRight[0], topLeft[1]];
- const clusters = this.props.clusterer.getClusters(bbox, Math.round(zoom));
const isDragging = viewport.isDragging === undefined ? false :
viewport.isDragging;
return (
,
document.querySelector(selector),
);
diff --git a/superset/assets/src/visualizations/table.js b/superset/assets/src/visualizations/table.js
index a875aad655832..2b20a7da1f37c 100644
--- a/superset/assets/src/visualizations/table.js
+++ b/superset/assets/src/visualizations/table.js
@@ -46,6 +46,7 @@ const propTypes = {
};
const formatValue = d3.format('0,000');
+const formatPercent = d3.format('.3p');
function NOOP() {}
function TableVis(element, props) {
@@ -76,7 +77,7 @@ function TableVis(element, props) {
// Add percent metrics
.concat((percentMetrics || []).map(m => '%' + m))
// Removing metrics (aggregates) that are strings
- .filter(m => !Number.isNaN(data[0][m]));
+ .filter(m => (typeof data[0][m]) === 'number');
function col(c) {
const arr = [];
@@ -131,9 +132,11 @@ function TableVis(element, props) {
}
if (isMetric) {
html = d3.format(format || '0.3s')(val);
- } else if (key[0] === '%') {
- html = d3.format('.3p')(val);
}
+ if (key[0] === '%') {
+ html = formatPercent(val);
+ }
+
return {
col: key,
val,
diff --git a/superset/assets/src/welcome/App.jsx b/superset/assets/src/welcome/App.jsx
index 5c694de928441..35283453e892e 100644
--- a/superset/assets/src/welcome/App.jsx
+++ b/superset/assets/src/welcome/App.jsx
@@ -1,71 +1,16 @@
import React from 'react';
-import PropTypes from 'prop-types';
-import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
-import RecentActivity from '../profile/components/RecentActivity';
-import Favorites from '../profile/components/Favorites';
-import DashboardTable from './DashboardTable';
-import { t } from '../locales';
+import { hot } from 'react-hot-loader';
+import { appSetup } from '../common';
+import Welcome from './Welcome';
-const propTypes = {
- user: PropTypes.object.isRequired,
-};
+appSetup();
-export default class App extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- search: '',
- };
- this.onSearchChange = this.onSearchChange.bind(this);
- }
- onSearchChange(event) {
- this.setState({ search: event.target.value });
- }
- render() {
- return (
-
-
-
-
-
- {t('Dashboards')}
-
-
-
-
-
-
-
-
-
-
-
- {t('Recently Viewed')}
-
-
-
-
-
-
-
-
- {t('Favorites')}
-
-
-
-
-
-
-
- );
- }
-}
+const container = document.getElementById('app');
+const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
+const user = { ...bootstrap.user };
-App.propTypes = propTypes;
+const App = () => (
+
+);
+
+export default hot(module)(App);
diff --git a/superset/assets/src/welcome/DashboardTable.jsx b/superset/assets/src/welcome/DashboardTable.jsx
index f7f3007ecc374..a45bd19dd5129 100644
--- a/superset/assets/src/welcome/DashboardTable.jsx
+++ b/superset/assets/src/welcome/DashboardTable.jsx
@@ -1,9 +1,6 @@
-/* eslint no-unused-vars: 0 */
import React from 'react';
-import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
-import { Table, Tr, Td, Thead, Th, unsafe } from 'reactable';
-
+import { Table, Tr, Td, unsafe } from 'reactable';
import Loading from '../components/Loading';
import '../../stylesheets/reactable-pagination.css';
@@ -64,4 +61,5 @@ export default class DashboardTable extends React.PureComponent {
return ;
}
}
+
DashboardTable.propTypes = propTypes;
diff --git a/superset/assets/src/welcome/Welcome.jsx b/superset/assets/src/welcome/Welcome.jsx
new file mode 100644
index 0000000000000..2f4de973ae34a
--- /dev/null
+++ b/superset/assets/src/welcome/Welcome.jsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
+import RecentActivity from '../profile/components/RecentActivity';
+import Favorites from '../profile/components/Favorites';
+import DashboardTable from './DashboardTable';
+import { t } from '../locales';
+
+const propTypes = {
+ user: PropTypes.object.isRequired,
+};
+
+export default class Welcome extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ search: '',
+ };
+ this.onSearchChange = this.onSearchChange.bind(this);
+ }
+ onSearchChange(event) {
+ this.setState({ search: event.target.value });
+ }
+ render() {
+ return (
+
+
+
+
+
+ {t('Dashboards')}
+
+
+
+
+
+
+
+
+
+
+
+ {t('Recently Viewed')}
+
+
+
+
+
+
+
+
+ {t('Favorites')}
+
+
+
+
+
+
+
+ );
+ }
+}
+
+Welcome.propTypes = propTypes;
diff --git a/superset/assets/src/welcome/index.jsx b/superset/assets/src/welcome/index.jsx
index df0f774a81bf5..3088170f013dd 100644
--- a/superset/assets/src/welcome/index.jsx
+++ b/superset/assets/src/welcome/index.jsx
@@ -1,22 +1,8 @@
-/* eslint no-unused-vars: 0 */
import React from 'react';
import ReactDOM from 'react-dom';
-import { Panel, Row, Col, FormControl } from 'react-bootstrap';
-
-import { appSetup } from '../common';
import App from './App';
-appSetup();
-
-const container = document.getElementById('app');
-const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
-const user = {
- ...bootstrap.user,
-};
-
ReactDOM.render(
- ,
- container,
+ ,
+ document.getElementById('app'),
);
diff --git a/superset/assets/webpack.config.js b/superset/assets/webpack.config.js
index 334717137307c..d1c5cbdd94398 100644
--- a/superset/assets/webpack.config.js
+++ b/superset/assets/webpack.config.js
@@ -35,6 +35,11 @@ const plugins = [
// create fresh dist/ upon build
new CleanWebpackPlugin(['dist']),
+
+ // expose mode variable to other modules
+ new webpack.DefinePlugin({
+ 'process.env.WEBPACK_MODE': JSON.stringify(mode),
+ }),
];
if (isDevMode) {
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index 5edcfa92fbaeb..c8724db3bc4bb 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -4832,7 +4832,7 @@ fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-fast-levenshtein@~2.0.4:
+fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -5493,7 +5493,7 @@ global-prefix@^1.0.1:
is-windows "^1.0.1"
which "^1.2.14"
-global@~4.3.0:
+global@^4.3.0, global@~4.3.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
dependencies:
@@ -9850,6 +9850,17 @@ react-gravatar@^2.6.1:
md5 "^2.1.0"
query-string "^4.2.2"
+react-hot-loader@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.3.6.tgz#26e1491f08daf2bad99d141b1927c9faadef2fb4"
+ dependencies:
+ fast-levenshtein "^2.0.6"
+ global "^4.3.0"
+ hoist-non-react-statics "^2.5.0"
+ prop-types "^15.6.1"
+ react-lifecycles-compat "^3.0.4"
+ shallowequal "^1.0.2"
+
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"
@@ -10081,9 +10092,9 @@ react@^15.6.1, react@^15.6.2:
object-assign "^4.1.0"
prop-types "^15.5.10"
-reactable@^0.14.1:
- version "0.14.1"
- resolved "https://registry.yarnpkg.com/reactable/-/reactable-0.14.1.tgz#2ab9895e3df6da2498625de46d3691592d18c93b"
+reactable@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/reactable/-/reactable-1.1.0.tgz#2f84bb8b3808df8ac64694b539be416438db8498"
reactcss@^1.2.0:
version "1.2.3"
@@ -10936,6 +10947,10 @@ shallow-copy@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170"
+shallowequal@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+
shapefile@0.3:
version "0.3.1"
resolved "https://registry.yarnpkg.com/shapefile/-/shapefile-0.3.1.tgz#9bb9a429bd6086a0cfb03962d14cfdf420ffba12"
diff --git a/superset/templates/superset/base.html b/superset/templates/superset/base.html
index 500ed0f87aeda..f974348b841a6 100644
--- a/superset/templates/superset/base.html
+++ b/superset/templates/superset/base.html
@@ -8,11 +8,15 @@
{% endfor %}
{% endblock %}
- {% block tail_js %}
+ {% block head_js %}
{{super()}}
{% for entry in js_manifest('theme') %}
{% endfor %}
+ {% endblock %}
+
+ {% block tail_js %}
+ {{super()}}
{% for entry in js_manifest('common') %}
{% endfor %}
diff --git a/superset/viz.py b/superset/viz.py
index 10112a38a41d6..8becb99054ec0 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -205,16 +205,16 @@ def get_df(self, query_obj=None):
df[DTTM_ALIAS] += self.time_shift
if self.enforce_numerical_metrics:
- self.df_metrics_to_num(df, query_obj.get('metrics') or [])
+ self.df_metrics_to_num(df)
df.replace([np.inf, -np.inf], np.nan)
fillna = self.get_fillna_for_columns(df.columns)
df = df.fillna(fillna)
return df
- @staticmethod
- def df_metrics_to_num(df, metrics):
+ def df_metrics_to_num(self, df):
"""Converting metrics to numeric when pandas.read_sql cannot"""
+ metrics = self.metric_labels
for col, dtype in df.dtypes.items():
if dtype.type == np.object_ and col in metrics:
df[col] = pd.to_numeric(df[col], errors='coerce')
@@ -2010,6 +2010,10 @@ def get_data(self, df):
],
}
+ x_series, y_series = df[fd.get('all_columns_x')], df[fd.get('all_columns_y')]
+ south_west = [x_series.min(), y_series.min()]
+ north_east = [x_series.max(), y_series.max()]
+
return {
'geoJSON': geo_json,
'customMetric': custom_metric,
@@ -2019,9 +2023,7 @@ def get_data(self, df):
'clusteringRadius': fd.get('clustering_radius'),
'pointRadiusUnit': fd.get('point_radius_unit'),
'globalOpacity': fd.get('global_opacity'),
- 'viewportLongitude': fd.get('viewport_longitude'),
- 'viewportLatitude': fd.get('viewport_latitude'),
- 'viewportZoom': fd.get('viewport_zoom'),
+ 'bounds': [south_west, north_east],
'renderWhileDragging': fd.get('render_while_dragging'),
'tooltip': fd.get('rich_tooltip'),
'color': fd.get('mapbox_color'),