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'),