You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by cc...@apache.org on 2018/06/22 00:54:22 UTC

[incubator-superset] 07/26: [dashboard builder] improve perf (#4855)

This is an automated email from the ASF dual-hosted git repository.

ccwilliams pushed a commit to branch dashboard-builder
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git

commit 22052040dc588739b460f4d07760d2a875171c56
Author: Chris Williams <wi...@users.noreply.github.com>
AuthorDate: Tue Apr 24 11:07:23 2018 -0700

    [dashboard builder] improve perf (#4855)
    
    * address major perf + css issues
    
    [dashboard builder] fix dashboard filters and some css
    
    [dashboard builder] use VIZ_TYPES, move stricter .eslintrc to dashboard/, more css fixes
    
    [builder] delete GridCell and GridLayout, remove some unused css. fix broken tabs.
    
    * [builder] fix errors post-rebase
    
    * [builder] add support for custom DragDroppable drag layer and add AddSliceDragPreview
    
    * [AddSliceDragPreview] fix type check
    
    * [dashboard builder] add prettier and update all files
    
    * [dashboard builder] merge v2/ directory int dashboard/
    
    * [dashboard builder] move component/*Container => containers/*
---
 superset/assets/images/loading.gif                 |  Bin 16671 -> 1945878 bytes
 superset/assets/package.json                       |    5 +-
 superset/assets/src/chart/Chart.jsx                |   80 +-
 superset/assets/src/chart/ChartContainer.jsx       |   22 +-
 superset/assets/src/chart/chartReducer.js          |    5 +-
 superset/assets/src/components/Loading.jsx         |    3 +
 superset/assets/src/dashboard/{v2 => }/.eslintrc   |    6 +-
 superset/assets/src/dashboard/.prettierrc          |    4 +
 .../dashboard/{v2 => }/actions/dashboardLayout.js  |   59 +-
 .../assets/src/dashboard/actions/dashboardState.js |   58 +-
 .../assets/src/dashboard/actions/datasources.js    |    3 +-
 .../dashboard/{v2 => }/actions/messageToasts.js    |   13 +-
 .../assets/src/dashboard/actions/sliceEntities.js  |   15 +-
 .../src/dashboard/components/ActionMenuItem.jsx    |   40 +-
 .../src/dashboard/components/AddSliceCard.jsx      |   59 +
 .../{v2 => }/components/BuilderComponentPane.jsx   |   15 +-
 .../assets/src/dashboard/components/CodeModal.jsx  |   10 +-
 .../assets/src/dashboard/components/Controls.jsx   |  123 +-
 .../assets/src/dashboard/components/CssEditor.jsx  |   11 +-
 .../assets/src/dashboard/components/Dashboard.jsx  |  240 +-
 .../{v2 => }/components/DashboardBuilder.jsx       |   39 +-
 .../dashboard/components/DashboardContainer.jsx    |   51 -
 .../{v2 => }/components/DashboardGrid.jsx          |  106 +-
 .../{v2 => }/components/DeleteComponentButton.jsx  |    7 +-
 .../assets/src/dashboard/components/GridCell.jsx   |  158 -
 .../assets/src/dashboard/components/GridLayout.jsx |  156 -
 .../assets/src/dashboard/components/Header.jsx     |  117 +-
 .../dashboard/{v2 => }/components/IconButton.jsx   |    0
 .../dashboard/components/RefreshIntervalModal.jsx  |    4 +-
 .../assets/src/dashboard/components/SaveModal.jsx  |   63 +-
 .../assets/src/dashboard/components/SliceAdder.jsx |  125 +-
 .../dashboard/components/SliceAdderContainer.jsx   |   25 -
 .../src/dashboard/components/SliceHeader.jsx       |  102 +-
 .../dashboard/components/SliceHeaderControls.jsx   |   29 +-
 .../src/dashboard/{v2 => }/components/Toast.jsx    |   11 +-
 .../{v2 => }/components/ToastPresenter.jsx         |    9 +-
 .../components/dnd/AddSliceDragPreview.jsx         |   70 +
 .../{v2 => }/components/dnd/DragDroppable.jsx      |   52 +-
 .../{v2 => }/components/dnd/DragHandle.jsx         |    8 +-
 .../{v2 => }/components/dnd/dragDroppableConfig.js |   12 +-
 .../{v2 => }/components/dnd/handleDrop.js          |   22 +-
 .../{v2 => }/components/dnd/handleHover.js         |    0
 .../dashboard/components/gridComponents/Chart.jsx  |  233 ++
 .../components/gridComponents/ChartHolder.jsx      |   63 +-
 .../{v2 => }/components/gridComponents/Column.jsx  |   55 +-
 .../{v2 => }/components/gridComponents/Divider.jsx |    5 +-
 .../{v2 => }/components/gridComponents/Header.jsx  |   18 +-
 .../{v2 => }/components/gridComponents/Row.jsx     |   48 +-
 .../{v2 => }/components/gridComponents/Tab.jsx     |   26 +-
 .../{v2 => }/components/gridComponents/Tabs.jsx    |   61 +-
 .../{v2 => }/components/gridComponents/index.js    |    2 -
 .../gridComponents/new/DraggableNewComponent.jsx   |    5 +-
 .../components/gridComponents/new/NewColumn.jsx    |   16 +
 .../components/gridComponents/new/NewDivider.jsx   |   16 +
 .../components/gridComponents/new/NewHeader.jsx    |   16 +
 .../components/gridComponents/new/NewRow.jsx       |   16 +
 .../components/gridComponents/new/NewTabs.jsx      |   16 +
 .../components/menu/BackgroundStyleDropdown.jsx    |    0
 .../{v2 => }/components/menu/HoverMenu.jsx         |    0
 .../{v2 => }/components/menu/PopoverDropdown.jsx   |    4 +-
 .../{v2 => }/components/menu/WithPopoverMenu.jsx   |   22 +-
 .../components/resizable/ResizableContainer.jsx    |   72 +-
 .../components/resizable/ResizableHandle.jsx       |   12 +-
 superset/assets/src/dashboard/containers/Chart.jsx |   59 +
 .../assets/src/dashboard/containers/Dashboard.jsx  |   49 +
 .../{v2 => }/containers/DashboardBuilder.jsx       |   18 +-
 .../{v2 => }/containers/DashboardComponent.jsx     |   44 +-
 .../{v2 => }/containers/DashboardGrid.jsx          |   16 +-
 .../{v2 => }/containers/DashboardHeader.jsx        |   48 +-
 .../{v2 => }/containers/ToastPresenter.jsx         |    0
 .../{v2 => }/fixtures/emptyDashboardLayout.js      |    4 +-
 superset/assets/src/dashboard/index.jsx            |    7 +-
 .../dashboard/{v2 => }/reducers/dashboardLayout.js |   80 +-
 .../src/dashboard/reducers/dashboardState.js       |   25 +-
 .../assets/src/dashboard/reducers/datasources.js   |    9 +-
 .../src/dashboard/reducers/getInitialState.js      |   26 +-
 superset/assets/src/dashboard/reducers/index.js    |   12 +-
 .../dashboard/{v2 => }/reducers/messageToasts.js   |    4 +-
 .../assets/src/dashboard/reducers/sliceEntities.js |   10 +-
 .../dashboard/reducers/undoableDashboardLayout.js  |   27 +
 .../{v2 => }/stylesheets/builder-sidepane.less     |   28 +-
 .../dashboard/{v2 => }/stylesheets/builder.less    |   23 +-
 .../dashboard/{v2 => }/stylesheets/buttons.less    |    0
 .../dashboard/stylesheets/components/chart.less    |   69 +
 .../{v2 => }/stylesheets/components/column.less    |   19 +-
 .../{v2 => }/stylesheets/components/divider.less   |    0
 .../{v2 => }/stylesheets/components/header.less    |    5 +-
 .../{v2 => }/stylesheets/components/index.less     |    0
 .../stylesheets/components/new-component.less      |    0
 .../{v2 => }/stylesheets/components/row.less       |   19 +-
 .../{v2 => }/stylesheets/components/tabs.less      |    0
 .../src/dashboard/stylesheets/dashboard.less       |  104 +
 .../src/dashboard/{v2 => }/stylesheets/dnd.less    |    2 +-
 .../src/dashboard/{v2 => }/stylesheets/grid.less   |   14 +-
 .../dashboard/{v2 => }/stylesheets/hover-menu.less |    0
 .../src/dashboard/{v2 => }/stylesheets/index.less  |    2 +
 .../{v2 => }/stylesheets/popover-menu.less         |    0
 .../dashboard/{v2 => }/stylesheets/resizable.less  |   26 +-
 .../src/dashboard/{v2 => }/stylesheets/toast.less  |    5 +-
 .../dashboard/{v2 => }/stylesheets/variables.less  |    0
 .../src/dashboard/util/backgroundStyleOptions.js   |   15 +
 .../util/charts/getEffectiveExtraFilters.js        |   42 +
 .../util/charts/getFormDataWithExtraFilters.js     |   42 +
 .../src/dashboard/util/componentIsResizable.js     |    5 +
 .../src/dashboard/{v2 => }/util/componentTypes.js  |    0
 .../src/dashboard/{v2 => }/util/constants.js       |    0
 .../assets/src/dashboard/util/dashboardHelper.js   |    9 -
 .../src/dashboard/util/dashboardLayoutConverter.js |   93 +-
 .../src/dashboard/{v2 => }/util/dnd-reorder.js     |   12 +-
 .../dashboard/{v2 => }/util/dropOverflowsParent.js |   26 +-
 .../src/dashboard/{v2 => }/util/findParentId.js    |    6 +-
 .../src/dashboard/util/getChartIdsFromLayout.js    |    8 +
 .../src/dashboard/{v2 => }/util/getChildWidth.js   |    6 +-
 .../src/dashboard/{v2 => }/util/getDropPosition.js |   26 +-
 .../dashboard/{v2 => }/util/headerStyleOptions.js  |    2 +-
 .../src/dashboard/{v2 => }/util/isValidChild.js    |   28 +-
 .../dashboard/{v2 => }/util/newComponentFactory.js |   10 +-
 .../dashboard/{v2 => }/util/newEntitiesFromDrop.js |   14 +-
 .../src/dashboard/{v2 => }/util/propShapes.jsx     |   20 +-
 .../src/dashboard/{v2 => }/util/resizableConfig.js |    0
 .../{v2 => }/util/shouldWrapChildInRow.js          |    0
 .../assets/src/dashboard/v2/actions/editMode.js    |    9 -
 .../src/dashboard/v2/components/Dashboard.jsx      |   30 -
 .../dashboard/v2/components/DashboardHeader.jsx    |   99 -
 .../dashboard/v2/components/StaticDashboard.jsx    |   19 -
 .../v2/components/gridComponents/new/NewChart.jsx  |   24 -
 .../v2/components/gridComponents/new/NewColumn.jsx |   24 -
 .../components/gridComponents/new/NewDivider.jsx   |   24 -
 .../v2/components/gridComponents/new/NewHeader.jsx |   24 -
 .../v2/components/gridComponents/new/NewRow.jsx    |   23 -
 .../v2/components/gridComponents/new/NewTabs.jsx   |   24 -
 .../assets/src/dashboard/v2/reducers/editMode.js   |   11 -
 superset/assets/src/dashboard/v2/reducers/index.js |    8 -
 .../dashboard/v2/stylesheets/components/chart.less |   20 -
 .../dashboard/v2/util/backgroundStyleOptions.js    |    7 -
 .../src/dashboard/v2/util/componentIsResizable.js  |   13 -
 .../src/explore/components/ExploreChartHeader.jsx  |    2 +-
 .../src/explore/components/ExploreChartPanel.jsx   |   15 +-
 .../explore/components/ExploreViewContainer.jsx    |    2 +-
 superset/assets/stylesheets/dashboard.less         |  156 -
 superset/assets/stylesheets/superset.less          |    2 +-
 superset/assets/stylesheets/welcome.css            |    2 +-
 superset/assets/yarn.lock                          | 3248 ++++----------------
 superset/templates/superset/dashboard.html         |    7 +-
 superset/views/core.py                             |    2 +-
 145 files changed, 2943 insertions(+), 4613 deletions(-)

diff --git a/superset/assets/images/loading.gif b/superset/assets/images/loading.gif
index 01ae393..ae5cbdd 100644
Binary files a/superset/assets/images/loading.gif and b/superset/assets/images/loading.gif differ
diff --git a/superset/assets/package.json b/superset/assets/package.json
index d20dad7..ab440f0 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -114,7 +114,7 @@
     "redux": "^3.5.2",
     "redux-localstorage": "^0.4.1",
     "redux-thunk": "^2.1.0",
-    "redux-undo": "^0.6.1",
+    "redux-undo": "^1.0.0-beta9-9-7",
     "shortid": "^2.2.6",
     "sprintf-js": "^1.1.1",
     "srcdoc-polyfill": "^1.0.0",
@@ -138,8 +138,10 @@
     "enzyme": "^2.0.0",
     "eslint": "^4.19.0",
     "eslint-config-airbnb": "^15.0.1",
+    "eslint-config-prettier": "^2.9.0",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-jsx-a11y": "^5.1.1",
+    "eslint-plugin-prettier": "^2.6.0",
     "eslint-plugin-react": "^7.0.1",
     "exports-loader": "^0.7.0",
     "extract-text-webpack-plugin": "3.0.2",
@@ -154,6 +156,7 @@
     "less-loader": "^4.0.3",
     "mocha": "^3.2.0",
     "npm-check-updates": "^2.14.0",
+    "prettier": "^1.12.1",
     "react-addons-test-utils": "^15.6.2",
     "react-test-renderer": "^15.6.2",
     "redux-mock-store": "^1.2.3",
diff --git a/superset/assets/src/chart/Chart.jsx b/superset/assets/src/chart/Chart.jsx
index e9f7c63..4a471e8 100644
--- a/superset/assets/src/chart/Chart.jsx
+++ b/superset/assets/src/chart/Chart.jsx
@@ -73,10 +73,16 @@ class Chart extends React.PureComponent {
 
   componentDidMount() {
     if (this.props.triggerQuery) {
-      this.props.actions.runQuery(this.props.formData, false,
+      const { formData } = this.props;
+      this.props.actions.runQuery(
+        formData,
+        false,
         this.props.timeout,
         this.props.chartId,
       );
+    } else {
+      // when drag/dropping in a dashboard, a chart may be unmounted/remounted but still have data
+      this.renderViz();
     }
   }
 
@@ -89,14 +95,16 @@ class Chart extends React.PureComponent {
   }
 
   componentDidUpdate(prevProps) {
-    if (this.props.queryResponse &&
+    if (
+      this.props.queryResponse &&
       ['success', 'rendered'].indexOf(this.props.chartStatus) > -1 &&
       !this.props.queryResponse.error && (
-      prevProps.annotationData !== this.props.annotationData ||
-      prevProps.queryResponse !== this.props.queryResponse ||
-      prevProps.height !== this.props.height ||
-      prevProps.width !== this.props.width ||
-      prevProps.lastRendered !== this.props.lastRendered)
+        prevProps.annotationData !== this.props.annotationData ||
+        prevProps.queryResponse !== this.props.queryResponse ||
+        prevProps.height !== this.props.height ||
+        prevProps.width !== this.props.width ||
+        prevProps.lastRendered !== this.props.lastRendered
+      )
     ) {
       this.renderViz();
     }
@@ -123,7 +131,8 @@ class Chart extends React.PureComponent {
   }
 
   width() {
-    return this.props.width || this.container.el.offsetWidth;
+    return this.props.width ||
+      (this.container && this.container.el && this.container.el.offsetWidth);
   }
 
   headerHeight() {
@@ -131,7 +140,8 @@ class Chart extends React.PureComponent {
   }
 
   height() {
-    return this.props.height || this.container.el.offsetHeight;
+    return this.props.height
+      || (this.container && this.container.el && this.container.el.offsetHeight);
   }
 
   d3format(col, number) {
@@ -159,7 +169,6 @@ class Chart extends React.PureComponent {
 
   renderTooltip() {
     if (this.state.tooltip) {
-      /* eslint-disable react/no-danger */
       return (
         <Tooltip
           className="chart-tooltip"
@@ -169,52 +178,55 @@ class Chart extends React.PureComponent {
           positionLeft={this.state.tooltip.x + 30}
           arrowOffsetTop={10}
         >
-          <div dangerouslySetInnerHTML={{ __html: this.state.tooltip.content }} />
+          <div // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: this.state.tooltip.content }}
+          />
         </Tooltip>
       );
-      /* eslint-enable react/no-danger */
     }
     return null;
   }
 
   renderViz() {
-    const viz = visMap[this.props.vizType];
-    const fd = this.props.formData;
-    const qr = this.props.queryResponse;
+    const { vizType, formData, queryResponse, setControlValue, chartId, chartStatus } = this.props;
+    const visRenderer = visMap[vizType];
     const renderStart = Logger.getTimestamp();
     try {
       // Executing user-defined data mutator function
-      if (fd.js_data) {
-        qr.data = sandboxedEval(fd.js_data)(qr.data);
+      if (formData.js_data) {
+        queryResponse.data = sandboxedEval(formData.js_data)(queryResponse.data);
+      }
+      visRenderer(this, queryResponse, setControlValue);
+      if (chartStatus !== 'rendered') {
+        this.props.actions.chartRenderingSucceeded(chartId);
       }
-      // [re]rendering the visualization
-      viz(this, qr, this.props.setControlValue);
       Logger.append(LOG_ACTIONS_RENDER_EVENT, {
-        label: 'slice_' + this.props.chartId,
-        vis_type: this.props.vizType,
+        label: 'slice_' + chartId,
+        vis_type: vizType,
         start_offset: renderStart,
         duration: Logger.getTimestamp() - renderStart,
       });
-      this.props.actions.chartRenderingSucceeded(this.props.chartId);
+      this.props.actions.chartRenderingSucceeded(chartId);
     } catch (e) {
-      this.props.actions.chartRenderingFailed(e, this.props.chartId);
+      console.error(e); // eslint-disable-line no-console
+      this.props.actions.chartRenderingFailed(e, chartId);
     }
   }
 
   render() {
     const isLoading = this.props.chartStatus === 'loading';
+
+    // this allows <Loading /> to be positioned in the middle of the chart
+    const containerStyles = isLoading ? { height: this.height(), width: this.width() } : null;
     return (
-      <div className={`${isLoading ? 'is-loading' : ''}`}>
+      <div className={`chart-container ${isLoading ? 'is-loading' : ''}`} style={containerStyles}>
         {this.renderTooltip()}
-        {isLoading &&
-          <Loading size={25} />
-        }
+        {isLoading && <Loading size={75} />}
         {this.props.chartAlert &&
-        <StackTraceMessage
-          message={this.props.chartAlert}
-          queryResponse={this.props.queryResponse}
-        />
-        }
+          <StackTraceMessage
+            message={this.props.chartAlert}
+            queryResponse={this.props.queryResponse}
+          />}
 
         {!isLoading &&
           !this.props.chartAlert &&
@@ -226,8 +238,8 @@ class Chart extends React.PureComponent {
             width={this.width()}
             onQuery={this.props.onQuery}
             onDismiss={this.props.onDismissRefreshOverlay}
-          />
-        }
+          />}
+
         {!isLoading && !this.props.chartAlert &&
           <ChartBody
             containerId={this.containerId}
diff --git a/superset/assets/src/chart/ChartContainer.jsx b/superset/assets/src/chart/ChartContainer.jsx
index e3cb1f9..b66fe5d 100644
--- a/superset/assets/src/chart/ChartContainer.jsx
+++ b/superset/assets/src/chart/ChartContainer.jsx
@@ -1,29 +1,13 @@
 import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
 
-import * as Actions from './chartAction';
+import * as actions from './chartAction';
 import Chart from './Chart';
 
-function mapStateToProps({ charts }, ownProps) {
-  const chart = charts[ownProps.chartId];
-  return {
-    annotationData: chart.annotationData,
-    chartAlert: chart.chartAlert,
-    chartStatus: chart.chartStatus,
-    chartUpdateEndTime: chart.chartUpdateEndTime,
-    chartUpdateStartTime: chart.chartUpdateStartTime,
-    latestQueryFormData: chart.latestQueryFormData,
-    lastRendered: chart.lastRendered,
-    queryResponse: chart.queryResponse,
-    queryRequest: chart.queryRequest,
-    triggerQuery: chart.triggerQuery,
-  };
-}
-
 function mapDispatchToProps(dispatch) {
   return {
-    actions: bindActionCreators(Actions, dispatch),
+    actions: bindActionCreators(actions, dispatch),
   };
 }
 
-export default connect(mapStateToProps, mapDispatchToProps)(Chart);
+export default connect(null, mapDispatchToProps)(Chart);
diff --git a/superset/assets/src/chart/chartReducer.js b/superset/assets/src/chart/chartReducer.js
index d57959a..ea8de8b 100644
--- a/superset/assets/src/chart/chartReducer.js
+++ b/superset/assets/src/chart/chartReducer.js
@@ -142,7 +142,10 @@ export default function chartReducer(charts = {}, action) {
   }
 
   if (action.type in actionHandlers) {
-    return { ...charts, [action.key]: actionHandlers[action.type](charts[action.key], action) };
+    return {
+      ...charts,
+      [action.key]: actionHandlers[action.type](charts[action.key], action),
+    };
   }
 
   return charts;
diff --git a/superset/assets/src/components/Loading.jsx b/superset/assets/src/components/Loading.jsx
index 416e770..810c581 100644
--- a/superset/assets/src/components/Loading.jsx
+++ b/superset/assets/src/components/Loading.jsx
@@ -20,6 +20,9 @@ export default function Loading(props) {
         padding: 0,
         margin: 0,
         position: 'absolute',
+        left: '50%',
+        top: '50%',
+        transform: 'translate(-50%, -60%)',
       }}
     />
   );
diff --git a/superset/assets/src/dashboard/v2/.eslintrc b/superset/assets/src/dashboard/.eslintrc
similarity index 84%
rename from superset/assets/src/dashboard/v2/.eslintrc
rename to superset/assets/src/dashboard/.eslintrc
index 70efc15..a3f86e3 100644
--- a/superset/assets/src/dashboard/v2/.eslintrc
+++ b/superset/assets/src/dashboard/.eslintrc
@@ -1,4 +1,6 @@
 {
+  "extends": "prettier",
+  "plugins": ["prettier"],
   "rules": {
     "prefer-template": 2,
     "new-cap": 2,
@@ -12,7 +14,7 @@
     "jsx-a11y/anchor-has-content": 2,
     "react/require-default-props": 2,
     "no-plusplus": 2,
-    "no-mixed-operators": 2,
+    "no-mixed-operators": 0,
     "no-continue": 2,
     "no-bitwise": 2,
     "no-undef": 2,
@@ -25,5 +27,7 @@
     "import/prefer-default-export": 2,
     "react/no-unescaped-entities": 2,
     "react/no-string-refs": 2,
+    "react/jsx-indent": 0,
+    "prettier/prettier": "error"
   }
 }
diff --git a/superset/assets/src/dashboard/.prettierrc b/superset/assets/src/dashboard/.prettierrc
new file mode 100644
index 0000000..a20502b
--- /dev/null
+++ b/superset/assets/src/dashboard/.prettierrc
@@ -0,0 +1,4 @@
+{
+  "singleQuote": true,
+  "trailingComma": "all"
+}
diff --git a/superset/assets/src/dashboard/v2/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js
similarity index 73%
rename from superset/assets/src/dashboard/v2/actions/dashboardLayout.js
rename to superset/assets/src/dashboard/actions/dashboardLayout.js
index b6d41c4..5a04de5 100644
--- a/superset/assets/src/dashboard/v2/actions/dashboardLayout.js
+++ b/superset/assets/src/dashboard/actions/dashboardLayout.js
@@ -1,6 +1,11 @@
 import { addInfoToast } from './messageToasts';
+import { setUnsavedChanges } from './dashboardState';
 import { CHART_TYPE, MARKDOWN_TYPE, TABS_TYPE } from '../util/componentTypes';
-import { DASHBOARD_ROOT_ID, NEW_COMPONENTS_SOURCE_ID } from '../util/constants';
+import {
+  DASHBOARD_ROOT_ID,
+  NEW_COMPONENTS_SOURCE_ID,
+  GRID_MIN_COLUMN_COUNT,
+} from '../util/constants';
 import dropOverflowsParent from '../util/dropOverflowsParent';
 import findParentId from '../util/findParentId';
 
@@ -62,12 +67,10 @@ export function resizeComponent({ id, width, height }) {
     const { dashboardLayout: undoableLayout } = getState();
     const { present: dashboard } = undoableLayout;
     const component = dashboard[id];
-
-    if (
-      component &&
-      (component.meta.width !== width || component.meta.height !== height)
-    ) {
-      // update the size of this component + any resizable children
+    const widthChanged = width && component.meta.width !== width;
+    const heightChanged = height && component.meta.height !== height;
+    if (component && (widthChanged || heightChanged)) {
+      // update the size of this component
       const updatedComponents = {
         [id]: {
           ...component,
@@ -79,14 +82,16 @@ export function resizeComponent({ id, width, height }) {
         },
       };
 
-      component.children.forEach((childId) => {
+      // set any resizable children to have a minimum width so that
+      // the chances that they are validly movable to future containers is maximized
+      component.children.forEach(childId => {
         const child = dashboard[childId];
         if ([CHART_TYPE, MARKDOWN_TYPE].includes(child.type)) {
           updatedComponents[childId] = {
             ...child,
             meta: {
               ...child.meta,
-              width: width || child.meta.width,
+              width: GRID_MIN_COLUMN_COUNT,
               height: height || child.meta.height,
             },
           };
@@ -94,6 +99,7 @@ export function resizeComponent({ id, width, height }) {
       });
 
       dispatch(updateComponents(updatedComponents));
+      dispatch(setUnsavedChanges(true));
     }
   };
 }
@@ -112,13 +118,18 @@ export function moveComponent(dropResult) {
 export const HANDLE_COMPONENT_DROP = 'HANDLE_COMPONENT_DROP';
 export function handleComponentDrop(dropResult) {
   return (dispatch, getState) => {
-    const overflowsParent = dropOverflowsParent(dropResult, getState().dashboardLayout.present);
+    const overflowsParent = dropOverflowsParent(
+      dropResult,
+      getState().dashboardLayout.present,
+    );
 
     if (overflowsParent) {
-      return dispatch(addInfoToast(
-        `Parent does not have enough space for this component.
+      return dispatch(
+        addInfoToast(
+          `Parent does not have enough space for this component.
          Try decreasing its width or add it to a new row.`,
-      ));
+        ),
+      );
     }
 
     const { source, destination } = dropResult;
@@ -130,12 +141,10 @@ export function handleComponentDrop(dropResult) {
     } else if (destination && isNewComponent) {
       dispatch(createComponent(dropResult));
     } else if (
-      destination
-      && source
-      && !( // ensure it has moved
-        destination.id === source.id
-        && destination.index === source.index
-      )
+      destination &&
+      source &&
+      !// ensure it has moved
+      (destination.id === source.id && destination.index === source.index)
     ) {
       dispatch(moveComponent(dropResult));
     }
@@ -146,12 +155,20 @@ export function handleComponentDrop(dropResult) {
       const { present: layout } = undoableLayout;
       const sourceComponent = layout[source.id];
 
-      if (sourceComponent.type === TABS_TYPE && sourceComponent.children.length === 0) {
-        const parentId = findParentId({ childId: source.id, components: layout });
+      if (
+        sourceComponent.type === TABS_TYPE &&
+        sourceComponent.children.length === 0
+      ) {
+        const parentId = findParentId({
+          childId: source.id,
+          components: layout,
+        });
         dispatch(deleteComponent(source.id, parentId));
       }
     }
 
+    dispatch(setUnsavedChanges(true));
+
     return null;
   };
 }
diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js
index 2262729..d80ec83 100644
--- a/superset/assets/src/dashboard/actions/dashboardState.js
+++ b/superset/assets/src/dashboard/actions/dashboardState.js
@@ -6,6 +6,11 @@ import { chart as initChart } from '../../chart/chartReducer';
 import { fetchDatasourceMetadata } from '../../dashboard/actions/datasources';
 import { applyDefaultFormData } from '../../explore/stores/store';
 
+export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
+export function setUnsavedChanges(hasUnsavedChanges) {
+  return { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges } };
+}
+
 export const ADD_FILTER = 'ADD_FILTER';
 export function addFilter(chart, col, vals, merge = true, refresh = true) {
   return { type: ADD_FILTER, chart, col, vals, merge, refresh };
@@ -39,20 +44,19 @@ export function toggleFaveStar(isStarred) {
 
 export const FETCH_FAVE_STAR = 'FETCH_FAVE_STAR';
 export function fetchFaveStar(id) {
-  return function (dispatch) {
+  return function fetchFaveStarThunk(dispatch) {
     const url = `${FAVESTAR_BASE_URL}/${id}/count`;
-    return $.get(url)
-      .done((data) => {
-        if (data.count > 0) {
-          dispatch(toggleFaveStar(true));
-        }
-      });
+    return $.get(url).done(data => {
+      if (data.count > 0) {
+        dispatch(toggleFaveStar(true));
+      }
+    });
   };
 }
 
 export const SAVE_FAVE_STAR = 'SAVE_FAVE_STAR';
 export function saveFaveStar(id, isStarred) {
-  return function (dispatch) {
+  return function saveFaveStarThunk(dispatch) {
     const urlSuffix = isStarred ? 'unselect' : 'select';
     const url = `${FAVESTAR_BASE_URL}/${id}/${urlSuffix}/`;
     $.get(url);
@@ -82,21 +86,29 @@ export function onSave() {
 
 export function fetchCharts(chartList = [], force = false, interval = 0) {
   return (dispatch, getState) => {
-    const timeout = getState().dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT;
+    const timeout = getState().dashboardInfo.common.conf
+      .SUPERSET_WEBSERVER_TIMEOUT;
     if (!interval) {
-      chartList.forEach(chart => (dispatch(refreshChart(chart, force, timeout))));
+      chartList.forEach(chart => dispatch(refreshChart(chart, force, timeout)));
       return;
     }
 
     const { metadata: meta } = getState().dashboardInfo;
     const refreshTime = Math.max(interval, meta.stagger_time || 5000); // default 5 seconds
     if (typeof meta.stagger_refresh !== 'boolean') {
-      meta.stagger_refresh = meta.stagger_refresh === undefined ?
-        true : meta.stagger_refresh === 'true';
+      meta.stagger_refresh =
+        meta.stagger_refresh === undefined
+          ? true
+          : meta.stagger_refresh === 'true';
     }
-    const delay = meta.stagger_refresh ? refreshTime / (chartList.length - 1) : 0;
+    const delay = meta.stagger_refresh
+      ? refreshTime / (chartList.length - 1)
+      : 0;
     chartList.forEach((chart, i) => {
-      setTimeout(() => dispatch(refreshChart(chart, force, timeout)), delay * i);
+      setTimeout(
+        () => dispatch(refreshChart(chart, force, timeout)),
+        delay * i,
+      );
     });
   };
 }
@@ -116,9 +128,9 @@ export function startPeriodicRender(interval) {
     const { metadata } = getState().dashboardInfo;
     const immune = metadata.timed_refresh_immune_slices || [];
     const refreshAll = () => {
-      const affected =
-        Object.values(getState().charts)
-          .filter(chart => immune.indexOf(chart.id) === -1);
+      const affected = Object.values(getState().charts).filter(
+        chart => immune.indexOf(chart.id) === -1,
+      );
       return dispatch(fetchCharts(affected, true, interval * 0.2));
     };
     const fetchAndRender = () => {
@@ -149,17 +161,15 @@ export function addSliceToDashboard(id) {
       formData: applyDefaultFormData(form_data),
     };
 
-    return Promise
-      .all([
-        dispatch(addChart(newChart, id)),
-        dispatch(fetchDatasourceMetadata(form_data.datasource)),
-      ])
-      .then(() => dispatch(addSlice(selectedSlice)));
+    return Promise.all([
+      dispatch(addChart(newChart, id)),
+      dispatch(fetchDatasourceMetadata(form_data.datasource)),
+    ]).then(() => dispatch(addSlice(selectedSlice)));
   };
 }
 
 export function removeSliceFromDashboard(chart) {
-  return (dispatch) => {
+  return dispatch => {
     dispatch(removeSlice(chart.id));
     dispatch(removeChart(chart.id));
   };
diff --git a/superset/assets/src/dashboard/actions/datasources.js b/superset/assets/src/dashboard/actions/datasources.js
index a00bb17..d97296e 100644
--- a/superset/assets/src/dashboard/actions/datasources.js
+++ b/superset/assets/src/dashboard/actions/datasources.js
@@ -29,7 +29,8 @@ export function fetchDatasourceMetadata(key) {
       type: 'GET',
       url,
       success: data => dispatch(setDatasource(data, key)),
-      error: error => dispatch(fetchDatasourceFailed(error.responseJSON.error, key)),
+      error: error =>
+        dispatch(fetchDatasourceFailed(error.responseJSON.error, key)),
     });
   };
 }
diff --git a/superset/assets/src/dashboard/v2/actions/messageToasts.js b/superset/assets/src/dashboard/actions/messageToasts.js
similarity index 85%
rename from superset/assets/src/dashboard/v2/actions/messageToasts.js
rename to superset/assets/src/dashboard/actions/messageToasts.js
index 2ebc06c..367b36f 100644
--- a/superset/assets/src/dashboard/v2/actions/messageToasts.js
+++ b/superset/assets/src/dashboard/actions/messageToasts.js
@@ -1,7 +1,16 @@
-import { INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST } from '../util/constants';
+import {
+  INFO_TOAST,
+  SUCCESS_TOAST,
+  WARNING_TOAST,
+  DANGER_TOAST,
+} from '../util/constants';
 
 function getToastUuid(type) {
-  return `${Math.random().toString(16).slice(2)}-${type}-${Math.random().toString(16).slice(2)}`;
+  return `${Math.random()
+    .toString(16)
+    .slice(2)}-${type}-${Math.random()
+    .toString(16)
+    .slice(2)}`;
 }
 
 export const ADD_TOAST = 'ADD_TOAST';
diff --git a/superset/assets/src/dashboard/actions/sliceEntities.js b/superset/assets/src/dashboard/actions/sliceEntities.js
index 3a1b1dc..6922753 100644
--- a/superset/assets/src/dashboard/actions/sliceEntities.js
+++ b/superset/assets/src/dashboard/actions/sliceEntities.js
@@ -9,16 +9,15 @@ export function updateSliceName(key, sliceName) {
 
 export function saveSliceName(slice, sliceName) {
   const oldName = slice.slice_name;
-  return (dispatch) => {
+  return dispatch => {
     const sliceParams = {};
     sliceParams.slice_id = slice.slice_id;
     sliceParams.action = 'overwrite';
     sliceParams.slice_name = sliceName;
 
-    const url = slice.slice_url + '&' +
-      Object.keys(sliceParams)
-      .map(key => (key + '=' + sliceParams[key]))
-      .join('&');
+    const url = `${slice.slice_url}&${Object.keys(sliceParams)
+      .map(key => `${key}=${sliceParams[key]}`)
+      .join('&')}`;
     const key = slice.slice_id;
     return $.ajax({
       url,
@@ -54,7 +53,7 @@ export function fetchAllSlicesFailed(error) {
 
 export function fetchAllSlices(userId) {
   return (dispatch, getState) => {
-    const { sliceEntities }  = getState();
+    const { sliceEntities } = getState();
     if (sliceEntities.lastUpdated === 0) {
       dispatch(fetchAllSlicesStarted());
 
@@ -62,9 +61,9 @@ export function fetchAllSlices(userId) {
       return $.ajax({
         url: uri,
         type: 'GET',
-        success: (response) => {
+        success: response => {
           const slices = {};
-          response.result.forEach((slice) => {
+          response.result.forEach(slice => {
             const form_data = JSON.parse(slice.params);
             slices[slice.id] = {
               slice_id: slice.id,
diff --git a/superset/assets/src/dashboard/components/ActionMenuItem.jsx b/superset/assets/src/dashboard/components/ActionMenuItem.jsx
index aaae4df..a0ecb78 100644
--- a/superset/assets/src/dashboard/components/ActionMenuItem.jsx
+++ b/superset/assets/src/dashboard/components/ActionMenuItem.jsx
@@ -7,9 +7,7 @@ import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
 export function MenuItemContent({ faIcon, text, tooltip, children }) {
   return (
     <span>
-      {faIcon &&
-        <i className={`fa fa-${faIcon}`}>&nbsp;</i>
-      }
+      {faIcon && <i className={`fa fa-${faIcon}`}>&nbsp;</i>}
       {text} {''}
       <InfoTooltipWithTrigger
         tooltip={tooltip}
@@ -20,6 +18,7 @@ export function MenuItemContent({ faIcon, text, tooltip, children }) {
     </span>
   );
 }
+
 MenuItemContent.propTypes = {
   faIcon: PropTypes.string,
   text: PropTypes.string,
@@ -27,19 +26,40 @@ MenuItemContent.propTypes = {
   children: PropTypes.node,
 };
 
-export function ActionMenuItem(props) {
+MenuItemContent.defaultProps = {
+  faIcon: '',
+  text: '',
+  tooltip: null,
+  children: null,
+};
+
+export function ActionMenuItem({
+  onClick,
+  href,
+  target,
+  text,
+  tooltip,
+  children,
+  faIcon,
+}) {
   return (
-    <MenuItem
-      onClick={props.onClick}
-      href={props.href}
-      target={props.target}
-    >
-      <MenuItemContent {...props} />
+    <MenuItem onClick={onClick} href={href} target={target}>
+      <MenuItemContent faIcon={faIcon} text={text} tooltip={tooltip}>
+        {children}
+      </MenuItemContent>
     </MenuItem>
   );
 }
+
 ActionMenuItem.propTypes = {
   onClick: PropTypes.func,
   href: PropTypes.string,
   target: PropTypes.string,
+  ...MenuItemContent.propTypes,
+};
+
+ActionMenuItem.defaultProps = {
+  onClick() {},
+  href: null,
+  target: null,
 };
diff --git a/superset/assets/src/dashboard/components/AddSliceCard.jsx b/superset/assets/src/dashboard/components/AddSliceCard.jsx
new file mode 100644
index 0000000..7fd9ba4
--- /dev/null
+++ b/superset/assets/src/dashboard/components/AddSliceCard.jsx
@@ -0,0 +1,59 @@
+import cx from 'classnames';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const propTypes = {
+  datasourceLink: PropTypes.string,
+  innerRef: PropTypes.func,
+  isSelected: PropTypes.bool,
+  lastModified: PropTypes.string.isRequired,
+  sliceName: PropTypes.string.isRequired,
+  style: PropTypes.object,
+  visType: PropTypes.string.isRequired,
+};
+
+const defaultProps = {
+  datasourceLink: '—',
+  innerRef: null,
+  isSelected: false,
+  style: null,
+};
+
+function AddSliceCard({
+  datasourceLink,
+  innerRef,
+  isSelected,
+  lastModified,
+  sliceName,
+  style,
+  visType,
+}) {
+  return (
+    <div ref={innerRef} className="chart-card-container" style={style}>
+      <div className={cx('chart-card', isSelected && 'is-selected')}>
+        <div className="card-title">{sliceName}</div>
+        <div className="card-body">
+          <div className="item">
+            <span>Modified </span>
+            <span>{lastModified}</span>
+          </div>
+          <div className="item">
+            <span>Visualization </span>
+            <span>{visType}</span>
+          </div>
+          <div className="item">
+            <span>Data source </span>
+            <span // eslint-disable-next-line react/no-danger
+              dangerouslySetInnerHTML={{ __html: datasourceLink }}
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+AddSliceCard.propTypes = propTypes;
+AddSliceCard.defaultProps = defaultProps;
+
+export default AddSliceCard;
diff --git a/superset/assets/src/dashboard/v2/components/BuilderComponentPane.jsx b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
similarity index 85%
rename from superset/assets/src/dashboard/v2/components/BuilderComponentPane.jsx
rename to superset/assets/src/dashboard/components/BuilderComponentPane.jsx
index f9a37cc..e5bc74c 100644
--- a/superset/assets/src/dashboard/v2/components/BuilderComponentPane.jsx
+++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
@@ -6,9 +6,7 @@ import NewDivider from './gridComponents/new/NewDivider';
 import NewHeader from './gridComponents/new/NewHeader';
 import NewRow from './gridComponents/new/NewRow';
 import NewTabs from './gridComponents/new/NewTabs';
-import SliceAdderContainer from '../../../dashboard/components/SliceAdderContainer';
-
-import '../stylesheets/builder-sidepane.less';
+import SliceAdderContainer from '../containers/SliceAdder';
 
 class BuilderComponentPane extends React.PureComponent {
   constructor(props) {
@@ -32,9 +30,13 @@ class BuilderComponentPane extends React.PureComponent {
       <div className="dashboard-builder-sidepane">
         <div className="dashboard-builder-sidepane-header">
           Insert components
-          {this.state.showSlices &&
-            <i className="fa fa-times close trigger" onClick={this.closeSlicesPane} role="none" />
-          }
+          {this.state.showSlices && (
+            <i
+              className="fa fa-times close trigger"
+              onClick={this.closeSlicesPane}
+              role="none"
+            />
+          )}
         </div>
 
         <div className="component-layer">
@@ -52,7 +54,6 @@ class BuilderComponentPane extends React.PureComponent {
 
           <NewHeader />
           <NewDivider />
-
           <NewTabs />
           <NewRow />
           <NewColumn />
diff --git a/superset/assets/src/dashboard/components/CodeModal.jsx b/superset/assets/src/dashboard/components/CodeModal.jsx
index 0e84ad1..cc0c9f2 100644
--- a/superset/assets/src/dashboard/components/CodeModal.jsx
+++ b/superset/assets/src/dashboard/components/CodeModal.jsx
@@ -12,13 +12,16 @@ const propTypes = {
 
 const defaultProps = {
   codeCallback: () => {},
+  code: '',
 };
 
 export default class CodeModal extends React.PureComponent {
   constructor(props) {
     super(props);
     this.state = { code: props.code };
+    this.beforeOpen = this.beforeOpen.bind(this);
   }
+
   beforeOpen() {
     let code = this.props.code;
     if (!code && this.props.codeCallback) {
@@ -26,18 +29,17 @@ export default class CodeModal extends React.PureComponent {
     }
     this.setState({ code });
   }
+
   render() {
     return (
       <ModalTrigger
         triggerNode={this.props.triggerNode}
         isButton
-        beforeOpen={this.beforeOpen.bind(this)}
+        beforeOpen={this.beforeOpen}
         modalTitle={t('Active Dashboard Filters')}
         modalBody={
           <div className="CodeModal">
-            <pre>
-              {this.state.code}
-            </pre>
+            <pre>{this.state.code}</pre>
           </div>
         }
       />
diff --git a/superset/assets/src/dashboard/components/Controls.jsx b/superset/assets/src/dashboard/components/Controls.jsx
index 8755e8f..06b4f7f 100644
--- a/superset/assets/src/dashboard/components/Controls.jsx
+++ b/superset/assets/src/dashboard/components/Controls.jsx
@@ -1,3 +1,4 @@
+/* global window */
 import React from 'react';
 import PropTypes from 'prop-types';
 import $ from 'jquery';
@@ -8,6 +9,24 @@ import SaveModal from './SaveModal';
 import { ActionMenuItem, MenuItemContent } from './ActionMenuItem';
 import { t } from '../../locales';
 
+function updateDom(css) {
+  const className = 'CssEditor-css';
+  const head = document.head || document.getElementsByTagName('head')[0];
+  let style = document.querySelector(`.${className}`);
+
+  if (!style) {
+    style = document.createElement('style');
+    style.className = className;
+    style.type = 'text/css';
+    head.appendChild(style);
+  }
+  if (style.styleSheet) {
+    style.styleSheet.cssText = css;
+  } else {
+    style.innerHTML = css;
+  }
+}
+
 const propTypes = {
   dashboardInfo: PropTypes.object.isRequired,
   dashboardTitle: PropTypes.string.isRequired,
@@ -15,13 +34,18 @@ const propTypes = {
   filters: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
   slices: PropTypes.array,
-  onSave: PropTypes.func,
-  onChange: PropTypes.func,
-  forceRefreshAllCharts: PropTypes.func,
-  startPeriodicRender: PropTypes.func,
+  onSave: PropTypes.func.isRequired,
+  onChange: PropTypes.func.isRequired,
+  forceRefreshAllCharts: PropTypes.func.isRequired,
+  startPeriodicRender: PropTypes.func.isRequired,
   editMode: PropTypes.bool,
 };
 
+const defaultProps = {
+  editMode: false,
+  slices: [],
+};
+
 class Controls extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -29,13 +53,12 @@ class Controls extends React.PureComponent {
       css: '',
       cssTemplates: [],
     };
-    this.toggleModal = this.toggleModal.bind(this);
-    this.updateDom = this.updateDom.bind(this);
   }
+
   componentWillMount() {
-    this.updateDom(this.state.css);
+    updateDom(this.state.css);
 
-    $.get('/csstemplateasyncmodelview/api/read', (data) => {
+    $.get('/csstemplateasyncmodelview/api/read', data => {
       const cssTemplates = data.result.map(row => ({
         value: row.template_name,
         css: row.css,
@@ -44,57 +67,48 @@ class Controls extends React.PureComponent {
       this.setState({ cssTemplates });
     });
   }
-  toggleModal(modal) {
-    let currentModal;
-    if (modal !== this.state.currentModal) {
-      currentModal = modal;
-    }
-    this.setState({ currentModal });
-  }
+
   changeCss(css) {
     this.setState({ css }, () => {
-      this.updateDom(css);
+      updateDom(css);
     });
     this.props.onChange();
   }
-  updateDom(css) {
-    const className = 'CssEditor-css';
-    const head = document.head || document.getElementsByTagName('head')[0];
-    let style = document.querySelector('.' + className);
 
-    if (!style) {
-      style = document.createElement('style');
-      style.className = className;
-      style.type = 'text/css';
-      head.appendChild(style);
-    }
-    if (style.styleSheet) {
-      style.styleSheet.cssText = css;
-    } else {
-      style.innerHTML = css;
-    }
-  }
   render() {
-    const { dashboardTitle, layout, filters, expandedSlices,
-      startPeriodicRender, forceRefreshAllCharts, onSave,
-      editMode } = this.props;
+    const {
+      dashboardTitle,
+      layout,
+      filters,
+      expandedSlices,
+      startPeriodicRender,
+      forceRefreshAllCharts,
+      onSave,
+      editMode,
+    } = this.props;
+
     const emailBody = t('Checkout this dashboard: %s', window.location.href);
-    const emailLink = 'mailto:?Subject=Superset%20Dashboard%20'
-      + `${dashboardTitle}&Body=${emailBody}`;
-    let saveText = t('Save as');
-    if (editMode) {
-      saveText = t('Save');
-    }
+    const emailLink =
+      'mailto:?Subject=Superset%20Dashboard%20' +
+      `${dashboardTitle}&Body=${emailBody}`;
+
     return (
       <span>
-        <DropdownButton title="Actions" bsSize="small" id="bg-nested-dropdown" pullRight>
+        <DropdownButton
+          title="Actions"
+          bsSize="small"
+          id="bg-nested-dropdown"
+          pullRight
+        >
           <ActionMenuItem
             text={t('Force Refresh')}
             tooltip={t('Force refresh the whole dashboard')}
             onClick={forceRefreshAllCharts}
           />
           <RefreshIntervalModal
-            onChange={refreshInterval => startPeriodicRender(refreshInterval * 1000)}
+            onChange={refreshInterval =>
+              startPeriodicRender(refreshInterval * 1000)
+            }
             triggerNode={
               <MenuItemContent
                 text={t('Set autorefresh')}
@@ -112,30 +126,39 @@ class Controls extends React.PureComponent {
             css={this.state.css}
             triggerNode={
               <MenuItemContent
-                text={saveText}
+                text={editMode ? t('Save') : t('Save as')}
                 tooltip={t('Save the dashboard')}
               />
             }
+            isMenuItem
           />
-          {editMode &&
+          {editMode && (
             <ActionMenuItem
               text={t('Edit properties')}
               tooltip={t("Edit the dashboards's properties")}
-              onClick={() => { window.location = `/dashboardmodelview/edit/${this.props.dashboardInfo.id}`; }}
+              onClick={() => {
+                window.location = `/dashboardmodelview/edit/${
+                  this.props.dashboardInfo.id
+                }`;
+              }}
             />
-          }
-          {editMode &&
+          )}
+          {editMode && (
             <ActionMenuItem
               text={t('Email')}
               tooltip={t('Email a link to this dashboard')}
-              onClick={() => { window.location = emailLink; }}
+              onClick={() => {
+                window.location = emailLink;
+              }}
             />
-          }
+          )}
         </DropdownButton>
       </span>
     );
   }
 }
+
 Controls.propTypes = propTypes;
+Controls.defaultProps = defaultProps;
 
 export default Controls;
diff --git a/superset/assets/src/dashboard/components/CssEditor.jsx b/superset/assets/src/dashboard/components/CssEditor.jsx
index 5abf5f8..45ef86d 100644
--- a/superset/assets/src/dashboard/components/CssEditor.jsx
+++ b/superset/assets/src/dashboard/components/CssEditor.jsx
@@ -29,15 +29,20 @@ class CssEditor extends React.PureComponent {
       css: props.initialCss,
       cssTemplateOptions: [],
     };
+    this.changeCss = this.changeCss.bind(this);
+    this.changeCssTemplate = this.changeCssTemplate.bind(this);
   }
+
   changeCss(css) {
     this.setState({ css }, () => {
       this.props.onChange(css);
     });
   }
+
   changeCssTemplate(opt) {
     this.changeCss(opt.css);
   }
+
   renderTemplateSelector() {
     if (this.props.templates) {
       return (
@@ -46,13 +51,14 @@ class CssEditor extends React.PureComponent {
           <Select
             options={this.props.templates}
             placeholder={t('Load a CSS template')}
-            onChange={this.changeCssTemplate.bind(this)}
+            onChange={this.changeCssTemplate}
           />
         </div>
       );
     }
     return null;
   }
+
   render() {
     return (
       <ModalTrigger
@@ -70,7 +76,7 @@ class CssEditor extends React.PureComponent {
                   theme="github"
                   minLines={8}
                   maxLines={30}
-                  onChange={this.changeCss.bind(this)}
+                  onChange={this.changeCss}
                   height="200px"
                   width="100%"
                   editorProps={{ $blockScrolling: true }}
@@ -85,6 +91,7 @@ class CssEditor extends React.PureComponent {
     );
   }
 }
+
 CssEditor.propTypes = propTypes;
 CssEditor.defaultProps = defaultProps;
 
diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx b/superset/assets/src/dashboard/components/Dashboard.jsx
index 939476c..2d85ebf 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -1,26 +1,36 @@
+/* global window */
 import React from 'react';
 import PropTypes from 'prop-types';
 
 import AlertsWrapper from '../../components/AlertsWrapper';
-import GridLayout from './GridLayout';
+import getChartIdsFromLayout from '../util/getChartIdsFromLayout';
+import DashboardBuilder from '../containers/DashboardBuilder';
 import {
   chartPropShape,
   slicePropShape,
   dashboardInfoPropShape,
   dashboardStatePropShape,
-} from '../v2/util/propShapes';
-import { exportChart } from '../../explore/exploreUtils';
+} from '../util/propShapes';
 import { areObjectsEqual } from '../../reduxUtils';
-import { getChartIdsFromLayout } from '../util/dashboardHelper';
-import { Logger, ActionLog, LOG_ACTIONS_PAGE_LOAD,
-  LOG_ACTIONS_LOAD_EVENT, LOG_ACTIONS_RENDER_EVENT } from '../../logger';
+import getFormDataWithExtraFilters from '../util/charts/getFormDataWithExtraFilters';
+import {
+  Logger,
+  ActionLog,
+  LOG_ACTIONS_PAGE_LOAD,
+  LOG_ACTIONS_LOAD_EVENT,
+  LOG_ACTIONS_RENDER_EVENT,
+} from '../../logger';
 import { t } from '../../locales';
 
-import '../../../stylesheets/dashboard.less';
-import '../v2/stylesheets/index.less';
+import '../stylesheets/index.less';
 
 const propTypes = {
-  actions: PropTypes.object.isRequired,
+  actions: PropTypes.shape({
+    addSliceToDashboard: PropTypes.func.isRequired,
+    onChange: PropTypes.func.isRequired,
+    removeSliceFromDashboard: PropTypes.func.isRequired,
+    runQuery: PropTypes.func.isRequired,
+  }).isRequired,
   dashboardInfo: dashboardInfoPropShape.isRequired,
   dashboardState: dashboardStatePropShape.isRequired,
   charts: PropTypes.objectOf(chartPropShape).isRequired,
@@ -40,6 +50,20 @@ const defaultProps = {
 };
 
 class Dashboard extends React.PureComponent {
+  static onBeforeUnload(hasChanged) {
+    if (hasChanged) {
+      window.addEventListener('beforeunload', Dashboard.unload);
+    } else {
+      window.removeEventListener('beforeunload', Dashboard.unload);
+    }
+  }
+
+  static unload() {
+    const message = t('You have unsaved changes.');
+    window.event.returnValue = message; // Gecko + IE
+    return message; // Gecko + Webkit, Safari, Chrome etc.
+  }
+
   constructor(props) {
     super(props);
 
@@ -52,31 +76,15 @@ class Dashboard extends React.PureComponent {
       eventNames: [LOG_ACTIONS_LOAD_EVENT, LOG_ACTIONS_RENDER_EVENT],
     });
     Logger.start(this.loadingLog);
-
-    this.rerenderCharts = this.rerenderCharts.bind(this);
-    this.getFilters = this.getFilters.bind(this);
-    this.refreshExcept = this.refreshExcept.bind(this);
-    this.getFormDataExtra = this.getFormDataExtra.bind(this);
-    this.exploreChart = this.exploreChart.bind(this);
-    this.exportCSV = this.exportCSV.bind(this);
-
-    this.props.actions.saveSliceName = this.props.actions.saveSliceName.bind(this);
-    this.props.actions.removeSliceFromDashboard =
-      this.props.actions.removeSliceFromDashboard.bind(this);
-    this.props.actions.toggleExpandSlice =
-      this.props.actions.toggleExpandSlice.bind(this);
-    this.props.actions.addFilter = this.props.actions.addFilter.bind(this);
-    this.props.actions.removeFilter = this.props.actions.removeFilter.bind(this);
-  }
-
-  componentDidMount() {
-    window.addEventListener('resize', this.rerenderCharts);
   }
 
   componentWillReceiveProps(nextProps) {
-    if (this.firstLoad &&
-      Object.values(nextProps.charts)
-        .every(chart => (['rendered', 'failed', 'stopped'].indexOf(chart.chartStatus) > -1))
+    if (
+      this.firstLoad &&
+      Object.values(nextProps.charts).every(
+        chart =>
+          ['rendered', 'failed', 'stopped'].indexOf(chart.chartStatus) > -1,
+      )
     ) {
       Logger.end(this.loadingLog);
       this.firstLoad = false;
@@ -86,13 +94,19 @@ class Dashboard extends React.PureComponent {
     const nextChartIds = getChartIdsFromLayout(nextProps.layout);
     if (currentChartIds.length < nextChartIds.length) {
       // adding new chart
-      const newChartId = nextChartIds.find(key => (currentChartIds.indexOf(key) === -1));
+      const newChartId = nextChartIds.find(
+        key => currentChartIds.indexOf(key) === -1,
+      );
       this.props.actions.addSliceToDashboard(newChartId);
       this.props.actions.onChange();
     } else if (currentChartIds.length > nextChartIds.length) {
       // remove chart
-      const removedChartId = currentChartIds.find(key => (nextChartIds.indexOf(key) === -1));
-      this.props.actions.removeSliceFromDashboard(this.props.charts[removedChartId]);
+      const removedChartId = currentChartIds.find(
+        key => nextChartIds.indexOf(key) === -1,
+      );
+      this.props.actions.removeSliceFromDashboard(
+        this.props.charts[removedChartId],
+      );
       this.props.actions.onChange();
     }
   }
@@ -101,11 +115,15 @@ class Dashboard extends React.PureComponent {
     const { refresh, filters, hasUnsavedChanges } = this.props.dashboardState;
     if (refresh) {
       let changedFilterKey;
-      const prevFiltersKeySet = new Set(Object.keys(prevProps.dashboardState.filters));
-      Object.keys(filters).some((key) => {
+      const prevFiltersKeySet = new Set(
+        Object.keys(prevProps.dashboardState.filters),
+      );
+      Object.keys(filters).some(key => {
         prevFiltersKeySet.delete(key);
-        if (prevProps.dashboardState.filters[key] === undefined ||
-          !areObjectsEqual(prevProps.dashboardState.filters[key], filters[key])) {
+        if (
+          prevProps.dashboardState.filters[key] === undefined ||
+          !areObjectsEqual(prevProps.dashboardState.filters[key], filters[key])
+        ) {
           changedFilterKey = key;
           return true;
         }
@@ -118,21 +136,9 @@ class Dashboard extends React.PureComponent {
     }
 
     if (hasUnsavedChanges) {
-      this.onBeforeUnload(true);
-    } else {
-      this.onBeforeUnload(false);
-    }
-  }
-
-  componentWillUnmount() {
-    window.removeEventListener('resize', this.rerenderCharts);
-  }
-
-  onBeforeUnload(hasChanged) {
-    if (hasChanged) {
-      window.addEventListener('beforeunload', this.unload);
+      Dashboard.onBeforeUnload(true);
     } else {
-      window.removeEventListener('beforeunload', this.unload);
+      Dashboard.onBeforeUnload(false);
     }
   }
 
@@ -141,131 +147,37 @@ class Dashboard extends React.PureComponent {
     return Object.values(this.props.charts);
   }
 
-  getFormDataExtra(chart) {
-    const extraFilters = this.effectiveExtraFilters(chart.id);
-    const formDataExtra = {
-      ...chart.formData,
-      extra_filters: extraFilters,
-    };
-    return formDataExtra;
-  }
-
-  getFilters(sliceId) {
-    return this.props.dashboardState.filters[sliceId];
-  }
-
-  unload() {
-    const message = t('You have unsaved changes.');
-    window.event.returnValue = message; // Gecko + IE
-    return message; // Gecko + Webkit, Safari, Chrome etc.
-  }
-
-  effectiveExtraFilters(sliceId) {
-    const metadata = this.props.dashboardInfo.metadata;
-    const filters = this.props.dashboardState.filters;
-    const f = [];
-    const immuneSlices = metadata.filter_immune_slices || [];
-    if (sliceId && immuneSlices.includes(sliceId)) {
-      // The slice is immune to dashboard filters
-      return f;
-    }
-
-    // Building a list of fields the slice is immune to filters on
-    let immuneToFields = [];
-    if (
-      sliceId &&
-      metadata.filter_immune_slice_fields &&
-      metadata.filter_immune_slice_fields[sliceId]) {
-      immuneToFields = metadata.filter_immune_slice_fields[sliceId];
-    }
-    for (const filteringSliceId in filters) {
-      if (filteringSliceId === sliceId.toString()) {
-        // Filters applied by the slice don't apply to itself
-        continue;
-      }
-      for (const field in filters[filteringSliceId]) {
-        if (!immuneToFields.includes(field)) {
-          f.push({
-            col: field,
-            op: 'in',
-            val: filters[filteringSliceId][field],
-          });
-        }
-      }
-    }
-    return f;
-  }
-
   refreshExcept(filterKey) {
     const immune = this.props.dashboardInfo.metadata.filter_immune_slices || [];
     let charts = this.getAllCharts();
     if (filterKey) {
       charts = charts.filter(
-        chart => (String(chart.id) !== filterKey && immune.indexOf(chart.id) === -1),
+        chart =>
+          String(chart.id) !== filterKey && immune.indexOf(chart.id) === -1,
       );
     }
-    charts.forEach((chart) => {
-      const updatedFormData = this.getFormDataExtra(chart);
-      this.props.actions.runQuery(updatedFormData, false, this.props.timeout, chart.id);
-    });
-  }
-
-  exploreChart(chartId) {
-    const chart = this.props.charts[chartId];
-    const formData = this.getFormDataExtra(chart);
-    exportChart(formData);
-  }
-
-  exportCSV(chartId) {
-    const chart = this.props.charts[chartId];
-    const formData = this.getFormDataExtra(chart);
-    exportChart(formData, 'csv');
-  }
+    charts.forEach(chart => {
+      const updatedFormData = getFormDataWithExtraFilters({
+        chart,
+        dashboardMetadata: this.props.dashboardInfo.metadata,
+        filters: this.props.dashboardState.filters,
+        sliceId: chart.id,
+      });
 
-  // re-render chart without fetch
-  rerenderCharts() {
-    this.getAllCharts().forEach((chart) => {
-      setTimeout(() => {
-        this.props.actions.renderTriggered(new Date().getTime(), chart.id);
-      }, 50);
+      this.props.actions.runQuery(
+        updatedFormData,
+        false,
+        this.props.timeout,
+        chart.id,
+      );
     });
   }
 
   render() {
-    const {
-      expandedSlices = {}, filters, sliceIds,
-      editMode, showBuilderPane,
-    } = this.props.dashboardState;
-
     return (
-      <div id="dashboard-container">
-        <div>
-          <AlertsWrapper initMessages={this.props.initMessages} />
-        </div>
-        <GridLayout
-          dashboardInfo={this.props.dashboardInfo}
-          layout={this.props.layout}
-          datasources={this.props.datasources}
-          slices={this.props.slices}
-          sliceIds={sliceIds}
-          expandedSlices={expandedSlices}
-          filters={filters}
-          charts={this.props.charts}
-          timeout={this.props.timeout}
-          onChange={this.onChange}
-          rerenderCharts={this.rerenderCharts}
-          getFormDataExtra={this.getFormDataExtra}
-          exploreChart={this.exploreChart}
-          exportCSV={this.exportCSV}
-          refreshChart={this.props.actions.refreshChart}
-          saveSliceName={this.props.actions.saveSliceName}
-          toggleExpandSlice={this.props.actions.toggleExpandSlice}
-          addFilter={this.props.actions.addFilter}
-          getFilters={this.getFilters}
-          removeFilter={this.props.actions.removeFilter}
-          editMode={editMode}
-          showBuilderPane={showBuilderPane}
-        />
+      <div>
+        <AlertsWrapper initMessages={this.props.initMessages} />
+        <DashboardBuilder />
       </div>
     );
   }
diff --git a/superset/assets/src/dashboard/v2/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
similarity index 81%
rename from superset/assets/src/dashboard/v2/components/DashboardBuilder.jsx
rename to superset/assets/src/dashboard/components/DashboardBuilder.jsx
index f3f5867..79eb35d 100644
--- a/superset/assets/src/dashboard/v2/components/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
@@ -20,8 +20,6 @@ import {
 } from '../util/constants';
 
 const propTypes = {
-  cells: PropTypes.object.isRequired,
-
   // redux
   dashboardLayout: PropTypes.object.isRequired,
   deleteTopLevelTabs: PropTypes.func.isRequired,
@@ -37,8 +35,10 @@ const defaultProps = {
 class DashboardBuilder extends React.Component {
   static shouldFocusTabs(event, container) {
     // don't focus the tabs when we click on a tab
-    return event.target.tagName === 'UL' || (
-      /icon-button/.test(event.target.className) && container.contains(event.target)
+    return (
+      event.target.tagName === 'UL' ||
+      (/icon-button/.test(event.target.className) &&
+        container.contains(event.target))
     );
   }
 
@@ -56,19 +56,27 @@ class DashboardBuilder extends React.Component {
 
   render() {
     const { tabIndex } = this.state;
-    const { handleComponentDrop, dashboardLayout, deleteTopLevelTabs, editMode } = this.props;
+    const {
+      handleComponentDrop,
+      dashboardLayout,
+      deleteTopLevelTabs,
+      editMode,
+    } = this.props;
     const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
     const rootChildId = dashboardRoot.children[0];
-    const topLevelTabs = rootChildId !== DASHBOARD_GRID_ID && dashboardLayout[rootChildId];
+    const topLevelTabs =
+      rootChildId !== DASHBOARD_GRID_ID && dashboardLayout[rootChildId];
 
     const gridComponentId = topLevelTabs
-      ? topLevelTabs.children[Math.min(topLevelTabs.children.length - 1, tabIndex)]
+      ? topLevelTabs.children[
+          Math.min(topLevelTabs.children.length - 1, tabIndex)
+        ]
       : DASHBOARD_GRID_ID;
 
     const gridComponent = dashboardLayout[gridComponentId];
 
     return (
-      <div className={cx('dashboard-v2', editMode && 'dashboard-v2--editing')}>
+      <div className={cx('dashboard', editMode && 'dashboard--editing')}>
         {topLevelTabs || !editMode ? ( // you cannot drop on/displace tabs if they already exist
           <DashboardHeader />
         ) : (
@@ -87,9 +95,10 @@ class DashboardBuilder extends React.Component {
                 {dropIndicatorProps && <div {...dropIndicatorProps} />}
               </div>
             )}
-          </DragDroppable>)}
+          </DragDroppable>
+        )}
 
-        {topLevelTabs &&
+        {topLevelTabs && (
           <WithPopoverMenu
             shouldFocus={DashboardBuilder.shouldFocusTabs}
             menuItems={[
@@ -108,19 +117,17 @@ class DashboardBuilder extends React.Component {
               index={0}
               renderTabContent={false}
               onChangeTab={this.handleChangeTab}
-              cells={this.props.cells}
             />
-          </WithPopoverMenu>}
+          </WithPopoverMenu>
+        )}
 
         <div className="dashboard-content">
           <DashboardGrid
             gridComponent={gridComponent}
             depth={DASHBOARD_ROOT_DEPTH + 1}
-            cells={this.props.cells}
           />
-          {this.props.editMode && this.props.showBuilderPane &&
-            <BuilderComponentPane />
-          }
+          {this.props.editMode &&
+            this.props.showBuilderPane && <BuilderComponentPane />}
         </div>
         <ToastPresenter />
       </div>
diff --git a/superset/assets/src/dashboard/components/DashboardContainer.jsx b/superset/assets/src/dashboard/components/DashboardContainer.jsx
deleted file mode 100644
index 31fe035..0000000
--- a/superset/assets/src/dashboard/components/DashboardContainer.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
-
-import {
-  toggleExpandSlice,
-  addFilter,
-  removeFilter,
-  addSliceToDashboard,
-  removeSliceFromDashboard,
-  onChange,
-} from '../actions/dashboardState';
-import { saveSliceName } from '../actions/sliceEntities';
-import { refreshChart, runQuery, renderTriggered } from '../../chart/chartAction';
-import Dashboard from './Dashboard';
-
-function mapStateToProps({ datasources, sliceEntities, charts,
-                           dashboardInfo, dashboardState,
-                           dashboardLayout, impressionId }) {
-  return {
-    initMessages: dashboardInfo.common.flash_messages,
-    timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
-    userId: dashboardInfo.userId,
-    dashboardInfo,
-    dashboardState,
-    charts,
-    datasources,
-    slices: sliceEntities.slices,
-    layout: dashboardLayout.present,
-    impressionId,
-  };
-}
-
-function mapDispatchToProps(dispatch) {
-  const actions = {
-    refreshChart,
-    runQuery,
-    renderTriggered,
-    saveSliceName,
-    toggleExpandSlice,
-    addFilter,
-    removeFilter,
-    addSliceToDashboard,
-    removeSliceFromDashboard,
-    onChange,
-  };
-  return {
-    actions: bindActionCreators(actions, dispatch),
-  };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
diff --git a/superset/assets/src/dashboard/v2/components/DashboardGrid.jsx b/superset/assets/src/dashboard/components/DashboardGrid.jsx
similarity index 57%
rename from superset/assets/src/dashboard/v2/components/DashboardGrid.jsx
rename to superset/assets/src/dashboard/components/DashboardGrid.jsx
index 2aa82af..3e6fc0c 100644
--- a/superset/assets/src/dashboard/v2/components/DashboardGrid.jsx
+++ b/superset/assets/src/dashboard/components/DashboardGrid.jsx
@@ -1,15 +1,14 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+// ParentSize uses resize observer so the dashboard will update size
+// when its container size changes, due to e.g., builder side panel opening
 import ParentSize from '@vx/responsive/build/components/ParentSize';
 
 import { componentShape } from '../util/propShapes';
 import DashboardComponent from '../containers/DashboardComponent';
 import DragDroppable from './dnd/DragDroppable';
 
-import {
-  GRID_GUTTER_SIZE,
-  GRID_COLUMN_COUNT,
-} from '../util/constants';
+import { GRID_GUTTER_SIZE, GRID_COLUMN_COUNT } from '../util/constants';
 
 const propTypes = {
   depth: PropTypes.number.isRequired,
@@ -19,8 +18,7 @@ const propTypes = {
   resizeComponent: PropTypes.func.isRequired,
 };
 
-const defaultProps = {
-};
+const defaultProps = {};
 
 class DashboardGrid extends React.PureComponent {
   constructor(props) {
@@ -34,15 +32,24 @@ class DashboardGrid extends React.PureComponent {
     this.handleResize = this.handleResize.bind(this);
     this.handleResizeStop = this.handleResizeStop.bind(this);
     this.getRowGuidePosition = this.getRowGuidePosition.bind(this);
+    this.setGridRef = this.setGridRef.bind(this);
   }
 
   getRowGuidePosition(resizeRef) {
     if (resizeRef && this.grid) {
-      return resizeRef.getBoundingClientRect().bottom - this.grid.getBoundingClientRect().top - 1;
+      return (
+        resizeRef.getBoundingClientRect().bottom -
+        this.grid.getBoundingClientRect().top -
+        1
+      );
     }
     return null;
   }
 
+  setGridRef(ref) {
+    this.grid = ref;
+  }
+
   handleResizeStart({ ref, direction }) {
     let rowGuideTop = null;
     if (direction === 'bottom' || direction === 'bottomRight') {
@@ -71,19 +78,36 @@ class DashboardGrid extends React.PureComponent {
   }
 
   render() {
-    const { gridComponent, handleComponentDrop, depth, editMode, cells } = this.props;
+    const { gridComponent, handleComponentDrop, depth, editMode } = this.props;
     const { isResizing, rowGuideTop } = this.state;
 
     return (
-      <div className="grid-container" ref={(ref) => { this.grid = ref; }}>
+      <div className="grid-container" ref={this.setGridRef}>
         <ParentSize>
           {({ width }) => {
-            // account for (COLUMN_COUNT - 1) gutters
-            const columnPlusGutterWidth = (width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT;
+            const columnPlusGutterWidth =
+              (width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT;
             const columnWidth = columnPlusGutterWidth - GRID_GUTTER_SIZE;
-
             return width < 50 ? null : (
               <div className="grid-content">
+                {editMode && (
+                  <DragDroppable
+                    component={gridComponent}
+                    depth={depth}
+                    parentComponent={null}
+                    index={0}
+                    orientation="column"
+                    onDrop={handleComponentDrop}
+                    editMode
+                  >
+                    {({ dropIndicatorProps }) =>
+                      dropIndicatorProps && (
+                        <div className="drop-indicator drop-indicator--bottom" />
+                      )
+                    }
+                  </DragDroppable>
+                )}
+
                 {gridComponent.children.map((id, index) => (
                   <DashboardComponent
                     key={id}
@@ -93,7 +117,6 @@ class DashboardGrid extends React.PureComponent {
                     index={index}
                     availableColumnCount={GRID_COLUMN_COUNT}
                     columnWidth={columnWidth}
-                    cells={cells}
                     onResizeStart={this.handleResizeStart}
                     onResize={this.handleResize}
                     onResizeStop={this.handleResizeStop}
@@ -101,7 +124,7 @@ class DashboardGrid extends React.PureComponent {
                 ))}
 
                 {/* render an empty drop target */}
-                {editMode &&
+                {editMode && (
                   <DragDroppable
                     component={gridComponent}
                     depth={depth}
@@ -112,29 +135,38 @@ class DashboardGrid extends React.PureComponent {
                     className="empty-grid-droptarget"
                     editMode
                   >
-                    {({ dropIndicatorProps }) => dropIndicatorProps &&
-                      <div className="drop-indicator drop-indicator--top" />}
-                  </DragDroppable>}
-
-                {isResizing && Array(GRID_COLUMN_COUNT).fill(null).map((_, i) => (
-                  <div
-                    key={`grid-column-${i}`}
-                    className="grid-column-guide"
-                    style={{
-                      left: (i * GRID_GUTTER_SIZE) + (i * columnWidth),
-                      width: columnWidth,
-                    }}
-                  />
-                ))}
-
-                {isResizing && rowGuideTop &&
-                  <div
-                    className="grid-row-guide"
-                    style={{
-                      top: rowGuideTop,
-                      width,
-                    }}
-                  />}
+                    {({ dropIndicatorProps }) =>
+                      dropIndicatorProps && (
+                        <div className="drop-indicator drop-indicator--top" />
+                      )
+                    }
+                  </DragDroppable>
+                )}
+
+                {isResizing &&
+                  Array(GRID_COLUMN_COUNT)
+                    .fill(null)
+                    .map((_, i) => (
+                      <div
+                        key={`grid-column-${i}`}
+                        className="grid-column-guide"
+                        style={{
+                          left: i * GRID_GUTTER_SIZE + i * columnWidth,
+                          width: columnWidth,
+                        }}
+                      />
+                    ))}
+
+                {isResizing &&
+                  rowGuideTop && (
+                    <div
+                      className="grid-row-guide"
+                      style={{
+                        top: rowGuideTop,
+                        width,
+                      }}
+                    />
+                  )}
               </div>
             );
           }}
diff --git a/superset/assets/src/dashboard/v2/components/DeleteComponentButton.jsx b/superset/assets/src/dashboard/components/DeleteComponentButton.jsx
similarity index 78%
rename from superset/assets/src/dashboard/v2/components/DeleteComponentButton.jsx
rename to superset/assets/src/dashboard/components/DeleteComponentButton.jsx
index 18efff4..8470947 100644
--- a/superset/assets/src/dashboard/v2/components/DeleteComponentButton.jsx
+++ b/superset/assets/src/dashboard/components/DeleteComponentButton.jsx
@@ -7,15 +7,12 @@ const propTypes = {
   onDelete: PropTypes.func.isRequired,
 };
 
-const defaultProps = {
-};
+const defaultProps = {};
 
 export default class DeleteComponentButton extends React.PureComponent {
   render() {
     const { onDelete } = this.props;
-    return (
-      <IconButton onClick={onDelete} className="fa fa-trash" />
-    );
+    return <IconButton onClick={onDelete} className="fa fa-trash" />;
   }
 }
 
diff --git a/superset/assets/src/dashboard/components/GridCell.jsx b/superset/assets/src/dashboard/components/GridCell.jsx
deleted file mode 100644
index 3273272..0000000
--- a/superset/assets/src/dashboard/components/GridCell.jsx
+++ /dev/null
@@ -1,158 +0,0 @@
-/* eslint-disable react/no-danger */
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import SliceHeader from './SliceHeader';
-import ChartContainer from '../../chart/ChartContainer';
-import { chartPropShape, slicePropShape } from '../v2/util/propShapes';
-
-const propTypes = {
-  timeout: PropTypes.number,
-  datasource: PropTypes.object,
-  isLoading: PropTypes.bool,
-  isCached: PropTypes.bool,
-  cachedDttm: PropTypes.string,
-  isExpanded: PropTypes.bool,
-  widgetHeight: PropTypes.number,
-  widgetWidth: PropTypes.number,
-  slice: slicePropShape.isRequired,
-  chart: chartPropShape.isRequired,
-  formData: PropTypes.object,
-  filters: PropTypes.object,
-  refreshChart: PropTypes.func,
-  updateSliceName: PropTypes.func,
-  toggleExpandSlice: PropTypes.func,
-  exploreChart: PropTypes.func,
-  exportCSV: PropTypes.func,
-  addFilter: PropTypes.func,
-  getFilters: PropTypes.func,
-  removeFilter: PropTypes.func,
-  editMode: PropTypes.bool,
-  annotationQuery: PropTypes.object,
-};
-
-const defaultProps = {
-  refreshChart: () => ({}),
-  updateSliceName: () => ({}),
-  toggleExpandSlice: () => ({}),
-  exploreChart: () => ({}),
-  exportCSV: () => ({}),
-  addFilter: () => ({}),
-  getFilters: () => ({}),
-  removeFilter: () => ({}),
-  editMode: false,
-};
-
-class GridCell extends React.PureComponent {
-  constructor(props) {
-    super(props);
-
-    const sliceId = this.props.slice.slice_id;
-    this.forceRefresh = this.forceRefresh.bind(this);
-    this.addFilter = this.props.addFilter.bind(this, this.props.chart);
-    this.getFilters = this.props.getFilters.bind(this, sliceId);
-    this.removeFilter = this.props.removeFilter.bind(this, sliceId);
-  }
-
-  getDescriptionId(slice) {
-    return 'description_' + slice.slice_id;
-  }
-
-  getHeaderId(slice) {
-    return 'header_' + slice.slice_id;
-  }
-
-  width() {
-    return this.props.widgetWidth - 32;
-  }
-
-  height(slice) {
-    const widgetHeight = this.props.widgetHeight;
-    const headerHeight = this.headerHeight(slice);
-    const descriptionId = this.getDescriptionId(slice);
-    let descriptionHeight = 0;
-    if (this.props.isExpanded && this.refs[descriptionId]) {
-      descriptionHeight = this.refs[descriptionId].offsetHeight + 10;
-    }
-
-    return widgetHeight - headerHeight - descriptionHeight - 32;
-  }
-
-  headerHeight(slice) {
-    const headerId = this.getHeaderId(slice);
-    return this.refs[headerId] ? this.refs[headerId].offsetHeight : 30;
-  }
-
-  forceRefresh() {
-    return this.props.refreshChart(this.props.chart, true, this.props.timeout);
-  }
-
-  render() {
-    const {
-      isExpanded, isLoading, isCached, cachedDttm,
-      updateSliceName, toggleExpandSlice,
-      chart, slice, datasource, formData, timeout, annotationQuery,
-      exploreChart, exportCSV, editMode,
-    } = this.props;
-
-    return (
-      <div
-        className={isLoading ? 'slice-cell-highlight' : 'slice-cell'}
-        id={`${slice.slice_id}-cell`}
-      >
-        <div ref={this.getHeaderId(slice)}>
-          <SliceHeader
-            slice={slice}
-            isExpanded={isExpanded}
-            isCached={isCached}
-            cachedDttm={cachedDttm}
-            updateSliceName={updateSliceName}
-            toggleExpandSlice={toggleExpandSlice}
-            forceRefresh={this.forceRefresh}
-            editMode={editMode}
-            annotationQuery={annotationQuery}
-            exploreChart={exploreChart}
-            exportCSV={exportCSV}
-          />
-        </div>
-        {
-        /* This usage of dangerouslySetInnerHTML is safe since it is being used to render
-           markdown that is sanitized with bleach. See:
-             https://github.com/apache/incubator-superset/pull/4390
-           and
-             https://github.com/apache/incubator-superset/commit/b6fcc22d5a2cb7a5e92599ed5795a0169385a825 */}
-        <div
-          className="slice_description bs-callout bs-callout-default"
-          style={isExpanded ? {} : { display: 'none' }}
-          ref={this.getDescriptionId(slice)}
-          dangerouslySetInnerHTML={{ __html: slice.description_markeddown }}
-        />
-        <div
-          className="chart-container"
-          style={{ width: this.width(), height: this.height(slice) }}
-        >
-          <input type="hidden" value="false" />
-          <ChartContainer
-            containerId={`slice-container-${slice.slice_id}`}
-            chartId={chart.id}
-            datasource={datasource}
-            formData={formData}
-            headerHeight={this.headerHeight(slice)}
-            height={this.height(slice)}
-            width={this.width()}
-            timeout={timeout}
-            vizType={slice.viz_type}
-            addFilter={this.addFilter}
-            getFilters={this.getFilters}
-            removeFilter={this.removeFilter}
-          />
-        </div>
-      </div>
-    );
-  }
-}
-
-GridCell.propTypes = propTypes;
-GridCell.defaultProps = defaultProps;
-
-export default GridCell;
diff --git a/superset/assets/src/dashboard/components/GridLayout.jsx b/superset/assets/src/dashboard/components/GridLayout.jsx
deleted file mode 100644
index fd561e2..0000000
--- a/superset/assets/src/dashboard/components/GridLayout.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import cx from 'classnames';
-
-import GridCell from './GridCell';
-import { slicePropShape, chartPropShape } from '../v2/util/propShapes';
-import DashboardBuilder from '../v2/containers/DashboardBuilder';
-
-const propTypes = {
-  dashboardInfo: PropTypes.shape().isRequired,
-  layout: PropTypes.object.isRequired,
-  datasources: PropTypes.object,
-  charts: PropTypes.objectOf(chartPropShape).isRequired,
-  slices: PropTypes.objectOf(slicePropShape).isRequired,
-  expandedSlices: PropTypes.object.isRequired,
-  sliceIds: PropTypes.object.isRequired,
-  filters: PropTypes.object,
-  timeout: PropTypes.number,
-  onChange: PropTypes.func,
-  rerenderCharts: PropTypes.func,
-  getFormDataExtra: PropTypes.func,
-  exploreChart: PropTypes.func,
-  exportCSV: PropTypes.func,
-  refreshChart: PropTypes.func,
-  saveSliceName: PropTypes.func,
-  toggleExpandSlice: PropTypes.func,
-  addFilter: PropTypes.func,
-  getFilters: PropTypes.func,
-  removeFilter: PropTypes.func,
-  editMode: PropTypes.bool.isRequired,
-  showBuilderPane: PropTypes.bool.isRequired,
-};
-
-const defaultProps = {
-  expandedSlices: {},
-  filters: {},
-  timeout: 60,
-  onChange: () => ({}),
-  getFormDataExtra: () => ({}),
-  exploreChart: () => ({}),
-  exportCSV: () => ({}),
-  refreshChart: () => ({}),
-  saveSliceName: () => ({}),
-  toggleExpandSlice: () => ({}),
-  addFilter: () => ({}),
-  getFilters: () => ({}),
-  removeFilter: () => ({}),
-};
-
-class GridLayout extends React.Component {
-  constructor(props) {
-    super(props);
-
-    this.updateSliceName = this.props.dashboardInfo.dash_edit_perm ?
-      this.updateSliceName.bind(this) : null;
-  }
-
-  componentDidUpdate(prevProps) {
-    if (prevProps.editMode !== this.props.editMode ||
-      prevProps.showBuilderPane !== this.props.showBuilderPane) {
-      this.props.rerenderCharts();
-    }
-  }
-
-  getWidgetId(sliceId) {
-    return 'widget_' + sliceId;
-  }
-
-  getWidgetHeight(sliceId) {
-    const widgetId = this.getWidgetId(sliceId);
-    if (!widgetId || !this.refs[widgetId]) {
-      return 400;
-    }
-    return this.refs[widgetId].parentNode.clientHeight;
-  }
-
-  getWidgetWidth(sliceId) {
-    const widgetId = this.getWidgetId(sliceId);
-    if (!widgetId || !this.refs[widgetId]) {
-      return 400;
-    }
-    return this.refs[widgetId].parentNode.clientWidth;
-  }
-
-  updateSliceName(sliceId, sliceName) {
-    const key = sliceId;
-    const currentSlice = this.props.slices[key];
-    if (!currentSlice || currentSlice.slice_name === sliceName) {
-      return;
-    }
-
-    this.props.saveSliceName(currentSlice, sliceName);
-  }
-
-  isExpanded(sliceId) {
-    return this.props.expandedSlices[sliceId];
-  }
-
-  render() {
-    const cells = {};
-    this.props.sliceIds.forEach((sliceId) => {
-      const key = sliceId;
-      const currentChart = this.props.charts[key];
-      const currentSlice = this.props.slices[key];
-      if (currentChart) {
-        const currentDatasource = this.props.datasources[currentChart.form_data.datasource];
-        const queryResponse = currentChart.queryResponse || {};
-        cells[key] = (
-          <div
-            id={key}
-            key={sliceId}
-            className={cx('widget', `${currentSlice.viz_type}`, { 'is-edit': this.props.editMode })}
-            ref={this.getWidgetId(sliceId)}
-          >
-            <GridCell
-              slice={currentSlice}
-              chart={currentChart}
-              datasource={currentDatasource}
-              filters={this.props.filters}
-              formData={this.props.getFormDataExtra(currentChart)}
-              timeout={this.props.timeout}
-              widgetHeight={this.getWidgetHeight(sliceId)}
-              widgetWidth={this.getWidgetWidth(sliceId)}
-              exploreChart={this.props.exploreChart}
-              exportCSV={this.props.exportCSV}
-              isExpanded={!!this.isExpanded(sliceId)}
-              isLoading={currentChart.chartStatus === 'loading'}
-              isCached={queryResponse.is_cached}
-              cachedDttm={queryResponse.cached_dttm}
-              toggleExpandSlice={this.props.toggleExpandSlice}
-              refreshChart={this.props.refreshChart}
-              updateSliceName={this.updateSliceName}
-              addFilter={this.props.addFilter}
-              getFilters={this.props.getFilters}
-              removeFilter={this.props.removeFilter}
-              editMode={this.props.editMode}
-              annotationQuery={currentChart.annotationQuery}
-              annotationError={currentChart.annotationError}
-            />
-          </div>
-        );
-      }
-    });
-
-    return (
-      <DashboardBuilder
-        cells={cells}
-      />
-    );
-  }
-}
-
-GridLayout.propTypes = propTypes;
-GridLayout.defaultProps = defaultProps;
-
-export default GridLayout;
diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx
index f533506..242102e 100644
--- a/superset/assets/src/dashboard/components/Header.jsx
+++ b/superset/assets/src/dashboard/components/Header.jsx
@@ -6,8 +6,9 @@ import Controls from './Controls';
 import EditableTitle from '../../components/EditableTitle';
 import Button from '../../components/Button';
 import FaveStar from '../../components/FaveStar';
-import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
-import { chartPropShape } from '../v2/util/propShapes';
+// import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
+import SaveModal from './SaveModal';
+import { chartPropShape } from '../util/propShapes';
 import { t } from '../../locales';
 
 const propTypes = {
@@ -20,9 +21,9 @@ const propTypes = {
   isStarred: PropTypes.bool.isRequired,
   onSave: PropTypes.func.isRequired,
   onChange: PropTypes.func.isRequired,
-  fetchFaveStar: PropTypes.func,
+  fetchFaveStar: PropTypes.func.isRequired,
   fetchCharts: PropTypes.func.isRequired,
-  saveFaveStar: PropTypes.func,
+  saveFaveStar: PropTypes.func.isRequired,
   startPeriodicRender: PropTypes.func.isRequired,
   updateDashboardTitle: PropTypes.func.isRequired,
   editMode: PropTypes.bool.isRequired,
@@ -41,13 +42,16 @@ const propTypes = {
 class Header extends React.PureComponent {
   constructor(props) {
     super(props);
+
     this.handleChangeText = this.handleChangeText.bind(this);
     this.toggleEditMode = this.toggleEditMode.bind(this);
     this.forceRefresh = this.forceRefresh.bind(this);
   }
+
   forceRefresh() {
     return this.props.fetchCharts(Object.values(this.props.charts), true);
   }
+
   handleChangeText(nextText) {
     const { updateDashboardTitle, onChange } = this.props;
     if (nextText && this.props.dashboardTitle !== nextText) {
@@ -55,45 +59,11 @@ class Header extends React.PureComponent {
       onChange();
     }
   }
+
   toggleEditMode() {
     this.props.setEditMode(!this.props.editMode);
   }
-  renderUnsaved() {
-    if (!this.props.hasUnsavedChanges) {
-      return null;
-    }
-    return (
-      <InfoTooltipWithTrigger
-        label="unsaved"
-        tooltip={t('Unsaved changes')}
-        icon="exclamation-triangle"
-        className="text-danger m-r-5"
-        placement="top"
-      />
-    );
-  }
-  renderInsertButton() {
-    if (!this.props.editMode) {
-      return null;
-    }
-    const btnText = this.props.showBuilderPane ? t('Hide builder pane') : t('Insert components');
-    return (
-      <Button bsSize="small" onClick={this.props.toggleBuilderPane}>
-        {btnText}
-      </Button>
-    );
-  }
-  renderEditButton() {
-    if (!this.props.dashboardInfo.dash_save_perm) {
-      return null;
-    }
-    const btnText = this.props.editMode ? t('Switch to View Mode') : t('Edit Dashboard');
-    return (
-      <Button bsSize="small" onClick={this.toggleEditMode}>
-        {btnText}
-      </Button>
-    );
-  }
+
   render() {
     const {
       dashboardTitle,
@@ -107,8 +77,13 @@ class Header extends React.PureComponent {
       onChange,
       onSave,
       editMode,
+      showBuilderPane,
+      dashboardInfo,
+      hasUnsavedChanges,
     } = this.props;
 
+    const userCanEdit = dashboardInfo.dash_save_perm;
+
     return (
       <div className="dashboard-header">
         <div className="dashboard-component-header header-large">
@@ -126,19 +101,58 @@ class Header extends React.PureComponent {
               isStarred={this.props.isStarred}
             />
           </span>
-          {this.renderUnsaved()}
         </div>
         <ButtonToolbar>
-          <ButtonGroup>
-            <Button bsSize="small" onClick={onUndo} disabled={!canUndo}>
-              Undo
-            </Button>
-            <Button bsSize="small" onClick={onRedo} disabled={!canRedo}>
-              Redo
-            </Button>
-            {this.renderInsertButton()}
-            {this.renderEditButton()}
-          </ButtonGroup>
+          {userCanEdit && (
+            <ButtonGroup>
+              {editMode && (
+                <Button bsSize="small" onClick={onUndo} disabled={!canUndo}>
+                  Undo
+                </Button>
+              )}
+
+              {editMode && (
+                <Button bsSize="small" onClick={onRedo} disabled={!canRedo}>
+                  Redo
+                </Button>
+              )}
+
+              {editMode && (
+                <Button bsSize="small" onClick={this.props.toggleBuilderPane}>
+                  {showBuilderPane
+                    ? t('Hide builder pane')
+                    : t('Insert components')}
+                </Button>
+              )}
+
+              {!hasUnsavedChanges ? (
+                <Button
+                  bsSize="small"
+                  onClick={this.toggleEditMode}
+                  bsStyle={editMode ? undefined : 'primary'}
+                >
+                  {editMode ? t('Switch to View Mode') : t('Edit Dashboard')}
+                </Button>
+              ) : (
+                <SaveModal
+                  dashboardId={this.props.dashboardInfo.id}
+                  dashboardTitle={dashboardTitle}
+                  layout={layout}
+                  filters={filters}
+                  expandedSlices={expandedSlices}
+                  onSave={onSave}
+                  // @TODO need to figure out css
+                  css=""
+                  triggerNode={
+                    <Button bsStyle="primary" bsSize="small">
+                      {t('Save changes')}
+                    </Button>
+                  }
+                />
+              )}
+            </ButtonGroup>
+          )}
+
           <Controls
             dashboardInfo={this.props.dashboardInfo}
             dashboardTitle={dashboardTitle}
@@ -156,6 +170,7 @@ class Header extends React.PureComponent {
     );
   }
 }
+
 Header.propTypes = propTypes;
 
 export default Header;
diff --git a/superset/assets/src/dashboard/v2/components/IconButton.jsx b/superset/assets/src/dashboard/components/IconButton.jsx
similarity index 100%
rename from superset/assets/src/dashboard/v2/components/IconButton.jsx
rename to superset/assets/src/dashboard/components/IconButton.jsx
diff --git a/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx b/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx
index c2a5637..1ed4f82 100644
--- a/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx
+++ b/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx
@@ -16,7 +16,7 @@ const defaultProps = {
 };
 
 const options = [
-  [0, t('Don\'t refresh')],
+  [0, t("Don't refresh")],
   [10, t('10 seconds')],
   [30, t('30 seconds')],
   [60, t('1 minute')],
@@ -47,7 +47,7 @@ class RefreshIntervalModal extends React.PureComponent {
             <Select
               options={options}
               value={this.state.refreshFrequency}
-              onChange={(opt) => {
+              onChange={opt => {
                 const value = opt ? opt.value : options[0].value;
                 this.setState({
                   refreshFrequency: value,
diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx b/superset/assets/src/dashboard/components/SaveModal.jsx
index 2e76bf4..41c6364 100644
--- a/superset/assets/src/dashboard/components/SaveModal.jsx
+++ b/superset/assets/src/dashboard/components/SaveModal.jsx
@@ -1,4 +1,4 @@
-/* global notify */
+/* global notify, window */
 import React from 'react';
 import PropTypes from 'prop-types';
 import $ from 'jquery';
@@ -17,6 +17,11 @@ const propTypes = {
   triggerNode: PropTypes.node.isRequired,
   filters: PropTypes.object.isRequired,
   onSave: PropTypes.func.isRequired,
+  isMenuItem: PropTypes.bool,
+};
+
+const defaultProps = {
+  isMenuItem: false,
 };
 
 class SaveModal extends React.PureComponent {
@@ -24,28 +29,38 @@ class SaveModal extends React.PureComponent {
     super(props);
     this.state = {
       saveType: 'overwrite',
-      newDashName: props.dashboardTitle + ' [copy]',
+      newDashName: `${props.dashboardTitle} [copy]`,
       duplicateSlices: false,
     };
     this.modal = null;
     this.handleSaveTypeChange = this.handleSaveTypeChange.bind(this);
     this.handleNameChange = this.handleNameChange.bind(this);
     this.saveDashboard = this.saveDashboard.bind(this);
+    this.setModalRef = this.setModalRef.bind(this);
+    this.toggleDuplicateSlices = this.toggleDuplicateSlices.bind(this);
+  }
+
+  setModalRef(ref) {
+    this.modal = ref;
   }
+
   toggleDuplicateSlices() {
     this.setState({ duplicateSlices: !this.state.duplicateSlices });
   }
+
   handleSaveTypeChange(event) {
     this.setState({
       saveType: event.target.value,
     });
   }
+
   handleNameChange(event) {
     this.setState({
       newDashName: event.target.value,
       saveType: 'newDashboard',
     });
   }
+
   saveDashboardRequest(data, url, saveType) {
     const saveModal = this.modal;
     const onSaveDashboard = this.props.onSave;
@@ -67,12 +82,25 @@ class SaveModal extends React.PureComponent {
       error(error) {
         saveModal.close();
         const errorMsg = getAjaxErrorMsg(error);
-        notify.error(t('Sorry, there was an error saving this dashboard: ') + errorMsg);
+        notify.error(
+          `${t(
+            'Sorry, there was an error saving this dashboard: ',
+          )} ${errorMsg}`,
+        );
       },
     });
   }
-  saveDashboard(saveType, newDashboardTitle) {
-    const { dashboardTitle, layout: positions, expandedSlices, filters, dashboardId } = this.props;
+
+  saveDashboard() {
+    const { saveType, newDashName } = this.state;
+    const {
+      dashboardTitle,
+      layout: positions,
+      expandedSlices,
+      filters,
+      dashboardId,
+    } = this.props;
+
     const data = {
       positions,
       expanded_slices: expandedSlices,
@@ -80,29 +108,27 @@ class SaveModal extends React.PureComponent {
       default_filters: JSON.stringify(filters),
       duplicate_slices: this.state.duplicateSlices,
     };
+
     let url = null;
     if (saveType === 'overwrite') {
       url = `/superset/save_dash/${dashboardId}/`;
       this.saveDashboardRequest(data, url, saveType);
     } else if (saveType === 'newDashboard') {
-      if (!newDashboardTitle) {
-        this.modal.close();
-        showModal({
-          title: t('Error'),
-          body: t('You must pick a name for the new dashboard'),
-        });
+      if (!newDashName) {
+        notify.error('You must pick a name for the new dashboard');
       } else {
-        data.dashboard_title = newDashboardTitle;
+        data.dashboard_title = newDashName;
         url = `/superset/copy_dash/${dashboardId}/`;
         this.saveDashboardRequest(data, url, saveType);
       }
     }
   }
+
   render() {
     return (
       <ModalTrigger
-        ref={(modal) => { this.modal = modal; }}
-        isMenuItem
+        ref={this.setModalRef}
+        isMenuItem={this.props.isMenuItem}
         triggerNode={this.props.triggerNode}
         modalTitle={t('Save Dashboard')}
         modalBody={
@@ -132,7 +158,7 @@ class SaveModal extends React.PureComponent {
             <div className="m-l-25 m-t-5">
               <Checkbox
                 checked={this.state.duplicateSlices}
-                onChange={this.toggleDuplicateSlices.bind(this)}
+                onChange={this.toggleDuplicateSlices}
               />
               <span className="m-l-5">also copy (duplicate) charts</span>
             </div>
@@ -140,10 +166,7 @@ class SaveModal extends React.PureComponent {
         }
         modalFooter={
           <div>
-            <Button
-              bsStyle="primary"
-              onClick={() => { this.saveDashboard(this.state.saveType, this.state.newDashName); }}
-            >
+            <Button bsStyle="primary" onClick={this.saveDashboard}>
               {t('Save')}
             </Button>
           </div>
@@ -152,6 +175,8 @@ class SaveModal extends React.PureComponent {
     );
   }
 }
+
 SaveModal.propTypes = propTypes;
+SaveModal.defaultProps = defaultProps;
 
 export default SaveModal;
diff --git a/superset/assets/src/dashboard/components/SliceAdder.jsx b/superset/assets/src/dashboard/components/SliceAdder.jsx
index 6477fc4..37ce21f 100644
--- a/superset/assets/src/dashboard/components/SliceAdder.jsx
+++ b/superset/assets/src/dashboard/components/SliceAdder.jsx
@@ -1,14 +1,15 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import cx from 'classnames';
 import { DropdownButton, MenuItem } from 'react-bootstrap';
 import { List } from 'react-virtualized';
 import SearchInput, { createFilter } from 'react-search-input';
 
-import DragDroppable from '../v2/components/dnd/DragDroppable';
-import { CHART_TYPE, NEW_COMPONENT_SOURCE_TYPE } from '../v2/util/componentTypes';
-import { NEW_CHART_ID, NEW_COMPONENTS_SOURCE_ID } from '../v2/util/constants';
-import { slicePropShape } from '../v2/util/propShapes';
+import AddSliceCard from './AddSliceCard';
+import AddSliceDragPreview from './dnd/AddSliceDragPreview';
+import DragDroppable from './dnd/DragDroppable';
+import { CHART_TYPE, NEW_COMPONENT_SOURCE_TYPE } from '../util/componentTypes';
+import { NEW_CHART_ID, NEW_COMPONENTS_SOURCE_ID } from '../util/constants';
+import { slicePropShape } from '../util/propShapes';
 
 const propTypes = {
   fetchAllSlices: PropTypes.func.isRequired,
@@ -24,6 +25,7 @@ const propTypes = {
 const defaultProps = {
   selectedSliceIds: new Set(),
   editMode: false,
+  errorMessage: '',
 };
 
 const KEYS_TO_FILTERS = ['slice_name', 'viz_type', 'datasource_name'];
@@ -35,12 +37,25 @@ const KEYS_TO_SORT = [
 ];
 
 class SliceAdder extends React.Component {
+  static sortByComparator(attr) {
+    const desc = attr === 'changed_on' ? -1 : 1;
+
+    return (a, b) => {
+      if (a[attr] < b[attr]) {
+        return -1 * desc;
+      } else if (a[attr] > b[attr]) {
+        return 1 * desc;
+      }
+      return 0;
+    };
+  }
+
   constructor(props) {
     super(props);
     this.state = {
       filteredSlices: [],
       searchTerm: '',
-      sortBy: KEYS_TO_SORT.findIndex(item => (item.key === 'changed_on')),
+      sortBy: KEYS_TO_SORT.findIndex(item => item.key === 'changed_on'),
     };
 
     this.rowRenderer = this.rowRenderer.bind(this);
@@ -58,7 +73,9 @@ class SliceAdder extends React.Component {
       this.setState({
         filteredSlices: Object.values(nextProps.slices)
           .filter(createFilter(this.state.searchTerm, KEYS_TO_FILTERS))
-          .sort(this.sortByComparator(KEYS_TO_SORT[this.state.sortBy].key)),
+          .sort(
+            SliceAdder.sortByComparator(KEYS_TO_SORT[this.state.sortBy].key),
+          ),
       });
     }
   }
@@ -72,20 +89,7 @@ class SliceAdder extends React.Component {
   getFilteredSortedSlices(searchTerm, sortBy) {
     return Object.values(this.props.slices)
       .filter(createFilter(searchTerm, KEYS_TO_FILTERS))
-      .sort(this.sortByComparator(KEYS_TO_SORT[sortBy].key));
-  }
-
-  sortByComparator(attr) {
-    const desc = (attr === 'changed_on') ? -1 : 1;
-
-    return (a, b) => {
-      if (a[attr] < b[attr]) {
-        return -1 * desc;
-      } else if (a[attr] > b[attr]) {
-        return 1 * desc;
-      }
-      return 0;
-    };
+      .sort(SliceAdder.sortByComparator(KEYS_TO_SORT[sortBy].key));
   }
 
   handleKeyPress(ev) {
@@ -99,20 +103,25 @@ class SliceAdder extends React.Component {
   searchUpdated(searchTerm) {
     this.setState({
       searchTerm,
-      filteredSlices: this.getFilteredSortedSlices(searchTerm, this.state.sortBy),
+      filteredSlices: this.getFilteredSortedSlices(
+        searchTerm,
+        this.state.sortBy,
+      ),
     });
   }
 
   handleSelect(sortBy) {
     this.setState({
       sortBy,
-      filteredSlices: this.getFilteredSortedSlices(this.state.searchTerm, sortBy),
+      filteredSlices: this.getFilteredSortedSlices(
+        this.state.searchTerm,
+        sortBy,
+      ),
     });
   }
 
   rowRenderer({ key, index, style }) {
     const cellData = this.state.filteredSlices[index];
-    const duration = cellData.modified ? cellData.modified.replace(/<[^>]*>/g, '') : '';
     const isSelected = this.props.selectedSliceIds.has(cellData.slice_id);
     const type = CHART_TYPE;
     const id = NEW_CHART_ID;
@@ -122,38 +131,32 @@ class SliceAdder extends React.Component {
 
     return (
       <DragDroppable
+        key={key}
         component={{ type, id, meta }}
-        parentComponent={{ id: NEW_COMPONENTS_SOURCE_ID, type: NEW_COMPONENT_SOURCE_TYPE }}
-        index={0}
+        parentComponent={{
+          id: NEW_COMPONENTS_SOURCE_ID,
+          type: NEW_COMPONENT_SOURCE_TYPE,
+        }}
+        index={index}
         depth={0}
         disableDragDrop={isSelected}
         editMode={this.props.editMode}
+        // we must use a custom drag preview within the List because
+        // it does not seem to work within a fixed-position container
+        useEmptyDragPreview
       >
         {({ dragSourceRef }) => (
-          <div
-            ref={dragSourceRef}
-            className="chart-card-container"
-            key={key}
+          <AddSliceCard
+            innerRef={dragSourceRef}
             style={style}
-          >
-            <div className={cx('chart-card', { 'is-selected': isSelected })}>
-              <div className="card-title">{cellData.slice_name}</div>
-              <div className="card-body">
-                <div className="item">
-                  <span>Modified </span>
-                  <span>{duration}</span>
-                </div>
-                <div className="item">
-                  <span>Visualization </span>
-                  <span>{cellData.viz_type}</span>
-                </div>
-                <div className="item">
-                  <span>Data source </span>
-                  <span dangerouslySetInnerHTML={{ __html: cellData.datasource_link }} />
-                </div>
-              </div>
-            </div>
-          </div>
+            sliceName={cellData.slice_name}
+            lastModified={
+              cellData.modified ? cellData.modified.replace(/<[^>]*>/g, '') : ''
+            }
+            visType={cellData.viz_type}
+            datasourceLink={cellData.datasource_link}
+            isSelected={isSelected}
+          />
         )}
       </DragDroppable>
     );
@@ -169,7 +172,9 @@ class SliceAdder extends React.Component {
             id="slice-adder-sortby"
           >
             {KEYS_TO_SORT.map((item, index) => (
-              <MenuItem key={item.key} eventKey={index}>{item.label}</MenuItem>
+              <MenuItem key={item.key} eventKey={index}>
+                {item.label}
+              </MenuItem>
             ))}
           </DropdownButton>
 
@@ -179,18 +184,18 @@ class SliceAdder extends React.Component {
           />
         </div>
 
-        {this.props.isLoading &&
+        {this.props.isLoading && (
           <img
             src="/static/assets/images/loading.gif"
             className="loading"
             alt="loading"
           />
-        }
-        <div className={this.props.errorMessage ? '' : 'hidden'}>
-          {this.props.errorMessage}
-        </div>
-        <div className={!this.props.isLoading ? '' : 'hidden'}>
-          {this.state.filteredSlices.length > 0 &&
+        )}
+
+        {this.props.errorMessage && <div>{this.props.errorMessage}</div>}
+
+        {!this.props.isLoading &&
+          this.state.filteredSlices.length > 0 && (
             <List
               width={376}
               height={500}
@@ -201,8 +206,10 @@ class SliceAdder extends React.Component {
               sortBy={this.state.sortBy}
               selectedSliceIds={this.props.selectedSliceIds}
             />
-          }
-        </div>
+          )}
+
+        {/* Drag preview is just a single fixed-position element */}
+        <AddSliceDragPreview slices={this.state.filteredSlices} />
       </div>
     );
   }
diff --git a/superset/assets/src/dashboard/components/SliceAdderContainer.jsx b/superset/assets/src/dashboard/components/SliceAdderContainer.jsx
deleted file mode 100644
index b4f10d9..0000000
--- a/superset/assets/src/dashboard/components/SliceAdderContainer.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
-
-import { fetchAllSlices } from '../actions/sliceEntities';
-import SliceAdder from './SliceAdder';
-
-function mapStateToProps({ sliceEntities, dashboardInfo, dashboardState }) {
-  return {
-    userId: dashboardInfo.userId,
-    selectedSliceIds: dashboardState.sliceIds,
-    slices: sliceEntities.slices,
-    isLoading: sliceEntities.isLoading,
-    errorMessage: sliceEntities.errorMessage,
-    lastUpdated: sliceEntities.lastUpdated,
-    editMode: dashboardState.editMode,
-  };
-}
-
-function mapDispatchToProps(dispatch) {
-  return bindActionCreators({
-    fetchAllSlices
-  }, dispatch);
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(SliceAdder);
diff --git a/superset/assets/src/dashboard/components/SliceHeader.jsx b/superset/assets/src/dashboard/components/SliceHeader.jsx
index f126949..bcdaedf 100644
--- a/superset/assets/src/dashboard/components/SliceHeader.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeader.jsx
@@ -7,9 +7,8 @@ import TooltipWrapper from '../../components/TooltipWrapper';
 import SliceHeaderControls from './SliceHeaderControls';
 
 const propTypes = {
+  innerRef: PropTypes.func,
   slice: PropTypes.object.isRequired,
-  supersetCanExplore: PropTypes.bool,
-  sliceCanEdit: PropTypes.bool,
   isExpanded: PropTypes.bool,
   isCached: PropTypes.bool,
   cachedDttm: PropTypes.string,
@@ -24,6 +23,7 @@ const propTypes = {
 };
 
 const defaultProps = {
+  innerRef: null,
   forceRefresh: () => ({}),
   removeSlice: () => ({}),
   updateSliceName: () => ({}),
@@ -31,6 +31,11 @@ const defaultProps = {
   exploreChart: () => ({}),
   exportCSV: () => ({}),
   editMode: false,
+  annotationQuery: {},
+  annotationError: {},
+  cachedDttm: null,
+  isCached: false,
+  isExpanded: false,
 };
 
 class SliceHeader extends React.PureComponent {
@@ -48,55 +53,62 @@ class SliceHeader extends React.PureComponent {
 
   render() {
     const {
-      slice, isExpanded, isCached, cachedDttm,
-      toggleExpandSlice, forceRefresh,
-      exploreChart, exportCSV,
+      slice,
+      isExpanded,
+      isCached,
+      cachedDttm,
+      toggleExpandSlice,
+      forceRefresh,
+      exploreChart,
+      exportCSV,
+      innerRef,
     } = this.props;
+
     const annoationsLoading = t('Annotation layers are still loading.');
     const annoationsError = t('One ore more annotation layers failed loading.');
 
     return (
-      <div className="row chart-header">
-        <div className="col-md-12">
-          <div className="header">
-            <EditableTitle
-              title={slice.slice_name}
-              canEdit={!!this.props.updateSliceName && this.props.editMode}
-              onSaveTitle={this.onSaveTitle}
-              showTooltip={this.props.editMode}
-              noPermitTooltip={'You don\'t have the rights to alter this dashboard.'}
-            />
-            {!!Object.values(this.props.annotationQuery || {}).length &&
-              <TooltipWrapper
-                label="annotations-loading"
-                placement="top"
-                tooltip={annoationsLoading}
-              >
-                <i className="fa fa-refresh warning" />
-              </TooltipWrapper>
-            }
-            {!!Object.values(this.props.annotationError || {}).length &&
-              <TooltipWrapper
-                label="annoation-errors"
-                placement="top"
-                tooltip={annoationsError}
-              >
-                <i className="fa fa-exclamation-circle danger" />
-              </TooltipWrapper>
+      <div className="chart-header" ref={innerRef}>
+        <div className="header">
+          <EditableTitle
+            title={slice.slice_name}
+            canEdit={!!this.props.updateSliceName && this.props.editMode}
+            onSaveTitle={this.onSaveTitle}
+            noPermitTooltip={
+              "You don't have the rights to alter this dashboard."
             }
-            {!this.props.editMode &&
-              <SliceHeaderControls
-                slice={slice}
-                isCached={isCached}
-                isExpanded={isExpanded}
-                cachedDttm={cachedDttm}
-                toggleExpandSlice={toggleExpandSlice}
-                forceRefresh={forceRefresh}
-                exploreChart={exploreChart}
-                exportCSV={exportCSV}
-              />
-            }
-          </div>
+            showTooltip={!!this.props.updateSliceName && this.props.editMode}
+          />
+          {!!Object.values(this.props.annotationQuery).length && (
+            <TooltipWrapper
+              label="annotations-loading"
+              placement="top"
+              tooltip={annoationsLoading}
+            >
+              <i className="fa fa-refresh warning" />
+            </TooltipWrapper>
+          )}
+          {!!Object.values(this.props.annotationError).length && (
+            <TooltipWrapper
+              label="annoation-errors"
+              placement="top"
+              tooltip={annoationsError}
+            >
+              <i className="fa fa-exclamation-circle danger" />
+            </TooltipWrapper>
+          )}
+          {!this.props.editMode && (
+            <SliceHeaderControls
+              slice={slice}
+              isCached={isCached}
+              isExpanded={isExpanded}
+              cachedDttm={cachedDttm}
+              toggleExpandSlice={toggleExpandSlice}
+              forceRefresh={forceRefresh}
+              exploreChart={exploreChart}
+              exportCSV={exportCSV}
+            />
+          )}
         </div>
       </div>
     );
diff --git a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
index f61e59b..0dae6f8 100644
--- a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
@@ -23,14 +23,23 @@ const defaultProps = {
   toggleExpandSlice: () => ({}),
   exploreChart: () => ({}),
   exportCSV: () => ({}),
+  cachedDttm: null,
+  isCached: false,
+  isExpanded: false,
 };
 
 class SliceHeaderControls extends React.PureComponent {
   constructor(props) {
     super(props);
     this.exportCSV = this.props.exportCSV.bind(this, this.props.slice.slice_id);
-    this.exploreChart = this.props.exploreChart.bind(this, this.props.slice.slice_id);
-    this.toggleExpandSlice = this.props.toggleExpandSlice.bind(this, this.props.slice.slice_id);
+    this.exploreChart = this.props.exploreChart.bind(
+      this,
+      this.props.slice.slice_id,
+    );
+    this.toggleExpandSlice = this.props.toggleExpandSlice.bind(
+      this,
+      this.props.slice.slice_id,
+    );
     this.toggleControls = this.toggleControls.bind(this);
 
     this.state = {
@@ -48,9 +57,9 @@ class SliceHeaderControls extends React.PureComponent {
     const slice = this.props.slice;
     const isCached = this.props.isCached;
     const cachedWhen = moment.utc(this.props.cachedDttm).fromNow();
-    const refreshTooltip = isCached ?
-      t('Served from data cached %s . Click to force refresh.', cachedWhen) :
-      t('Force refresh data');
+    const refreshTooltip = isCached
+      ? t('Served from data cached %s . Click to force refresh.', cachedWhen)
+      : t('Force refresh data');
 
     // @TODO account for
     //  dashboard.dashboard.superset_can_explore
@@ -59,7 +68,9 @@ class SliceHeaderControls extends React.PureComponent {
       <DropdownButton
         title=""
         id={`slice_${slice.slice_id}-controls`}
-        className={cx('slice-header-controls-trigger', 'fa fa-ellipsis-v', { 'is-cached': isCached })}
+        className={cx('slice-header-controls-trigger', 'fa fa-ellipsis-v', {
+          'is-cached': isCached,
+        })}
         pullRight
         noCaret
       >
@@ -69,17 +80,17 @@ class SliceHeaderControls extends React.PureComponent {
           onClick={this.props.forceRefresh}
         />
 
-        {slice.description &&
+        {slice.description && (
           <ActionMenuItem
             text={t('Toggle chart description')}
             tooltip={t('Toggle chart description')}
             onClick={this.toggleExpandSlice}
           />
-        }
+        )}
 
         <ActionMenuItem
           text={t('Edit chart')}
-          tooltip={t('Edit the chart\'s properties')}
+          tooltip={t("Edit the chart's properties")}
           href={slice.edit_url}
           target="_blank"
         />
diff --git a/superset/assets/src/dashboard/v2/components/Toast.jsx b/superset/assets/src/dashboard/components/Toast.jsx
similarity index 93%
rename from superset/assets/src/dashboard/v2/components/Toast.jsx
rename to superset/assets/src/dashboard/components/Toast.jsx
index 537388d..3c5a3ca 100644
--- a/superset/assets/src/dashboard/v2/components/Toast.jsx
+++ b/superset/assets/src/dashboard/components/Toast.jsx
@@ -4,7 +4,12 @@ import PropTypes from 'prop-types';
 import React from 'react';
 
 import { toastShape } from '../util/propShapes';
-import { INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST } from '../util/constants';
+import {
+  INFO_TOAST,
+  SUCCESS_TOAST,
+  WARNING_TOAST,
+  DANGER_TOAST,
+} from '../util/constants';
 
 const propTypes = {
   toast: toastShape.isRequired,
@@ -60,7 +65,9 @@ class Toast extends React.Component {
 
   render() {
     const { visible } = this.state;
-    const { toast: { toastType, text } } = this.props;
+    const {
+      toast: { toastType, text },
+    } = this.props;
 
     return (
       <Alert
diff --git a/superset/assets/src/dashboard/v2/components/ToastPresenter.jsx b/superset/assets/src/dashboard/components/ToastPresenter.jsx
similarity index 81%
rename from superset/assets/src/dashboard/v2/components/ToastPresenter.jsx
rename to superset/assets/src/dashboard/components/ToastPresenter.jsx
index 95a0251..19d44b0 100644
--- a/superset/assets/src/dashboard/v2/components/ToastPresenter.jsx
+++ b/superset/assets/src/dashboard/components/ToastPresenter.jsx
@@ -19,16 +19,13 @@ class ToastPresenter extends React.Component {
     const { toasts, removeToast } = this.props;
 
     return (
-      toasts.length > 0 &&
+      toasts.length > 0 && (
         <div className="toast-presenter">
           {toasts.map(toast => (
-            <Toast
-              key={toast.id}
-              toast={toast}
-              onCloseToast={removeToast}
-            />
+            <Toast key={toast.id} toast={toast} onCloseToast={removeToast} />
           ))}
         </div>
+      )
     );
   }
 }
diff --git a/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx b/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx
new file mode 100644
index 0000000..94cab42
--- /dev/null
+++ b/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { DragLayer } from 'react-dnd';
+
+import AddSliceCard from '../AddSliceCard';
+import { slicePropShape } from '../../util/propShapes';
+import {
+  NEW_COMPONENT_SOURCE_TYPE,
+  CHART_TYPE,
+} from '../../util/componentTypes';
+
+const propTypes = {
+  dragItem: PropTypes.shape({
+    index: PropTypes.number.isRequired,
+  }),
+  slices: PropTypes.arrayOf(slicePropShape),
+  isDragging: PropTypes.bool.isRequired,
+  currentOffset: PropTypes.shape({
+    x: PropTypes.number.isRequired,
+    y: PropTypes.number.isRequired,
+  }),
+};
+
+const defaultProps = {
+  currentOffset: null,
+  dragItem: null,
+  slices: null,
+};
+
+function AddSliceDragPreview({ dragItem, slices, isDragging, currentOffset }) {
+  if (!isDragging || !currentOffset || !dragItem || !slices) return null;
+
+  const slice = slices[dragItem.index];
+
+  // make sure it's a new component and a chart
+  const shouldRender =
+    slice &&
+    dragItem.parentType === NEW_COMPONENT_SOURCE_TYPE &&
+    dragItem.type === CHART_TYPE;
+
+  return !shouldRender ? null : (
+    <AddSliceCard
+      style={{
+        position: 'fixed',
+        background: 'white',
+        pointerEvents: 'none',
+        top: 0,
+        left: 0,
+        zIndex: 100,
+        transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
+      }}
+      sliceName={slice.slice_name}
+      lastModified={
+        slice.modified ? slice.modified.replace(/<[^>]*>/g, '') : ''
+      }
+      visType={slice.viz_type}
+      datasourceLink={slice.datasource_link}
+    />
+  );
+}
+
+AddSliceDragPreview.propTypes = propTypes;
+AddSliceDragPreview.defaultProps = defaultProps;
+
+// This injects these props into the component
+export default DragLayer(monitor => ({
+  dragItem: monitor.getItem(),
+  currentOffset: monitor.getSourceClientOffset(),
+  isDragging: monitor.isDragging(),
+}))(AddSliceDragPreview);
diff --git a/superset/assets/src/dashboard/v2/components/dnd/DragDroppable.jsx b/superset/assets/src/dashboard/components/dnd/DragDroppable.jsx
similarity index 65%
rename from superset/assets/src/dashboard/v2/components/dnd/DragDroppable.jsx
rename to superset/assets/src/dashboard/components/dnd/DragDroppable.jsx
index 775e092..bfe4973 100644
--- a/superset/assets/src/dashboard/v2/components/dnd/DragDroppable.jsx
+++ b/superset/assets/src/dashboard/components/dnd/DragDroppable.jsx
@@ -1,3 +1,4 @@
+import { getEmptyImage } from 'react-dnd-html5-backend';
 import React from 'react';
 import PropTypes from 'prop-types';
 import { DragSource, DropTarget } from 'react-dnd';
@@ -5,7 +6,12 @@ import cx from 'classnames';
 
 import { componentShape } from '../../util/propShapes';
 import { dragConfig, dropConfig } from './dragDroppableConfig';
-import { DROP_TOP, DROP_RIGHT, DROP_BOTTOM, DROP_LEFT } from '../../util/getDropPosition';
+import {
+  DROP_TOP,
+  DROP_RIGHT,
+  DROP_BOTTOM,
+  DROP_LEFT,
+} from '../../util/getDropPosition';
 
 const propTypes = {
   children: PropTypes.func,
@@ -19,6 +25,7 @@ const propTypes = {
   style: PropTypes.object,
   onDrop: PropTypes.func,
   editMode: PropTypes.bool.isRequired,
+  useEmptyDragPreview: PropTypes.bool,
 
   // from react-dnd
   isDragging: PropTypes.bool.isRequired,
@@ -37,6 +44,7 @@ const defaultProps = {
   children() {},
   onDrop() {},
   orientation: 'row',
+  useEmptyDragPreview: false,
 };
 
 class DragDroppable extends React.Component {
@@ -58,7 +66,16 @@ class DragDroppable extends React.Component {
 
   setRef(ref) {
     this.ref = ref;
-    this.props.dragPreviewRef(ref);
+    // this is needed for a custom drag preview
+    if (this.props.useEmptyDragPreview) {
+      this.props.dragPreviewRef(getEmptyImage(), {
+        // IE fallback: specify that we'd rather screenshot the node
+        // when it already knows it's being dragged so we can hide it with CSS.
+        captureDraggingState: true,
+      });
+    } else {
+      this.props.dragPreviewRef(ref);
+    }
     this.props.droppableRef(ref);
   }
 
@@ -74,8 +91,6 @@ class DragDroppable extends React.Component {
       editMode,
     } = this.props;
 
-    if (!editMode) return children({});
-
     const { dropIndicator } = this.state;
 
     return (
@@ -90,18 +105,23 @@ class DragDroppable extends React.Component {
           className,
         )}
       >
-        {children({
-          dragSourceRef,
-          dropIndicatorProps: isDraggingOver && dropIndicator && {
-            className: cx(
-              'drop-indicator',
-              dropIndicator === DROP_TOP && 'drop-indicator--top',
-              dropIndicator === DROP_BOTTOM && 'drop-indicator--bottom',
-              dropIndicator === DROP_LEFT && 'drop-indicator--left',
-              dropIndicator === DROP_RIGHT && 'drop-indicator--right',
-            ),
-          },
-        })}
+        {children(
+          !editMode
+            ? {}
+            : {
+                dragSourceRef,
+                dropIndicatorProps: isDraggingOver &&
+                  dropIndicator && {
+                    className: cx(
+                      'drop-indicator',
+                      dropIndicator === DROP_TOP && 'drop-indicator--top',
+                      dropIndicator === DROP_BOTTOM && 'drop-indicator--bottom',
+                      dropIndicator === DROP_LEFT && 'drop-indicator--left',
+                      dropIndicator === DROP_RIGHT && 'drop-indicator--right',
+                    ),
+                  },
+              },
+        )}
       </div>
     );
   }
diff --git a/superset/assets/src/dashboard/v2/components/dnd/DragHandle.jsx b/superset/assets/src/dashboard/components/dnd/DragHandle.jsx
similarity index 82%
rename from superset/assets/src/dashboard/v2/components/dnd/DragHandle.jsx
rename to superset/assets/src/dashboard/components/dnd/DragHandle.jsx
index 36d1e6b..23d7d11 100644
--- a/superset/assets/src/dashboard/v2/components/dnd/DragHandle.jsx
+++ b/superset/assets/src/dashboard/components/dnd/DragHandle.jsx
@@ -26,9 +26,11 @@ export default class DragHandle extends React.PureComponent {
           position === 'top' && 'drag-handle--top',
         )}
       >
-        {Array(dotCount).fill(null).map((_, i) => (
-          <div key={`handle-dot-${i}`} className="drag-handle-dot" />
-        ))}
+        {Array(dotCount)
+          .fill(null)
+          .map((_, i) => (
+            <div key={`handle-dot-${i}`} className="drag-handle-dot" />
+          ))}
       </div>
     );
   }
diff --git a/superset/assets/src/dashboard/v2/components/dnd/dragDroppableConfig.js b/superset/assets/src/dashboard/components/dnd/dragDroppableConfig.js
similarity index 88%
rename from superset/assets/src/dashboard/v2/components/dnd/dragDroppableConfig.js
rename to superset/assets/src/dashboard/components/dnd/dragDroppableConfig.js
index 54ce67e..36c34a0 100644
--- a/superset/assets/src/dashboard/v2/components/dnd/dragDroppableConfig.js
+++ b/superset/assets/src/dashboard/components/dnd/dragDroppableConfig.js
@@ -38,15 +38,11 @@ export const dropConfig = [
   {
     hover(props, monitor, component) {
       if (
-        component
-        && component.decoratedComponentInstance
-        && component.decoratedComponentInstance.mounted
+        component &&
+        component.decoratedComponentInstance &&
+        component.decoratedComponentInstance.mounted
       ) {
-        handleHover(
-          props,
-          monitor,
-          component.decoratedComponentInstance,
-        );
+        handleHover(props, monitor, component.decoratedComponentInstance);
       }
     },
     // note:
diff --git a/superset/assets/src/dashboard/v2/components/dnd/handleDrop.js b/superset/assets/src/dashboard/components/dnd/handleDrop.js
similarity index 73%
rename from superset/assets/src/dashboard/v2/components/dnd/handleDrop.js
rename to superset/assets/src/dashboard/components/dnd/handleDrop.js
index 7cb630d..3739b18 100644
--- a/superset/assets/src/dashboard/v2/components/dnd/handleDrop.js
+++ b/superset/assets/src/dashboard/components/dnd/handleDrop.js
@@ -1,4 +1,9 @@
-import getDropPosition, { DROP_TOP, DROP_RIGHT, DROP_BOTTOM, DROP_LEFT } from '../../util/getDropPosition';
+import getDropPosition, {
+  DROP_TOP,
+  DROP_RIGHT,
+  DROP_BOTTOM,
+  DROP_LEFT,
+} from '../../util/getDropPosition';
 
 export default function handleDrop(props, monitor, Component) {
   // this may happen due to throttling
@@ -22,9 +27,12 @@ export default function handleDrop(props, monitor, Component) {
   const draggingItem = monitor.getItem();
 
   const dropAsChildOrSibling =
-    (orientation === 'row' && (dropPosition === DROP_TOP || dropPosition === DROP_BOTTOM)) ||
-    (orientation === 'column' && (dropPosition === DROP_LEFT || dropPosition === DROP_RIGHT))
-    ? 'sibling' : 'child';
+    (orientation === 'row' &&
+      (dropPosition === DROP_TOP || dropPosition === DROP_BOTTOM)) ||
+    (orientation === 'column' &&
+      (dropPosition === DROP_LEFT || dropPosition === DROP_RIGHT))
+      ? 'sibling'
+      : 'child';
 
   const dropResult = {
     source: {
@@ -49,8 +57,10 @@ export default function handleDrop(props, monitor, Component) {
   } else {
     // if the item is in the same list with a smaller index, you must account for the
     // "missing" index upon movement within the list
-    const sameParent = parentComponent && draggingItem.parentId === parentComponent.id;
-    const sameParentLowerIndex = sameParent && draggingItem.index < componentIndex;
+    const sameParent =
+      parentComponent && draggingItem.parentId === parentComponent.id;
+    const sameParentLowerIndex =
+      sameParent && draggingItem.index < componentIndex;
 
     let nextIndex = sameParentLowerIndex ? componentIndex - 1 : componentIndex;
     if (dropPosition === DROP_BOTTOM || dropPosition === DROP_RIGHT) {
diff --git a/superset/assets/src/dashboard/v2/components/dnd/handleHover.js b/superset/assets/src/dashboard/components/dnd/handleHover.js
similarity index 100%
rename from superset/assets/src/dashboard/v2/components/dnd/handleHover.js
rename to superset/assets/src/dashboard/components/dnd/handleHover.js
diff --git a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
new file mode 100644
index 0000000..54e1536
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
@@ -0,0 +1,233 @@
+import cx from 'classnames';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { exportChart } from '../../../explore/exploreUtils';
+import SliceHeader from '../SliceHeader';
+import ChartContainer from '../../../chart/ChartContainer';
+import { chartPropType } from '../../../chart/chartReducer';
+import { slicePropShape } from '../../util/propShapes';
+import { VIZ_TYPES } from '../../../visualizations/main';
+
+const propTypes = {
+  id: PropTypes.number.isRequired,
+  width: PropTypes.number.isRequired,
+  height: PropTypes.number.isRequired,
+
+  // from redux
+  chart: PropTypes.shape(chartPropType).isRequired,
+  formData: PropTypes.object.isRequired,
+  datasource: PropTypes.object.isRequired,
+  slice: slicePropShape.isRequired,
+  timeout: PropTypes.number.isRequired,
+  filters: PropTypes.object.isRequired,
+  refreshChart: PropTypes.func.isRequired,
+  saveSliceName: PropTypes.func.isRequired,
+  toggleExpandSlice: PropTypes.func.isRequired,
+  addFilter: PropTypes.func.isRequired,
+  removeFilter: PropTypes.func.isRequired,
+  editMode: PropTypes.bool.isRequired,
+  isExpanded: PropTypes.bool.isRequired,
+};
+
+// we use state + shouldComponentUpdate() logic to prevent perf-wrecking
+// resizing across all slices on a dashboard on every update
+const RESIZE_TIMEOUT = 350;
+const SHOULD_UPDATE_ON_PROP_CHANGES = Object.keys(propTypes).filter(
+  prop => prop !== 'width' && prop !== 'height',
+);
+const OVERFLOWABLE_VIZ_TYPES = new Set([VIZ_TYPES.filter_box]);
+
+class Chart extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      width: props.width,
+      height: props.height,
+    };
+
+    this.addFilter = this.addFilter.bind(this);
+    this.exploreChart = this.exploreChart.bind(this);
+    this.exportCSV = this.exportCSV.bind(this);
+    this.forceRefresh = this.forceRefresh.bind(this);
+    this.getFilters = this.getFilters.bind(this);
+    this.removeFilter = this.removeFilter.bind(this);
+    this.resize = this.resize.bind(this);
+    this.setDescriptionRef = this.setDescriptionRef.bind(this);
+    this.setHeaderRef = this.setHeaderRef.bind(this);
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    // this logic mostly pertains to chart resizing. we keep a copy of the dimensions in
+    // state so that we can buffer component size updates and only update on the final call
+    // which improves performance significantly
+    if (
+      nextState.width !== this.state.width ||
+      nextState.height !== this.state.height
+    ) {
+      return true;
+    }
+
+    for (let i = 0; i < SHOULD_UPDATE_ON_PROP_CHANGES.length; i += 1) {
+      const prop = SHOULD_UPDATE_ON_PROP_CHANGES[i];
+      if (nextProps[prop] !== this.props[prop]) {
+        return true;
+      }
+    }
+
+    if (
+      nextProps.width !== this.props.width ||
+      nextProps.height !== this.props.height
+    ) {
+      clearTimeout(this.resizeTimeout);
+      this.resizeTimeout = setTimeout(this.resize, RESIZE_TIMEOUT);
+    }
+
+    return false;
+  }
+
+  componentWillUnmount() {
+    clearTimeout(this.resizeTimeout);
+  }
+
+  getFilters() {
+    return this.props.filters;
+  }
+
+  getChartHeight() {
+    const headerHeight = this.getHeaderHeight();
+    const descriptionHeight =
+      this.props.isExpanded && this.descriptionRef
+        ? this.descriptionRef.offsetHeight
+        : 0;
+
+    return this.state.height - headerHeight - descriptionHeight;
+  }
+
+  getHeaderHeight() {
+    return (this.headerRef && this.headerRef.offsetHeight) || 30;
+  }
+
+  setDescriptionRef(ref) {
+    this.descriptionRef = ref;
+  }
+
+  setHeaderRef(ref) {
+    this.headerRef = ref;
+  }
+
+  resize() {
+    const { width, height } = this.props;
+    this.setState(() => ({ width, height }));
+  }
+
+  addFilter(...args) {
+    this.props.addFilter(this.props.chart, ...args);
+  }
+
+  exploreChart() {
+    exportChart(this.props.formData);
+  }
+
+  exportCSV() {
+    exportChart(this.props.formData, 'csv');
+  }
+
+  forceRefresh() {
+    return this.props.refreshChart(this.props.chart, true, this.props.timeout);
+  }
+
+  removeFilter(args) {
+    this.props.removeFilter(this.props.id, ...args);
+  }
+
+  render() {
+    const {
+      id,
+      chart,
+      slice,
+      datasource,
+      isExpanded,
+      editMode,
+      formData,
+      toggleExpandSlice,
+      timeout,
+    } = this.props;
+
+    const { width } = this.state;
+    const { queryResponse } = chart;
+    const isCached = queryResponse && queryResponse.is_cached;
+    const cachedDttm = queryResponse && queryResponse.cached_dttm;
+    const isOverflowable = OVERFLOWABLE_VIZ_TYPES.has(slice && slice.viz_type);
+
+    return (
+      <div
+        className={cx(
+          'dashboard-chart',
+          isOverflowable && 'dashboard-chart--overflowable',
+        )}
+      >
+        <SliceHeader
+          innerRef={this.setHeaderRef}
+          slice={slice}
+          isExpanded={!!isExpanded}
+          isCached={isCached}
+          cachedDttm={cachedDttm}
+          updateSliceName={this.updateSliceName}
+          toggleExpandSlice={toggleExpandSlice}
+          forceRefresh={this.forceRefresh}
+          editMode={editMode}
+          annotationQuery={chart.annotationQuery}
+          exploreChart={this.exploreChart}
+          exportCSV={this.exportCSV}
+        />
+
+        {/*
+          This usage of dangerouslySetInnerHTML is safe since it is being used to render
+          markdown that is sanitized with bleach. See:
+             https://github.com/apache/incubator-superset/pull/4390
+          and
+             https://github.com/apache/incubator-superset/commit/b6fcc22d5a2cb7a5e92599ed5795a0169385a825
+        */}
+        {isExpanded &&
+          slice.description_markeddown && (
+            <div
+              className="slice_description bs-callout bs-callout-default"
+              ref={this.setDescriptionRef}
+              // eslint-disable-next-line react/no-danger
+              dangerouslySetInnerHTML={{ __html: slice.description_markeddown }}
+            />
+          )}
+
+        <ChartContainer
+          containerId={`slice-container-${id}`}
+          chartId={id}
+          datasource={datasource}
+          formData={formData}
+          headerHeight={this.getHeaderHeight()}
+          height={this.getChartHeight()}
+          width={width}
+          timeout={timeout}
+          vizType={slice.viz_type}
+          addFilter={this.addFilter}
+          getFilters={this.getFilters}
+          removeFilter={this.removeFilter}
+          annotationData={chart.annotationData}
+          chartAlert={chart.chartAlert}
+          chartStatus={chart.chartStatus}
+          chartUpdateEndTime={chart.chartUpdateEndTime}
+          chartUpdateStartTime={chart.chartUpdateStartTime}
+          latestQueryFormData={chart.latestQueryFormData}
+          lastRendered={chart.lastRendered}
+          queryResponse={chart.queryResponse}
+          queryRequest={chart.queryRequest}
+          triggerQuery={chart.triggerQuery}
+        />
+      </div>
+    );
+  }
+}
+
+Chart.propTypes = propTypes;
+
+export default Chart;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/ChartHolder.jsx b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx
similarity index 68%
rename from superset/assets/src/dashboard/v2/components/gridComponents/ChartHolder.jsx
rename to superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx
index 2aed4b2..a684230 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/ChartHolder.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx
@@ -1,15 +1,21 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import Chart from '../../containers/Chart';
 import DeleteComponentButton from '../DeleteComponentButton';
 import DragDroppable from '../dnd/DragDroppable';
 import DragHandle from '../dnd/DragHandle';
 import HoverMenu from '../menu/HoverMenu';
 import ResizableContainer from '../resizable/ResizableContainer';
-import WithPopoverMenu from '../menu/WithPopoverMenu';
 import { componentShape } from '../../util/propShapes';
-import { ROW_TYPE } from '../../util/componentTypes';
-import { GRID_MIN_COLUMN_COUNT, GRID_MIN_ROW_UNITS } from '../../util/constants';
+import { ROW_TYPE, COLUMN_TYPE } from '../../util/componentTypes';
+import {
+  GRID_MIN_COLUMN_COUNT,
+  GRID_MIN_ROW_UNITS,
+  GRID_BASE_UNIT,
+} from '../../util/constants';
+
+const CHART_MARGIN = 32;
 
 const propTypes = {
   id: PropTypes.string.isRequired,
@@ -19,7 +25,6 @@ const propTypes = {
   index: PropTypes.number.isRequired,
   depth: PropTypes.number.isRequired,
   editMode: PropTypes.bool.isRequired,
-  chart: PropTypes.object.isRequired,
 
   // grid related
   availableColumnCount: PropTypes.number.isRequired,
@@ -33,8 +38,7 @@ const propTypes = {
   handleComponentDrop: PropTypes.func.isRequired,
 };
 
-const defaultProps = {
-};
+const defaultProps = {};
 
 class ChartHolder extends React.Component {
   constructor(props) {
@@ -73,6 +77,12 @@ class ChartHolder extends React.Component {
       editMode,
     } = this.props;
 
+    // inherit the size of parent columns
+    const widthMultiple =
+      parentComponent.type === COLUMN_TYPE
+        ? parentComponent.meta.width || GRID_MIN_COLUMN_COUNT
+        : component.meta.width || GRID_MIN_COLUMN_COUNT;
+
     return (
       <DragDroppable
         component={component}
@@ -90,34 +100,37 @@ class ChartHolder extends React.Component {
             adjustableWidth={parentComponent.type === ROW_TYPE}
             adjustableHeight
             widthStep={columnWidth}
-            widthMultiple={component.meta.width}
+            widthMultiple={widthMultiple}
+            heightStep={GRID_BASE_UNIT}
             heightMultiple={component.meta.height}
             minWidthMultiple={GRID_MIN_COLUMN_COUNT}
             minHeightMultiple={GRID_MIN_ROW_UNITS}
-            maxWidthMultiple={availableColumnCount + (component.meta.width || 0)}
+            maxWidthMultiple={availableColumnCount + widthMultiple}
             onResizeStart={onResizeStart}
             onResize={onResize}
             onResizeStop={onResizeStop}
             editMode={editMode}
           >
-            {editMode &&
-              <HoverMenu innerRef={dragSourceRef} position="top">
-                <DragHandle position="top" />
-              </HoverMenu>}
-
-            <WithPopoverMenu
-              onChangeFocus={this.handleChangeFocus}
-              menuItems={[
-                <DeleteComponentButton onDelete={this.handleDeleteComponent} />,
-              ]}
-              editMode={editMode}
+            <div
+              ref={dragSourceRef}
+              className="dashboard-component dashboard-component-chart-holder"
             >
-              <div className="dashboard-component dashboard-component-chart">
-                {this.props.chart}
-              </div>
-
-              {dropIndicatorProps && <div {...dropIndicatorProps} />}
-            </WithPopoverMenu>
+              <Chart
+                id={component.meta.chartId}
+                width={widthMultiple * columnWidth}
+                height={component.meta.height * GRID_BASE_UNIT - CHART_MARGIN}
+              />
+              {editMode && (
+                <HoverMenu position="top">
+                  <DragHandle position="top" />
+                  <DeleteComponentButton
+                    onDelete={this.handleDeleteComponent}
+                  />
+                </HoverMenu>
+              )}
+            </div>
+
+            {dropIndicatorProps && <div {...dropIndicatorProps} />}
           </ResizableContainer>
         )}
       </DragDroppable>
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/Column.jsx b/superset/assets/src/dashboard/components/gridComponents/Column.jsx
similarity index 82%
rename from superset/assets/src/dashboard/v2/components/gridComponents/Column.jsx
rename to superset/assets/src/dashboard/components/gridComponents/Column.jsx
index 490d7bd..a71d732 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/Column.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Column.jsx
@@ -25,7 +25,6 @@ const propTypes = {
   index: PropTypes.number.isRequired,
   depth: PropTypes.number.isRequired,
   editMode: PropTypes.bool.isRequired,
-  cells: PropTypes.object.isRequired,
 
   // grid related
   availableColumnCount: PropTypes.number.isRequired,
@@ -41,8 +40,7 @@ const propTypes = {
   updateComponents: PropTypes.func.isRequired,
 };
 
-const defaultProps = {
-};
+const defaultProps = {};
 
 class Column extends React.PureComponent {
   constructor(props) {
@@ -50,7 +48,10 @@ class Column extends React.PureComponent {
     this.state = {
       isFocused: false,
     };
-    this.handleChangeBackground = this.handleUpdateMeta.bind(this, 'background');
+    this.handleChangeBackground = this.handleUpdateMeta.bind(
+      this,
+      'background',
+    );
     this.handleChangeFocus = this.handleChangeFocus.bind(this);
     this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
   }
@@ -93,12 +94,13 @@ class Column extends React.PureComponent {
       onResizeStop,
       handleComponentDrop,
       editMode,
-      cells,
     } = this.props;
 
     const columnItems = columnComponent.children || [];
     const backgroundStyle = backgroundStyleOptions.find(
-      opt => opt.value === (columnComponent.meta.background || BACKGROUND_TRANSPARENT),
+      opt =>
+        opt.value ===
+        (columnComponent.meta.background || BACKGROUND_TRANSPARENT),
     );
 
     return (
@@ -119,7 +121,9 @@ class Column extends React.PureComponent {
             widthStep={columnWidth}
             widthMultiple={columnComponent.meta.width}
             minWidthMultiple={minColumnWidth}
-            maxWidthMultiple={availableColumnCount + (columnComponent.meta.width || 0)}
+            maxWidthMultiple={
+              availableColumnCount + (columnComponent.meta.width || 0)
+            }
             onResizeStart={onResizeStart}
             onResize={onResize}
             onResizeStop={onResizeStop}
@@ -145,31 +149,33 @@ class Column extends React.PureComponent {
                   backgroundStyle.className,
                 )}
               >
-                {editMode &&
+                {editMode && (
                   <HoverMenu innerRef={dragSourceRef} position="top">
                     <DragHandle position="top" />
-                    <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+                    <DeleteComponentButton
+                      onDelete={this.handleDeleteComponent}
+                    />
                     <IconButton
                       onClick={this.handleChangeFocus}
                       className="fa fa-cog"
                     />
-                  </HoverMenu>}
+                  </HoverMenu>
+                )}
 
                 {columnItems.map((componentId, itemIndex) => (
-                    <DashboardComponent
-                      key={componentId}
-                      id={componentId}
-                      parentId={columnComponent.id}
-                      depth={depth + 1}
-                      index={itemIndex }
-                      availableColumnCount={columnComponent.meta.width}
-                      columnWidth={columnWidth}
-                      cells={cells}
-                      onResizeStart={onResizeStart}
-                      onResize={onResize}
-                      onResizeStop={onResizeStop}
-                    />
-                  ))}
+                  <DashboardComponent
+                    key={componentId}
+                    id={componentId}
+                    parentId={columnComponent.id}
+                    depth={depth + 1}
+                    index={itemIndex}
+                    availableColumnCount={columnComponent.meta.width}
+                    columnWidth={columnWidth}
+                    onResizeStart={onResizeStart}
+                    onResize={onResize}
+                    onResizeStop={onResizeStop}
+                  />
+                ))}
 
                 {dropIndicatorProps && <div {...dropIndicatorProps} />}
               </div>
@@ -177,7 +183,6 @@ class Column extends React.PureComponent {
           </ResizableContainer>
         )}
       </DragDroppable>
-
     );
   }
 }
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/Divider.jsx b/superset/assets/src/dashboard/components/gridComponents/Divider.jsx
similarity index 96%
rename from superset/assets/src/dashboard/v2/components/gridComponents/Divider.jsx
rename to superset/assets/src/dashboard/components/gridComponents/Divider.jsx
index b3010e9..7c7936d 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/Divider.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Divider.jsx
@@ -51,10 +51,11 @@ class Divider extends React.PureComponent {
       >
         {({ dropIndicatorProps, dragSourceRef }) => (
           <div ref={dragSourceRef}>
-            {editMode &&
+            {editMode && (
               <HoverMenu position="left">
                 <DeleteComponentButton onDelete={this.handleDeleteComponent} />
-              </HoverMenu>}
+              </HoverMenu>
+            )}
 
             <div className="dashboard-component dashboard-component-divider" />
 
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/Header.jsx b/superset/assets/src/dashboard/components/gridComponents/Header.jsx
similarity index 93%
rename from superset/assets/src/dashboard/v2/components/gridComponents/Header.jsx
rename to superset/assets/src/dashboard/components/gridComponents/Header.jsx
index 97945a9..5114a77 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/Header.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Header.jsx
@@ -4,7 +4,7 @@ import cx from 'classnames';
 
 import DragDroppable from '../dnd/DragDroppable';
 import DragHandle from '../dnd/DragHandle';
-import EditableTitle from '../../../../components/EditableTitle';
+import EditableTitle from '../../../components/EditableTitle';
 import HoverMenu from '../menu/HoverMenu';
 import WithPopoverMenu from '../menu/WithPopoverMenu';
 import BackgroundStyleDropdown from '../menu/BackgroundStyleDropdown';
@@ -30,8 +30,7 @@ const propTypes = {
   updateComponents: PropTypes.func.isRequired,
 };
 
-const defaultProps = {
-};
+const defaultProps = {};
 
 class Header extends React.PureComponent {
   constructor(props) {
@@ -43,7 +42,10 @@ class Header extends React.PureComponent {
     this.handleChangeFocus = this.handleChangeFocus.bind(this);
     this.handleUpdateMeta = this.handleUpdateMeta.bind(this);
     this.handleChangeSize = this.handleUpdateMeta.bind(this, 'headerSize');
-    this.handleChangeBackground = this.handleUpdateMeta.bind(this, 'background');
+    this.handleChangeBackground = this.handleUpdateMeta.bind(
+      this,
+      'background',
+    );
     this.handleChangeText = this.handleUpdateMeta.bind(this, 'text');
   }
 
@@ -88,7 +90,8 @@ class Header extends React.PureComponent {
     );
 
     const rowStyle = backgroundStyleOptions.find(
-      opt => opt.value === (component.meta.background || BACKGROUND_TRANSPARENT),
+      opt =>
+        opt.value === (component.meta.background || BACKGROUND_TRANSPARENT),
     );
 
     return (
@@ -104,10 +107,11 @@ class Header extends React.PureComponent {
       >
         {({ dropIndicatorProps, dragSourceRef }) => (
           <div ref={dragSourceRef}>
-            {editMode &&
+            {editMode && (
               <HoverMenu position="left">
                 <DragHandle position="left" />
-              </HoverMenu>}
+              </HoverMenu>
+            )}
 
             <WithPopoverMenu
               onChangeFocus={this.handleChangeFocus}
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/Row.jsx b/superset/assets/src/dashboard/components/gridComponents/Row.jsx
similarity index 82%
rename from superset/assets/src/dashboard/v2/components/gridComponents/Row.jsx
rename to superset/assets/src/dashboard/components/gridComponents/Row.jsx
index 8faaee1..91f200d 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/Row.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Row.jsx
@@ -23,7 +23,6 @@ const propTypes = {
   index: PropTypes.number.isRequired,
   depth: PropTypes.number.isRequired,
   editMode: PropTypes.bool.isRequired,
-  cells: PropTypes.object.isRequired,
 
   // grid related
   availableColumnCount: PropTypes.number.isRequired,
@@ -51,7 +50,10 @@ class Row extends React.PureComponent {
     };
     this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
     this.handleUpdateMeta = this.handleUpdateMeta.bind(this);
-    this.handleChangeBackground = this.handleUpdateMeta.bind(this, 'background');
+    this.handleChangeBackground = this.handleUpdateMeta.bind(
+      this,
+      'background',
+    );
     this.handleChangeFocus = this.handleChangeFocus.bind(this);
   }
 
@@ -93,13 +95,13 @@ class Row extends React.PureComponent {
       onResizeStop,
       handleComponentDrop,
       editMode,
-      cells,
     } = this.props;
 
     const rowItems = rowComponent.children || [];
 
     const backgroundStyle = backgroundStyleOptions.find(
-      opt => opt.value === (rowComponent.meta.background || BACKGROUND_TRANSPARENT),
+      opt =>
+        opt.value === (rowComponent.meta.background || BACKGROUND_TRANSPARENT),
     );
 
     return (
@@ -133,31 +135,35 @@ class Row extends React.PureComponent {
                 backgroundStyle.className,
               )}
             >
-              {editMode &&
+              {editMode && (
                 <HoverMenu innerRef={dragSourceRef} position="left">
                   <DragHandle position="left" />
-                  <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+                  <DeleteComponentButton
+                    onDelete={this.handleDeleteComponent}
+                  />
                   <IconButton
                     onClick={this.handleChangeFocus}
                     className="fa fa-cog"
                   />
-                </HoverMenu>}
+                </HoverMenu>
+              )}
 
               {rowItems.map((componentId, itemIndex) => (
-
-                  <DashboardComponent
-                    key={componentId}
-                    id={componentId}
-                    parentId={rowComponent.id}
-                    depth={depth + 1}
-                    index={itemIndex }
-                    availableColumnCount={availableColumnCount - occupiedColumnCount}
-                    columnWidth={columnWidth}
-                    cells={cells}onResizeStart={onResizeStart}
-                    onResize={onResize}
-                    onResizeStop={onResizeStop}
-                  />
-                ))}
+                <DashboardComponent
+                  key={componentId}
+                  id={componentId}
+                  parentId={rowComponent.id}
+                  depth={depth + 1}
+                  index={itemIndex}
+                  availableColumnCount={
+                    availableColumnCount - occupiedColumnCount
+                  }
+                  columnWidth={columnWidth}
+                  onResizeStart={onResizeStart}
+                  onResize={onResize}
+                  onResizeStop={onResizeStop}
+                />
+              ))}
 
               {dropIndicatorProps && <div {...dropIndicatorProps} />}
             </div>
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/Tab.jsx b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
similarity index 90%
rename from superset/assets/src/dashboard/v2/components/gridComponents/Tab.jsx
rename to superset/assets/src/dashboard/components/gridComponents/Tab.jsx
index 218c4e7..d73bc0c 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/Tab.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 
 import DashboardComponent from '../../containers/DashboardComponent';
 import DragDroppable from '../dnd/DragDroppable';
-import EditableTitle from '../../../../components/EditableTitle';
+import EditableTitle from '../../../components/EditableTitle';
 import DeleteComponentButton from '../DeleteComponentButton';
 import WithPopoverMenu from '../menu/WithPopoverMenu';
 import { componentShape } from '../../util/propShapes';
@@ -123,13 +123,7 @@ export default class Tab extends React.PureComponent {
 
   renderTab() {
     const { isFocused } = this.state;
-    const {
-      component,
-      parentComponent,
-      index,
-      depth,
-      editMode,
-    } = this.props;
+    const { component, parentComponent, index, depth, editMode } = this.props;
 
     return (
       <DragDroppable
@@ -149,9 +143,15 @@ export default class Tab extends React.PureComponent {
           <div className="dragdroppable-tab" ref={dragSourceRef}>
             <WithPopoverMenu
               onChangeFocus={this.handleChangeFocus}
-              menuItems={parentComponent.children.length <= 1 ? [] : [
-                <DeleteComponentButton onDelete={this.handleDeleteComponent} />,
-              ]}
+              menuItems={
+                parentComponent.children.length <= 1
+                  ? []
+                  : [
+                      <DeleteComponentButton
+                        onDelete={this.handleDeleteComponent}
+                      />,
+                    ]
+              }
               editMode={editMode}
             >
               <EditableTitle
@@ -171,7 +171,9 @@ export default class Tab extends React.PureComponent {
 
   render() {
     const { renderType } = this.props;
-    return renderType === RENDER_TAB ? this.renderTab() : this.renderTabContent();
+    return renderType === RENDER_TAB
+      ? this.renderTab()
+      : this.renderTabContent();
   }
 }
 
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/Tabs.jsx b/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx
similarity index 81%
rename from superset/assets/src/dashboard/v2/components/gridComponents/Tabs.jsx
rename to superset/assets/src/dashboard/components/gridComponents/Tabs.jsx
index 1f5f0c6..585041f 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/Tabs.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx
@@ -106,9 +106,10 @@ class Tabs extends React.PureComponent {
     // Ensure dropped tab is visible
     const { destination } = dropResult;
     if (destination) {
-      const dropTabIndex = destination.id === component.id
-        ? destination.index // dropped ON tabs
-        : component.children.indexOf(destination.id); // dropped IN tab
+      const dropTabIndex =
+        destination.id === component.id
+          ? destination.index // dropped ON tabs
+          : component.children.indexOf(destination.id); // dropped IN tab
 
       if (dropTabIndex > -1) {
         setTimeout(() => {
@@ -147,13 +148,17 @@ class Tabs extends React.PureComponent {
         onDrop={handleComponentDrop}
         editMode={editMode}
       >
-        {({ dropIndicatorProps: tabsDropIndicatorProps, dragSourceRef: tabsDragSourceRef }) => (
+        {({
+          dropIndicatorProps: tabsDropIndicatorProps,
+          dragSourceRef: tabsDragSourceRef,
+        }) => (
           <div className="dashboard-component dashboard-component-tabs">
-            {editMode &&
+            {editMode && (
               <HoverMenu innerRef={tabsDragSourceRef} position="left">
                 <DragHandle position="left" />
                 <DeleteComponentButton onDelete={this.handleDeleteComponent} />
-              </HoverMenu>}
+              </HoverMenu>
+            )}
 
             <BootstrapTabs
               id={tabsComponent.id}
@@ -187,37 +192,39 @@ class Tabs extends React.PureComponent {
                     render potentially-expensive charts (this also enables lazy loading
                     their content)
                   */}
-                  {tabIndex === selectedTabIndex && renderTabContent &&
-                    <DashboardComponent
-                      id={tabId}
-                      parentId={tabsComponent.id}
-                      depth={depth} // see isValidChild.js for why tabs don't increment child depth
-                      index={tabIndex}
-                      renderType={RENDER_TAB_CONTENT}
-                      availableColumnCount={availableColumnCount}
-                      columnWidth={columnWidth}
-                      onResizeStart={onResizeStart}
-                      onResize={onResize}
-                      onResizeStop={onResizeStop}
-                      onDropOnTab={this.handleDropOnTab}
-                    />}
+                  {tabIndex === selectedTabIndex &&
+                    renderTabContent && (
+                      <DashboardComponent
+                        id={tabId}
+                        parentId={tabsComponent.id}
+                        depth={depth} // see isValidChild.js for why tabs don't increment child depth
+                        index={tabIndex}
+                        renderType={RENDER_TAB_CONTENT}
+                        availableColumnCount={availableColumnCount}
+                        columnWidth={columnWidth}
+                        onResizeStart={onResizeStart}
+                        onResize={onResize}
+                        onResizeStop={onResizeStop}
+                        onDropOnTab={this.handleDropOnTab}
+                      />
+                    )}
                 </BootstrapTab>
               ))}
 
               {editMode &&
-                tabIds.length < MAX_TAB_COUNT &&
+                tabIds.length < MAX_TAB_COUNT && (
                   <BootstrapTab
                     eventKey={NEW_TAB_INDEX}
                     title={<div className="fa fa-plus" />}
-                  />}
-
+                  />
+                )}
             </BootstrapTabs>
 
             {/* don't indicate that a drop on root is allowed when tabs already exist */}
-            {tabsDropIndicatorProps
-              && parentComponent.id !== DASHBOARD_ROOT_ID
-              && <div {...tabsDropIndicatorProps} />}
-
+            {tabsDropIndicatorProps &&
+              parentComponent.id !== DASHBOARD_ROOT_ID && (
+                <div {...tabsDropIndicatorProps} />
+              )}
           </div>
         )}
       </DragDroppable>
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/index.js b/superset/assets/src/dashboard/components/gridComponents/index.js
similarity index 94%
rename from superset/assets/src/dashboard/v2/components/gridComponents/index.js
rename to superset/assets/src/dashboard/components/gridComponents/index.js
index ef6d13f..016ab03 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/index.js
+++ b/superset/assets/src/dashboard/components/gridComponents/index.js
@@ -3,7 +3,6 @@ import {
   COLUMN_TYPE,
   DIVIDER_TYPE,
   HEADER_TYPE,
-  INVISIBLE_ROW_TYPE,
   ROW_TYPE,
   TAB_TYPE,
   TABS_TYPE,
@@ -30,7 +29,6 @@ export default {
   [COLUMN_TYPE]: Column,
   [DIVIDER_TYPE]: Divider,
   [HEADER_TYPE]: Header,
-  [INVISIBLE_ROW_TYPE]: Row,
   [ROW_TYPE]: Row,
   [TAB_TYPE]: Tab,
   [TABS_TYPE]: Tabs,
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx b/superset/assets/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx
similarity index 90%
rename from superset/assets/src/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx
rename to superset/assets/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx
index eebd6e0..d579dc1 100644
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/new/DraggableNewComponent.jsx
@@ -23,7 +23,10 @@ export default class DraggableNewComponent extends React.PureComponent {
     return (
       <DragDroppable
         component={{ type, id }}
-        parentComponent={{ id: NEW_COMPONENTS_SOURCE_ID, type: NEW_COMPONENT_SOURCE_TYPE }}
+        parentComponent={{
+          id: NEW_COMPONENTS_SOURCE_ID,
+          type: NEW_COMPONENT_SOURCE_TYPE,
+        }}
         index={0}
         depth={0}
         editMode
diff --git a/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx b/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx
new file mode 100644
index 0000000..f624e58
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { COLUMN_TYPE } from '../../../util/componentTypes';
+import { NEW_COLUMN_ID } from '../../../util/constants';
+import DraggableNewComponent from './DraggableNewComponent';
+
+export default function DraggableNewColumn() {
+  return (
+    <DraggableNewComponent
+      id={NEW_COLUMN_ID}
+      type={COLUMN_TYPE}
+      label="Column"
+      className="fa fa-long-arrow-down"
+    />
+  );
+}
diff --git a/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx b/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx
new file mode 100644
index 0000000..de07a24
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { DIVIDER_TYPE } from '../../../util/componentTypes';
+import { NEW_DIVIDER_ID } from '../../../util/constants';
+import DraggableNewComponent from './DraggableNewComponent';
+
+export default function DraggableNewDivider() {
+  return (
+    <DraggableNewComponent
+      id={NEW_DIVIDER_ID}
+      type={DIVIDER_TYPE}
+      label="Divider"
+      className="divider-placeholder"
+    />
+  );
+}
diff --git a/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx b/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx
new file mode 100644
index 0000000..50bd600
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { HEADER_TYPE } from '../../../util/componentTypes';
+import { NEW_HEADER_ID } from '../../../util/constants';
+import DraggableNewComponent from './DraggableNewComponent';
+
+export default function DraggableNewHeader() {
+  return (
+    <DraggableNewComponent
+      id={NEW_HEADER_ID}
+      type={HEADER_TYPE}
+      label="Header"
+      className="fa fa-header"
+    />
+  );
+}
diff --git a/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx b/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx
new file mode 100644
index 0000000..81bdc93
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { ROW_TYPE } from '../../../util/componentTypes';
+import { NEW_ROW_ID } from '../../../util/constants';
+import DraggableNewComponent from './DraggableNewComponent';
+
+export default function DraggableNewRow() {
+  return (
+    <DraggableNewComponent
+      id={NEW_ROW_ID}
+      type={ROW_TYPE}
+      label="Row"
+      className="fa fa-long-arrow-right"
+    />
+  );
+}
diff --git a/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx b/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx
new file mode 100644
index 0000000..fd9366b
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { TABS_TYPE } from '../../../util/componentTypes';
+import { NEW_TABS_ID } from '../../../util/constants';
+import DraggableNewComponent from './DraggableNewComponent';
+
+export default function DraggableNewTabs() {
+  return (
+    <DraggableNewComponent
+      id={NEW_TABS_ID}
+      type={TABS_TYPE}
+      label="Tabs"
+      className="fa fa-window-restore"
+    />
+  );
+}
diff --git a/superset/assets/src/dashboard/v2/components/menu/BackgroundStyleDropdown.jsx b/superset/assets/src/dashboard/components/menu/BackgroundStyleDropdown.jsx
similarity index 100%
rename from superset/assets/src/dashboard/v2/components/menu/BackgroundStyleDropdown.jsx
rename to superset/assets/src/dashboard/components/menu/BackgroundStyleDropdown.jsx
diff --git a/superset/assets/src/dashboard/v2/components/menu/HoverMenu.jsx b/superset/assets/src/dashboard/components/menu/HoverMenu.jsx
similarity index 100%
rename from superset/assets/src/dashboard/v2/components/menu/HoverMenu.jsx
rename to superset/assets/src/dashboard/components/menu/HoverMenu.jsx
diff --git a/superset/assets/src/dashboard/v2/components/menu/PopoverDropdown.jsx b/superset/assets/src/dashboard/components/menu/PopoverDropdown.jsx
similarity index 94%
rename from superset/assets/src/dashboard/v2/components/menu/PopoverDropdown.jsx
rename to superset/assets/src/dashboard/components/menu/PopoverDropdown.jsx
index 6a56eab..4971793 100644
--- a/superset/assets/src/dashboard/v2/components/menu/PopoverDropdown.jsx
+++ b/superset/assets/src/dashboard/components/menu/PopoverDropdown.jsx
@@ -19,7 +19,9 @@ const propTypes = {
 
 const defaultProps = {
   renderButton: option => option.label,
-  renderOption: option => <div className={option.className}>{option.label}</div>,
+  renderOption: option => (
+    <div className={option.className}>{option.label}</div>
+  ),
 };
 
 class PopoverDropdown extends React.PureComponent {
diff --git a/superset/assets/src/dashboard/v2/components/menu/WithPopoverMenu.jsx b/superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx
similarity index 91%
rename from superset/assets/src/dashboard/v2/components/menu/WithPopoverMenu.jsx
rename to superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx
index f213442..8a87fca 100644
--- a/superset/assets/src/dashboard/v2/components/menu/WithPopoverMenu.jsx
+++ b/superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx
@@ -54,12 +54,15 @@ class WithPopoverMenu extends React.PureComponent {
   }
 
   handleClick(event) {
-    const { onChangeFocus, shouldFocus: shouldFocusFunc, disableClick, editMode } = this.props;
-    const shouldFocus = shouldFocusFunc(event, this.container);
-
-    if (!editMode) {
+    if (!this.props.editMode) {
       return;
     }
+    const {
+      onChangeFocus,
+      shouldFocus: shouldFocusFunc,
+      disableClick,
+    } = this.props;
+    const shouldFocus = shouldFocusFunc(event, this.container);
 
     if (!disableClick && shouldFocus && !this.state.isFocused) {
       // if not focused, set focus and add a window event listener to capture outside clicks
@@ -97,12 +100,15 @@ class WithPopoverMenu extends React.PureComponent {
         {children}
         {editMode &&
           isFocused &&
-          menuItems.length > 0 &&
-            <div className="popover-menu" >
+          menuItems.length > 0 && (
+            <div className="popover-menu">
               {menuItems.map((node, i) => (
-                <div className="menu-item" key={`menu-item-${i}`}>{node}</div>
+                <div className="menu-item" key={`menu-item-${i}`}>
+                  {node}
+                </div>
               ))}
-            </div>}
+            </div>
+          )}
       </div>
     );
   }
diff --git a/superset/assets/src/dashboard/v2/components/resizable/ResizableContainer.jsx b/superset/assets/src/dashboard/components/resizable/ResizableContainer.jsx
similarity index 78%
rename from superset/assets/src/dashboard/v2/components/resizable/ResizableContainer.jsx
rename to superset/assets/src/dashboard/components/resizable/ResizableContainer.jsx
index a532ff0..7e09e73 100644
--- a/superset/assets/src/dashboard/v2/components/resizable/ResizableContainer.jsx
+++ b/superset/assets/src/dashboard/components/resizable/ResizableContainer.jsx
@@ -56,7 +56,10 @@ const defaultProps = {
 // because columns are not multiples of a single variable (width = n*cols + (n-1) * gutters)
 // we snap to the base unit and then snap to _actual_ column multiples on stop
 const SNAP_TO_GRID = [GRID_BASE_UNIT, GRID_BASE_UNIT];
-
+const HANDLE_CLASSES = {
+  right: 'resizable-container-handle--right',
+  bottom: 'resizable-container-handle--bottom',
+};
 class ResizableContainer extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -139,29 +142,26 @@ class ResizableContainer extends React.PureComponent {
 
     const size = {
       width: adjustableWidth
-        ? ((widthStep + gutterWidth) * widthMultiple) - gutterWidth
-        : (staticWidthMultiple && staticWidthMultiple * widthStep)
-          || staticWidth
-          || undefined,
+        ? (widthStep + gutterWidth) * widthMultiple - gutterWidth
+        : (staticWidthMultiple && staticWidthMultiple * widthStep) ||
+          staticWidth ||
+          undefined,
       height: adjustableHeight
         ? heightStep * heightMultiple
-        : (staticHeightMultiple && staticHeightMultiple * heightStep)
-          || staticHeight
-          || undefined,
+        : (staticHeightMultiple && staticHeightMultiple * heightStep) ||
+          staticHeight ||
+          undefined,
     };
 
-    if (!editMode) {
-      return (
-        <div style={{ ...size }}>
-          {children}
-        </div>
-      );
-    }
-
     let enableConfig = resizableConfig.notAdjustable;
-    if (adjustableWidth && adjustableHeight) enableConfig = resizableConfig.widthAndHeight;
-    else if (adjustableWidth) enableConfig = resizableConfig.widthOnly;
-    else if (adjustableHeight) enableConfig = resizableConfig.heightOnly;
+
+    if (editMode && adjustableWidth && adjustableHeight) {
+      enableConfig = resizableConfig.widthAndHeight;
+    } else if (editMode && adjustableWidth) {
+      enableConfig = resizableConfig.widthOnly;
+    } else if (editMode && adjustableHeight) {
+      enableConfig = resizableConfig.heightOnly;
+    }
 
     const { isResizing } = this.state;
 
@@ -169,18 +169,27 @@ class ResizableContainer extends React.PureComponent {
       <Resizable
         enable={enableConfig}
         grid={SNAP_TO_GRID}
-        minWidth={adjustableWidth
-          ? (minWidthMultiple * (widthStep + gutterWidth)) - gutterWidth
-          : undefined}
-        minHeight={adjustableHeight
-          ? (minHeightMultiple * heightStep)
-          : undefined}
-        maxWidth={adjustableWidth
-          ? Math.max(size.width, (maxWidthMultiple * (widthStep + gutterWidth)) - gutterWidth)
-          : undefined}
-        maxHeight={adjustableHeight
-          ? Math.max(size.height, maxHeightMultiple * heightStep)
-          : undefined}
+        minWidth={
+          adjustableWidth
+            ? minWidthMultiple * (widthStep + gutterWidth) - gutterWidth
+            : undefined
+        }
+        minHeight={
+          adjustableHeight ? minHeightMultiple * heightStep : undefined
+        }
+        maxWidth={
+          adjustableWidth
+            ? Math.max(
+                size.width,
+                maxWidthMultiple * (widthStep + gutterWidth) - gutterWidth,
+              )
+            : undefined
+        }
+        maxHeight={
+          adjustableHeight
+            ? Math.max(size.height, maxHeightMultiple * heightStep)
+            : undefined
+        }
         size={size}
         onResizeStart={this.handleResizeStart}
         onResize={this.handleResize}
@@ -190,6 +199,7 @@ class ResizableContainer extends React.PureComponent {
           'resizable-container',
           isResizing && 'resizable-container--resizing',
         )}
+        handleClasses={HANDLE_CLASSES}
       >
         {children}
       </Resizable>
diff --git a/superset/assets/src/dashboard/v2/components/resizable/ResizableHandle.jsx b/superset/assets/src/dashboard/components/resizable/ResizableHandle.jsx
similarity index 54%
rename from superset/assets/src/dashboard/v2/components/resizable/ResizableHandle.jsx
rename to superset/assets/src/dashboard/components/resizable/ResizableHandle.jsx
index 9536f6b..b696b26 100644
--- a/superset/assets/src/dashboard/v2/components/resizable/ResizableHandle.jsx
+++ b/superset/assets/src/dashboard/components/resizable/ResizableHandle.jsx
@@ -1,21 +1,15 @@
 import React from 'react';
 
 export function BottomRightResizeHandle() {
-  return (
-    <div className="resize-handle resize-handle--bottom-right" />
-  );
+  return <div className="resize-handle resize-handle--bottom-right" />;
 }
 
 export function RightResizeHandle() {
-  return (
-    <div className="resize-handle resize-handle--right" />
-  );
+  return <div className="resize-handle resize-handle--right" />;
 }
 
 export function BottomResizeHandle() {
-  return (
-    <div className="resize-handle resize-handle--bottom" />
-  );
+  return <div className="resize-handle resize-handle--bottom" />;
 }
 
 export default {
diff --git a/superset/assets/src/dashboard/containers/Chart.jsx b/superset/assets/src/dashboard/containers/Chart.jsx
new file mode 100644
index 0000000..470176b
--- /dev/null
+++ b/superset/assets/src/dashboard/containers/Chart.jsx
@@ -0,0 +1,59 @@
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import {
+  addFilter,
+  removeFilter,
+  toggleExpandSlice,
+} from '../actions/dashboardState';
+import { refreshChart } from '../../chart/chartAction';
+import getFormDataWithExtraFilters from '../util/charts/getFormDataWithExtraFilters';
+import { saveSliceName } from '../actions/sliceEntities';
+import Chart from '../components/gridComponents/Chart';
+
+function mapStateToProps(
+  {
+    charts: chartQueries,
+    dashboardInfo,
+    dashboardState,
+    datasources,
+    sliceEntities,
+  },
+  ownProps,
+) {
+  const { id } = ownProps;
+  const chart = chartQueries[id];
+  const { filters } = dashboardState;
+
+  return {
+    chart,
+    datasource: datasources[chart.form_data.datasource],
+    slice: sliceEntities.slices[id],
+    timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
+    filters,
+    // note: this method caches filters if possible to prevent render cascades
+    formData: getFormDataWithExtraFilters({
+      chart,
+      dashboardMetadata: dashboardInfo.metadata,
+      filters,
+      sliceId: id,
+    }),
+    editMode: dashboardState.editMode,
+    isExpanded: !!dashboardState.expandedSlices[id],
+  };
+}
+
+function mapDispatchToProps(dispatch) {
+  return bindActionCreators(
+    {
+      saveSliceName,
+      toggleExpandSlice,
+      addFilter,
+      refreshChart,
+      removeFilter,
+    },
+    dispatch,
+  );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Chart);
diff --git a/superset/assets/src/dashboard/containers/Dashboard.jsx b/superset/assets/src/dashboard/containers/Dashboard.jsx
new file mode 100644
index 0000000..9af0e81
--- /dev/null
+++ b/superset/assets/src/dashboard/containers/Dashboard.jsx
@@ -0,0 +1,49 @@
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import {
+  addSliceToDashboard,
+  removeSliceFromDashboard,
+  onChange,
+} from '../actions/dashboardState';
+import { runQuery } from '../../chart/chartAction';
+import Dashboard from '../components/Dashboard';
+
+function mapStateToProps({
+  datasources,
+  sliceEntities,
+  charts,
+  dashboardInfo,
+  dashboardState,
+  dashboardLayout,
+  impressionId,
+}) {
+  return {
+    initMessages: dashboardInfo.common.flash_messages,
+    timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
+    userId: dashboardInfo.userId,
+    dashboardInfo,
+    dashboardState,
+    charts,
+    datasources,
+    slices: sliceEntities.slices,
+    layout: dashboardLayout.present,
+    impressionId,
+  };
+}
+
+function mapDispatchToProps(dispatch) {
+  return {
+    actions: bindActionCreators(
+      {
+        addSliceToDashboard,
+        onChange,
+        removeSliceFromDashboard,
+        runQuery,
+      },
+      dispatch,
+    ),
+  };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
diff --git a/superset/assets/src/dashboard/v2/containers/DashboardBuilder.jsx b/superset/assets/src/dashboard/containers/DashboardBuilder.jsx
similarity index 66%
rename from superset/assets/src/dashboard/v2/containers/DashboardBuilder.jsx
rename to superset/assets/src/dashboard/containers/DashboardBuilder.jsx
index 62fc94a..6bece3d 100644
--- a/superset/assets/src/dashboard/v2/containers/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardBuilder.jsx
@@ -7,20 +7,22 @@ import {
   handleComponentDrop,
 } from '../actions/dashboardLayout';
 
-function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState: dashboard }, ownProps) {
+function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState }) {
   return {
     dashboardLayout: undoableLayout.present,
-    cells: ownProps.cells,
-    editMode: dashboard.editMode,
-    showBuilderPane: dashboard.showBuilderPane,
+    editMode: dashboardState.editMode,
+    showBuilderPane: dashboardState.showBuilderPane,
   };
 }
 
 function mapDispatchToProps(dispatch) {
-  return bindActionCreators({
-    deleteTopLevelTabs,
-    handleComponentDrop,
-  }, dispatch);
+  return bindActionCreators(
+    {
+      deleteTopLevelTabs,
+      handleComponentDrop,
+    },
+    dispatch,
+  );
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(DashboardBuilder);
diff --git a/superset/assets/src/dashboard/v2/containers/DashboardComponent.jsx b/superset/assets/src/dashboard/containers/DashboardComponent.jsx
similarity index 73%
rename from superset/assets/src/dashboard/v2/containers/DashboardComponent.jsx
rename to superset/assets/src/dashboard/containers/DashboardComponent.jsx
index 01f7805..650313e 100644
--- a/superset/assets/src/dashboard/v2/containers/DashboardComponent.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardComponent.jsx
@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
 import ComponentLookup from '../components/gridComponents';
 import getTotalChildWidth from '../util/getChildWidth';
 import { componentShape } from '../util/propShapes';
-import { CHART_TYPE, COLUMN_TYPE, ROW_TYPE } from '../util/componentTypes';
+import { COLUMN_TYPE, ROW_TYPE } from '../util/componentTypes';
 import { GRID_MIN_COLUMN_COUNT } from '../util/constants';
 
 import {
@@ -25,24 +25,36 @@ const propTypes = {
   handleComponentDrop: PropTypes.func.isRequired,
 };
 
-function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState: dashboard }, ownProps) {
+function mapStateToProps(
+  {
+    dashboardLayout: undoableLayout,
+    dashboardState,
+    sliceEntities,
+    charts,
+    datasources,
+  },
+  ownProps,
+) {
   const dashboardLayout = undoableLayout.present;
-  const { id, parentId, cells } = ownProps;
+  const { id, parentId } = ownProps;
   const component = dashboardLayout[id];
   const props = {
     component,
     parentComponent: dashboardLayout[parentId],
-    editMode: dashboard.editMode,
+    editMode: dashboardState.editMode,
   };
 
   // rows and columns need more data about their child dimensions
   // doing this allows us to not pass the entire component lookup to all Components
   if (props.component.type === ROW_TYPE) {
-    props.occupiedColumnCount = getTotalChildWidth({ id, components: dashboardLayout });
+    props.occupiedColumnCount = getTotalChildWidth({
+      id,
+      components: dashboardLayout,
+    });
   } else if (props.component.type === COLUMN_TYPE) {
     props.minColumnWidth = GRID_MIN_COLUMN_COUNT;
 
-    component.children.forEach((childId) => {
+    component.children.forEach(childId => {
       // rows don't have widths, so find the width of its children
       if (dashboardLayout[childId].type === ROW_TYPE) {
         props.minColumnWidth = Math.max(
@@ -51,23 +63,21 @@ function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState: dash
         );
       }
     });
-  } else if (props.component.type === CHART_TYPE) {
-    const chartId = props.component.meta && props.component.meta.chartId;
-    if (chartId) {
-      props.chart = cells[chartId];
-    }
   }
 
   return props;
 }
 
 function mapDispatchToProps(dispatch) {
-  return bindActionCreators({
-    createComponent,
-    deleteComponent,
-    updateComponents,
-    handleComponentDrop,
-  }, dispatch);
+  return bindActionCreators(
+    {
+      createComponent,
+      deleteComponent,
+      updateComponents,
+      handleComponentDrop,
+    },
+    dispatch,
+  );
 }
 
 class DashboardComponent extends React.PureComponent {
diff --git a/superset/assets/src/dashboard/v2/containers/DashboardGrid.jsx b/superset/assets/src/dashboard/containers/DashboardGrid.jsx
similarity index 62%
rename from superset/assets/src/dashboard/v2/containers/DashboardGrid.jsx
rename to superset/assets/src/dashboard/containers/DashboardGrid.jsx
index 2adc390..718b543 100644
--- a/superset/assets/src/dashboard/v2/containers/DashboardGrid.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardGrid.jsx
@@ -7,18 +7,20 @@ import {
   resizeComponent,
 } from '../actions/dashboardLayout';
 
-function mapStateToProps({ dashboardState: dashboard }, ownProps) {
+function mapStateToProps({ dashboardState }) {
   return {
-    editMode: dashboard.editMode,
-    cells: ownProps.cells,
+    editMode: dashboardState.editMode,
   };
 }
 
 function mapDispatchToProps(dispatch) {
-  return bindActionCreators({
-    handleComponentDrop,
-    resizeComponent,
-  }, dispatch);
+  return bindActionCreators(
+    {
+      handleComponentDrop,
+      resizeComponent,
+    },
+    dispatch,
+  );
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(DashboardGrid);
diff --git a/superset/assets/src/dashboard/v2/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
similarity index 59%
rename from superset/assets/src/dashboard/v2/containers/DashboardHeader.jsx
rename to superset/assets/src/dashboard/containers/DashboardHeader.jsx
index cc8e944..2b3431a 100644
--- a/superset/assets/src/dashboard/v2/containers/DashboardHeader.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
@@ -2,7 +2,7 @@ import { ActionCreators as UndoActionCreators } from 'redux-undo';
 import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 
-import DashboardHeader from '../../components/Header';
+import DashboardHeader from '../components/Header';
 import {
   setEditMode,
   toggleBuilderPane,
@@ -13,13 +13,15 @@ import {
   updateDashboardTitle,
   onChange,
   onSave,
-} from '../../actions/dashboardState';
-import {
-  handleComponentDrop,
-} from '../actions/dashboardLayout';
+} from '../actions/dashboardState';
+import { handleComponentDrop } from '../actions/dashboardLayout';
 
-function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState: dashboard,
-                           dashboardInfo, charts }) {
+function mapStateToProps({
+  dashboardLayout: undoableLayout,
+  dashboardState: dashboard,
+  dashboardInfo,
+  charts,
+}) {
   return {
     dashboardInfo,
     canUndo: undoableLayout.past.length > 0,
@@ -38,21 +40,23 @@ function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState: dash
 }
 
 function mapDispatchToProps(dispatch) {
-  return bindActionCreators({
-    handleComponentDrop,
-    onUndo: UndoActionCreators.undo,
-    onRedo: UndoActionCreators.redo,
-    setEditMode,
-    toggleBuilderPane,
-    fetchFaveStar,
-    saveFaveStar,
-    fetchCharts,
-    startPeriodicRender,
-    updateDashboardTitle,
-    onChange,
-    onSave,
-  }, dispatch);
+  return bindActionCreators(
+    {
+      handleComponentDrop,
+      onUndo: UndoActionCreators.undo,
+      onRedo: UndoActionCreators.redo,
+      setEditMode,
+      toggleBuilderPane,
+      fetchFaveStar,
+      saveFaveStar,
+      fetchCharts,
+      startPeriodicRender,
+      updateDashboardTitle,
+      onChange,
+      onSave,
+    },
+    dispatch,
+  );
 }
 
-
 export default connect(mapStateToProps, mapDispatchToProps)(DashboardHeader);
diff --git a/superset/assets/src/dashboard/v2/containers/ToastPresenter.jsx b/superset/assets/src/dashboard/containers/ToastPresenter.jsx
similarity index 100%
rename from superset/assets/src/dashboard/v2/containers/ToastPresenter.jsx
rename to superset/assets/src/dashboard/containers/ToastPresenter.jsx
diff --git a/superset/assets/src/dashboard/v2/fixtures/emptyDashboardLayout.js b/superset/assets/src/dashboard/fixtures/emptyDashboardLayout.js
similarity index 92%
rename from superset/assets/src/dashboard/v2/fixtures/emptyDashboardLayout.js
rename to superset/assets/src/dashboard/fixtures/emptyDashboardLayout.js
index 7816cc2..cee948a 100644
--- a/superset/assets/src/dashboard/v2/fixtures/emptyDashboardLayout.js
+++ b/superset/assets/src/dashboard/fixtures/emptyDashboardLayout.js
@@ -14,9 +14,7 @@ export default {
   [DASHBOARD_ROOT_ID]: {
     type: DASHBOARD_ROOT_TYPE,
     id: DASHBOARD_ROOT_ID,
-    children: [
-      DASHBOARD_GRID_ID,
-    ],
+    children: [DASHBOARD_GRID_ID],
   },
 
   [DASHBOARD_GRID_ID]: {
diff --git a/superset/assets/src/dashboard/index.jsx b/superset/assets/src/dashboard/index.jsx
index 9c00f9e..846b82d 100644
--- a/superset/assets/src/dashboard/index.jsx
+++ b/superset/assets/src/dashboard/index.jsx
@@ -7,7 +7,7 @@ import thunk from 'redux-thunk';
 import { initEnhancer } from '../reduxUtils';
 import { appSetup } from '../common';
 import { initJQueryAjax } from '../modules/utils';
-import DashboardContainer from './components/DashboardContainer';
+import DashboardContainer from './containers/Dashboard';
 import getInitialState from './reducers/getInitialState';
 import rootReducer from './reducers/index';
 
@@ -19,7 +19,10 @@ const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
 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}>
diff --git a/superset/assets/src/dashboard/v2/reducers/dashboardLayout.js b/superset/assets/src/dashboard/reducers/dashboardLayout.js
similarity index 76%
rename from superset/assets/src/dashboard/v2/reducers/dashboardLayout.js
rename to superset/assets/src/dashboard/reducers/dashboardLayout.js
index 994ac47..573a143 100644
--- a/superset/assets/src/dashboard/v2/reducers/dashboardLayout.js
+++ b/superset/assets/src/dashboard/reducers/dashboardLayout.js
@@ -1,4 +1,9 @@
-import { DASHBOARD_ROOT_ID, DASHBOARD_GRID_ID, NEW_COMPONENTS_SOURCE_ID } from '../util/constants';
+import {
+  DASHBOARD_ROOT_ID,
+  DASHBOARD_GRID_ID,
+  GRID_MIN_COLUMN_COUNT,
+  NEW_COMPONENTS_SOURCE_ID,
+} from '../util/constants';
 import newComponentFactory from '../util/newComponentFactory';
 import newEntitiesFromDrop from '../util/newEntitiesFromDrop';
 import reorderItem from '../util/dnd-reorder';
@@ -23,7 +28,9 @@ import {
 
 const actionHandlers = {
   [UPDATE_COMPONENTS](state, action) {
-    const { payload: { nextComponents } } = action;
+    const {
+      payload: { nextComponents },
+    } = action;
     return {
       ...state,
       ...nextComponents,
@@ -31,7 +38,9 @@ const actionHandlers = {
   },
 
   [DELETE_COMPONENT](state, action) {
-    const { payload: { id, parentId } } = action;
+    const {
+      payload: { id, parentId },
+    } = action;
 
     if (!parentId || !id || !state[id] || !state[parentId]) return state;
 
@@ -44,10 +53,13 @@ const actionHandlers = {
       delete nextComponents[componentId];
 
       const { children = [] } = component;
-      children.forEach((childId) => { recursivelyDeleteChildren(childId, componentId); });
+      children.forEach(childId => {
+        recursivelyDeleteChildren(childId, componentId);
+      });
 
       const parent = nextComponents[componentParentId];
-      if (parent) { // may have been deleted in another recursion
+      if (parent) {
+        // may have been deleted in another recursion
         const componentIndex = (parent.children || []).indexOf(componentId);
         if (componentIndex > -1) {
           const nextChildren = [...parent.children];
@@ -66,21 +78,28 @@ const actionHandlers = {
   },
 
   [CREATE_COMPONENT](state, action) {
-    const { payload: { dropResult } } = action;
+    const {
+      payload: { dropResult },
+    } = action;
     const { destination, dragging } = dropResult;
     const newEntities = newEntitiesFromDrop({ dropResult, components: state });
 
-    // inherit the width of a column parent
-    if (destination.type === COLUMN_TYPE && [CHART_TYPE, MARKDOWN_TYPE].includes(dragging.type)) {
+    // if column is a parent, set any resizable children to have a minimum width so that
+    // the chances that they are validly movable to future containers is maximized
+    if (
+      destination.type === COLUMN_TYPE &&
+      [CHART_TYPE, MARKDOWN_TYPE].includes(dragging.type)
+    ) {
       const newEntitiesArray = Object.values(newEntities);
-      const component = newEntitiesArray.find(entity => entity.type === dragging.type);
-      const parentColumn = newEntities[destination.id];
+      const component = newEntitiesArray.find(
+        entity => entity.type === dragging.type,
+      );
 
       newEntities[component.id] = {
         ...component,
         meta: {
           ...component.meta,
-          width: parentColumn.meta.width,
+          width: GRID_MIN_COLUMN_COUNT,
         },
       };
     }
@@ -92,7 +111,9 @@ const actionHandlers = {
   },
 
   [MOVE_COMPONENT](state, action) {
-    const { payload: { dropResult } } = action;
+    const {
+      payload: { dropResult },
+    } = action;
     const { source, destination, dragging } = dropResult;
 
     if (!source || !destination || !dragging) return state;
@@ -119,7 +140,10 @@ const actionHandlers = {
     }
 
     // inherit the width of a column parent
-    if (destination.type === COLUMN_TYPE && [CHART_TYPE, MARKDOWN_TYPE].includes(dragging.type)) {
+    if (
+      destination.type === COLUMN_TYPE &&
+      [CHART_TYPE, MARKDOWN_TYPE].includes(dragging.type)
+    ) {
       const component = nextEntities[dragging.id];
       const parentColumn = nextEntities[destination.id];
       nextEntities[dragging.id] = {
@@ -138,7 +162,9 @@ const actionHandlers = {
   },
 
   [CREATE_TOP_LEVEL_TABS](state, action) {
-    const { payload: { dropResult } } = action;
+    const {
+      payload: { dropResult },
+    } = action;
     const { source, dragging } = dropResult;
 
     // move children of current root to be children of the dragging tab
@@ -153,7 +179,9 @@ const actionHandlers = {
       const draggingTab = state[draggingTabId];
 
       // move all children except the one that is dragging
-      const childrenToMove = [...topLevelComponent.children].filter(id => id !== dragging.id);
+      const childrenToMove = [...topLevelComponent.children].filter(
+        id => id !== dragging.id,
+      );
 
       return {
         ...state,
@@ -167,10 +195,7 @@ const actionHandlers = {
         },
         [draggingTabId]: {
           ...draggingTab,
-          children: [
-            ...draggingTab.children,
-            ...childrenToMove,
-          ],
+          children: [...draggingTab.children, ...childrenToMove],
         },
       };
     }
@@ -178,12 +203,19 @@ const actionHandlers = {
     // create new component
     const newEntities = newEntitiesFromDrop({ dropResult, components: state });
     const newEntitiesArray = Object.values(newEntities);
-    const tabComponent = newEntitiesArray.find(component => component.type === TAB_TYPE);
-    const tabsComponent = newEntitiesArray.find(component => component.type === TABS_TYPE);
+    const tabComponent = newEntitiesArray.find(
+      component => component.type === TAB_TYPE,
+    );
+    const tabsComponent = newEntitiesArray.find(
+      component => component.type === TABS_TYPE,
+    );
 
     tabComponent.children = [...topLevelComponent.children];
     newEntities[topLevelId] = { ...topLevelComponent, children: [] };
-    newEntities[DASHBOARD_ROOT_ID] = { ...rootComponent, children: [tabsComponent.id] };
+    newEntities[DASHBOARD_ROOT_ID] = {
+      ...rootComponent,
+      children: [tabsComponent.id],
+    };
 
     return {
       ...state,
@@ -201,7 +233,7 @@ const actionHandlers = {
     let childrenToMove = [];
     const nextEntities = { ...state };
 
-    topLevelTabs.children.forEach((tabId) => {
+    topLevelTabs.children.forEach(tabId => {
       const tabComponent = state[tabId];
       childrenToMove = [...childrenToMove, ...tabComponent.children];
       delete nextEntities[tabId];
@@ -215,7 +247,7 @@ const actionHandlers = {
     };
 
     nextEntities[DASHBOARD_GRID_ID] = {
-      ...(state[DASHBOARD_GRID_ID]),
+      ...state[DASHBOARD_GRID_ID],
       children: childrenToMove,
     };
 
diff --git a/superset/assets/src/dashboard/reducers/dashboardState.js b/superset/assets/src/dashboard/reducers/dashboardState.js
index 84ee58e..7b5a17a 100644
--- a/superset/assets/src/dashboard/reducers/dashboardState.js
+++ b/superset/assets/src/dashboard/reducers/dashboardState.js
@@ -9,13 +9,14 @@ import {
   REMOVE_SLICE,
   REMOVE_FILTER,
   SET_EDIT_MODE,
+  SET_UNSAVED_CHANGES,
   TOGGLE_BUILDER_PANE,
   TOGGLE_EXPAND_SLICE,
   TOGGLE_FAVE_STAR,
   UPDATE_DASHBOARD_TITLE,
 } from '../actions/dashboardState';
 
-export default function (state = {}, action) {
+export default function dashboardStateReducer(state = {}, action) {
   const actionHandlers = {
     [UPDATE_DASHBOARD_TITLE]() {
       return { ...state, title: action.title };
@@ -84,15 +85,23 @@ export default function (state = {}, action) {
       let filters = state.filters;
       const { chart, col, vals, merge, refresh } = action;
       const sliceId = chart.id;
-      const filterKeys = ['__from', '__to', '__time_col',
-        '__time_grain', '__time_origin', '__granularity'];
-      if (filterKeys.indexOf(col) >= 0 ||
-        action.chart.formData.groupby.indexOf(col) !== -1) {
+      const filterKeys = [
+        '__from',
+        '__to',
+        '__time_col',
+        '__time_grain',
+        '__time_origin',
+        '__granularity',
+      ];
+      if (
+        filterKeys.indexOf(col) >= 0 ||
+        action.chart.formData.groupby.indexOf(col) !== -1
+      ) {
         let newFilter = {};
         if (!(sliceId in filters)) {
           // Straight up set the filters if none existed for the slice
           newFilter = { [col]: vals };
-        } else if (filters[sliceId] && !(col in filters[sliceId]) || !merge) {
+        } else if ((filters[sliceId] && !(col in filters[sliceId])) || !merge) {
           newFilter = { ...filters[sliceId], [col]: vals };
           // d3.merge pass in array of arrays while some value form filter components
           // from and to filter box require string to be process and return
@@ -119,6 +128,10 @@ export default function (state = {}, action) {
       }
       return { ...state, filters, refresh };
     },
+    [SET_UNSAVED_CHANGES]() {
+      const { hasUnsavedChanges } = action.payload;
+      return { ...state, hasUnsavedChanges };
+    },
   };
 
   if (action.type in actionHandlers) {
diff --git a/superset/assets/src/dashboard/reducers/datasources.js b/superset/assets/src/dashboard/reducers/datasources.js
index 4df7507..87f6d09 100644
--- a/superset/assets/src/dashboard/reducers/datasources.js
+++ b/superset/assets/src/dashboard/reducers/datasources.js
@@ -1,8 +1,8 @@
-import * as actions from '../actions/datasources';
+import { SET_DATASOURCE } from '../actions/datasources';
 
 export default function datasourceReducer(datasources = {}, action) {
   const actionHandlers = {
-    [actions.SET_DATASOURCE]() {
+    [SET_DATASOURCE]() {
       return action.datasource;
     },
   };
@@ -10,7 +10,10 @@ export default function datasourceReducer(datasources = {}, action) {
   if (action.type in actionHandlers) {
     return {
       ...datasources,
-      [action.key]: actionHandlers[action.type](datasources[action.key], action),
+      [action.key]: actionHandlers[action.type](
+        datasources[action.key],
+        action,
+      ),
     };
   }
   return datasources;
diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js
index 1129210..d0b4d7b 100644
--- a/superset/assets/src/dashboard/reducers/getInitialState.js
+++ b/superset/assets/src/dashboard/reducers/getInitialState.js
@@ -7,9 +7,9 @@ import { getParam } from '../../modules/utils';
 import { applyDefaultFormData } from '../../explore/stores/store';
 import { getColorFromScheme } from '../../modules/colors';
 import layoutConverter from '../util/dashboardLayoutConverter';
-import { DASHBOARD_ROOT_ID } from '../v2/util/constants';
+import { DASHBOARD_ROOT_ID } from '../util/constants';
 
-export default function (bootstrapData) {
+export default function(bootstrapData) {
   const { user_id, datasources, common } = bootstrapData;
   delete common.locale;
   delete common.language_pack;
@@ -18,7 +18,9 @@ export default function (bootstrapData) {
   let filters = {};
   try {
     // allow request parameter overwrite dashboard metadata
-    filters = JSON.parse(getParam('preselect_filters') || dashboard.metadata.default_filters);
+    filters = JSON.parse(
+      getParam('preselect_filters') || dashboard.metadata.default_filters,
+    );
   } catch (e) {
     //
   }
@@ -27,9 +29,9 @@ export default function (bootstrapData) {
   // the dashboard's JSON metadata
   if (dashboard.metadata && dashboard.metadata.label_colors) {
     const colorMap = dashboard.metadata.label_colors;
-    for (const label in colorMap) {
+    Object.keys(colorMap).forEach(label => {
       getColorFromScheme(label, null, colorMap[label]);
-    }
+    });
   }
 
   // dashboard layout
@@ -52,9 +54,10 @@ export default function (bootstrapData) {
   const chartQueries = {};
   const slices = {};
   const sliceIds = new Set();
-  dashboard.slices.forEach((slice) => {
+  dashboard.slices.forEach(slice => {
     const key = slice.slice_id;
-    chartQueries[key] = { ...chart,
+    chartQueries[key] = {
+      ...chart,
       id: key,
       form_data: slice.form_data,
       formData: applyDefaultFormData(slice.form_data),
@@ -79,13 +82,16 @@ export default function (bootstrapData) {
     datasources,
     sliceEntities: { ...initSliceEntities, slices, isLoading: false },
     charts: chartQueries,
-    dashboardInfo: {  /* readOnly props */
+    dashboardInfo: {
+      // read-only data
       id: dashboard.id,
       slug: dashboard.slug,
       metadata: {
-        filter_immune_slice_fields: dashboard.metadata.filter_immune_slice_fields,
+        filter_immune_slice_fields:
+          dashboard.metadata.filter_immune_slice_fields,
         filter_immune_slices: dashboard.metadata.filter_immune_slices,
-        timed_refresh_immune_slices: dashboard.metadata.timed_refresh_immune_slices,
+        timed_refresh_immune_slices:
+          dashboard.metadata.timed_refresh_immune_slices,
       },
       userId: user_id,
       dash_edit_perm: dashboard.dash_edit_perm,
diff --git a/superset/assets/src/dashboard/reducers/index.js b/superset/assets/src/dashboard/reducers/index.js
index a2397e0..787cd5f 100644
--- a/superset/assets/src/dashboard/reducers/index.js
+++ b/superset/assets/src/dashboard/reducers/index.js
@@ -4,19 +4,19 @@ import charts from '../../chart/chartReducer';
 import dashboardState from './dashboardState';
 import datasources from './datasources';
 import sliceEntities from './sliceEntities';
-import dashboardLayout from '../v2/reducers/index';
-import messageToasts from '../v2/reducers/messageToasts';
+import dashboardLayout from '../reducers/undoableDashboardLayout';
+import messageToasts from '../reducers/messageToasts';
 
-const dashboardInfo = (state = {}) => (state);
-const impressionId = (state = '') => (state);
+const dashboardInfo = (state = {}) => state;
+const impressionId = (state = '') => state;
 
 export default combineReducers({
   charts,
   datasources,
-  sliceEntities,
   dashboardInfo,
   dashboardState,
   dashboardLayout,
-  messageToasts,
   impressionId,
+  messageToasts,
+  sliceEntities,
 });
diff --git a/superset/assets/src/dashboard/v2/reducers/messageToasts.js b/superset/assets/src/dashboard/reducers/messageToasts.js
similarity index 87%
rename from superset/assets/src/dashboard/v2/reducers/messageToasts.js
rename to superset/assets/src/dashboard/reducers/messageToasts.js
index 1f5728a..7383ab0 100644
--- a/superset/assets/src/dashboard/v2/reducers/messageToasts.js
+++ b/superset/assets/src/dashboard/reducers/messageToasts.js
@@ -8,7 +8,9 @@ export default function messageToastsReducer(toasts = [], action) {
     }
 
     case REMOVE_TOAST: {
-      const { payload: { id } } = action;
+      const {
+        payload: { id },
+      } = action;
       return [...toasts].filter(toast => toast.id !== id);
     }
 
diff --git a/superset/assets/src/dashboard/reducers/sliceEntities.js b/superset/assets/src/dashboard/reducers/sliceEntities.js
index 61a58f6..c1453f5 100644
--- a/superset/assets/src/dashboard/reducers/sliceEntities.js
+++ b/superset/assets/src/dashboard/reducers/sliceEntities.js
@@ -13,7 +13,10 @@ export const initSliceEntities = {
   lastUpdated: 0,
 };
 
-export default function (state = initSliceEntities, action) {
+export default function sliceEntitiesReducer(
+  state = initSliceEntities,
+  action,
+) {
   const actionHandlers = {
     [UPDATE_SLICE_NAME]() {
       const updatedSlice = {
@@ -44,8 +47,9 @@ export default function (state = initSliceEntities, action) {
       const respJSON = action.error.responseJSON;
       const errorMessage =
         t('Sorry, there was an error adding slices to this dashboard: ') +
-        (respJSON && respJSON.message) ? respJSON.message :
-          error.responseText;
+        (respJSON && respJSON.message)
+          ? respJSON.message
+          : action.error.responseText;
       return {
         ...state,
         isLoading: false,
diff --git a/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js b/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js
new file mode 100644
index 0000000..b78c273
--- /dev/null
+++ b/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js
@@ -0,0 +1,27 @@
+import undoable, { includeAction } from 'redux-undo';
+import {
+  UPDATE_COMPONENTS,
+  DELETE_COMPONENT,
+  CREATE_COMPONENT,
+  CREATE_TOP_LEVEL_TABS,
+  DELETE_TOP_LEVEL_TABS,
+  RESIZE_COMPONENT,
+  MOVE_COMPONENT,
+  HANDLE_COMPONENT_DROP,
+} from '../actions/dashboardLayout';
+
+import dashboardLayout from './dashboardLayout';
+
+export default undoable(dashboardLayout, {
+  limit: 15,
+  filter: includeAction([
+    UPDATE_COMPONENTS,
+    DELETE_COMPONENT,
+    CREATE_COMPONENT,
+    CREATE_TOP_LEVEL_TABS,
+    DELETE_TOP_LEVEL_TABS,
+    RESIZE_COMPONENT,
+    MOVE_COMPONENT,
+    HANDLE_COMPONENT_DROP,
+  ]),
+});
diff --git a/superset/assets/src/dashboard/v2/stylesheets/builder-sidepane.less b/superset/assets/src/dashboard/stylesheets/builder-sidepane.less
similarity index 76%
rename from superset/assets/src/dashboard/v2/stylesheets/builder-sidepane.less
rename to superset/assets/src/dashboard/stylesheets/builder-sidepane.less
index d9f1069..bdf342b 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/builder-sidepane.less
+++ b/superset/assets/src/dashboard/stylesheets/builder-sidepane.less
@@ -1,8 +1,21 @@
 .dashboard-builder-sidepane {
+  background: white;
+  flex: 0 0 376px;
+  border: 1px solid @gray-light;
+  z-index: 10;
+  position: relative;
+
+  .dashboard-builder-sidepane-header {
+    font-size: 15px;
+    font-weight: 700;
+    border-bottom: 1px solid @gray-light;
+    padding: 14px;
+  }
+
   .trigger {
     height: 25px;
     width: 25px;
-    color: #879399;
+    color: @gray;
     position: relative;
 
     &.close {
@@ -25,8 +38,8 @@
     position: absolute;
     width: 2px;
     top: 51px;
-    right: 1px;
-    background: #fff;
+    right: 0;
+    background: white;
     transition-property: width;
     transition-duration: 1s;
     transition-timing-function: ease;
@@ -39,18 +52,17 @@
 
   .chart-card-container {
     padding: 16px;
-    cursor: move;
 
     .chart-card {
-      border: 1px solid #ccc;
+      border: 1px solid @gray-light;
       height: 120px;
       padding: 16px;
-      pointer-events: unset;
+      cursor: move;
     }
 
     .chart-card.is-selected {
       opacity: 0.45;
-      pointer-events: none;
+      cursor: not-allowed;
     }
 
     .card-title {
@@ -88,7 +100,7 @@
       input {
         margin-left: 16px;
         width: 169px;
-        border: 1px solid #b3b3b3;
+        border: 1px solid @gray;
 
         &:focus {
           outline: none;
diff --git a/superset/assets/src/dashboard/v2/stylesheets/builder.less b/superset/assets/src/dashboard/stylesheets/builder.less
similarity index 72%
rename from superset/assets/src/dashboard/v2/stylesheets/builder.less
rename to superset/assets/src/dashboard/stylesheets/builder.less
index 2ff99a4..7c14056 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/builder.less
+++ b/superset/assets/src/dashboard/stylesheets/builder.less
@@ -1,7 +1,7 @@
-.dashboard-v2 {
-  //margin-top: -20px;
+.dashboard {
   position: relative;
   color: @almost-black;
+  margin-top: -20px;
 }
 
 .dashboard-header {
@@ -22,12 +22,12 @@
 }
 
 /* only top-level tabs have popover, give it more padding to match header + tabs */
-.dashboard-v2 > .with-popover-menu > .popover-menu {
+.dashboard > .with-popover-menu > .popover-menu {
   left: 24px;
 }
 
 /* drop shadow for top-level tabs only */
-.dashboard-v2 .dashboard-component-tabs {
+.dashboard .dashboard-component-tabs {
   box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1);
   padding-left: 8px; /* note this is added to tab-level padding, to match header */
 }
@@ -43,21 +43,6 @@
   position: relative;
 }
 
-.dashboard-builder-sidepane {
-  background: white;
-  flex: 0 0 376px;
-  border: 1px solid @gray-light;
-  z-index: 1;
-  position: relative;
-}
-
-.dashboard-builder-sidepane-header {
-  font-size: 15px;
-  font-weight: 700;
-  border-bottom: 1px solid @gray-light;
-  padding: 14px;
-}
-
 /* @TODO remove upon new theme */
 .btn.btn-primary {
   background: @almost-black !important;
diff --git a/superset/assets/src/dashboard/v2/stylesheets/buttons.less b/superset/assets/src/dashboard/stylesheets/buttons.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/buttons.less
rename to superset/assets/src/dashboard/stylesheets/buttons.less
diff --git a/superset/assets/src/dashboard/stylesheets/components/chart.less b/superset/assets/src/dashboard/stylesheets/components/chart.less
new file mode 100644
index 0000000..dc366a1
--- /dev/null
+++ b/superset/assets/src/dashboard/stylesheets/components/chart.less
@@ -0,0 +1,69 @@
+.dashboard-component-chart-holder {
+  width: 100%;
+  height: 100%;
+  color: @gray-dark;
+  background-color: white;
+  position: relative;
+  padding: 16px;
+}
+
+.dashboard-chart {
+  overflow: hidden;
+}
+
+.dashboard-chart.dashboard-chart--overflowable {
+  overflow: visible;
+}
+
+.dashboard--editing .dashboard-component-chart-holder:after {
+  content: '';
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  top: 0px;
+  left: 0px;
+  z-index: 1;
+  pointer-events: none;
+  border: 1px solid transparent;
+}
+
+.dashboard--editing
+  .resizable-container:hover
+  > .dashboard-component-chart-holder:after,
+.dashboard--editing .dashboard-component-chart-holder:hover:after {
+  border: 1px solid @gray-light;
+}
+
+.dashboard--editing
+  .resizable-container.resizable-container--resizing:hover
+  > .dashboard-component-chart-holder:after {
+  border: 1px solid @indicator-color;
+}
+
+.dashboard--editing
+  .dashboard-component-chart-holder
+  .dashboard-chart
+  .chart-container {
+  cursor: move;
+  opacity: 0.2;
+}
+
+.dashboard--editing
+  .dashboard-component-chart-holder:hover
+  .dashboard-chart
+  .chart-container {
+  opacity: 0.7;
+}
+
+.dashboard--editing
+  .dashboard-component-chart-holder
+  .dashboard-chart
+  .slice_container {
+  /* disable chart interactions in edit mode */
+  pointer-events: none;
+}
+
+.dashboard-chart .chart-header {
+  font-size: 16px;
+  font-weight: bold;
+}
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/column.less b/superset/assets/src/dashboard/stylesheets/components/column.less
similarity index 60%
rename from superset/assets/src/dashboard/v2/stylesheets/components/column.less
rename to superset/assets/src/dashboard/stylesheets/components/column.less
index 9565112..5fcb442 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/components/column.less
+++ b/superset/assets/src/dashboard/stylesheets/components/column.less
@@ -1,5 +1,6 @@
 .grid-column {
   width: 100%;
+  position: relative;
 }
 
 /* gutters between elements in a column */
@@ -7,20 +8,24 @@
   margin-bottom: 16px;
 }
 
-.dashboard-v2--editing .grid-column:after {
-  border: 1px dashed transparent;
-  content: "";
+.dashboard--editing .grid-column:after {
+  border: 1px solid transparent;
+  content: '';
   position: absolute;
   width: 100%;
   height: 100%;
-  top: 1px;
+  top: 0;
   left: 0;
   z-index: 1;
   pointer-events: none;
 }
 
-.dashboard-v2--editing .grid-column:hover:after {
-  border: 1px solid @gray-light;
+.dashboard--editing
+  .resizable-container.resizable-container--resizing:hover
+  > .grid-column:after,
+.dashboard--editing .grid-column:hover:after {
+  border: 1px dashed @gray-light;
+  box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1);
 }
 
 .grid-column > .hover-menu--top {
@@ -32,7 +37,7 @@
 }
 
 .grid-column--empty:before {
-  content: "Empty column";
+  content: 'Empty column';
   position: absolute;
   top: 0;
   left: 0;
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/divider.less b/superset/assets/src/dashboard/stylesheets/components/divider.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/components/divider.less
rename to superset/assets/src/dashboard/stylesheets/components/divider.less
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/header.less b/superset/assets/src/dashboard/stylesheets/components/header.less
similarity index 92%
rename from superset/assets/src/dashboard/v2/stylesheets/components/header.less
rename to superset/assets/src/dashboard/stylesheets/components/header.less
index 37c7598..8b93164 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/components/header.less
+++ b/superset/assets/src/dashboard/stylesheets/components/header.less
@@ -11,6 +11,10 @@
   width: auto;
 }
 
+.dashboard-header .btn-group button {
+  margin-right: 8px;
+}
+
 .dragdroppable-row .dashboard-component-header {
   cursor: move;
 }
@@ -40,7 +44,6 @@
  * grids add margin between items, so don't double pad within columns
  * we'll not worry about double padding on top as it can serve as a visual separator
  */
-// .grid-content > :not(:only-child):not(:last-child) .dashboard-component-header,
 .grid-column > :not(:only-child):not(:last-child) .dashboard-component-header {
   margin-bottom: -16px;
 }
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/index.less b/superset/assets/src/dashboard/stylesheets/components/index.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/components/index.less
rename to superset/assets/src/dashboard/stylesheets/components/index.less
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/new-component.less b/superset/assets/src/dashboard/stylesheets/components/new-component.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/components/new-component.less
rename to superset/assets/src/dashboard/stylesheets/components/new-component.less
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/row.less b/superset/assets/src/dashboard/stylesheets/components/row.less
similarity index 66%
rename from superset/assets/src/dashboard/v2/stylesheets/components/row.less
rename to superset/assets/src/dashboard/stylesheets/components/row.less
index 956966d..7df5675 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/components/row.less
+++ b/superset/assets/src/dashboard/stylesheets/components/row.less
@@ -1,7 +1,8 @@
 .grid-row {
+  position: relative;
   display: flex;
   flex-direction: row;
-  flex-wrap: wrap;
+  flex-wrap: nowrap;
   align-items: flex-start;
   width: 100%;
   height: fit-content;
@@ -13,20 +14,24 @@
 }
 
 /* hover indicator */
-.dashboard-v2--editing .grid-row:after {
+.dashboard--editing .grid-row:after {
   border: 1px dashed transparent;
-  content: "";
+  content: '';
   position: absolute;
   width: 100%;
   height: 100%;
-  top: 1px;
+  top: 0;
   left: 0;
   z-index: 1;
   pointer-events: none;
 }
 
-.dashboard-v2--editing .grid-row:hover:after {
-  border: 1px solid @gray-light;
+.dashboard--editing
+  .resizable-container.resizable-container--resizing:hover
+  > .grid-row:after,
+.dashboard--editing .grid-row:hover:after {
+  border: 1px dashed @gray-light;
+  box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1);
 }
 
 .grid-row.grid-row--empty {
@@ -38,7 +43,7 @@
   position: absolute;
   top: 0;
   left: 0;
-  content: "Empty row";
+  content: 'Empty row';
   display: flex;
   align-items: center;
   justify-content: center;
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/tabs.less b/superset/assets/src/dashboard/stylesheets/components/tabs.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/components/tabs.less
rename to superset/assets/src/dashboard/stylesheets/components/tabs.less
diff --git a/superset/assets/src/dashboard/stylesheets/dashboard.less b/superset/assets/src/dashboard/stylesheets/dashboard.less
new file mode 100644
index 0000000..03c804b
--- /dev/null
+++ b/superset/assets/src/dashboard/stylesheets/dashboard.less
@@ -0,0 +1,104 @@
+// @import './less/cosmo/variables.less';
+
+.dashboard .chart-header {
+  position: relative;
+
+  .dropdown.btn-group {
+    position: absolute;
+    right: 0;
+  }
+
+  .dropdown-menu.dropdown-menu-right {
+    right: 7px;
+    top: -3px;
+  }
+}
+
+.slice-header-controls-trigger {
+  border: 0;
+  padding: 0 0 0 20px;
+  background: none;
+  outline: none;
+  box-shadow: none;
+  color: #263238;
+
+  &.is-cached {
+    color: red;
+  }
+
+  &:hover,
+  &:focus {
+    background: none;
+    cursor: pointer;
+  }
+
+  .controls-container.dropdown-menu {
+    top: 0;
+    left: unset;
+    right: 10px;
+
+    &.is-open {
+      display: block;
+    }
+
+    & li {
+      white-space: nowrap;
+    }
+  }
+}
+
+.modal img.loading {
+  width: 50px;
+  margin: 0;
+  position: relative;
+}
+
+.react-bs-container-body {
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.hidden,
+#pageDropDown {
+  display: none;
+}
+
+.separator .chart-container {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+}
+
+.dashboard .title {
+  margin: 0 20px;
+}
+
+.dashboard .title .favstar {
+  font-size: 20px;
+  line-height: 1em;
+  position: relative;
+  top: -5px;
+}
+
+.ace_gutter {
+  z-index: 0;
+}
+.ace_content {
+  z-index: 0;
+}
+.ace_scrollbar {
+  z-index: 0;
+}
+.slice_container .alert {
+  margin: 10px;
+}
+
+i.danger {
+  color: red;
+}
+
+i.warning {
+  color: orange;
+}
diff --git a/superset/assets/src/dashboard/v2/stylesheets/dnd.less b/superset/assets/src/dashboard/stylesheets/dnd.less
similarity index 98%
rename from superset/assets/src/dashboard/v2/stylesheets/dnd.less
rename to superset/assets/src/dashboard/stylesheets/dnd.less
index 45a9784..835b62b 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/dnd.less
+++ b/superset/assets/src/dashboard/stylesheets/dnd.less
@@ -12,7 +12,7 @@
 
 /* drop indicators */
 .drop-indicator {
-  margin: auto;
+  display: block;
   background-color: @indicator-color;
   position: absolute;
   z-index: 10;
diff --git a/superset/assets/src/dashboard/v2/stylesheets/grid.less b/superset/assets/src/dashboard/stylesheets/grid.less
similarity index 68%
rename from superset/assets/src/dashboard/v2/stylesheets/grid.less
rename to superset/assets/src/dashboard/stylesheets/grid.less
index 45b8a42..a12ac97 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/grid.less
+++ b/superset/assets/src/dashboard/stylesheets/grid.less
@@ -1,12 +1,22 @@
 .grid-container {
+  min-height: 100%;
   position: relative;
   margin: 24px;
+  /* without this, the grid will not get smaller upon toggling the builder panel on */
+  min-width: 0;
+  width: 100%;
+}
+
+/* this is the ParentSize wrapper  */
+.grid-container > div:first-child {
+  height: inherit !important;
 }
 
 .grid-content {
-  height: 100%;
+  min-height: 100%;
   display: flex;
   flex-direction: column;
+  margin-bottom: 100px;
 }
 
 /* gutters between rows */
@@ -23,7 +33,7 @@
 .grid-column-guide {
   position: absolute;
   top: 0;
-  height: 100%;
+  min-height: 100%;
   background-color: rgba(68, 192, 255, 0.05);
   pointer-events: none;
   box-shadow: inset 0 0 0 1px rgba(68, 192, 255, 0.5);
diff --git a/superset/assets/src/dashboard/v2/stylesheets/hover-menu.less b/superset/assets/src/dashboard/stylesheets/hover-menu.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/hover-menu.less
rename to superset/assets/src/dashboard/stylesheets/hover-menu.less
diff --git a/superset/assets/src/dashboard/v2/stylesheets/index.less b/superset/assets/src/dashboard/stylesheets/index.less
similarity index 81%
rename from superset/assets/src/dashboard/v2/stylesheets/index.less
rename to superset/assets/src/dashboard/stylesheets/index.less
index 49ff5da..b69c7b0 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/index.less
+++ b/superset/assets/src/dashboard/stylesheets/index.less
@@ -1,7 +1,9 @@
 @import './variables.less';
 
 @import './builder.less';
+@import './builder-sidepane.less';
 @import './buttons.less';
+@import './dashboard.less';
 @import './dnd.less';
 @import './grid.less';
 @import './hover-menu.less';
diff --git a/superset/assets/src/dashboard/v2/stylesheets/popover-menu.less b/superset/assets/src/dashboard/stylesheets/popover-menu.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/popover-menu.less
rename to superset/assets/src/dashboard/stylesheets/popover-menu.less
diff --git a/superset/assets/src/dashboard/v2/stylesheets/resizable.less b/superset/assets/src/dashboard/stylesheets/resizable.less
similarity index 69%
rename from superset/assets/src/dashboard/v2/stylesheets/resizable.less
rename to superset/assets/src/dashboard/stylesheets/resizable.less
index 7bdd5f8..973daab 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/resizable.less
+++ b/superset/assets/src/dashboard/stylesheets/resizable.less
@@ -16,6 +16,7 @@
 
 .resize-handle {
   opacity: 0;
+  z-index: 10;
 }
 
   .resizable-container:hover .resize-handle,
@@ -35,26 +36,43 @@
   height: 8px;
 }
 
+
 .resize-handle--right {
   width: 2px;
   height: 20px;
-  right: 2px;
-  top: ~"calc(50% - 9px)"; /* escape for .less */
+  right: 4px;
+  top: 50%;
+  transform: translate(0, -50%);
   position: absolute;
   border-left: 1px solid @gray;
   border-right: 1px solid @gray;
 }
 
+.dragdroppable-column .resizable-container-handle--right {
+  /* override the default because the inner column's handle's mouse target is very small */
+  right: -10px !important;
+}
+
+.dragdroppable-column .dragdroppable-column .resizable-container-handle--right {
+  /* override the default because the inner column's handle's mouse target is very small */
+  right: 0px !important;
+}
+
 .resize-handle--bottom {
   height: 2px;
   width: 20px;
-  bottom: 2px;
-  left: ~"calc(50% - 10px)"; /* escape for .less */
+  bottom: 4px;
+  left: 50%;
+  transform: translate(-50%);
   position: absolute;
   border-top: 1px solid @gray;
   border-bottom: 1px solid @gray;
 }
 
+.resizable-container-handle--bottom {
+  bottom: 0 !important;
+}
+
 .resizable-container--resizing > span .resize-handle {
   border-color: @indicator-color;
 }
diff --git a/superset/assets/src/dashboard/v2/stylesheets/toast.less b/superset/assets/src/dashboard/stylesheets/toast.less
similarity index 91%
rename from superset/assets/src/dashboard/v2/stylesheets/toast.less
rename to superset/assets/src/dashboard/stylesheets/toast.less
index a508637..1d1ebc5 100644
--- a/superset/assets/src/dashboard/v2/stylesheets/toast.less
+++ b/superset/assets/src/dashboard/stylesheets/toast.less
@@ -13,8 +13,7 @@
   opacity: 0;
   position: relative;
   white-space: pre-line;
-  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.15);
-  border-radius: 2px;
+  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.35);
   will-change: transform, opacity;
   transform: translateY(-100%);
   transition: transform .3s, opacity .3s;
@@ -38,7 +37,7 @@
   position: absolute;
   top: 0;
   left: 0;
-  width: 4px;
+  width: 6px;
   height: 100%;
 }
 
diff --git a/superset/assets/src/dashboard/v2/stylesheets/variables.less b/superset/assets/src/dashboard/stylesheets/variables.less
similarity index 100%
rename from superset/assets/src/dashboard/v2/stylesheets/variables.less
rename to superset/assets/src/dashboard/stylesheets/variables.less
diff --git a/superset/assets/src/dashboard/util/backgroundStyleOptions.js b/superset/assets/src/dashboard/util/backgroundStyleOptions.js
new file mode 100644
index 0000000..926e7f1
--- /dev/null
+++ b/superset/assets/src/dashboard/util/backgroundStyleOptions.js
@@ -0,0 +1,15 @@
+import { t } from '../../locales';
+import { BACKGROUND_TRANSPARENT, BACKGROUND_WHITE } from './constants';
+
+export default [
+  {
+    value: BACKGROUND_TRANSPARENT,
+    label: t('Transparent'),
+    className: 'background--transparent',
+  },
+  {
+    value: BACKGROUND_WHITE,
+    label: t('White'),
+    className: 'background--white',
+  },
+];
diff --git a/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
new file mode 100644
index 0000000..f48631f
--- /dev/null
+++ b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
@@ -0,0 +1,42 @@
+export default function getEffectiveExtraFilters({
+  dashboardMetadata,
+  filters,
+  sliceId,
+}) {
+  const immuneSlices = dashboardMetadata.filter_immune_slices || [];
+
+  const effectiveFilters = [];
+
+  if (sliceId && immuneSlices.includes(sliceId)) {
+    // The slice is immune to dashboard filters
+    return effectiveFilters;
+  }
+
+  // Build a list of fields the slice is immune to filters on
+  let immuneToFields = [];
+  if (
+    sliceId &&
+    dashboardMetadata.filter_immune_slice_fields &&
+    dashboardMetadata.filter_immune_slice_fields[sliceId]
+  ) {
+    immuneToFields = dashboardMetadata.filter_immune_slice_fields[sliceId];
+  }
+
+  Object.keys(filters).forEach(filteringSliceId => {
+    if (filteringSliceId === sliceId.toString()) {
+      // Filters applied by the slice don't apply to itself
+      return;
+    }
+    Object.keys(filters[filteringSliceId]).forEach(field => {
+      if (!immuneToFields.includes(field)) {
+        effectiveFilters.push({
+          col: field,
+          op: 'in',
+          val: filters[filteringSliceId][field],
+        });
+      }
+    });
+  });
+
+  return effectiveFilters;
+}
diff --git a/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
new file mode 100644
index 0000000..031d90d
--- /dev/null
+++ b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
@@ -0,0 +1,42 @@
+import getEffectiveExtraFilters from './getEffectiveExtraFilters';
+
+// We cache formData objects so that our connected container components don't always trigger
+// render cascades. we cannot leverage the reselect library because our cache size is >1
+const cachedDashboardMetadataByChart = {};
+const cachedFiltersByChart = {};
+const cachedFormdataByChart = {};
+
+export default function getFormDataWithExtraFilters({
+  chart,
+  dashboardMetadata,
+  filters,
+  sliceId,
+}) {
+  // if dashboard metadata + filters have not changed, use cache if possible
+  if (
+    cachedDashboardMetadataByChart[sliceId] &&
+    cachedDashboardMetadataByChart[sliceId] === dashboardMetadata &&
+    cachedFiltersByChart[sliceId] &&
+    cachedFiltersByChart[sliceId] === filters &&
+    cachedFormdataByChart[sliceId]
+  ) {
+    return cachedFormdataByChart[sliceId];
+  }
+
+  const extraFilters = getEffectiveExtraFilters({
+    dashboardMetadata,
+    filters,
+    sliceId,
+  });
+
+  const formData = {
+    ...chart.formData,
+    extra_filters: [...chart.formData.filters, ...extraFilters],
+  };
+
+  cachedDashboardMetadataByChart[sliceId] = dashboardMetadata;
+  cachedFiltersByChart[sliceId] = filters;
+  cachedFormdataByChart[sliceId] = formData;
+
+  return formData;
+}
diff --git a/superset/assets/src/dashboard/util/componentIsResizable.js b/superset/assets/src/dashboard/util/componentIsResizable.js
new file mode 100644
index 0000000..45812d7
--- /dev/null
+++ b/superset/assets/src/dashboard/util/componentIsResizable.js
@@ -0,0 +1,5 @@
+import { COLUMN_TYPE, CHART_TYPE, MARKDOWN_TYPE } from './componentTypes';
+
+export default function componentIsResizable(entity) {
+  return [COLUMN_TYPE, CHART_TYPE, MARKDOWN_TYPE].indexOf(entity.type) > -1;
+}
diff --git a/superset/assets/src/dashboard/v2/util/componentTypes.js b/superset/assets/src/dashboard/util/componentTypes.js
similarity index 100%
rename from superset/assets/src/dashboard/v2/util/componentTypes.js
rename to superset/assets/src/dashboard/util/componentTypes.js
diff --git a/superset/assets/src/dashboard/v2/util/constants.js b/superset/assets/src/dashboard/util/constants.js
similarity index 100%
rename from superset/assets/src/dashboard/v2/util/constants.js
rename to superset/assets/src/dashboard/util/constants.js
diff --git a/superset/assets/src/dashboard/util/dashboardHelper.js b/superset/assets/src/dashboard/util/dashboardHelper.js
deleted file mode 100644
index c9a6021..0000000
--- a/superset/assets/src/dashboard/util/dashboardHelper.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export function getChartIdsFromLayout(layout) {
-  return Object.values(layout)
-    .reduce((chartIds, value) => {
-      if (value && value.meta && value.meta.chartId) {
-        chartIds.push(value.meta.chartId);
-      }
-      return chartIds;
-    }, []);
-}
diff --git a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js
index 854ca65..f04b50e 100644
--- a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js
+++ b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js
@@ -8,19 +8,23 @@ import {
   DASHBOARD_HEADER_TYPE,
   DASHBOARD_ROOT_TYPE,
   DASHBOARD_GRID_TYPE,
-} from '../v2/util/componentTypes';
+} from './componentTypes';
 import {
   DASHBOARD_GRID_ID,
   DASHBOARD_HEADER_ID,
   DASHBOARD_ROOT_ID,
-} from '../v2/util/constants';
+} from './constants';
 
 const MAX_RECURSIVE_LEVEL = 6;
 const GRID_RATIO = 4;
 const ROW_HEIGHT = 8;
 const generateId = (() => {
   let componentId = 1;
-  return () => (componentId++);
+  return () => {
+    const id = componentId;
+    componentId += 1;
+    return id;
+  };
 })();
 
 /**
@@ -33,7 +37,7 @@ function getBoundary(positions) {
   let bottom = 0;
   let left = Number.MAX_VALUE;
   let right = 1;
-  positions.forEach((item) => {
+  positions.forEach(item => {
     const { row, col, size_x, size_y } = item;
     if (row <= top) top = row;
     if (col <= left) left = col;
@@ -50,11 +54,10 @@ function getBoundary(positions) {
 }
 
 function getRowContainer() {
-  const id = 'DASHBOARD_ROW_TYPE-' + generateId();
   return {
     version: 'v2',
     type: ROW_TYPE,
-    id,
+    id: `DASHBOARD_ROW_TYPE-${generateId()}`,
     children: [],
     meta: {
       background: 'BACKGROUND_TRANSPARENT',
@@ -63,11 +66,10 @@ function getRowContainer() {
 }
 
 function getColContainer() {
-  const id = 'DASHBOARD_COLUMN_TYPE-' + generateId();
   return {
     version: 'v2',
     type: COLUMN_TYPE,
-    id,
+    id: `DASHBOARD_COLUMN_TYPE-${generateId()}`,
     children: [],
     meta: {
       background: 'BACKGROUND_TRANSPARENT',
@@ -88,7 +90,7 @@ function getChartHolder(item) {
   return {
     version: 'v2',
     type: CHART_TYPE,
-    id: 'DASHBOARD_CHART_TYPE-' + generateId(),
+    id: `DASHBOARD_CHART_TYPE-${generateId()}`,
     children: [],
     meta: {
       width: converted.size_x,
@@ -99,13 +101,31 @@ function getChartHolder(item) {
 }
 
 function getChildrenMax(items, attr, layout) {
-  return Math.max.apply(null, items.map(child => (layout[child].meta[attr])));
+  return Math.max.apply(null, items.map(child => layout[child].meta[attr]));
 }
 
 function getChildrenSum(items, attr, layout) {
-  return items.reduce((preValue, child) => (preValue + layout[child].meta[attr]), 0);
+  return items.reduce(
+    (preValue, child) => preValue + layout[child].meta[attr],
+    0,
+  );
 }
 
+// function getChildrenMax(items, attr, layout) {
+//   return Math.max.apply(null, items.map((childId) => {
+//     const child = layout[childId];
+//     if (child.type === ROW_TYPE && attr === 'width') {
+//       // rows don't have widths themselves
+//       return getChildrenSum(child.children, attr, layout);
+//     } else if (child.type === COLUMN_TYPE && attr === 'height') {
+//       // columns don't have heights themselves
+//       return getChildrenSum(child.children, attr, layout);
+//     }
+//
+//     return child.meta[attr];
+//   }));
+// }
+
 function sortByRowId(item1, item2) {
   return item1.row - item2.row;
 }
@@ -115,7 +135,8 @@ function sortByColId(item1, item2) {
 }
 
 function hasOverlap(positions, xAxis = true) {
-  return positions.slice()
+  return positions
+    .slice()
     .sort(xAxis ? sortByColId : sortByRowId)
     .some((item, index, arr) => {
       if (index === arr.length - 1) {
@@ -123,9 +144,9 @@ function hasOverlap(positions, xAxis = true) {
       }
 
       if (xAxis) {
-        return (item.col + item.size_x) > arr[index + 1].col;
+        return item.col + item.size_x > arr[index + 1].col;
       }
-      return (item.row + item.size_y) > arr[index + 1].row;
+      return item.row + item.size_y > arr[index + 1].row;
     });
 }
 
@@ -158,7 +179,7 @@ function doConvert(positions, level, parent, root) {
     const upper = [];
     const lower = [];
 
-    const isRowDivider = currentItems.every((item) => {
+    const isRowDivider = currentItems.every(item => {
       const { row, size_y } = item;
       if (row + size_y <= currentRow) {
         lower.push(item);
@@ -174,10 +195,10 @@ function doConvert(positions, level, parent, root) {
       currentItems = upper.slice();
       layers.push(lower);
     }
-    currentRow++;
+    currentRow += 1;
   }
 
-  layers.forEach((layer) => {
+  layers.forEach(layer => {
     if (layer.length === 0) {
       return;
     }
@@ -196,7 +217,7 @@ function doConvert(positions, level, parent, root) {
 
     currentItems = layer.slice();
     if (!hasOverlap(currentItems)) {
-      currentItems.sort(sortByColId).forEach((item) => {
+      currentItems.sort(sortByColId).forEach(item => {
         const chartHolder = getChartHolder(item);
         root[chartHolder.id] = chartHolder;
         rowContainer.children.push(chartHolder.id);
@@ -208,7 +229,7 @@ function doConvert(positions, level, parent, root) {
         const upper = [];
         const lower = [];
 
-        const isColDivider = currentItems.every((item) => {
+        const isColDivider = currentItems.every(item => {
           const { col, size_x } = item;
           if (col + size_x <= currentCol) {
             lower.push(item);
@@ -232,7 +253,7 @@ function doConvert(positions, level, parent, root) {
             rowContainer.children.push(colContainer.id);
 
             if (!hasOverlap(lower, false)) {
-              lower.sort(sortByRowId).forEach((item) => {
+              lower.sort(sortByRowId).forEach(item => {
                 const chartHolder = getChartHolder(item);
                 root[chartHolder.id] = chartHolder;
                 colContainer.children.push(chartHolder.id);
@@ -242,37 +263,47 @@ function doConvert(positions, level, parent, root) {
             }
 
             // add col meta
-            colContainer.meta.width = getChildrenMax(colContainer.children, 'width', root);
+            colContainer.meta.width = getChildrenMax(
+              colContainer.children,
+              'width',
+              root,
+            );
           }
 
           currentItems = upper.slice();
         }
-        currentCol++;
+        currentCol += 1;
       }
     }
 
-    rowContainer.meta.width = getChildrenSum(rowContainer.children, 'width', root);
+    rowContainer.meta.width = getChildrenSum(
+      rowContainer.children,
+      'width',
+      root,
+    );
   });
 }
 
-export default function (dashboard) {
+export default function(dashboard) {
   const positions = [];
 
   // position data clean up. some dashboard didn't have position_json
   let { position_json } = dashboard;
   const posDict = {};
   if (Array.isArray(position_json)) {
-    position_json.forEach((position) => {
+    position_json.forEach(position => {
       posDict[position.slice_id] = position;
     });
   } else {
     position_json = [];
   }
 
-  const lastRowId = Math.max(0, Math.max.apply(null,
-    position_json.map(pos => (pos.row + pos.size_y))));
+  const lastRowId = Math.max(
+    0,
+    Math.max.apply(null, position_json.map(pos => pos.row + pos.size_y)),
+  );
   let newSliceCounter = 0;
-  dashboard.slices.forEach((slice) => {
+  dashboard.slices.forEach(slice => {
     const sliceId = slice.slice_id;
     let pos = posDict[sliceId];
     if (!pos) {
@@ -284,7 +315,7 @@ export default function (dashboard) {
         size_y: 16,
         slice_id: String(sliceId),
       };
-      newSliceCounter++;
+      newSliceCounter += 1;
     }
 
     positions.push(pos);
@@ -292,7 +323,6 @@ export default function (dashboard) {
 
   const root = {
     [DASHBOARD_ROOT_ID]: {
-      version: 'v2',
       type: DASHBOARD_ROOT_TYPE,
       id: DASHBOARD_ROOT_ID,
       children: [DASHBOARD_GRID_ID],
@@ -310,13 +340,12 @@ export default function (dashboard) {
   doConvert(positions, 0, root[DASHBOARD_GRID_ID], root);
 
   // remove row's width/height and col's height
-  Object.values(root).forEach((item) => {
+  Object.values(root).forEach(item => {
     if (ROW_TYPE === item.type) {
       const meta = item.meta;
       delete meta.width;
     }
   });
 
-  // console.log(JSON.stringify(root));
   return root;
 }
diff --git a/superset/assets/src/dashboard/v2/util/dnd-reorder.js b/superset/assets/src/dashboard/util/dnd-reorder.js
similarity index 84%
rename from superset/assets/src/dashboard/v2/util/dnd-reorder.js
rename to superset/assets/src/dashboard/util/dnd-reorder.js
index 9a0dedf..76fb56c 100644
--- a/superset/assets/src/dashboard/v2/util/dnd-reorder.js
+++ b/superset/assets/src/dashboard/util/dnd-reorder.js
@@ -6,22 +6,14 @@ export function reorder(list, startIndex, endIndex) {
   return result;
 }
 
-export default function reorderItem({
-  entitiesMap,
-  source,
-  destination,
-}) {
+export default function reorderItem({ entitiesMap, source, destination }) {
   const current = [...entitiesMap[source.id].children];
   const next = [...entitiesMap[destination.id].children];
   const target = current[source.index];
 
   // moving to same list
   if (source.id === destination.id) {
-    const reordered = reorder(
-      current,
-      source.index,
-      destination.index,
-    );
+    const reordered = reorder(current, source.index, destination.index);
 
     const result = {
       ...entitiesMap,
diff --git a/superset/assets/src/dashboard/v2/util/dropOverflowsParent.js b/superset/assets/src/dashboard/util/dropOverflowsParent.js
similarity index 55%
rename from superset/assets/src/dashboard/v2/util/dropOverflowsParent.js
rename to superset/assets/src/dashboard/util/dropOverflowsParent.js
index 0fd0c4e..bc7195f 100644
--- a/superset/assets/src/dashboard/v2/util/dropOverflowsParent.js
+++ b/superset/assets/src/dashboard/util/dropOverflowsParent.js
@@ -1,23 +1,37 @@
 import { COLUMN_TYPE } from '../util/componentTypes';
-import { GRID_COLUMN_COUNT, NEW_COMPONENTS_SOURCE_ID } from './constants';
+import {
+  GRID_COLUMN_COUNT,
+  NEW_COMPONENTS_SOURCE_ID,
+  GRID_MIN_COLUMN_COUNT,
+} from './constants';
 import findParentId from './findParentId';
 import getChildWidth from './getChildWidth';
 import newComponentFactory from './newComponentFactory';
 
 export default function doesChildOverflowParent(dropResult, components) {
   const { source, destination, dragging } = dropResult;
-  const isNewComponent = source.id === NEW_COMPONENTS_SOURCE_ID;
 
+  // moving a component within a container should never overflow
+  if (source.id === destination.id) {
+    return false;
+  }
+
+  const isNewComponent = source.id === NEW_COMPONENTS_SOURCE_ID;
   const grandparentId = findParentId({ childId: destination.id, components });
 
-  const child = isNewComponent ? newComponentFactory(dragging.type) : components[dragging.id] || {};
+  const child = isNewComponent
+    ? newComponentFactory(dragging.type)
+    : components[dragging.id] || {};
   const parent = components[destination.id] || {};
   const grandparent = components[grandparentId] || {};
 
-  const grandparentWidth = (grandparent.meta && grandparent.meta.width) || GRID_COLUMN_COUNT;
+  const grandparentWidth =
+    (grandparent.meta && grandparent.meta.width) || GRID_COLUMN_COUNT;
   const parentWidth = (parent.meta && parent.meta.width) || grandparentWidth;
-  const parentChildWidth = parent.type === COLUMN_TYPE
-    ? 0 : getChildWidth({ id: destination.id, components });
+  const parentChildWidth =
+    parent.type === COLUMN_TYPE
+      ? (parent.meta && parent.meta.width) || GRID_MIN_COLUMN_COUNT
+      : getChildWidth({ id: destination.id, components });
   const childWidth = (child.meta && child.meta.width) || 0;
 
   return parentWidth - parentChildWidth < childWidth;
diff --git a/superset/assets/src/dashboard/v2/util/findParentId.js b/superset/assets/src/dashboard/util/findParentId.js
similarity index 73%
rename from superset/assets/src/dashboard/v2/util/findParentId.js
rename to superset/assets/src/dashboard/util/findParentId.js
index 0ca15a6..f84b0de 100644
--- a/superset/assets/src/dashboard/v2/util/findParentId.js
+++ b/superset/assets/src/dashboard/util/findParentId.js
@@ -5,7 +5,11 @@ export default function findParentId({ childId, components = {} }) {
   for (let i = 0; i < ids.length - 1; i += 1) {
     const id = ids[i];
     const component = components[id] || {};
-    if (id !== childId && component.children && component.children.includes(childId)) {
+    if (
+      id !== childId &&
+      component.children &&
+      component.children.includes(childId)
+    ) {
       parentId = id;
       break;
     }
diff --git a/superset/assets/src/dashboard/util/getChartIdsFromLayout.js b/superset/assets/src/dashboard/util/getChartIdsFromLayout.js
new file mode 100644
index 0000000..f0963c1
--- /dev/null
+++ b/superset/assets/src/dashboard/util/getChartIdsFromLayout.js
@@ -0,0 +1,8 @@
+export default function getChartIdsFromLayout(layout) {
+  return Object.values(layout).reduce((chartIds, value) => {
+    if (value && value.meta && value.meta.chartId) {
+      chartIds.push(value.meta.chartId);
+    }
+    return chartIds;
+  }, []);
+}
diff --git a/superset/assets/src/dashboard/v2/util/getChildWidth.js b/superset/assets/src/dashboard/util/getChildWidth.js
similarity index 55%
rename from superset/assets/src/dashboard/v2/util/getChildWidth.js
rename to superset/assets/src/dashboard/util/getChildWidth.js
index aa32b96..69d2792 100644
--- a/superset/assets/src/dashboard/v2/util/getChildWidth.js
+++ b/superset/assets/src/dashboard/util/getChildWidth.js
@@ -4,9 +4,9 @@ export default function getTotalChildWidth({ id, components }) {
 
   let width = 0;
 
-  (component.children || []).forEach((childId) => {
-    const child = components[childId];
-    width += child.meta.width || 0;
+  (component.children || []).forEach(childId => {
+    const child = components[childId] || {};
+    width += (child.meta || {}).width || 0;
   });
 
   return width;
diff --git a/superset/assets/src/dashboard/v2/util/getDropPosition.js b/superset/assets/src/dashboard/util/getDropPosition.js
similarity index 80%
rename from superset/assets/src/dashboard/v2/util/getDropPosition.js
rename to superset/assets/src/dashboard/util/getDropPosition.js
index 9605db2..2a02702 100644
--- a/superset/assets/src/dashboard/v2/util/getDropPosition.js
+++ b/superset/assets/src/dashboard/util/getDropPosition.js
@@ -8,7 +8,7 @@ export const DROP_LEFT = 'DROP_LEFT';
 
 // this defines how close the mouse must be to the edge of a component to display
 // a sibling type drop indicator
-const SIBLING_DROP_THRESHOLD = 15;
+const SIBLING_DROP_THRESHOLD = 20;
 
 export default function getDropPosition(monitor, Component) {
   const {
@@ -22,7 +22,11 @@ export default function getDropPosition(monitor, Component) {
   const draggingItem = monitor.getItem();
 
   // if dropped self on self, do nothing
-  if (!draggingItem || draggingItem.id === component.id || !isDraggingOverShallow) {
+  if (
+    !draggingItem ||
+    draggingItem.id === component.id ||
+    !isDraggingOverShallow
+  ) {
     return null;
   }
 
@@ -34,7 +38,8 @@ export default function getDropPosition(monitor, Component) {
 
   const parentType = parentComponent && parentComponent.type;
   const parentDepth = // see isValidChild.js for why tabs don't increment child depth
-    componentDepth + (parentType === TAB_TYPE || parentType === TABS_TYPE ? 0 : -1);
+    componentDepth +
+    (parentType === TAB_TYPE || parentType === TABS_TYPE ? 0 : -1);
 
   const validSibling = isValidChild({
     parentType,
@@ -47,10 +52,13 @@ export default function getDropPosition(monitor, Component) {
   }
 
   const hasChildren = (component.children || []).length > 0;
-  const childDropOrientation = orientation === 'row' ? 'vertical' : 'horizontal';
-  const siblingDropOrientation = orientation === 'row' ? 'horizontal' : 'vertical';
+  const childDropOrientation =
+    orientation === 'row' ? 'vertical' : 'horizontal';
+  const siblingDropOrientation =
+    orientation === 'row' ? 'horizontal' : 'vertical';
 
-  if (validChild && !validSibling) { // easiest case, insert as child
+  if (validChild && !validSibling) {
+    // easiest case, insert as child
     if (childDropOrientation === 'vertical') {
       return hasChildren ? DROP_RIGHT : DROP_LEFT;
     }
@@ -64,10 +72,12 @@ export default function getDropPosition(monitor, Component) {
   if (validSibling && !validChild) {
     if (siblingDropOrientation === 'vertical') {
       const refMiddleX =
-        refBoundingRect.left + ((refBoundingRect.right - refBoundingRect.left) / 2);
+        refBoundingRect.left +
+        (refBoundingRect.right - refBoundingRect.left) / 2;
       return clientOffset.x < refMiddleX ? DROP_LEFT : DROP_RIGHT;
     }
-    const refMiddleY = refBoundingRect.top + ((refBoundingRect.bottom - refBoundingRect.top) / 2);
+    const refMiddleY =
+      refBoundingRect.top + (refBoundingRect.bottom - refBoundingRect.top) / 2;
     return clientOffset.y < refMiddleY ? DROP_TOP : DROP_BOTTOM;
   }
 
diff --git a/superset/assets/src/dashboard/v2/util/headerStyleOptions.js b/superset/assets/src/dashboard/util/headerStyleOptions.js
similarity index 89%
rename from superset/assets/src/dashboard/v2/util/headerStyleOptions.js
rename to superset/assets/src/dashboard/util/headerStyleOptions.js
index 309d482..7efa040 100644
--- a/superset/assets/src/dashboard/v2/util/headerStyleOptions.js
+++ b/superset/assets/src/dashboard/util/headerStyleOptions.js
@@ -1,4 +1,4 @@
-import { t } from '../../../locales';
+import { t } from '../../locales';
 import { SMALL_HEADER, MEDIUM_HEADER, LARGE_HEADER } from './constants';
 
 export default [
diff --git a/superset/assets/src/dashboard/v2/util/isValidChild.js b/superset/assets/src/dashboard/util/isValidChild.js
similarity index 65%
rename from superset/assets/src/dashboard/v2/util/isValidChild.js
rename to superset/assets/src/dashboard/util/isValidChild.js
index 66942f0..d789f45 100644
--- a/superset/assets/src/dashboard/v2/util/isValidChild.js
+++ b/superset/assets/src/dashboard/util/isValidChild.js
@@ -1,19 +1,19 @@
 /* eslint max-len: 0 */
 /**
-  * When determining if a component is a valid child of another component we must consider both
-  *   - parent + child component types
-  *   - component depth, or depth of nesting of container components
-  *
-  * We consider types because some components aren't containers (e.g. a heading) and we consider
-  * depth to prevent infinite nesting of container components.
-  *
-  * The following example container nestings should be valid, which means that some containers
-  * don't increase the (depth) of their children, namely tabs and tab:
-  *   (a) root (0) > grid (1) >                         row (2) > column (3) > row (4) > non-container (5)
-  *   (b) root (0) > grid (1) >    tabs (2) > tab (2) > row (2) > column (3) > row (4) > non-container (5)
-  *   (c) root (0) > top-tab (1) >                      row (2) > column (3) > row (4) > non-container (5)
-  *   (d) root (0) > top-tab (1) > tabs (2) > tab (2) > row (2) > column (3) > row (4) > non-container (5)
-  */
+ * When determining if a component is a valid child of another component we must consider both
+ *   - parent + child component types
+ *   - component depth, or depth of nesting of container components
+ *
+ * We consider types because some components aren't containers (e.g. a heading) and we consider
+ * depth to prevent infinite nesting of container components.
+ *
+ * The following example container nestings should be valid, which means that some containers
+ * don't increase the (depth) of their children, namely tabs and tab:
+ *   (a) root (0) > grid (1) >                         row (2) > column (3) > row (4) > non-container (5)
+ *   (b) root (0) > grid (1) >    tabs (2) > tab (2) > row (2) > column (3) > row (4) > non-container (5)
+ *   (c) root (0) > top-tab (1) >                      row (2) > column (3) > row (4) > non-container (5)
+ *   (d) root (0) > top-tab (1) > tabs (2) > tab (2) > row (2) > column (3) > row (4) > non-container (5)
+ */
 import {
   CHART_TYPE,
   COLUMN_TYPE,
diff --git a/superset/assets/src/dashboard/v2/util/newComponentFactory.js b/superset/assets/src/dashboard/util/newComponentFactory.js
similarity index 81%
rename from superset/assets/src/dashboard/v2/util/newComponentFactory.js
rename to superset/assets/src/dashboard/util/newComponentFactory.js
index b428ddd..4e2de37 100644
--- a/superset/assets/src/dashboard/v2/util/newComponentFactory.js
+++ b/superset/assets/src/dashboard/util/newComponentFactory.js
@@ -1,3 +1,5 @@
+import shortid from 'shortid';
+
 import {
   CHART_TYPE,
   COLUMN_TYPE,
@@ -9,10 +11,7 @@ import {
   TAB_TYPE,
 } from './componentTypes';
 
-import {
-  MEDIUM_HEADER,
-  BACKGROUND_TRANSPARENT,
-} from './constants';
+import { MEDIUM_HEADER, BACKGROUND_TRANSPARENT } from './constants';
 
 const typeToDefaultMetaData = {
   [CHART_TYPE]: { width: 3, height: 30 },
@@ -29,9 +28,8 @@ const typeToDefaultMetaData = {
   [TAB_TYPE]: { text: 'New Tab' },
 };
 
-// @TODO this should be replaced by a more robust algorithm
 function uuid(type) {
-  return `${type}-${Math.random().toString(16)}`;
+  return `${type}-${shortid.generate()}`;
 }
 
 export default function entityFactory(type, meta) {
diff --git a/superset/assets/src/dashboard/v2/util/newEntitiesFromDrop.js b/superset/assets/src/dashboard/util/newEntitiesFromDrop.js
similarity index 81%
rename from superset/assets/src/dashboard/v2/util/newEntitiesFromDrop.js
rename to superset/assets/src/dashboard/util/newEntitiesFromDrop.js
index 7cccc5f..7fe7f4e 100644
--- a/superset/assets/src/dashboard/v2/util/newEntitiesFromDrop.js
+++ b/superset/assets/src/dashboard/util/newEntitiesFromDrop.js
@@ -1,11 +1,7 @@
 import shouldWrapChildInRow from './shouldWrapChildInRow';
 import newComponentFactory from './newComponentFactory';
 
-import {
-  ROW_TYPE,
-  TABS_TYPE,
-  TAB_TYPE,
-} from './componentTypes';
+import { ROW_TYPE, TABS_TYPE, TAB_TYPE } from './componentTypes';
 
 export default function newEntitiesFromDrop({ dropResult, components }) {
   const { dragging, destination } = dropResult;
@@ -15,7 +11,10 @@ export default function newEntitiesFromDrop({ dropResult, components }) {
   const dropEntity = components[destination.id];
   const dropType = dropEntity.type;
   let newDropChild = newComponentFactory(dragType, dragMeta);
-  const wrapChildInRow = shouldWrapChildInRow({ parentType: dropType, childType: dragType });
+  const wrapChildInRow = shouldWrapChildInRow({
+    parentType: dropType,
+    childType: dragType,
+  });
 
   const newEntities = {
     [newDropChild.id]: newDropChild,
@@ -26,7 +25,8 @@ export default function newEntitiesFromDrop({ dropResult, components }) {
     rowWrapper.children = [newDropChild.id];
     newEntities[rowWrapper.id] = rowWrapper;
     newDropChild = rowWrapper;
-  } else if (dragType === TABS_TYPE) { // create a new tab component
+  } else if (dragType === TABS_TYPE) {
+    // create a new tab component
     const tabChild = newComponentFactory(TAB_TYPE);
     newDropChild.children = [tabChild.id];
     newEntities[tabChild.id] = tabChild;
diff --git a/superset/assets/src/dashboard/v2/util/propShapes.jsx b/superset/assets/src/dashboard/util/propShapes.jsx
similarity index 88%
rename from superset/assets/src/dashboard/v2/util/propShapes.jsx
rename to superset/assets/src/dashboard/util/propShapes.jsx
index 388c726..73a10b0 100644
--- a/superset/assets/src/dashboard/v2/util/propShapes.jsx
+++ b/superset/assets/src/dashboard/util/propShapes.jsx
@@ -2,13 +2,16 @@ 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';
+import {
+  INFO_TOAST,
+  SUCCESS_TOAST,
+  WARNING_TOAST,
+  DANGER_TOAST,
+} from './constants';
 
-export const componentShape = PropTypes.shape({ // eslint-disable-line
+export const componentShape = PropTypes.shape({
   id: PropTypes.string.isRequired,
-  type: PropTypes.oneOf(
-    Object.values(componentTypes),
-  ).isRequired,
+  type: PropTypes.oneOf(Object.values(componentTypes)).isRequired,
   children: PropTypes.arrayOf(PropTypes.string),
   meta: PropTypes.shape({
     // Dimensions
@@ -25,7 +28,12 @@ export const componentShape = PropTypes.shape({ // eslint-disable-line
 
 export const toastShape = PropTypes.shape({
   id: PropTypes.string.isRequired,
-  toastType: PropTypes.oneOf([INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST]).isRequired,
+  toastType: PropTypes.oneOf([
+    INFO_TOAST,
+    SUCCESS_TOAST,
+    WARNING_TOAST,
+    DANGER_TOAST,
+  ]).isRequired,
   text: PropTypes.string.isRequired,
 });
 
diff --git a/superset/assets/src/dashboard/v2/util/resizableConfig.js b/superset/assets/src/dashboard/util/resizableConfig.js
similarity index 100%
rename from superset/assets/src/dashboard/v2/util/resizableConfig.js
rename to superset/assets/src/dashboard/util/resizableConfig.js
diff --git a/superset/assets/src/dashboard/v2/util/shouldWrapChildInRow.js b/superset/assets/src/dashboard/util/shouldWrapChildInRow.js
similarity index 100%
rename from superset/assets/src/dashboard/v2/util/shouldWrapChildInRow.js
rename to superset/assets/src/dashboard/util/shouldWrapChildInRow.js
diff --git a/superset/assets/src/dashboard/v2/actions/editMode.js b/superset/assets/src/dashboard/v2/actions/editMode.js
deleted file mode 100644
index 0a849ea..0000000
--- a/superset/assets/src/dashboard/v2/actions/editMode.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export const SET_EDIT_MODE = 'SET_EDIT_MODE';
-export function setEditMode(editMode) {
-  return {
-    type: SET_EDIT_MODE,
-    payload: {
-      editMode,
-    },
-  };
-}
diff --git a/superset/assets/src/dashboard/v2/components/Dashboard.jsx b/superset/assets/src/dashboard/v2/components/Dashboard.jsx
deleted file mode 100644
index ffd1280..0000000
--- a/superset/assets/src/dashboard/v2/components/Dashboard.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import DashboardBuilder from '../containers/DashboardBuilder';
-
-import '../stylesheets/index.less';
-
-const propTypes = {
-  actions: PropTypes.shape({
-    updateDashboardTitle: PropTypes.func.isRequired,
-    setEditMode: PropTypes.func.isRequired,
-  }),
-  editMode: PropTypes.bool,
-};
-
-const defaultProps = {
-  editMode: true,
-};
-
-class Dashboard extends React.Component {
-  render() {
-    // @TODO delete this component?
-    return <DashboardBuilder />;
-  }
-}
-
-Dashboard.propTypes = propTypes;
-Dashboard.defaultProps = defaultProps;
-
-export default Dashboard;
diff --git a/superset/assets/src/dashboard/v2/components/DashboardHeader.jsx b/superset/assets/src/dashboard/v2/components/DashboardHeader.jsx
deleted file mode 100644
index d3ec7ac..0000000
--- a/superset/assets/src/dashboard/v2/components/DashboardHeader.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { ButtonGroup, ButtonToolbar, DropdownButton, MenuItem } from 'react-bootstrap';
-
-import Button from '../../../components/Button';
-import { componentShape } from '../util/propShapes';
-import EditableTitle from '../../../components/EditableTitle';
-
-const propTypes = {
-  editMode: PropTypes.bool.isRequired,
-  component: componentShape.isRequired,
-
-  // redux
-  updateComponents: PropTypes.func.isRequired,
-  onUndo: PropTypes.func.isRequired,
-  onRedo: PropTypes.func.isRequired,
-  canUndo: PropTypes.bool.isRequired,
-  canRedo: PropTypes.bool.isRequired,
-  setEditMode: PropTypes.func.isRequired,
-};
-
-class DashboardHeader extends React.Component {
-  constructor(props) {
-    super(props);
-    this.handleChangeText = this.handleChangeText.bind(this);
-    this.toggleEditMode = this.toggleEditMode.bind(this);
-  }
-
-  toggleEditMode() {
-    this.props.setEditMode(!this.props.editMode);
-  }
-
-  handleChangeText(nextText) {
-    const { updateComponents, component } = this.props;
-    if (nextText && component.meta.text !== nextText) {
-      updateComponents({
-        [component.id]: {
-          ...component,
-          meta: {
-            ...component.meta,
-            text: nextText,
-          },
-        },
-      });
-    }
-  }
-
-  render() {
-    const { component, onUndo, onRedo, canUndo, canRedo, editMode } = this.props;
-
-    return (
-      <div className="dashboard-header">
-        <div className="dashboard-component-header header-large">
-          <EditableTitle
-            title={'Test title'}
-            onSaveTitle={this.handleChangeText}
-            showTooltip={false}
-            canEdit={editMode}
-          />
-        </div>
-        <ButtonToolbar>
-          <ButtonGroup>
-            <Button
-              bsSize="small"
-              onClick={onUndo}
-              disabled={!canUndo}
-            >
-              Undo
-            </Button>
-            <Button
-              bsSize="small"
-              onClick={onRedo}
-              disabled={!canRedo}
-            >
-              Redo
-            </Button>
-          </ButtonGroup>
-
-          <DropdownButton title="Actions" bsSize="small" id="btn-dashboard-actions">
-            <MenuItem>Action 1</MenuItem>
-            <MenuItem>Action 2</MenuItem>
-            <MenuItem>Action 3</MenuItem>
-          </DropdownButton>
-
-          <Button
-            bsStyle="primary"
-            onClick={this.toggleEditMode}
-          >
-            {editMode ? 'Save changes' : 'Edit dashboard'}
-          </Button>
-        </ButtonToolbar>
-      </div>
-    );
-  }
-}
-
-DashboardHeader.propTypes = propTypes;
-
-export default DashboardHeader;
diff --git a/superset/assets/src/dashboard/v2/components/StaticDashboard.jsx b/superset/assets/src/dashboard/v2/components/StaticDashboard.jsx
deleted file mode 100644
index 4fd2397..0000000
--- a/superset/assets/src/dashboard/v2/components/StaticDashboard.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-const propTypes = {
-};
-
-class StaticDashboard extends React.Component {
-  render() {
-    return (
-      <div>
-        Static dashboard ...
-      </div>
-    );
-  }
-}
-
-StaticDashboard.propTypes = propTypes;
-
-export default StaticDashboard;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewChart.jsx b/superset/assets/src/dashboard/v2/components/gridComponents/new/NewChart.jsx
deleted file mode 100644
index 0255755..0000000
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewChart.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { CHART_TYPE } from '../../../util/componentTypes';
-import { NEW_CHART_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewChart extends React.PureComponent {
-  render() {
-    return (
-      <DraggableNewComponent
-        id={NEW_CHART_ID}
-        type={CHART_TYPE}
-        label="Chart"
-        className="fa fa-area-chart"
-      />
-    );
-  }
-}
-
-DraggableNewChart.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewColumn.jsx b/superset/assets/src/dashboard/v2/components/gridComponents/new/NewColumn.jsx
deleted file mode 100644
index 654c60b..0000000
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewColumn.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { COLUMN_TYPE } from '../../../util/componentTypes';
-import { NEW_COLUMN_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewColumn extends React.PureComponent {
-  render() {
-    return (
-      <DraggableNewComponent
-        id={NEW_COLUMN_ID}
-        type={COLUMN_TYPE}
-        label="Column"
-        className="fa fa-long-arrow-down"
-      />
-    );
-  }
-}
-
-DraggableNewColumn.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewDivider.jsx b/superset/assets/src/dashboard/v2/components/gridComponents/new/NewDivider.jsx
deleted file mode 100644
index 5d70041..0000000
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewDivider.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { DIVIDER_TYPE } from '../../../util/componentTypes';
-import { NEW_DIVIDER_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewDivider extends React.PureComponent {
-  render() {
-    return (
-      <DraggableNewComponent
-        id={NEW_DIVIDER_ID}
-        type={DIVIDER_TYPE}
-        label="Divider"
-        className="divider-placeholder"
-      />
-    );
-  }
-}
-
-DraggableNewDivider.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewHeader.jsx b/superset/assets/src/dashboard/v2/components/gridComponents/new/NewHeader.jsx
deleted file mode 100644
index d207a9c..0000000
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewHeader.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { HEADER_TYPE } from '../../../util/componentTypes';
-import { NEW_HEADER_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewHeader extends React.Component {
-  render() {
-    return (
-      <DraggableNewComponent
-        id={NEW_HEADER_ID}
-        type={HEADER_TYPE}
-        label="Header"
-        className="fa fa-header"
-      />
-    );
-  }
-}
-
-DraggableNewHeader.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewRow.jsx b/superset/assets/src/dashboard/v2/components/gridComponents/new/NewRow.jsx
deleted file mode 100644
index 1d9ab10..0000000
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewRow.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-
-import { ROW_TYPE } from '../../../util/componentTypes';
-import { NEW_ROW_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewRow extends React.PureComponent {
-  render() {
-    return (
-      <DraggableNewComponent
-        id={NEW_ROW_ID}
-        type={ROW_TYPE}
-        label="Row"
-        className="fa fa-long-arrow-right"
-      />
-    );
-  }
-}
-
-DraggableNewRow.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewTabs.jsx b/superset/assets/src/dashboard/v2/components/gridComponents/new/NewTabs.jsx
deleted file mode 100644
index a473281..0000000
--- a/superset/assets/src/dashboard/v2/components/gridComponents/new/NewTabs.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { TABS_TYPE } from '../../../util/componentTypes';
-import { NEW_TABS_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewTabs extends React.PureComponent {
-  render() {
-    return (
-      <DraggableNewComponent
-        id={NEW_TABS_ID}
-        type={TABS_TYPE}
-        label="Tabs"
-        className="fa fa-window-restore"
-      />
-    );
-  }
-}
-
-DraggableNewTabs.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/v2/reducers/editMode.js b/superset/assets/src/dashboard/v2/reducers/editMode.js
deleted file mode 100644
index b1a1630..0000000
--- a/superset/assets/src/dashboard/v2/reducers/editMode.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { SET_EDIT_MODE } from '../actions/editMode';
-
-export default function editModeReducer(editMode = false, action) {
-  switch (action.type) {
-    case SET_EDIT_MODE:
-      return action.payload.editMode;
-
-    default:
-      return editMode;
-  }
-}
diff --git a/superset/assets/src/dashboard/v2/reducers/index.js b/superset/assets/src/dashboard/v2/reducers/index.js
deleted file mode 100644
index 061255d..0000000
--- a/superset/assets/src/dashboard/v2/reducers/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import undoable, { distinctState } from 'redux-undo';
-
-import dashboardLayout from './dashboardLayout';
-
-export default undoable(dashboardLayout, {
-  limit: 15,
-  filter: distinctState(),
-});
diff --git a/superset/assets/src/dashboard/v2/stylesheets/components/chart.less b/superset/assets/src/dashboard/v2/stylesheets/components/chart.less
deleted file mode 100644
index ce03797..0000000
--- a/superset/assets/src/dashboard/v2/stylesheets/components/chart.less
+++ /dev/null
@@ -1,20 +0,0 @@
-.dashboard-component-chart {
-  width: 100%;
-  height: 100%;
-  color: @gray-dark;
-  background-color: white;
-  padding: 16px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  position: relative;
-}
-
-.dashboard-component-chart .fa {
-  //font-size: 100px;
-  opacity: 0.3;
-}
-
-.dashboard-v2--editing .dashboard-component-chart:hover {
-  box-shadow: inset 0 0 0 1px @gray-light;
-}
diff --git a/superset/assets/src/dashboard/v2/util/backgroundStyleOptions.js b/superset/assets/src/dashboard/v2/util/backgroundStyleOptions.js
deleted file mode 100644
index cda678f..0000000
--- a/superset/assets/src/dashboard/v2/util/backgroundStyleOptions.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { t } from '../../../locales';
-import { BACKGROUND_TRANSPARENT, BACKGROUND_WHITE } from './constants';
-
-export default [
-  { value: BACKGROUND_TRANSPARENT, label: t('Transparent'), className: 'background--transparent' },
-  { value: BACKGROUND_WHITE, label: t('White'), className: 'background--white' },
-];
diff --git a/superset/assets/src/dashboard/v2/util/componentIsResizable.js b/superset/assets/src/dashboard/v2/util/componentIsResizable.js
deleted file mode 100644
index c0016f3..0000000
--- a/superset/assets/src/dashboard/v2/util/componentIsResizable.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import {
-  COLUMN_TYPE,
-  CHART_TYPE,
-  MARKDOWN_TYPE,
-} from './componentTypes';
-
-export default function componentIsResizable(entity) {
-  return [
-    COLUMN_TYPE,
-    CHART_TYPE,
-    MARKDOWN_TYPE,
-  ].indexOf(entity.type) > -1;
-}
diff --git a/superset/assets/src/explore/components/ExploreChartHeader.jsx b/superset/assets/src/explore/components/ExploreChartHeader.jsx
index 19416b0..3825335 100644
--- a/superset/assets/src/explore/components/ExploreChartHeader.jsx
+++ b/superset/assets/src/explore/components/ExploreChartHeader.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { chartPropShape } from '../../dashboard/v2/util/propShapes';
+import { chartPropShape } from '../../dashboard/util/propShapes';
 import ExploreActionButtons from './ExploreActionButtons';
 import RowCountLabel from './RowCountLabel';
 import EditableTitle from '../../components/EditableTitle';
diff --git a/superset/assets/src/explore/components/ExploreChartPanel.jsx b/superset/assets/src/explore/components/ExploreChartPanel.jsx
index 21c6a64..bcda75d 100644
--- a/superset/assets/src/explore/components/ExploreChartPanel.jsx
+++ b/superset/assets/src/explore/components/ExploreChartPanel.jsx
@@ -3,7 +3,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Panel } from 'react-bootstrap';
 
-import { chartPropShape } from '../../dashboard/v2/util/propShapes';
+import { chartPropShape } from '../../dashboard/util/propShapes';
 import ChartContainer from '../../chart/ChartContainer';
 import ExploreChartHeader from './ExploreChartHeader';
 
@@ -38,14 +38,15 @@ class ExploreChartPanel extends React.PureComponent {
   }
 
   renderChart() {
+    const { chart } = this.props;
     return (
       <ChartContainer
+        chartId={chart.id}
         containerId={this.props.containerId}
         datasource={this.props.datasource}
         formData={this.props.form_data}
         height={this.getHeight()}
         slice={this.props.slice}
-        chartId={this.props.chart.id}
         setControlValue={this.props.actions.setControlValue}
         timeout={this.props.timeout}
         vizType={this.props.vizType}
@@ -53,6 +54,16 @@ class ExploreChartPanel extends React.PureComponent {
         errorMessage={this.props.errorMessage}
         onQuery={this.props.onQuery}
         onDismissRefreshOverlay={this.props.onDismissRefreshOverlay}
+        annotationData={chart.annotationData}
+        chartAlert={chart.chartAlert}
+        chartStatus={chart.chartStatus}
+        chartUpdateEndTime={chart.chartUpdateEndTime}
+        chartUpdateStartTime={chart.chartUpdateStartTime}
+        latestQueryFormData={chart.latestQueryFormData}
+        lastRendered={chart.lastRendered}
+        queryResponse={chart.queryResponse}
+        queryRequest={chart.queryRequest}
+        triggerQuery={chart.triggerQuery}
       />
     );
   }
diff --git a/superset/assets/src/explore/components/ExploreViewContainer.jsx b/superset/assets/src/explore/components/ExploreViewContainer.jsx
index 3e761eb..a648464 100644
--- a/superset/assets/src/explore/components/ExploreViewContainer.jsx
+++ b/superset/assets/src/explore/components/ExploreViewContainer.jsx
@@ -11,7 +11,7 @@ import QueryAndSaveBtns from './QueryAndSaveBtns';
 import { getExploreUrlAndPayload, getExploreLongUrl } from '../exploreUtils';
 import { areObjectsEqual } from '../../reduxUtils';
 import { getFormDataFromControls } from '../store';
-import { chartPropShape } from '../../dashboard/v2/util/propShapes';
+import { chartPropShape } from '../../dashboard/util/propShapes';
 import * as exploreActions from '../actions/exploreActions';
 import * as saveModalActions from '../actions/saveModalActions';
 import * as chartActions from '../../chart/chartAction';
diff --git a/superset/assets/stylesheets/dashboard.less b/superset/assets/stylesheets/dashboard.less
deleted file mode 100644
index b812a42..0000000
--- a/superset/assets/stylesheets/dashboard.less
+++ /dev/null
@@ -1,156 +0,0 @@
-@import "./less/cosmo/variables.less";
-
-.dashboard a i {
-  cursor: pointer;
-}
-.dashboard i.drag {
-  cursor: move !important;
-}
-.dashboard .slice-grid .preview-holder {
-  z-index: 1;
-  position: absolute;
-  background-color: #AAA;
-  border-color: #AAA;
-  opacity: 0.3;
-}
-.dashboard .widget {
-  position: absolute;
-  top: 16px;
-  left: 16px;
-  box-shadow: none;
-  background-color: transparent;
-  overflow: visible;
-}
-.dashboard .chart-header {
-  .dropdown.btn-group {
-    position: absolute;
-    top: 0;
-    right: 0;
-  }
-
-  .dropdown-menu.dropdown-menu-right {
-    right: 7px;
-    top: -3px
-  }
-}
-
-.slice-header-controls-trigger {
-  border: 0;
-  padding: 0 0 0 20px;
-  background: none;
-  outline: none;
-  box-shadow: none;
-  color: #263238;
-
-  &.is-cached {
-    color: red;
-  }
-
-  &:hover, &:focus {
-    background: none;
-    cursor: pointer;
-  }
-
-  .controls-container.dropdown-menu {
-    top: 0;
-    left: unset;
-    right: 10px;
-
-    &.is-open {
-      display: block;
-    }
-
-    & li {
-      white-space: nowrap;
-    }
-  }
-}
-.slice-grid .slice_container {
-  background-color: #fff;
-}
-
-.dashboard .slice-grid .dragging,
-.dashboard .slice-grid .resizing {
-  opacity: 0.5;
-}
-.dashboard img.loading {
-  width: 20px;
-  margin: 5px;
-  position: absolute;
-}
-
-.dashboard .slice_title {
-  text-align: center;
-  font-weight: bold;
-  font-size: 14px;
-  padding: 5px;
-}
-.dashboard div.slice_content {
-  width: 100%;
-  height: 100%;
-}
-
-.modal img.loading {
-  width: 50px;
-  margin: 0;
-  position: relative;
-}
-
-.react-bs-container-body {
-  max-height: 400px;
-  overflow-y: auto;
-}
-
-.hidden, #pageDropDown {
-  display: none;
-}
-
-.slice-cell {
-  box-shadow: 0px 0px 20px 5px rgba(0,0,0,0);
-  transition: box-shadow 1s ease-in;
-
-  .dropdown,
-  .dropdown-menu {
-    .fa {
-      font-size: 14px;
-    }
-  }
-}
-
-.slice-cell-highlight {
-  box-shadow: 0px 0px 20px 5px rgba(0,0,0,0.2);
-  height: 100%;
-}
-
-.slice-cell .editable-title input[type="button"] {
-  font-weight: bold;
-}
-
-.chart-container {
-  box-sizing: border-box;
-}
-
-.chart-header .header {
-  font-size: 16px;
-  margin: 0 -10px;
-}
-.ace_gutter {
-    z-index: 0;
-}
-.ace_content {
-    z-index: 0;
-}
-.ace_scrollbar {
-    z-index: 0;
-}
-.slice_container .alert {
-    margin: 10px;
-}
-
-i.danger {
-  color: red;
-}
-
-i.warning {
-  color: orange;
-}
diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less
index 6987544..d756551 100644
--- a/superset/assets/stylesheets/superset.less
+++ b/superset/assets/stylesheets/superset.less
@@ -141,7 +141,7 @@ div.navbar {
 img.loading {
   width: 40px;
   position: relative;
-  z-index: 10;
+  z-index: 1;
   margin: 10px;
 }
 img.viz-thumb-option {
diff --git a/superset/assets/stylesheets/welcome.css b/superset/assets/stylesheets/welcome.css
index 8e2496e..1f72852 100644
--- a/superset/assets/stylesheets/welcome.css
+++ b/superset/assets/stylesheets/welcome.css
@@ -3,7 +3,7 @@
 }
 
 img.loading {
-    width: 25px;
+  width: 25px;
 }
 
 .welcome table {
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index d3efc78..77ca84d 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -2,27 +2,27 @@
 # yarn lockfile v1
 
 
-"@data-ui/event-flow@^0.0.54":
-  version "0.0.54"
-  resolved "https://registry.yarnpkg.com/@data-ui/event-flow/-/event-flow-0.0.54.tgz#bb03e1fd2b5634248655b8df9d3c6c38a747e65e"
-  dependencies:
-    "@data-ui/forms" "0.0.50"
-    "@data-ui/radial-chart" "0.0.54"
-    "@data-ui/theme" "0.0.48"
-    "@vx/axis" "0.0.140"
-    "@vx/bounds" "0.0.140"
-    "@vx/clip-path" "0.0.140"
-    "@vx/glyph" "0.0.140"
-    "@vx/gradient" "0.0.140"
-    "@vx/grid" "0.0.140"
-    "@vx/group" "0.0.140"
-    "@vx/legend" "0.0.140"
-    "@vx/pattern" "0.0.140"
-    "@vx/point" "0.0.136"
-    "@vx/responsive" "0.0.140"
-    "@vx/scale" "0.0.140"
-    "@vx/shape" "0.0.140"
-    "@vx/tooltip" "0.0.140"
+"@data-ui/event-flow@^0.0.8":
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/@data-ui/event-flow/-/event-flow-0.0.8.tgz#237ef225e3085fae9e47bbb1ae11b6fab27eaede"
+  dependencies:
+    "@data-ui/forms" "0.0.4"
+    "@data-ui/radial-chart" "0.0.8"
+    "@data-ui/theme" "0.0.9"
+    "@vx/axis" "0.0.120"
+    "@vx/bounds" "0.0.129"
+    "@vx/clip-path" "0.0.126"
+    "@vx/glyph" "0.0.126"
+    "@vx/gradient" "0.0.120"
+    "@vx/grid" "0.0.120"
+    "@vx/group" "0.0.120"
+    "@vx/legend" "0.0.121"
+    "@vx/pattern" "0.0.120"
+    "@vx/point" "0.0.112"
+    "@vx/responsive" "0.0.120"
+    "@vx/scale" "0.0.121"
+    "@vx/shape" "0.0.120"
+    "@vx/tooltip" "0.0.126"
     aphrodite "^1.2.0"
     d3-array "^1.2.0"
     d3-format "^1.2.0"
@@ -35,29 +35,28 @@
     react-with-styles-interface-aphrodite "^1.2.0"
     recompose "^0.23.5"
 
-"@data-ui/forms@0.0.50":
-  version "0.0.50"
-  resolved "https://registry.yarnpkg.com/@data-ui/forms/-/forms-0.0.50.tgz#c55a699ee4b7cf44ff263d30784299c38d939932"
+"@data-ui/forms@0.0.4":
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/@data-ui/forms/-/forms-0.0.4.tgz#3c1efc55904289e4bffc6536c5f89b3b18f39e4d"
   dependencies:
     prop-types "^15.5.10"
     react-select "^1.0.0-rc.5"
 
-"@data-ui/radial-chart@0.0.54":
-  version "0.0.54"
-  resolved "https://registry.yarnpkg.com/@data-ui/radial-chart/-/radial-chart-0.0.54.tgz#0d28b07681d9b6027d9ac23b729241827d513001"
-  dependencies:
-    "@data-ui/shared" "0.0.54"
-    "@data-ui/theme" "0.0.48"
-    "@vx/event" "0.0.140"
-    "@vx/group" "0.0.140"
-    "@vx/scale" "0.0.140"
-    "@vx/shape" "0.0.140"
-    "@vx/tooltip" "0.0.140"
+"@data-ui/radial-chart@0.0.8":
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/@data-ui/radial-chart/-/radial-chart-0.0.8.tgz#44c0d789a3981c0e9e90f9dbb5e0cbaaafcea09d"
+  dependencies:
+    "@data-ui/theme" "0.0.9"
+    "@vx/event" "0.0.127"
+    "@vx/group" "0.0.127"
+    "@vx/scale" "0.0.127"
+    "@vx/shape" "0.0.131"
+    "@vx/tooltip" "0.0.134"
     prop-types "^15.5.10"
 
-"@data-ui/shared@0.0.54":
-  version "0.0.54"
-  resolved "https://registry.yarnpkg.com/@data-ui/shared/-/shared-0.0.54.tgz#2fb0d6dee90dac20bf8f3c2913c6850a8223d59b"
+"@data-ui/shared@0.0.49":
+  version "0.0.49"
+  resolved "https://registry.yarnpkg.com/@data-ui/shared/-/shared-0.0.49.tgz#cea1fff1434e54a2dcc2d5f9612300738a466a19"
   dependencies:
     "@data-ui/theme" "0.0.48"
     "@vx/event" "0.0.143"
@@ -67,11 +66,11 @@
     d3-array "^1.2.1"
     prop-types "^15.5.10"
 
-"@data-ui/sparkline@^0.0.54":
-  version "0.0.54"
-  resolved "https://registry.yarnpkg.com/@data-ui/sparkline/-/sparkline-0.0.54.tgz#ce3d166d9e0b239a0ba02f3894cb9e8c84171cef"
+"@data-ui/sparkline@^0.0.49":
+  version "0.0.49"
+  resolved "https://registry.yarnpkg.com/@data-ui/sparkline/-/sparkline-0.0.49.tgz#5ae28769289af7f8a1bffc6b2ac5299f1fb9e846"
   dependencies:
-    "@data-ui/shared" "0.0.54"
+    "@data-ui/shared" "0.0.49"
     "@data-ui/theme" "0.0.8"
     "@vx/axis" "0.0.140"
     "@vx/curve" "0.0.140"
@@ -95,6 +94,10 @@
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/@data-ui/theme/-/theme-0.0.8.tgz#3116723d04b99f65c7750f81a500e9608b4837c3"
 
+"@data-ui/theme@0.0.9":
+  version "0.0.9"
+  resolved "https://registry.yarnpkg.com/@data-ui/theme/-/theme-0.0.9.tgz#a9d66b20d74018009c129eed4326ca4ef3b83127"
+
 "@mapbox/geojson-area@0.2.2":
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz#18d7814aa36bf23fbbcc379f8e26a22927debf10"
@@ -105,13 +108,9 @@
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/@mapbox/gl-matrix/-/gl-matrix-0.0.1.tgz#e5126aab4d64c36b81c7a97d0ae0dddde5773d2b"
 
-"@mapbox/jsonlint-lines-primitives@^2.0.1":
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.1.tgz#bc4c1593e2ec2371e2771c518068d6eab8eeae58"
-
-"@mapbox/mapbox-gl-supported@^1.3.1":
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.3.1.tgz#81bd3a5e3cfdc40047608656ee9b519e02216bf1"
+"@mapbox/mapbox-gl-supported@^1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.3.0.tgz#89daee16845400ea1c76e084bdfab2971e552a9c"
 
 "@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0":
   version "0.1.0"
@@ -135,42 +134,22 @@
   dependencies:
     "@mapbox/point-geometry" "~0.1.0"
 
-"@mapbox/vector-tile@^1.3.1":
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666"
-  dependencies:
-    "@mapbox/point-geometry" "~0.1.0"
-
 "@mapbox/whoots-js@^3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.0.0.tgz#c1de4293081424da3ac30c23afa850af1019bb54"
 
-"@mrmlnc/readdir-enhanced@^2.2.1":
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
-  dependencies:
-    call-me-maybe "^1.0.1"
-    glob-to-regexp "^0.3.0"
-
-"@nodelib/fs.stat@^1.0.1":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a"
-
-"@samverschueren/stream-to-observable@^0.3.0":
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
-  dependencies:
-    any-observable "^0.3.0"
-
-"@sindresorhus/is@^0.7.0":
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
+"@types/react@>=15":
+  version "16.0.12"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.12.tgz#63f460337e83b24549db744aa2f033121d6d55db"
 
-"@sinonjs/formatio@^2.0.0":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
+"@vx/axis@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/axis/-/axis-0.0.120.tgz#5dd5841d03e992990491b20ce918b242f9061d4e"
   dependencies:
-    samsam "1.3.0"
+    "@vx/group" "0.0.120"
+    "@vx/point" "0.0.112"
+    "@vx/shape" "0.0.120"
+    classnames "^2.2.5"
 
 "@vx/axis@0.0.140":
   version "0.0.140"
@@ -182,11 +161,12 @@
     classnames "^2.2.5"
     prop-types "15.5.10"
 
-"@vx/bounds@0.0.140":
-  version "0.0.140"
-  resolved "https://registry.yarnpkg.com/@vx/bounds/-/bounds-0.0.140.tgz#4ede9766aabb41b791a4fbf4c27fcc19ed83f910"
+"@vx/bounds@0.0.129":
+  version "0.0.129"
+  resolved "https://registry.yarnpkg.com/@vx/bounds/-/bounds-0.0.129.tgz#7ecbdfaa3f5041e3f87c851bf4981547e2d19ffe"
   dependencies:
     prop-types "^15.5.10"
+    react-dom "^15.0.0 || 15.x"
 
 "@vx/bounds@0.0.147":
   version "0.0.147"
@@ -194,9 +174,21 @@
   dependencies:
     prop-types "^15.5.10"
 
-"@vx/clip-path@0.0.140":
-  version "0.0.140"
-  resolved "https://registry.yarnpkg.com/@vx/clip-path/-/clip-path-0.0.140.tgz#b2623d004dd5c3c8a6afe8d060de59df51472d94"
+"@vx/clip-path@0.0.126":
+  version "0.0.126"
+  resolved "https://registry.yarnpkg.com/@vx/clip-path/-/clip-path-0.0.126.tgz#58f595dbd5b7fbb27f6a98bac9885a3d701e976e"
+
+"@vx/curve@0.0.112":
+  version "0.0.112"
+  resolved "https://registry.yarnpkg.com/@vx/curve/-/curve-0.0.112.tgz#8e4880a7ae8902bbd5b337ad1b0719bdfb28df3d"
+  dependencies:
+    d3-shape "^1.0.6"
+
+"@vx/curve@0.0.127":
+  version "0.0.127"
+  resolved "https://registry.yarnpkg.com/@vx/curve/-/curve-0.0.127.tgz#f05ea9871a97e1e1d59129b847f216751ffb31c3"
+  dependencies:
+    d3-shape "^1.0.6"
 
 "@vx/curve@0.0.140":
   version "0.0.140"
@@ -210,11 +202,11 @@
   dependencies:
     d3-shape "^1.0.6"
 
-"@vx/event@0.0.140":
-  version "0.0.140"
-  resolved "https://registry.yarnpkg.com/@vx/event/-/event-0.0.140.tgz#658ec4de92cd61df40b883296168d4e0824015bf"
+"@vx/event@0.0.127":
+  version "0.0.127"
+  resolved "https://registry.yarnpkg.com/@vx/event/-/event-0.0.127.tgz#8b0079a63cf0aeefd884123dd99ee18efef0af69"
   dependencies:
-    "@vx/point" "0.0.136"
+    "@vx/point" "0.0.127"
 
 "@vx/event@0.0.143":
   version "0.0.143"
@@ -222,6 +214,14 @@
   dependencies:
     "@vx/point" "0.0.143"
 
+"@vx/glyph@0.0.126":
+  version "0.0.126"
+  resolved "https://registry.yarnpkg.com/@vx/glyph/-/glyph-0.0.126.tgz#9ebf0924aa15098b6e4b8c44bfbdfbb8c8a2c658"
+  dependencies:
+    "@vx/group" "0.0.126"
+    classnames "^2.2.5"
+    d3-shape "^1.2.0"
+
 "@vx/glyph@0.0.140":
   version "0.0.140"
   resolved "https://registry.yarnpkg.com/@vx/glyph/-/glyph-0.0.140.tgz#f8323f82aee22192b675bef25789bbb7d74691ba"
@@ -230,6 +230,12 @@
     classnames "^2.2.5"
     d3-shape "^1.2.0"
 
+"@vx/gradient@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/gradient/-/gradient-0.0.120.tgz#eeac03390d543bcba354d5d5de169ec5f1945c2d"
+  dependencies:
+    classnames "^2.2.5"
+
 "@vx/gradient@0.0.140":
   version "0.0.140"
   resolved "https://registry.yarnpkg.com/@vx/gradient/-/gradient-0.0.140.tgz#56b421016cbae0dcb00190cfffb9e860a28febf4"
@@ -237,13 +243,31 @@
     classnames "^2.2.5"
     prop-types "^15.5.7"
 
-"@vx/grid@0.0.140":
-  version "0.0.140"
-  resolved "https://registry.yarnpkg.com/@vx/grid/-/grid-0.0.140.tgz#9dfd3071bc5d90d4b457dd55d7f795699233b230"
+"@vx/grid@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/grid/-/grid-0.0.120.tgz#3471d5cc8a4bef32082ff476ef73fc04db29335e"
+  dependencies:
+    "@vx/group" "0.0.120"
+    "@vx/point" "0.0.112"
+    "@vx/shape" "0.0.120"
+    classnames "^2.2.5"
+
+"@vx/group@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/group/-/group-0.0.120.tgz#0cc8bc8f40e371585c8c523d98c41cc62684aab9"
+  dependencies:
+    classnames "^2.2.5"
+
+"@vx/group@0.0.126":
+  version "0.0.126"
+  resolved "https://registry.yarnpkg.com/@vx/group/-/group-0.0.126.tgz#5610ce74d0f118db28287806315ae479f313479c"
+  dependencies:
+    classnames "^2.2.5"
+
+"@vx/group@0.0.127":
+  version "0.0.127"
+  resolved "https://registry.yarnpkg.com/@vx/group/-/group-0.0.127.tgz#d607c957119cdb787aa709818532f40e7e04eb8f"
   dependencies:
-    "@vx/group" "0.0.140"
-    "@vx/point" "0.0.136"
-    "@vx/shape" "0.0.140"
     classnames "^2.2.5"
 
 "@vx/group@0.0.140":
@@ -258,11 +282,18 @@
   dependencies:
     classnames "^2.2.5"
 
-"@vx/legend@0.0.140":
-  version "0.0.140"
-  resolved "https://registry.yarnpkg.com/@vx/legend/-/legend-0.0.140.tgz#4062c27d6bc9c4d607309d77eff12b844727ae99"
+"@vx/legend@0.0.121":
+  version "0.0.121"
+  resolved "https://registry.yarnpkg.com/@vx/legend/-/legend-0.0.121.tgz#0c8cd6860f45af6cbc524ec6526256d2951cca34"
+  dependencies:
+    "@vx/group" "0.0.120"
+    classnames "^2.2.5"
+    prop-types "^15.5.10"
+
+"@vx/pattern@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/pattern/-/pattern-0.0.120.tgz#cff57b6279c9d6b097a20df3562b8b81c17bdb35"
   dependencies:
-    "@vx/group" "0.0.140"
     classnames "^2.2.5"
     prop-types "^15.5.10"
 
@@ -273,6 +304,14 @@
     classnames "^2.2.5"
     prop-types "^15.5.10"
 
+"@vx/point@0.0.112":
+  version "0.0.112"
+  resolved "https://registry.yarnpkg.com/@vx/point/-/point-0.0.112.tgz#cd5b0740268bb432550902ad5e00261bad641cef"
+
+"@vx/point@0.0.127":
+  version "0.0.127"
+  resolved "https://registry.yarnpkg.com/@vx/point/-/point-0.0.127.tgz#51ef7f648488feed758a6ec1cc8c71319602e3e7"
+
 "@vx/point@0.0.136":
   version "0.0.136"
   resolved "https://registry.yarnpkg.com/@vx/point/-/point-0.0.136.tgz#93b325b4b95c9d5b96df740f4204017f57396559"
@@ -281,18 +320,65 @@
   version "0.0.143"
   resolved "https://registry.yarnpkg.com/@vx/point/-/point-0.0.143.tgz#7b6dfa611175ee1b74e3c392072589a79dadf265"
 
+"@vx/responsive@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/responsive/-/responsive-0.0.120.tgz#31c021f213796570787cdd2d2ddb90c4c9444e39"
+  dependencies:
+    lodash "^4.0.8"
+
 "@vx/responsive@0.0.140":
   version "0.0.140"
   resolved "https://registry.yarnpkg.com/@vx/responsive/-/responsive-0.0.140.tgz#c73ec68b9e89a181605f1ac9ecc09f35216779a8"
   dependencies:
     lodash "^4.0.8"
 
+"@vx/responsive@0.0.153":
+  version "0.0.153"
+  resolved "https://registry.yarnpkg.com/@vx/responsive/-/responsive-0.0.153.tgz#2ce7e819341d2e59ff4151b40e5792aea460e202"
+  dependencies:
+    lodash "^4.0.8"
+    resize-observer-polyfill "1.5.0"
+
+"@vx/scale@0.0.121":
+  version "0.0.121"
+  resolved "https://registry.yarnpkg.com/@vx/scale/-/scale-0.0.121.tgz#5f49ea2060469ded0bf0e3ef5a5bb1416b81180e"
+  dependencies:
+    d3-scale "^1.0.5"
+
+"@vx/scale@0.0.127":
+  version "0.0.127"
+  resolved "https://registry.yarnpkg.com/@vx/scale/-/scale-0.0.127.tgz#2f81530c89b1ad837be387aaccebedd507f16549"
+  dependencies:
+    d3-scale "^1.0.5"
+
 "@vx/scale@0.0.140":
   version "0.0.140"
   resolved "https://registry.yarnpkg.com/@vx/scale/-/scale-0.0.140.tgz#1eb087d11d0000b250c2cdc4061b9e2212edb10d"
   dependencies:
     d3-scale "^1.0.5"
 
+"@vx/shape@0.0.120":
+  version "0.0.120"
+  resolved "https://registry.yarnpkg.com/@vx/shape/-/shape-0.0.120.tgz#53d5457ec58e298bed507baff015fed2507ac741"
+  dependencies:
+    "@vx/curve" "0.0.112"
+    "@vx/group" "0.0.120"
+    "@vx/point" "0.0.112"
+    classnames "^2.2.5"
+    d3-shape "^1.2.0"
+    prop-types "^15.5.10"
+
+"@vx/shape@0.0.131":
+  version "0.0.131"
+  resolved "https://registry.yarnpkg.com/@vx/shape/-/shape-0.0.131.tgz#38585e93319c9f958d317485b0b6520ff295f179"
+  dependencies:
+    "@vx/curve" "0.0.127"
+    "@vx/group" "0.0.127"
+    "@vx/point" "0.0.127"
+    classnames "^2.2.5"
+    d3-shape "^1.2.0"
+    prop-types "^15.5.10"
+
 "@vx/shape@0.0.140":
   version "0.0.140"
   resolved "https://registry.yarnpkg.com/@vx/shape/-/shape-0.0.140.tgz#6a282d5fdf3a5752b6e938bb3debe983e89ff6d3"
@@ -315,11 +401,19 @@
     d3-shape "^1.2.0"
     prop-types "^15.5.10"
 
-"@vx/tooltip@0.0.140":
-  version "0.0.140"
-  resolved "https://registry.yarnpkg.com/@vx/tooltip/-/tooltip-0.0.140.tgz#c5c8306272877c1bbd4e8b478ea5291f1019ffe3"
+"@vx/tooltip@0.0.126":
+  version "0.0.126"
+  resolved "https://registry.yarnpkg.com/@vx/tooltip/-/tooltip-0.0.126.tgz#bcbd48bddf614585b11dff4d5b75ba35a42df68c"
   dependencies:
-    "@vx/bounds" "0.0.140"
+    classnames "^2.2.5"
+    prop-types "^15.5.10"
+    recompose "^0.23.5"
+
+"@vx/tooltip@0.0.134":
+  version "0.0.134"
+  resolved "https://registry.yarnpkg.com/@vx/tooltip/-/tooltip-0.0.134.tgz#8337f0876a98b3eec8e9636b4694126789eba60c"
+  dependencies:
+    "@vx/bounds" "0.0.129"
     classnames "^2.2.5"
     prop-types "^15.5.10"
 
@@ -332,12 +426,16 @@
     prop-types "^15.5.10"
 
 JSONStream@^1.3.2:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf"
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea"
   dependencies:
     jsonparse "^1.2.0"
     through ">=2.2.7 <3"
 
+"JSV@>= 4.0.x":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57"
+
 abab@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
@@ -390,26 +488,26 @@ acorn@^5.5.0:
   version "5.5.3"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
 
-agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
+agent-base@4, agent-base@^4.1.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
   dependencies:
     es6-promisify "^5.0.0"
 
-agentkeepalive@^3.3.0, agentkeepalive@^3.4.1:
+agentkeepalive@^3.3.0:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.4.1.tgz#aa95aebc3a749bca5ed53e3880a09f5235b48f0c"
   dependencies:
     humanize-ms "^1.2.1"
 
+ajv-keywords@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
+
 ajv-keywords@^2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
 
-ajv-keywords@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
-
 ajv@^4.9.1:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
@@ -417,7 +515,7 @@ ajv@^4.9.1:
     co "^4.6.0"
     json-stable-stringify "^1.0.1"
 
-ajv@^5.0.0, ajv@^5.1.0:
+ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5:
   version "5.2.3"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2"
   dependencies:
@@ -435,15 +533,6 @@ ajv@^5.2.3, ajv@^5.3.0:
     fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.3.0"
 
-ajv@^6.1.0:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.0.tgz#4c8affdf80887d8f132c9c52ab8a2dc4d0b7b24c"
-  dependencies:
-    fast-deep-equal "^2.0.1"
-    fast-json-stable-stringify "^2.0.0"
-    json-schema-traverse "^0.3.0"
-    uri-js "^4.2.1"
-
 align-text@^0.1.1, align-text@^0.1.3:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
@@ -466,7 +555,7 @@ ansi-align@^2.0.0:
   dependencies:
     string-width "^2.0.0"
 
-ansi-escapes@^1.0.0, ansi-escapes@^1.1.0, ansi-escapes@^1.3.0:
+ansi-escapes@^1.1.0, ansi-escapes@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
 
@@ -514,10 +603,6 @@ ansistyles@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539"
 
-any-observable@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
-
 anymatch@^1.3.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
@@ -525,13 +610,6 @@ anymatch@^1.3.0:
     micromatch "^2.1.5"
     normalize-path "^2.0.0"
 
-anymatch@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
-  dependencies:
-    micromatch "^3.1.4"
-    normalize-path "^2.1.1"
-
 aphrodite@^1.2.0:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/aphrodite/-/aphrodite-1.2.4.tgz#5dc1622aa6f1b02c775e1f1ed850df08839e203c"
@@ -594,22 +672,10 @@ arr-diff@^2.0.0:
   dependencies:
     arr-flatten "^1.0.1"
 
-arr-diff@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
-
-arr-flatten@^1.0.1, arr-flatten@^1.1.0:
+arr-flatten@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
 
-arr-union@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
-
-array-differ@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
-
 array-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
@@ -646,15 +712,11 @@ array-unique@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
 
-array-unique@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
-
-arrify@^1.0.0, arrify@^1.0.1:
+arrify@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
 
-asap@^2.0.0, asap@^2.0.3, asap@~2.0.3, asap@~2.0.5:
+asap@^2.0.0, asap@^2.0.3, asap@^2.0.6, asap@~2.0.3, asap@~2.0.5:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
 
@@ -696,37 +758,19 @@ assertion-error@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
 
-assign-symbols@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
-
 ast-types-flow@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
 
-ast-types@0.10.1:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd"
-
-ast-types@0.11.3:
-  version "0.11.3"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8"
-
 async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
 
-async@1.x, async@^1.4.0, async@^1.5.0, async@~1.5:
+async@1.x, async@^1.4.0, async@~1.5:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
-async@^2.1.2, async@^2.6.0:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-  dependencies:
-    lodash "^4.17.10"
-
-async@^2.1.4, async@^2.4.1:
+async@^2.1.2, async@^2.1.4, async@^2.4.1:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
   dependencies:
@@ -744,10 +788,6 @@ asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
 
-atob@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a"
-
 autoprefixer@^6.3.1:
   version "6.7.7"
   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
@@ -847,14 +887,6 @@ babel-generator@^6.18.0, babel-generator@^6.26.0:
     source-map "^0.5.6"
     trim-right "^1.0.1"
 
-babel-helper-bindify-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
@@ -897,15 +929,6 @@ babel-helper-explode-assignable-expression@^6.24.1:
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-helper-explode-class@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb"
-  dependencies:
-    babel-helper-bindify-decorators "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-helper-function-name@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
@@ -1025,34 +1048,10 @@ babel-plugin-syntax-async-functions@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
 
-babel-plugin-syntax-async-generators@^6.5.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
-
-babel-plugin-syntax-class-constructor-call@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416"
-
-babel-plugin-syntax-class-properties@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
-
-babel-plugin-syntax-decorators@^6.13.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
-
-babel-plugin-syntax-dynamic-import@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
-
 babel-plugin-syntax-exponentiation-operator@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
 
-babel-plugin-syntax-export-extensions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
-
 babel-plugin-syntax-flow@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
@@ -1069,15 +1068,7 @@ babel-plugin-syntax-trailing-function-commas@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
 
-babel-plugin-transform-async-generator-functions@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-generators "^6.5.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1:
+babel-plugin-transform-async-to-generator@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
   dependencies:
@@ -1085,33 +1076,6 @@ babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-
     babel-plugin-syntax-async-functions "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-class-constructor-call@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9"
-  dependencies:
-    babel-plugin-syntax-class-constructor-call "^6.18.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-class-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-plugin-syntax-class-properties "^6.8.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d"
-  dependencies:
-    babel-helper-explode-class "^6.24.1"
-    babel-plugin-syntax-decorators "^6.13.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-plugin-transform-es2015-arrow-functions@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
@@ -1124,7 +1088,7 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1:
+babel-plugin-transform-es2015-block-scoping@^6.23.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
   dependencies:
@@ -1134,7 +1098,7 @@ babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es20
     babel-types "^6.26.0"
     lodash "^4.17.4"
 
-babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1:
+babel-plugin-transform-es2015-classes@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
   dependencies:
@@ -1148,33 +1112,33 @@ babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-cla
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1:
+babel-plugin-transform-es2015-computed-properties@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
   dependencies:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0:
+babel-plugin-transform-es2015-destructuring@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
   dependencies:
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0:
+babel-plugin-transform-es2015-for-of@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1:
+babel-plugin-transform-es2015-function-name@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
   dependencies:
@@ -1205,7 +1169,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-e
     babel-template "^6.26.0"
     babel-types "^6.26.0"
 
-babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
+babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
   dependencies:
@@ -1213,7 +1177,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-e
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1:
+babel-plugin-transform-es2015-modules-umd@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
   dependencies:
@@ -1221,14 +1185,14 @@ babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1:
+babel-plugin-transform-es2015-object-super@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
   dependencies:
     babel-helper-replace-supers "^6.24.1"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1:
+babel-plugin-transform-es2015-parameters@^6.23.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
   dependencies:
@@ -1239,7 +1203,7 @@ babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
   dependencies:
@@ -1252,7 +1216,7 @@ babel-plugin-transform-es2015-spread@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1:
+babel-plugin-transform-es2015-sticky-regex@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
   dependencies:
@@ -1266,13 +1230,13 @@ babel-plugin-transform-es2015-template-literals@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
+babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1:
+babel-plugin-transform-es2015-unicode-regex@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
   dependencies:
@@ -1300,14 +1264,7 @@ babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-e
     babel-plugin-syntax-exponentiation-operator "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-export-extensions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653"
-  dependencies:
-    babel-plugin-syntax-export-extensions "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-flow-strip-types@^6.22.0, babel-plugin-transform-flow-strip-types@^6.8.0:
+babel-plugin-transform-flow-strip-types@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
   dependencies:
@@ -1320,7 +1277,7 @@ babel-plugin-transform-jscript@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-object-rest-spread@^6.22.0, babel-plugin-transform-object-rest-spread@^6.23.0:
+babel-plugin-transform-object-rest-spread@^6.23.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
   dependencies:
@@ -1355,7 +1312,7 @@ babel-plugin-transform-react-jsx@^6.24.1:
     babel-plugin-syntax-jsx "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
+babel-plugin-transform-regenerator@^6.22.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
   dependencies:
@@ -1427,35 +1384,6 @@ babel-preset-env@^1.5.2:
     invariant "^2.2.2"
     semver "^5.3.0"
 
-babel-preset-es2015@^6.9.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.24.1"
-    babel-plugin-transform-es2015-classes "^6.24.1"
-    babel-plugin-transform-es2015-computed-properties "^6.24.1"
-    babel-plugin-transform-es2015-destructuring "^6.22.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-    babel-plugin-transform-es2015-for-of "^6.22.0"
-    babel-plugin-transform-es2015-function-name "^6.24.1"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-umd "^6.24.1"
-    babel-plugin-transform-es2015-object-super "^6.24.1"
-    babel-plugin-transform-es2015-parameters "^6.24.1"
-    babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.24.1"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-    babel-plugin-transform-regenerator "^6.24.1"
-
 babel-preset-flow@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d"
@@ -1473,34 +1401,7 @@ babel-preset-react@^6.24.1:
     babel-plugin-transform-react-jsx-source "^6.22.0"
     babel-preset-flow "^6.23.0"
 
-babel-preset-stage-1@^6.5.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0"
-  dependencies:
-    babel-plugin-transform-class-constructor-call "^6.24.1"
-    babel-plugin-transform-export-extensions "^6.22.0"
-    babel-preset-stage-2 "^6.24.1"
-
-babel-preset-stage-2@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1"
-  dependencies:
-    babel-plugin-syntax-dynamic-import "^6.18.0"
-    babel-plugin-transform-class-properties "^6.24.1"
-    babel-plugin-transform-decorators "^6.24.1"
-    babel-preset-stage-3 "^6.24.1"
-
-babel-preset-stage-3@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
-  dependencies:
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-generator-functions "^6.24.1"
-    babel-plugin-transform-async-to-generator "^6.24.1"
-    babel-plugin-transform-exponentiation-operator "^6.24.1"
-    babel-plugin-transform-object-rest-spread "^6.22.0"
-
-babel-register@^6.24.1, babel-register@^6.26.0, babel-register@^6.9.0:
+babel-register@^6.24.1, babel-register@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
   dependencies:
@@ -1552,14 +1453,10 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26
     lodash "^4.17.4"
     to-fast-properties "^1.0.3"
 
-babylon@^6.15.0, babylon@^6.17.3, babylon@^6.18.0:
+babylon@^6.15.0, babylon@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
 
-babylon@^7.0.0-beta.30:
-  version "7.0.0-beta.47"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.47.tgz#6d1fa44f0abec41ab7c780481e62fd9aafbdea80"
-
 balanced-match@^0.4.2:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
@@ -1576,18 +1473,6 @@ base64-js@^1.0.2:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
 
-base@^0.11.1:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
-  dependencies:
-    cache-base "^1.0.1"
-    class-utils "^0.3.5"
-    component-emitter "^1.2.1"
-    define-property "^1.0.0"
-    isobject "^3.0.1"
-    mixin-deep "^1.2.0"
-    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"
@@ -1598,7 +1483,7 @@ big.js@^3.1.3:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
 
-bin-links@^1.1.2:
+bin-links@^1.1.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-1.1.2.tgz#fb74bd54bae6b7befc6c6221f25322ac830d9757"
   dependencies:
@@ -1612,10 +1497,6 @@ binary-extensions@^1.0.0:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0"
 
-binaryextensions@2:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
-
 bl@~0.9.4:
   version "0.9.5"
   resolved "https://registry.yarnpkg.com/bl/-/bl-0.9.5.tgz#c06b797af085ea00bc527afc8efcf11de2232054"
@@ -1674,15 +1555,15 @@ boom@5.x.x:
   dependencies:
     hoek "4.x.x"
 
-bootstrap-slider@9.9.0:
-  version "9.9.0"
-  resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-9.9.0.tgz#4e14ecc6401901da1ddf7681aa24e33b00dadce8"
-
 bootstrap-slider@^10.0.0:
   version "10.0.0"
   resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-10.0.0.tgz#d4edd3a10af03197d020e3792d32ea6d37cb3b28"
 
-bootstrap@^3.3.6:
+bootstrap-slider@^9.8.0:
+  version "9.10.0"
+  resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-9.10.0.tgz#1103d6bc00cfbfa8cfc9a2599ab518c55643da3f"
+
+bootstrap@^3.3.6, bootstrap@^3.3.7:
   version "3.3.7"
   resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
 
@@ -1740,10 +1621,6 @@ brace@^0.10.0:
   dependencies:
     w3c-blob "0.0.1"
 
-brace@^0.11.1:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58"
-
 braces@^1.8.2:
   version "1.8.5"
   resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
@@ -1752,21 +1629,6 @@ braces@^1.8.2:
     preserve "^0.2.0"
     repeat-element "^1.1.2"
 
-braces@^2.3.0, braces@^2.3.1:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
-  dependencies:
-    arr-flatten "^1.1.0"
-    array-unique "^0.3.2"
-    extend-shallow "^2.0.1"
-    fill-range "^4.0.0"
-    isobject "^3.0.1"
-    repeat-element "^1.1.2"
-    snapdragon "^0.8.1"
-    snapdragon-node "^2.0.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"
@@ -1780,15 +1642,6 @@ brfs@^1.3.0, brfs@^1.4.0, brfs@^1.4.3:
     static-module "^1.1.0"
     through2 "^2.0.0"
 
-brfs@^1.4.4:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3"
-  dependencies:
-    quote-stream "^1.0.1"
-    resolve "^1.1.5"
-    static-module "^2.2.0"
-    through2 "^2.0.0"
-
 brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -1930,10 +1783,6 @@ byline@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
 
-byte-size@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.3.tgz#b7c095efc68eadf82985fccd9a2df43a74fa2ccd"
-
 cacache@^10.0.0, cacache@^10.0.4:
   version "10.0.4"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
@@ -1970,51 +1819,6 @@ cacache@^10.0.1:
     unique-filename "^1.1.0"
     y18n "^3.2.1"
 
-cacache@^11.0.1, cacache@^11.0.2:
-  version "11.0.2"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.0.2.tgz#ff30541a05302200108a759e660e30786f788764"
-  dependencies:
-    bluebird "^3.5.1"
-    chownr "^1.0.1"
-    figgy-pudding "^3.1.0"
-    glob "^7.1.2"
-    graceful-fs "^4.1.11"
-    lru-cache "^4.1.2"
-    mississippi "^3.0.0"
-    mkdirp "^0.5.1"
-    move-concurrently "^1.0.1"
-    promise-inflight "^1.0.1"
-    rimraf "^2.6.2"
-    ssri "^6.0.0"
-    unique-filename "^1.1.0"
-    y18n "^4.0.0"
-
-cache-base@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
-  dependencies:
-    collection-visit "^1.0.0"
-    component-emitter "^1.2.1"
-    get-value "^2.0.6"
-    has-value "^1.0.0"
-    isobject "^3.0.1"
-    set-value "^2.0.0"
-    to-object-path "^0.3.0"
-    union-value "^1.0.0"
-    unset-value "^1.0.0"
-
-cacheable-request@^2.1.1:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
-  dependencies:
-    clone-response "1.0.2"
-    get-stream "3.0.0"
-    http-cache-semantics "3.8.1"
-    keyv "3.0.0"
-    lowercase-keys "1.0.0"
-    normalize-url "2.0.1"
-    responselike "1.0.2"
-
 call-limit@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.0.tgz#6fd61b03f3da42a2cd0ec2b60f02bd0e71991fea"
@@ -2028,10 +1832,6 @@ call-matcher@^1.0.1:
     espurify "^1.6.0"
     estraverse "^4.0.0"
 
-call-me-maybe@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
-
 caller-path@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -2138,14 +1938,6 @@ chalk@^2.1.0:
     escape-string-regexp "^1.0.5"
     supports-color "^4.0.0"
 
-chalk@^2.3.0, chalk@^2.4.1:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
 chalk@~0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
@@ -2191,7 +1983,7 @@ cheerio@^0.22.0:
     lodash.reject "^4.4.0"
     lodash.some "^4.4.0"
 
-chokidar@^1.6.1:
+chokidar@^1.6.1, chokidar@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
   dependencies:
@@ -2206,24 +1998,6 @@ chokidar@^1.6.1:
   optionalDependencies:
     fsevents "^1.0.0"
 
-chokidar@^2.0.2:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176"
-  dependencies:
-    anymatch "^2.0.0"
-    async-each "^1.0.0"
-    braces "^2.3.0"
-    glob-parent "^3.1.0"
-    inherits "^2.0.1"
-    is-binary-path "^1.0.0"
-    is-glob "^4.0.0"
-    normalize-path "^2.1.1"
-    path-is-absolute "^1.0.0"
-    readdirp "^2.0.0"
-    upath "^1.0.0"
-  optionalDependencies:
-    fsevents "^1.1.2"
-
 chownr@^1.0.1, chownr@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
@@ -2232,11 +2006,9 @@ ci-info@^1.0.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2"
 
-cidr-regex@^2.0.8:
-  version "2.0.8"
-  resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-2.0.8.tgz#c79bae6223d241c0860d93bfde1fb1c1c4fdcab6"
-  dependencies:
-    ip-regex "^2.1.0"
+cidr-regex@1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-1.0.6.tgz#74abfd619df370b9d54ab14475568e97dd64c0c1"
 
 cint@^8.2.1:
   version "8.2.1"
@@ -2259,16 +2031,7 @@ clap@^1.0.9:
   dependencies:
     chalk "^1.1.3"
 
-class-utils@^0.3.5:
-  version "0.3.6"
-  resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
-  dependencies:
-    arr-union "^3.1.0"
-    define-property "^0.2.5"
-    isobject "^3.0.0"
-    static-extend "^0.1.1"
-
-classnames@2.x, classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
+classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
 
@@ -2280,16 +2043,9 @@ clean-webpack-plugin@^0.1.16:
 
 cli-boxes@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
-
-cli-columns@^3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-3.1.2.tgz#6732d972979efc2ae444a1f08e08fa139c96a18e"
-  dependencies:
-    string-width "^2.0.0"
-    strip-ansi "^3.0.1"
+  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
 
-cli-cursor@^1.0.1, cli-cursor@^1.0.2:
+cli-cursor@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
   dependencies:
@@ -2301,11 +2057,7 @@ cli-cursor@^2.1.0:
   dependencies:
     restore-cursor "^2.0.0"
 
-cli-spinners@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
-
-cli-table2@^0.2.0, cli-table2@~0.2.0:
+cli-table2@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97"
   dependencies:
@@ -2320,25 +2072,10 @@ cli-table@^0.3.1:
   dependencies:
     colors "1.0.3"
 
-cli-truncate@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
-  dependencies:
-    slice-ansi "0.0.4"
-    string-width "^1.0.1"
-
 cli-width@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
 
-clipboard@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a"
-  dependencies:
-    good-listener "^1.2.2"
-    select "^1.1.2"
-    tiny-emitter "^2.0.0"
-
 clite@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/clite/-/clite-0.3.0.tgz#e7fcbc8cc5bd3e7f8b84ed48db12e9474cc73441"
@@ -2370,17 +2107,13 @@ cliui@^3.2.0:
     wrap-ansi "^2.0.0"
 
 cliui@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
   dependencies:
     string-width "^2.1.1"
     strip-ansi "^4.0.0"
     wrap-ansi "^2.0.0"
 
-clone-buffer@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
-
 clone-deep@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8"
@@ -2390,24 +2123,6 @@ clone-deep@^0.3.0:
     kind-of "^3.2.2"
     shallow-clone "^0.1.2"
 
-clone-response@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
-  dependencies:
-    mimic-response "^1.0.0"
-
-clone-stats@^0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
-
-clone-stats@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
-
-clone@^1.0.0:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
-
 clone@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
@@ -2416,14 +2131,6 @@ clone@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb"
 
-cloneable-readable@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.2.tgz#d591dee4a8f8bc15da43ce97dceeba13d43e2a65"
-  dependencies:
-    inherits "^2.0.1"
-    process-nextick-args "^2.0.0"
-    readable-stream "^2.3.5"
-
 cmd-shim@^2.0.2, cmd-shim@~2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb"
@@ -2445,13 +2152,6 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
-collection-visit@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
-  dependencies:
-    map-visit "^1.0.0"
-    object-visit "^1.0.0"
-
 color-convert@^1.3.0, color-convert@^1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
@@ -2493,8 +2193,8 @@ colors@1.0.3:
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
 
 colors@^1.1.2:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e"
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794"
 
 colors@~1.1.2:
   version "1.1.2"
@@ -2519,12 +2219,6 @@ combined-stream@~0.0.4:
   dependencies:
     delayed-stream "0.0.5"
 
-comma-separated-tokens@^1.0.0:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db"
-  dependencies:
-    trim "0.0.1"
-
 commander@2.9.0:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
@@ -2547,10 +2241,6 @@ complex.js@2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.4.tgz#d8e7cfb9652d1e853e723386421c1a0ca7a48373"
 
-component-emitter@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -2629,7 +2319,7 @@ console-browserify@^1.1.0:
   dependencies:
     date-now "^0.1.4"
 
-console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0:
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
 
@@ -2649,10 +2339,6 @@ convert-source-map@^1.1.1, convert-source-map@^1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
 
-convert-source-map@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
-
 cookie-jar@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/cookie-jar/-/cookie-jar-0.3.0.tgz#bc9a27d4e2b97e186cd57c9e2063cb99fa68cccc"
@@ -2668,10 +2354,6 @@ copy-concurrently@^1.0.0:
     rimraf "^2.5.4"
     run-queue "^1.0.0"
 
-copy-descriptor@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
-
 core-js@^1.0.0:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
@@ -2680,10 +2362,6 @@ core-js@^2.0.0, core-js@^2.4.0, core-js@^2.5.0:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
 
-core-js@^2.4.1:
-  version "2.5.7"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
-
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -2721,7 +2399,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-create-react-class@^15.5.2, create-react-class@^15.6.0:
+create-react-class@^15.5.2, create-react-class@^15.5.x, create-react-class@^15.6.0:
   version "15.6.2"
   resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a"
   dependencies:
@@ -2737,16 +2415,6 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0:
     shebang-command "^1.2.0"
     which "^1.2.9"
 
-cross-spawn@^6.0.5:
-  version "6.0.5"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
-  dependencies:
-    nice-try "^1.0.4"
-    path-key "^2.0.1"
-    semver "^5.5.0"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
 crypt@~0.0.1:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
@@ -2935,7 +2603,7 @@ d3-cloud@^1.2.1:
   dependencies:
     d3-dispatch "1"
 
-d3-collection@1, d3-collection@^1.0.4:
+d3-collection@1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
 
@@ -3018,10 +2686,6 @@ d3-selection@1, d3-selection@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.1.0.tgz#1998684896488f839ca0372123da34f1d318809c"
 
-d3-selection@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
-
 d3-shape@^1.0.6, d3-shape@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
@@ -3046,12 +2710,11 @@ d3-timer@1:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531"
 
-d3-tip@^0.9.1:
-  version "0.9.1"
-  resolved "https://registry.yarnpkg.com/d3-tip/-/d3-tip-0.9.1.tgz#84e6d331c4e6650d80c5228a07e41820609ab64b"
+d3-tip@^0.6.7:
+  version "0.6.8"
+  resolved "https://registry.yarnpkg.com/d3-tip/-/d3-tip-0.6.8.tgz#e5b4491ae8983fde646ea49008ff542a033c0a2c"
   dependencies:
-    d3-collection "^1.0.4"
-    d3-selection "^1.3.0"
+    d3 "^3.5.5"
 
 d3-transition@1:
   version "1.1.0"
@@ -3074,7 +2737,7 @@ d3-zoom@^1.3.0:
     d3-selection "1"
     d3-transition "1"
 
-d3@3, d3@^3.5.17, d3@^3.5.6:
+d3@3, d3@^3.5.17, d3@^3.5.5, d3@^3.5.6:
   version "3.5.17"
   resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
 
@@ -3088,10 +2751,6 @@ damerau-levenshtein@^1.0.0:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
 
-dargs@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/dargs/-/dargs-5.1.0.tgz#ec7ea50c78564cd36c9d5ec18f66329fade27829"
-
 dashdash@^1.12.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -3118,18 +2777,10 @@ datatables.net@1.10.16:
   dependencies:
     jquery ">=1.7"
 
-date-fns@^1.27.2:
-  version "1.29.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
-
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
 
-dateformat@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
-
 debug@2.6.8:
   version "2.6.8"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
@@ -3142,7 +2793,7 @@ debug@3.1.0, debug@^3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3, debug@^2.6.8:
+debug@^2.1.2, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   dependencies:
@@ -3178,12 +2829,6 @@ decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
 
-decompress-response@^3.2.0, decompress-response@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
-  dependencies:
-    mimic-response "^1.0.0"
-
 deep-eql@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
@@ -3194,10 +2839,6 @@ deep-equal@^1.0.0, deep-equal@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
 
-deep-extend@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
-
 deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
@@ -3229,25 +2870,6 @@ define-properties@^1.1.2:
     foreach "^2.0.5"
     object-keys "^1.0.8"
 
-define-property@^0.2.5:
-  version "0.2.5"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
-  dependencies:
-    is-descriptor "^0.1.0"
-
-define-property@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
-  dependencies:
-    is-descriptor "^1.0.0"
-
-define-property@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
-  dependencies:
-    is-descriptor "^1.0.2"
-    isobject "^3.0.1"
-
 defined@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
@@ -3272,10 +2894,6 @@ delayed-stream@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
 
-delegate@^3.1.2:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
-
 delegates@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -3287,10 +2905,6 @@ des.js@^1.0.0:
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
 
-detect-conflict@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e"
-
 detect-indent@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
@@ -3301,10 +2915,6 @@ detect-indent@~5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
 
-detect-libc@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-
 detect-newline@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
@@ -3320,9 +2930,9 @@ diff@3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
 
-diff@^3.1.0, diff@^3.3.1, diff@^3.5.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
+diff@^3.1.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
 
 diffie-hellman@^5.0.0:
   version "5.0.2"
@@ -3332,12 +2942,9 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-dir-glob@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
-  dependencies:
-    arrify "^1.0.1"
-    path-type "^3.0.0"
+disposables@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/disposables/-/disposables-1.0.2.tgz#36c6a674475f55a2d6913567a601444e487b4b6e"
 
 distributions@^1.0.0:
   version "1.0.0"
@@ -3345,6 +2952,15 @@ distributions@^1.0.0:
   dependencies:
     mathfn "^1.0.0"
 
+dnd-core@^2.6.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-2.6.0.tgz#12bad66d58742c6e5f7cf2943fb6859440f809c4"
+  dependencies:
+    asap "^2.0.6"
+    invariant "^2.0.0"
+    lodash "^4.2.0"
+    redux "^3.7.1"
+
 doctrine@1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -3428,7 +3044,7 @@ dotenv@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef"
 
-duplexer2@^0.1.4, duplexer2@~0.1.4:
+duplexer2@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
   dependencies:
@@ -3476,26 +3092,14 @@ ecc-jsbn@~0.1.1:
   dependencies:
     jsbn "~0.1.0"
 
-editions@^1.3.3:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
-
 editor@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742"
 
-ejs@^2.5.9:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
-
 electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18:
   version "1.3.24"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6"
 
-elegant-spinner@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
-
 elliptic@^6.0.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
@@ -3541,22 +3145,10 @@ enhanced-resolve@^3.4.0:
     object-assign "^4.0.1"
     tapable "^0.2.7"
 
-enhanced-resolve@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz#e34a6eaa790f62fccd71d93959f56b2b432db10a"
-  dependencies:
-    graceful-fs "^4.1.2"
-    memory-fs "^0.4.0"
-    tapable "^1.0.0"
-
 entities@^1.1.1, entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
-envinfo@^5.7.0:
-  version "5.9.0"
-  resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.9.0.tgz#1e681b7400c384c679ffc886b6e3f811d7e687b5"
-
 enzyme@^2.0.0:
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.9.1.tgz#07d5ce691241240fb817bf2c4b18d6e530240df6"
@@ -3594,19 +3186,12 @@ errno@~0.1.7:
   dependencies:
     prr "~1.0.1"
 
-error-ex@^1.2.0, error-ex@^1.3.1:
+error-ex@^1.2.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
   dependencies:
     is-arrayish "^0.2.1"
 
-error@^7.0.2:
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
-  dependencies:
-    string-template "~0.2.1"
-    xtend "~4.0.0"
-
 es-abstract@^1.6.1, es-abstract@^1.7.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227"
@@ -3632,15 +3217,7 @@ es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
     es6-iterator "2"
     es6-symbol "~3.1"
 
-es5-ext@^0.10.35:
-  version "0.10.44"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.44.tgz#7a877e994bb190aebd4718aa2d62784a12e2ca20"
-  dependencies:
-    es6-iterator "~2.0.3"
-    es6-symbol "~3.1.1"
-    next-tick "1"
-
-es6-iterator@2:
+es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
   dependencies:
@@ -3648,14 +3225,6 @@ es6-iterator@2:
     es5-ext "^0.10.14"
     es6-symbol "^3.1"
 
-es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
-  dependencies:
-    d "1"
-    es5-ext "^0.10.35"
-    es6-symbol "^3.1.1"
-
 es6-map@^0.1.3:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
@@ -3707,10 +3276,6 @@ es6-weak-map@^2.0.1:
     es6-iterator "^2.0.1"
     es6-symbol "^3.1.1"
 
-es6bindall@^0.0.9:
-  version "0.0.9"
-  resolved "https://registry.yarnpkg.com/es6bindall/-/es6bindall-0.0.9.tgz#71e00afa69f8dd59ac5ac898a0d31c978df817d5"
-
 escape-latex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.0.0.tgz#74b9e94d8c178645704c33791e95a4155b59718f"
@@ -3741,17 +3306,6 @@ escodegen@^1.6.1:
   optionalDependencies:
     source-map "~0.5.6"
 
-escodegen@^1.8.1, escodegen@~1.9.0:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2"
-  dependencies:
-    esprima "^3.1.3"
-    estraverse "^4.2.0"
-    esutils "^2.0.2"
-    optionator "^0.8.1"
-  optionalDependencies:
-    source-map "~0.6.1"
-
 escodegen@~0.0.24:
   version "0.0.28"
   resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-0.0.28.tgz#0e4ff1715f328775d6cab51ac44a406cd7abffd3"
@@ -3792,6 +3346,12 @@ eslint-config-airbnb@^15.0.1:
   dependencies:
     eslint-config-airbnb-base "^11.3.0"
 
+eslint-config-prettier@^2.9.0:
+  version "2.9.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz#5ecd65174d486c22dff389fe036febf502d468a3"
+  dependencies:
+    get-stdin "^5.0.1"
+
 eslint-import-resolver-node@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc"
@@ -3833,6 +3393,13 @@ eslint-plugin-jsx-a11y@^5.1.1:
     emoji-regex "^6.1.0"
     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"
+  dependencies:
+    fast-diff "^1.1.1"
+    jest-docblock "^21.0.0"
+
 eslint-plugin-react@^7.0.1:
   version "7.4.0"
   resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz#300a95861b9729c087d362dd64abcc351a74364a"
@@ -3915,7 +3482,7 @@ esprima@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
 
-esprima@^4.0.0, esprima@~4.0.0:
+esprima@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
 
@@ -4000,10 +3567,6 @@ execa@^0.7.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-exenv@^1.2.0:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
-
 exit-hook@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -4014,40 +3577,22 @@ expand-brackets@^0.1.4:
   dependencies:
     is-posix-bracket "^0.1.0"
 
-expand-brackets@^2.1.4:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
-  dependencies:
-    debug "^2.3.3"
-    define-property "^0.2.5"
-    extend-shallow "^2.0.1"
-    posix-character-classes "^0.1.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
 expand-range@^1.8.1:
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
   dependencies:
     fill-range "^2.1.0"
 
-expand-tilde@^2.0.0, expand-tilde@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
-  dependencies:
-    homedir-polyfill "^1.0.1"
-
 expect.js@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.2.0.tgz#1028533d2c1c363f74a6796ff57ec0520ded2be1"
 
-exports-loader@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.7.0.tgz#84881c784dea6036b8e1cd1dac3da9b6409e21a5"
+exports-loader@^0.6.3:
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.6.4.tgz#d70fc6121975b35fc12830cf52754be2740fc886"
   dependencies:
-    loader-utils "^1.1.0"
-    source-map "0.5.0"
+    loader-utils "^1.0.2"
+    source-map "0.5.x"
 
 extend-shallow@^2.0.1:
   version "2.0.1"
@@ -4055,13 +3600,6 @@ extend-shallow@^2.0.1:
   dependencies:
     is-extendable "^0.1.0"
 
-extend-shallow@^3.0.0, extend-shallow@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
-  dependencies:
-    assign-symbols "^1.0.0"
-    is-extendable "^1.0.1"
-
 extend@~3.0.0, extend@~3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
@@ -4078,36 +3616,15 @@ external-editor@^2.0.4:
     iconv-lite "^0.4.17"
     tmp "^0.0.33"
 
-external-editor@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
-  dependencies:
-    chardet "^0.4.0"
-    iconv-lite "^0.4.17"
-    tmp "^0.0.33"
-
 extglob@^0.3.1:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
   dependencies:
     is-extglob "^1.0.0"
 
-extglob@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
-  dependencies:
-    array-unique "^0.3.2"
-    define-property "^1.0.0"
-    expand-brackets "^2.1.4"
-    extend-shallow "^2.0.1"
-    fragment-cache "^0.2.1"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-extract-text-webpack-plugin@3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7"
+extract-text-webpack-plugin@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.0.tgz#90caa7907bc449f335005e3ac7532b41b00de612"
   dependencies:
     async "^2.4.1"
     loader-utils "^1.1.0"
@@ -4131,25 +3648,10 @@ fast-deep-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
 
-fast-deep-equal@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
-
-fast-diff@^1.0.1:
+fast-diff@^1.0.1, fast-diff@^1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
 
-fast-glob@^2.0.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.2.tgz#71723338ac9b4e0e2fff1d6748a2a13d5ed352bf"
-  dependencies:
-    "@mrmlnc/readdir-enhanced" "^2.2.1"
-    "@nodelib/fs.stat" "^1.0.1"
-    glob-parent "^3.1.0"
-    is-glob "^4.0.0"
-    merge2 "^1.2.1"
-    micromatch "^3.1.10"
-
 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"
@@ -4184,11 +3686,7 @@ fbjs@^0.8.1, fbjs@^0.8.12, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.9:
     setimmediate "^1.0.5"
     ua-parser-js "^0.7.9"
 
-figgy-pudding@^3.0.0, figgy-pudding@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.1.0.tgz#a77ed2284175976c424b390b298569e9df86dd1e"
-
-figures@^1.3.5, figures@^1.7.0:
+figures@^1.3.5:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
   dependencies:
@@ -4235,15 +3733,6 @@ fill-range@^2.1.0:
     repeat-element "^1.1.2"
     repeat-string "^1.5.2"
 
-fill-range@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-number "^3.0.0"
-    repeat-string "^1.6.1"
-    to-regex-range "^2.1.0"
-
 filled-array@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84"
@@ -4273,12 +3762,6 @@ find-up@^2.0.0, find-up@^2.1.0:
   dependencies:
     locate-path "^2.0.0"
 
-first-chunk-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70"
-  dependencies:
-    readable-stream "^2.0.2"
-
 flat-cache@^1.2.1:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481"
@@ -4292,10 +3775,6 @@ flatten@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
 
-flow-parser@^0.*:
-  version "0.73.0"
-  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.73.0.tgz#525ac0776f743e16b6dca1a3dd6c602260b15773"
-
 flow-remove-types@^1.1.2:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-1.2.1.tgz#58e261bf8b842bd234c86cafb982a1213aff0edb"
@@ -4314,7 +3793,7 @@ for-in@^0.1.3:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
 
-for-in@^1.0.1, for-in@^1.0.2:
+for-in@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
 
@@ -4374,16 +3853,16 @@ form-data@~2.3.1:
     combined-stream "^1.0.5"
     mime-types "^2.1.12"
 
+formatio@1.2.0, formatio@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
+  dependencies:
+    samsam "1.x"
+
 fraction.js@4.0.4:
   version "4.0.4"
   resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.4.tgz#04e567110718adf7b52974a10434ab4c67a5183e"
 
-fragment-cache@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
-  dependencies:
-    map-cache "^0.2.2"
-
 from2@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/from2/-/from2-1.3.0.tgz#88413baaa5f9a597cfde9221d86986cd3c061dfd"
@@ -4391,7 +3870,7 @@ from2@^1.3.0:
     inherits "~2.0.1"
     readable-stream "~1.1.10"
 
-from2@^2.1.0, from2@^2.1.1:
+from2@^2.1.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
   dependencies:
@@ -4446,13 +3925,6 @@ fsevents@^1.0.0:
     nan "^2.3.0"
     node-pre-gyp "^0.6.36"
 
-fsevents@^1.1.2:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
-  dependencies:
-    nan "^2.9.2"
-    node-pre-gyp "^0.10.0"
-
 fstream-ignore@^1.0.0, fstream-ignore@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
@@ -4493,6 +3965,10 @@ functional-red-black-tree@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
 
+fuse.js@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.2.0.tgz#f0448e8069855bf2a3e683cdc1d320e7e2a07ef4"
+
 gauge@~2.6.0:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.6.0.tgz#d35301ad18e96902b4751dcbbe40f4218b942a46"
@@ -4607,9 +4083,9 @@ geojson-vt@^2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-2.4.0.tgz#3c1cf44493f35eb4d2c70c95da6550de66072c05"
 
-geojson-vt@^3.1.0:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.1.2.tgz#ea98849bd7717979db4c86fed8ef4e47475e4bab"
+geojson-vt@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.0.0.tgz#a24cae5488ab4897e86ca0e4bf0d9760d628ae0a"
 
 geolib@^2.0.24:
   version "2.0.24"
@@ -4627,14 +4103,10 @@ get-stdin@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
 
-get-stream@3.0.0, get-stream@^3.0.0:
+get-stream@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
 
-get-value@^2.0.3, get-value@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
-
 getpass@^0.1.1:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@@ -4647,13 +4119,6 @@ gettext-parser@1.1.0:
   dependencies:
     encoding "^0.1.11"
 
-gh-got@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-6.0.0.tgz#d74353004c6ec466647520a10bd46f7299d268d0"
-  dependencies:
-    got "^7.0.0"
-    is-plain-obj "^1.1.0"
-
 ghauth@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/ghauth/-/ghauth-3.0.0.tgz#8292a24ef47899f180a39c780c4809561294bdbc"
@@ -4688,12 +4153,6 @@ github-commit-stream@0.1.0:
     request "~2.22.0"
     through "~2.3.4"
 
-github-username@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
-  dependencies:
-    gh-got "^6.0.0"
-
 github@0.1.16:
   version "0.1.16"
   resolved "https://registry.yarnpkg.com/github/-/github-0.1.16.tgz#895d2a85b0feb7980d89ac0ce4f44dcaa03f17b5"
@@ -4746,13 +4205,6 @@ glamorous@^3.13.1:
     react-html-attributes "^1.3.0"
     svg-tag-names "^1.1.0"
 
-glob-all@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.1.0.tgz#8913ddfb5ee1ac7812656241b03d5217c64b02ab"
-  dependencies:
-    glob "^7.0.5"
-    yargs "~1.2.6"
-
 glob-base@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -4766,13 +4218,6 @@ glob-parent@^2.0.0:
   dependencies:
     is-glob "^2.0.0"
 
-glob-parent@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
-  dependencies:
-    is-glob "^3.1.0"
-    path-dirname "^1.0.0"
-
 glob-to-regexp@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@@ -4798,7 +4243,7 @@ glob@7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.0, glob@~7.1.2:
+glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.0, glob@~7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
   dependencies:
@@ -4822,24 +4267,6 @@ global-dirs@^0.1.0:
   dependencies:
     ini "^1.3.4"
 
-global-modules@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
-  dependencies:
-    global-prefix "^1.0.1"
-    is-windows "^1.0.1"
-    resolve-dir "^1.0.0"
-
-global-prefix@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
-  dependencies:
-    expand-tilde "^2.0.2"
-    homedir-polyfill "^1.0.1"
-    ini "^1.3.4"
-    is-windows "^1.0.1"
-    which "^1.2.14"
-
 globals@^11.0.1:
   version "11.4.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.4.0.tgz#b85c793349561c16076a3c13549238a27945f1bc"
@@ -4859,24 +4286,6 @@ globby@^5.0.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
-globby@^8.0.0, globby@^8.0.1:
-  version "8.0.1"
... 3609 lines suppressed ...