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/04/18 07:08:32 UTC
[incubator-superset] 01/01: [dashboard builder] address major perf
+ css issues
This is an automated email from the ASF dual-hosted git repository.
ccwilliams pushed a commit to branch chris--dashboard-perf
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit ae44001347bb7fd4ba013487c7552864390156cd
Author: Chris Williams <ch...@airbnb.com>
AuthorDate: Wed Apr 18 00:08:01 2018 -0700
[dashboard builder] address major perf + css issues
---
superset/assets/images/loading.gif | Bin 16671 -> 1945878 bytes
superset/assets/javascripts/chart/Chart.jsx | 85 +++++----
.../assets/javascripts/chart/ChartContainer.jsx | 22 +--
superset/assets/javascripts/chart/chartReducer.js | 5 +-
superset/assets/javascripts/components/Loading.jsx | 3 +
.../javascripts/dashboard/components/Dashboard.jsx | 175 ++++++++---------
.../dashboard/components/DashboardContainer.jsx | 1 +
.../javascripts/dashboard/components/GridCell.jsx | 20 +-
.../dashboard/components/GridLayout.jsx | 178 +++++++++---------
.../dashboard/components/SliceAdder.jsx | 2 +-
.../dashboard/components/SliceHeader.jsx | 17 +-
.../dashboard/util/dashboardLayoutConverter.js | 29 +--
.../dashboard/v2/actions/dashboardLayout.js | 20 +-
.../v2/components/BuilderComponentPane.jsx | 7 +-
.../dashboard/v2/components/DashboardBuilder.jsx | 4 -
.../dashboard/v2/components/DashboardGrid.jsx | 126 +++++++------
.../dashboard/v2/components/WithKeyListener.jsx | 55 ++++++
.../dashboard/v2/components/dnd/DragDroppable.jsx | 4 +-
.../v2/components/gridComponents/Chart.jsx | 206 +++++++++++++++++++++
.../v2/components/gridComponents/ChartHolder.jsx | 53 +++---
.../v2/components/gridComponents/Column.jsx | 29 ++-
.../dashboard/v2/components/gridComponents/Row.jsx | 29 ++-
.../v2/components/menu/WithPopoverMenu.jsx | 7 +-
.../v2/components/resizable/ResizableContainer.jsx | 25 +--
.../javascripts/dashboard/v2/containers/Chart.jsx | 44 +++++
.../dashboard/v2/containers/DashboardBuilder.jsx | 3 +-
.../dashboard/v2/containers/DashboardComponent.jsx | 17 +-
.../dashboard/v2/containers/DashboardGrid.jsx | 3 +-
.../dashboard/v2/reducers/dashboardLayout.js | 12 +-
.../javascripts/dashboard/v2/reducers/index.js | 25 ++-
.../dashboard/v2/stylesheets/components/chart.less | 31 +++-
.../v2/stylesheets/components/column.less | 5 +-
.../dashboard/v2/stylesheets/components/row.less | 5 +-
.../javascripts/dashboard/v2/stylesheets/dnd.less | 2 +-
.../javascripts/dashboard/v2/stylesheets/grid.less | 14 +-
.../dashboard/v2/stylesheets/resizable.less | 26 ++-
.../v2/util/charts/getEffectiveExtraFilters.js | 41 ++++
.../v2/util/charts/getFormDataWithExtraFilters.js | 40 ++++
.../dashboard/v2/util/dropOverflowsParent.js | 12 +-
.../dashboard/v2/util/getDropPosition.js | 2 +-
.../explore/components/ExploreChartPanel.jsx | 14 +-
superset/assets/package.json | 2 +-
superset/assets/stylesheets/dashboard.less | 20 +-
superset/assets/visualizations/nvd3_vis.css | 5 -
superset/templates/superset/dashboard.html | 7 +-
45 files changed, 940 insertions(+), 492 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/javascripts/chart/Chart.jsx b/superset/assets/javascripts/chart/Chart.jsx
index 78a9175..b223c9e 100644
--- a/superset/assets/javascripts/chart/Chart.jsx
+++ b/superset/assets/javascripts/chart/Chart.jsx
@@ -5,7 +5,6 @@ import Mustache from 'mustache';
import { Tooltip } from 'react-bootstrap';
import { d3format } from '../modules/utils';
-import { chartPropType } from './chartReducer';
import ChartBody from './ChartBody';
import Loading from '../components/Loading';
import { Logger, LOG_ACTIONS_RENDER_EVENT } from '../logger';
@@ -18,7 +17,7 @@ import './chart.css';
const propTypes = {
annotationData: PropTypes.object,
actions: PropTypes.object,
- chart: PropTypes.shape(chartPropType).isRequired,
+ sliceId: PropTypes.string.isRequired,
containerId: PropTypes.string.isRequired,
datasource: PropTypes.object.isRequired,
formData: PropTypes.object,
@@ -62,7 +61,7 @@ class Chart extends React.PureComponent {
this.annotationData = props.annotationData;
this.containerId = props.containerId;
this.selector = `#${this.containerId}`;
- this.formData = props.formData || props.chart.formData;
+ this.formData = props.formData;
this.datasource = props.datasource;
this.addFilter = this.addFilter.bind(this);
this.getFilters = this.getFilters.bind(this);
@@ -73,12 +72,15 @@ class Chart extends React.PureComponent {
}
componentDidMount() {
- const formData = this.props.formData || this.props.chart.formData;
if (this.props.triggerQuery) {
+ const formData = this.props.formData;
this.props.actions.runQuery(formData, false,
this.props.timeout,
- this.props.chart.chartKey,
+ this.props.sliceId,
);
+ } else {
+ // when drag/dropping in a dashboard, a chart may be unmounted/remounted but still have data
+ this.renderViz();
}
}
@@ -86,7 +88,7 @@ class Chart extends React.PureComponent {
this.annotationData = nextProps.annotationData;
this.containerId = nextProps.containerId;
this.selector = `#${this.containerId}`;
- this.formData = nextProps.formData || nextProps.chart.formData;
+ this.formData = nextProps.formData;
this.datasource = nextProps.datasource;
}
@@ -95,11 +97,12 @@ class Chart extends React.PureComponent {
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();
}
@@ -128,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() {
@@ -136,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) {
@@ -156,7 +161,6 @@ class Chart extends React.PureComponent {
renderTooltip() {
if (this.state.tooltip) {
- /* eslint-disable react/no-danger */
return (
<Tooltip
className="chart-tooltip"
@@ -166,55 +170,54 @@ 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];
- // allow props.formData overwrite chart's own formData
- const fd = this.props.formData || this.props.chart.formData;
- const qr = this.props.queryResponse;
+ const { vizType, formData, queryResponse, setControlValue, sliceId, 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(sliceId);
}
- // [re]rendering the visualization
- viz(this, qr, this.props.setControlValue);
Logger.append(LOG_ACTIONS_RENDER_EVENT, {
- label: this.props.chart.chartKey,
- vis_type: this.props.vizType,
+ label: sliceId,
+ vis_type: vizType,
start_offset: renderStart,
duration: Logger.getTimestamp() - renderStart,
});
- this.props.actions.chartRenderingSucceeded(this.props.chart.chartKey);
} catch (e) {
- console.error(e); // eslint-disable-line
- this.props.actions.chartRenderingFailed(e, this.props.chart.chartKey);
+ console.error(e); // eslint-disable-line no-console
+ this.props.actions.chartRenderingFailed(e, sliceId);
}
}
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={`token col-md-12 ${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 &&
@@ -225,8 +228,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/javascripts/chart/ChartContainer.jsx b/superset/assets/javascripts/chart/ChartContainer.jsx
index ff072dc..b66fe5d 100644
--- a/superset/assets/javascripts/chart/ChartContainer.jsx
+++ b/superset/assets/javascripts/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({}, ownProps) {
- const chart = ownProps.chart;
- 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/javascripts/chart/chartReducer.js b/superset/assets/javascripts/chart/chartReducer.js
index 5c3ad59..edf4e85 100644
--- a/superset/assets/javascripts/chart/chartReducer.js
+++ b/superset/assets/javascripts/chart/chartReducer.js
@@ -159,7 +159,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/javascripts/components/Loading.jsx b/superset/assets/javascripts/components/Loading.jsx
index 416e770..810c581 100644
--- a/superset/assets/javascripts/components/Loading.jsx
+++ b/superset/assets/javascripts/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/javascripts/dashboard/components/Dashboard.jsx b/superset/assets/javascripts/dashboard/components/Dashboard.jsx
index cd5fee6..292c359 100644
--- a/superset/assets/javascripts/dashboard/components/Dashboard.jsx
+++ b/superset/assets/javascripts/dashboard/components/Dashboard.jsx
@@ -2,9 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import AlertsWrapper from '../../components/AlertsWrapper';
-import GridLayout from './GridLayout';
+import DashboardBuilder from '../v2/containers/DashboardBuilder';
+// import GridLayout from './GridLayout';
import { slicePropShape } from '../reducers/propShapes';
-import { exportChart } from '../../explore/exploreUtils';
+// import { exportChart } from '../../explore/exploreUtils';
import { areObjectsEqual } from '../../reduxUtils';
import { Logger, ActionLog, LOG_ACTIONS_PAGE_LOAD,
LOG_ACTIONS_LOAD_EVENT, LOG_ACTIONS_RENDER_EVENT } from '../../logger';
@@ -18,7 +19,7 @@ const propTypes = {
initMessages: PropTypes.array,
dashboard: PropTypes.object.isRequired,
charts: PropTypes.object.isRequired,
- slices: PropTypes.objectOf(slicePropShape).isRequired,
+ slices: PropTypes.objectOf(slicePropShape).isRequired,
datasources: PropTypes.object.isRequired,
layout: PropTypes.object.isRequired,
filters: PropTypes.object,
@@ -57,22 +58,23 @@ class Dashboard extends React.PureComponent {
});
Logger.start(this.loadingLog);
- this.rerenderCharts = this.rerenderCharts.bind(this);
- this.getFormDataExtra = this.getFormDataExtra.bind(this);
- this.exploreChart = this.exploreChart.bind(this);
- this.exportCSV = this.exportCSV.bind(this);
+ // this.rerenderCharts = this.rerenderCharts.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);
+ // 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);
+ // grid does this now
+ // window.addEventListener('resize', this.rerenderCharts);
}
componentWillReceiveProps(nextProps) {
@@ -126,7 +128,7 @@ class Dashboard extends React.PureComponent {
}
componentWillUnmount() {
- window.removeEventListener('resize', this.rerenderCharts);
+ // window.removeEventListener('resize', this.rerenderCharts);
}
onBeforeUnload(hasChanged) {
@@ -142,12 +144,12 @@ class Dashboard extends React.PureComponent {
return Object.values(this.props.charts);
}
- getFormDataExtra(chart) {
- const formDataExtra = Object.assign({}, chart.formData);
- const extraFilters = this.effectiveExtraFilters(chart.slice_id);
- formDataExtra.extra_filters = formDataExtra.filters.concat(extraFilters);
- return formDataExtra;
- }
+ // getFormDataExtra(chart) {
+ // const formDataExtra = Object.assign({}, chart.formData);
+ // const extraFilters = this.effectiveExtraFilters(chart.slice_id);
+ // formDataExtra.extra_filters = formDataExtra.filters.concat(extraFilters);
+ // return formDataExtra;
+ // }
getFilters(sliceId) {
return this.props.filters[sliceId];
@@ -169,41 +171,41 @@ class Dashboard extends React.PureComponent {
return message; // Gecko + Webkit, Safari, Chrome etc.
}
- effectiveExtraFilters(sliceId) {
- const metadata = this.props.dashboard.metadata;
- const filters = this.props.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;
- }
+ // effectiveExtraFilters(sliceId) {
+ // const metadata = this.props.dashboard.metadata;
+ // const filters = this.props.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.dashboard.metadata.filter_immune_slices || [];
@@ -220,26 +222,26 @@ class Dashboard extends React.PureComponent {
});
}
- exploreChart(chartKey) {
- const chart = this.props.charts[chartKey];
- const formData = this.getFormDataExtra(chart);
- exportChart(formData);
- }
-
- exportCSV(chartKey) {
- const chart = this.props.charts[chartKey];
- const formData = this.getFormDataExtra(chart);
- exportChart(formData, 'csv');
- }
+ // exploreChart(chartKey) {
+ // const chart = this.props.charts[chartKey];
+ // const formData = this.getFormDataExtra(chart);
+ // exportChart(formData);
+ // }
+ //
+ // exportCSV(chartKey) {
+ // const chart = this.props.charts[chartKey];
+ // const formData = this.getFormDataExtra(chart);
+ // exportChart(formData, 'csv');
+ // }
// re-render chart without fetch
- rerenderCharts() {
- this.getAllCharts().forEach((chart) => {
- setTimeout(() => {
- this.props.actions.renderTriggered(new Date().getTime(), chart.chartKey);
- }, 50);
- });
- }
+ // rerenderCharts() {
+ // this.getAllCharts().forEach((chart) => {
+ // setTimeout(() => {
+ // this.props.actions.renderTriggered(new Date().getTime(), chart.chartKey);
+ // }, 50);
+ // });
+ // }
render() {
return (
@@ -247,27 +249,8 @@ class Dashboard extends React.PureComponent {
<div id="dashboard-header">
<AlertsWrapper initMessages={this.props.initMessages} />
</div>
- <GridLayout
- dashboard={this.props.dashboard}
- layout={this.props.layout}
- datasources={this.props.datasources}
- slices={this.props.slices}
- filters={this.props.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={this.props.editMode}
- />
+
+ <DashboardBuilder />
</div>
);
}
diff --git a/superset/assets/javascripts/dashboard/components/DashboardContainer.jsx b/superset/assets/javascripts/dashboard/components/DashboardContainer.jsx
index 7140655..858fc27 100644
--- a/superset/assets/javascripts/dashboard/components/DashboardContainer.jsx
+++ b/superset/assets/javascripts/dashboard/components/DashboardContainer.jsx
@@ -6,6 +6,7 @@ import { saveSliceName } from '../actions/allSlices';
import * as chartActions from '../../chart/chartAction';
import Dashboard from './Dashboard';
+// @TODO remove unneeded actionsn + props
function mapStateToProps({ datasources, allSlices, charts, dashboard, dashboardLayout, impressionId }) {
return {
initMessages: dashboard.common.flash_messages,
diff --git a/superset/assets/javascripts/dashboard/components/GridCell.jsx b/superset/assets/javascripts/dashboard/components/GridCell.jsx
index c3afe27..e992236 100644
--- a/superset/assets/javascripts/dashboard/components/GridCell.jsx
+++ b/superset/assets/javascripts/dashboard/components/GridCell.jsx
@@ -19,7 +19,7 @@ const propTypes = {
slice: slicePropShape.isRequired,
chart: PropTypes.shape(chartPropType).isRequired,
formData: PropTypes.object,
- filters: PropTypes.object,
+ // filters: PropTypes.object,
refreshChart: PropTypes.func,
updateSliceName: PropTypes.func,
toggleExpandSlice: PropTypes.func,
@@ -90,10 +90,20 @@ class GridCell extends React.PureComponent {
render() {
const {
- isExpanded, isLoading, isCached, cachedDttm,
- updateSliceName, toggleExpandSlice,
- chart, slice, datasource, formData, timeout, annotationQuery,
- exploreChart, exportCSV,
+ isExpanded,
+ isLoading,
+ isCached,
+ cachedDttm,
+ updateSliceName,
+ toggleExpandSlice,
+ chart,
+ slice,
+ datasource,
+ formData,
+ timeout,
+ annotationQuery,
+ exploreChart,
+ exportCSV,
} = this.props;
return (
diff --git a/superset/assets/javascripts/dashboard/components/GridLayout.jsx b/superset/assets/javascripts/dashboard/components/GridLayout.jsx
index d01a3ab..87dd4b5 100644
--- a/superset/assets/javascripts/dashboard/components/GridLayout.jsx
+++ b/superset/assets/javascripts/dashboard/components/GridLayout.jsx
@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
-import cx from 'classnames';
+// import cx from 'classnames';
-import GridCell from './GridCell';
+// import GridCell from './GridCell';
import { slicePropShape } from '../reducers/propShapes';
import DashboardBuilder from '../v2/containers/DashboardBuilder';
@@ -49,97 +49,99 @@ class GridLayout extends React.Component {
this.updateSliceName.bind(this) : null;
}
- getWidgetId(sliceId) {
- return 'widget_' + sliceId;
- }
+ // 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;
- }
+ // 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 = 'slice_' + sliceId;
+ // // const currentSlice = this.props.slices[key];
+ // // if (!currentSlice || currentSlice.slice_name === sliceName) {
+ // // return;
+ // // }
+ // //
+ // // this.props.saveSliceName(currentSlice, sliceName);
+ // // }
- getWidgetWidth(sliceId) {
- const widgetId = this.getWidgetId(sliceId);
- if (!widgetId || !this.refs[widgetId]) {
- return 400;
- }
- return this.refs[widgetId].parentNode.clientWidth;
- }
+ // isExpanded(sliceId) {
+ // return this.props.dashboard.metadata.expanded_slices &&
+ // this.props.dashboard.metadata.expanded_slices[sliceId];
+ // }
- updateSliceName(sliceId, sliceName) {
- const key = 'slice_' + sliceId;
- const currentSlice = this.props.slices[key];
- if (!currentSlice || currentSlice.slice_name === sliceName) {
- return;
- }
-
- this.props.saveSliceName(currentSlice, sliceName);
- }
-
- isExpanded(sliceId) {
- return this.props.dashboard.metadata.expanded_slices &&
- this.props.dashboard.metadata.expanded_slices[sliceId];
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.editMode !== this.props.editMode) {
- this.props.rerenderCharts();
- }
- }
+ // componentDidUpdate(prevProps) {
+ // if (prevProps.editMode !== this.props.editMode) {
+ // this.props.rerenderCharts();
+ // }
+ // }
render() {
- const cells = {};
- this.props.dashboard.sliceIds.forEach((sliceId) => {
- const key = `slice_${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 />;
- return (
- <DashboardBuilder
- cells={cells}
- />
- );
+ // const cells = {};
+ // this.props.dashboard.sliceIds.forEach((sliceId) => {
+ // const key = `slice_${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}
+ // />
+ // );
}
}
diff --git a/superset/assets/javascripts/dashboard/components/SliceAdder.jsx b/superset/assets/javascripts/dashboard/components/SliceAdder.jsx
index 2a1e983..11be188 100644
--- a/superset/assets/javascripts/dashboard/components/SliceAdder.jsx
+++ b/superset/assets/javascripts/dashboard/components/SliceAdder.jsx
@@ -123,6 +123,7 @@ 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}
@@ -134,7 +135,6 @@ class SliceAdder extends React.Component {
<div
ref={dragSourceRef}
className="chart-card-container"
- key={key}
style={style}
>
<div className={cx('chart-card', { 'is-selected': isSelected })}>
diff --git a/superset/assets/javascripts/dashboard/components/SliceHeader.jsx b/superset/assets/javascripts/dashboard/components/SliceHeader.jsx
index 264542a..d291b3b 100644
--- a/superset/assets/javascripts/dashboard/components/SliceHeader.jsx
+++ b/superset/assets/javascripts/dashboard/components/SliceHeader.jsx
@@ -7,6 +7,7 @@ import TooltipWrapper from '../../components/TooltipWrapper';
import SliceHeaderControls from './SliceHeaderControls';
const propTypes = {
+ innerRef: PropTypes.func,
slice: PropTypes.object.isRequired,
isExpanded: PropTypes.bool,
isCached: PropTypes.bool,
@@ -22,6 +23,7 @@ const propTypes = {
};
const defaultProps = {
+ innerRef: null,
forceRefresh: () => ({}),
removeSlice: () => ({}),
updateSliceName: () => ({}),
@@ -46,15 +48,22 @@ 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="chart-header" ref={innerRef}>
<div className="col-md-12">
<div className="header">
<EditableTitle
diff --git a/superset/assets/javascripts/dashboard/util/dashboardLayoutConverter.js b/superset/assets/javascripts/dashboard/util/dashboardLayoutConverter.js
index c58e792..a3f6f0a 100644
--- a/superset/assets/javascripts/dashboard/util/dashboardLayoutConverter.js
+++ b/superset/assets/javascripts/dashboard/util/dashboardLayoutConverter.js
@@ -93,18 +93,28 @@ function getChartHolder(item) {
};
}
-function getChildrenMax(items, attr, layout) {
- return Math.max.apply(null, items.map(child => {
- return layout[child].meta[attr];
- }));
-}
-
function getChildrenSum(items, attr, layout) {
return items.reduce((preValue, child) => {
return 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;
}
@@ -232,17 +242,15 @@ function doConvert(positions, level, parent, root) {
}
// add col meta
+ // colContainer.meta.width = getChildrenMax(colContainer.children, 'width', root);
+ // colContainer.meta.height = getChildrenSum(colContainer.children, 'height', root);
colContainer.meta.width = getChildrenMax(colContainer.children, 'width', root);
- colContainer.meta.height = getChildrenSum(colContainer.children, 'height', root);
currentItems = upper.slice();
}
currentCol++;
}
}
-
- rowContainer.meta.width = getChildrenSum(rowContainer.children, 'width', root);
- rowContainer.meta.height = getChildrenMax(rowContainer.children, 'height', root);
});
}
@@ -305,4 +313,3 @@ export default function(dashboard) {
// console.log(JSON.stringify(root));
return root;
}
-
diff --git a/superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js b/superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js
index 4958710..4cc795b 100644
--- a/superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js
+++ b/superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js
@@ -1,6 +1,6 @@
import { addInfoToast } from './messageToasts';
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 +62,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,6 +77,8 @@ export function resizeComponent({ id, width, height }) {
},
};
+ // 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)) {
@@ -86,14 +86,16 @@ export function resizeComponent({ id, width, height }) {
...child,
meta: {
...child.meta,
- width: width || child.meta.width,
+ width: GRID_MIN_COLUMN_COUNT,
height: height || child.meta.height,
},
};
}
});
- dispatch(updateComponents(updatedComponents));
+ dispatch(
+ updateComponents(updatedComponents),
+ );
}
};
}
diff --git a/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx b/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx
index e9e2327..b09834b 100644
--- a/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx
@@ -37,8 +37,7 @@ class BuilderComponentPane extends React.PureComponent {
<div className="dashboard-builder-sidepane-header">
Insert components
{this.state.showSlices &&
- <i className="fa fa-times close trigger" onClick={this.closeSlicesPane}/>
- }
+ <i className="fa fa-times close trigger" onClick={this.closeSlicesPane} />}
</div>
<div className="component-layer">
@@ -51,9 +50,7 @@ class BuilderComponentPane extends React.PureComponent {
</div>
<NewHeader />
- <NewDivider />
-
-
+ <NewDivider />
<NewTabs />
<NewRow />
<NewColumn />
diff --git a/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx b/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx
index f3f5867..28ea951 100644
--- a/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx
+++ b/superset/assets/javascripts/dashboard/v2/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,
@@ -108,7 +106,6 @@ class DashboardBuilder extends React.Component {
index={0}
renderTabContent={false}
onChangeTab={this.handleChangeTab}
- cells={this.props.cells}
/>
</WithPopoverMenu>}
@@ -116,7 +113,6 @@ class DashboardBuilder extends React.Component {
<DashboardGrid
gridComponent={gridComponent}
depth={DASHBOARD_ROOT_DEPTH + 1}
- cells={this.props.cells}
/>
{this.props.editMode && this.props.showBuilderPane &&
<BuilderComponentPane />
diff --git a/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx b/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx
index 2aa82af..4cc73b9 100644
--- a/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx
@@ -1,5 +1,7 @@
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';
@@ -34,6 +36,7 @@ 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) {
@@ -43,6 +46,10 @@ class DashboardGrid extends React.PureComponent {
return null;
}
+ setGridRef(ref) {
+ this.grid = ref;
+ }
+
handleResizeStart({ ref, direction }) {
let rowGuideTop = null;
if (direction === 'bottom' || direction === 'bottomRight') {
@@ -71,73 +78,72 @@ 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
+ {(({ width }) => {
const columnPlusGutterWidth = (width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT;
const columnWidth = columnPlusGutterWidth - GRID_GUTTER_SIZE;
-
- return width < 50 ? null : (
- <div className="grid-content">
- {gridComponent.children.map((id, index) => (
- <DashboardComponent
- key={id}
- id={id}
- parentId={gridComponent.id}
- depth={depth + 1}
- index={index}
- availableColumnCount={GRID_COLUMN_COUNT}
- columnWidth={columnWidth}
- cells={cells}
- onResizeStart={this.handleResizeStart}
- onResize={this.handleResize}
- onResizeStop={this.handleResizeStop}
- />
- ))}
-
- {/* render an empty drop target */}
- {editMode &&
- <DragDroppable
- component={gridComponent}
- depth={depth}
- parentComponent={null}
- index={gridComponent.children.length}
- orientation="column"
- onDrop={handleComponentDrop}
- 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,
- }}
- />}
- </div>
+ return (
+ width < 50 ? null : (
+ <div className="grid-content">
+ {gridComponent.children.map((id, index) => (
+ <DashboardComponent
+ key={id}
+ id={id}
+ parentId={gridComponent.id}
+ depth={depth + 1}
+ index={index}
+ availableColumnCount={GRID_COLUMN_COUNT}
+ columnWidth={columnWidth}
+ onResizeStart={this.handleResizeStart}
+ onResize={this.handleResize}
+ onResizeStop={this.handleResizeStop}
+ />
+ ))}
+
+ {/* render an empty drop target */}
+ {editMode &&
+ <DragDroppable
+ component={gridComponent}
+ depth={depth}
+ parentComponent={null}
+ index={gridComponent.children.length}
+ orientation="column"
+ onDrop={handleComponentDrop}
+ 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,
+ }}
+ />}
+ </div>
+ )
);
- }}
+ })}
</ParentSize>
</div>
);
diff --git a/superset/assets/javascripts/dashboard/v2/components/WithKeyListener.jsx b/superset/assets/javascripts/dashboard/v2/components/WithKeyListener.jsx
new file mode 100644
index 0000000..b391387
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/components/WithKeyListener.jsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const TEST_CACHE = {
+ cmdZ: e => (e.metaKey || e.ctrlKey) && e.keyCode === 90,
+};
+
+const propTypes = {
+ // accepts keyCode
+ // or a test func (which is lazily cached using the cacheKey string)
+ keyCode: PropTypes.number,
+ cacheKey: PropTypes.string,
+ test: PropTypes.func, // (event) => Boolean
+ onPress: PropTypes.func.isRequired,
+};
+
+export default class WithKeyListener extends React.PureComponent {
+ componentDidMount() {
+ const { keyCode, test, cacheKey, onPress } = this.props;
+ let eventListener;
+ if (test && cacheKey) { // overwrite cache
+ TEST_CACHE[cacheKey] = test;
+ eventListener = test;
+ } else if (cacheKey && TEST_CACHE[cacheKey]) { // use cache
+ eventListener = TEST_CACHE[cacheKey]
+ } else if (typeof keyCode === 'number') {
+ if (TEST_CACHE[keyCode]) {
+ eventListener = TEST_CACHE[cacheKey]; // use keyCode cache
+ } else {
+ TEST_CACHE[cacheKey] = e => e.keyCode === keyCode; // set cache
+ eventListener = TEST_CACHE[cacheKey];
+ }
+ } else {
+ console.warn('Missing cacheKey, test, or keyCode');
+ return;
+ }
+
+ document.addEventListener('keydown', (e) => {
+ if (eventListener(e)) {
+ onPress(e);
+ alert('keydown');
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('keydown');
+ }
+
+ render() {
+ return null;
+ }
+}
+
+WithKeyListener.propTypes = propTypes;
diff --git a/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx b/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx
index 775e092..6e2838a 100644
--- a/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx
@@ -74,7 +74,7 @@ class DragDroppable extends React.Component {
editMode,
} = this.props;
- if (!editMode) return children({});
+ // if (!editMode) return children({});
const { dropIndicator } = this.state;
@@ -90,7 +90,7 @@ class DragDroppable extends React.Component {
className,
)}
>
- {children({
+ {children(!editMode ? {} : {
dragSourceRef,
dropIndicatorProps: isDraggingOver && dropIndicator && {
className: cx(
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx
new file mode 100644
index 0000000..889a455
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx
@@ -0,0 +1,206 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { exportChart } from '../../../../explore/exploreUtils';
+import SliceHeader from '../../../components/SliceHeader';
+import ChartContainer from '../../../../chart/ChartContainer';
+import { chartPropType } from '../../../../chart/chartReducer';
+import { slicePropShape } from '../../../reducers/propShapes';
+
+const propTypes = {
+ id: PropTypes.string.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,
+};
+
+const updateOnPropChange = Object.keys(propTypes)
+ .filter(prop => prop !== 'width' && prop !== 'height');
+
+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) {
+ if (nextState.width !== this.state.width || nextState.height !== this.state.height) {
+ return true;
+ }
+
+ for (let i = 0; i < updateOnPropChange.length; i += 1) {
+ const prop = updateOnPropChange[i];
+ if (nextProps[prop] !== this.props[prop]) {
+ console.log(prop, 'changed')
+ return true;
+ }
+ }
+
+ if (nextProps.width !== this.props.width || nextProps.height !== this.props.height) {
+ clearTimeout(this.resizeTimeout);
+ this.resizeTimeout = setTimeout(this.resize, 350);
+ }
+
+ 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;
+
+ return (
+ <div className="dashboard-chart">
+ <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
+ */}
+ <div
+ className="slice_description bs-callout bs-callout-default"
+ style={isExpanded ? null : { display: 'none' }}
+ ref={this.setDescriptionRef}
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: slice.description_markeddown }}
+ />
+ <ChartContainer
+ containerId={`slice-container-${slice.slice_id}`}
+ sliceId={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/javascripts/dashboard/v2/components/gridComponents/ChartHolder.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/ChartHolder.jsx
index ae304ad..28ddad1 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/ChartHolder.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/ChartHolder.jsx
@@ -1,15 +1,19 @@
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 propTypes = {
id: PropTypes.string.isRequired,
@@ -19,7 +23,6 @@ const propTypes = {
index: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
editMode: PropTypes.bool.isRequired,
- chart: PropTypes.object,
// grid related
availableColumnCount: PropTypes.number.isRequired,
@@ -73,6 +76,11 @@ 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 +98,31 @@ 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 className="dashboard-component dashboard-component-chart">
- {this.props.chart}
- </div>
-
- {dropIndicatorProps && <div {...dropIndicatorProps} />}
- </WithPopoverMenu>
+ <div ref={dragSourceRef} className="dashboard-component dashboard-component-chart">
+ <Chart
+ id={component.meta.chartKey}
+ width={widthMultiple * columnWidth}
+ height={component.meta.height * GRID_BASE_UNIT}
+ />
+ {editMode &&
+ <HoverMenu position="top">
+ <DragHandle position="top" />
+ <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+ </HoverMenu>}
+ </div>
+
+ {dropIndicatorProps && <div {...dropIndicatorProps} />}
</ResizableContainer>
)}
</DragDroppable>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx
index 634c1a4..03e0ab4 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx
+++ b/superset/assets/javascripts/dashboard/v2/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,
@@ -93,7 +92,6 @@ class Column extends React.PureComponent {
onResizeStop,
handleComponentDrop,
editMode,
- cells,
} = this.props;
const columnItems = columnComponent.children || [];
@@ -156,20 +154,19 @@ class Column extends React.PureComponent {
</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>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx
index 8faaee1..9866bc8 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx
+++ b/superset/assets/javascripts/dashboard/v2/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,
@@ -93,7 +92,6 @@ class Row extends React.PureComponent {
onResizeStop,
handleComponentDrop,
editMode,
- cells,
} = this.props;
const rowItems = rowComponent.children || [];
@@ -144,20 +142,19 @@ class Row extends React.PureComponent {
</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/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx b/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx
index f213442..1e93181 100644
--- a/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx
@@ -54,12 +54,11 @@ 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
diff --git a/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx b/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx
index a532ff0..2bb6c08 100644
--- a/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx
+++ b/superset/assets/javascripts/dashboard/v2/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);
@@ -150,18 +153,15 @@ class ResizableContainer extends React.PureComponent {
|| 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;
@@ -190,6 +190,7 @@ class ResizableContainer extends React.PureComponent {
'resizable-container',
isResizing && 'resizable-container--resizing',
)}
+ handleClasses={HANDLE_CLASSES}
>
{children}
</Resizable>
diff --git a/superset/assets/javascripts/dashboard/v2/containers/Chart.jsx b/superset/assets/javascripts/dashboard/v2/containers/Chart.jsx
new file mode 100644
index 0000000..d527c9b
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/containers/Chart.jsx
@@ -0,0 +1,44 @@
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import { addFilter, removeFilter, toggleExpandSlice } from '../../actions/dashboard';
+import { refreshChart } from '../../../chart/chartAction';
+import getFormDataWithExtraFilters from '../../v2/util/charts/getFormDataWithExtraFilters';
+import { saveSliceName } from '../../actions/allSlices';
+import Chart from '../components/gridComponents/Chart';
+
+function mapStateToProps({ datasources, allSlices, charts, dashboard }, ownProps) {
+ const { id } = ownProps;
+ const chart = charts[id];
+ const { filters } = dashboard;
+ const isExpanded = !!(dashboard.dashboard.metadata.expanded_slices || {})[id];
+
+ return {
+ chart,
+ datasource: datasources[chart.form_data.datasource],
+ slice: allSlices.slices[id],
+ timeout: dashboard.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
+ filters,
+ // note: this method caches filters if possible to prevent render cascades
+ formData: getFormDataWithExtraFilters({
+ chart,
+ dashboardMetadata: dashboard.dashboard.metadata,
+ filters,
+ sliceId: id,
+ }),
+ editMode: dashboard.editMode,
+ isExpanded,
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return bindActionCreators({
+ saveSliceName,
+ toggleExpandSlice,
+ addFilter,
+ refreshChart,
+ removeFilter,
+ }, dispatch);
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Chart);
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx
index ba9fc45..a70126c 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx
+++ b/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx
@@ -7,10 +7,9 @@ import {
handleComponentDrop,
} from '../actions/dashboardLayout';
-function mapStateToProps({ dashboardLayout: undoableLayout, dashboard }, ownProps) {
+function mapStateToProps({ dashboardLayout: undoableLayout, dashboard }) {
return {
dashboardLayout: undoableLayout.present,
- cells: ownProps.cells,
editMode: dashboard.editMode,
showBuilderPane: dashboard.showBuilderPane,
};
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx
index 3118ce8..3baa84b 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx
+++ b/superset/assets/javascripts/dashboard/v2/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,9 +25,15 @@ const propTypes = {
handleComponentDrop: PropTypes.func.isRequired,
};
-function mapStateToProps({ dashboardLayout: undoableLayout, dashboard }, ownProps) {
+function mapStateToProps({
+ dashboardLayout: undoableLayout,
+ dashboard,
+ allSlices,
+ charts,
+ datasources,
+}, ownProps) {
const dashboardLayout = undoableLayout.present;
- const { id, parentId, cells } = ownProps;
+ const { id, parentId } = ownProps;
const component = dashboardLayout[id];
const props = {
component,
@@ -51,11 +57,6 @@ function mapStateToProps({ dashboardLayout: undoableLayout, dashboard }, ownProp
);
}
});
- } else if (props.component.type === CHART_TYPE) {
- const chartKey = props.component.meta && props.component.meta.chartKey;
- if (chartKey) {
- props.chart = cells[chartKey];
- }
}
return props;
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx
index 9aa3447..ef2eb8c 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx
+++ b/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx
@@ -7,10 +7,9 @@ import {
resizeComponent,
} from '../actions/dashboardLayout';
-function mapStateToProps({ dashboard }, ownProps) {
+function mapStateToProps({ dashboard }) {
return {
editMode: dashboard.editMode,
- cells: ownProps.cells,
};
}
diff --git a/superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js b/superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js
index 994ac47..421463a 100644
--- a/superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js
+++ b/superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js
@@ -1,4 +1,8 @@
-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';
@@ -70,17 +74,17 @@ const actionHandlers = {
const { destination, dragging } = dropResult;
const newEntities = newEntitiesFromDrop({ dropResult, components: state });
- // inherit the width of a column parent
+ // 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];
newEntities[component.id] = {
...component,
meta: {
...component.meta,
- width: parentColumn.meta.width,
+ width: GRID_MIN_COLUMN_COUNT,
},
};
}
diff --git a/superset/assets/javascripts/dashboard/v2/reducers/index.js b/superset/assets/javascripts/dashboard/v2/reducers/index.js
index 0134767..d6e9f84 100644
--- a/superset/assets/javascripts/dashboard/v2/reducers/index.js
+++ b/superset/assets/javascripts/dashboard/v2/reducers/index.js
@@ -1,10 +1,29 @@
-import undoable, { distinctState } from 'redux-undo';
+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 const undoableLayout = undoable(dashboardLayout, {
+const undoableLayout = undoable(dashboardLayout, {
limit: 15,
- filter: distinctState(),
+ filter: includeAction([
+ UPDATE_COMPONENTS,
+ DELETE_COMPONENT,
+ CREATE_COMPONENT,
+ CREATE_TOP_LEVEL_TABS,
+ DELETE_TOP_LEVEL_TABS,
+ RESIZE_COMPONENT,
+ MOVE_COMPONENT,
+ HANDLE_COMPONENT_DROP,
+ ]),
});
export default undoableLayout;
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less
index ce03797..8419b48 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less
@@ -3,18 +3,33 @@
height: 100%;
color: @gray-dark;
background-color: white;
- padding: 16px;
- display: flex;
- align-items: center;
- justify-content: center;
position: relative;
+ overflow: hidden;
}
-.dashboard-component-chart .fa {
- //font-size: 100px;
- opacity: 0.3;
+.dashboard-v2--editing .dashboard-component-chart {
+ border: 1px solid transparent;
}
.dashboard-v2--editing .dashboard-component-chart:hover {
- box-shadow: inset 0 0 0 1px @gray-light;
+ border: 1px solid @indicator-color;
+}
+
+.dashboard-v2--editing .dashboard-component-chart .dashboard-chart .chart-container {
+ cursor: move;
+ opacity: 0.7;
+}
+
+.dashboard-v2--editing .dashboard-component-chart:hover .dashboard-chart .chart-container {
+ opacity: 1;
+}
+
+
+.dashboard-v2--editing .dashboard-component-chart .dashboard-chart .slice_container {
+ /* disable chart interactions in edit mode */
+ pointer-events: none;
+}
+
+.chart-header {
+ padding: 16px;
}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less
index 9565112..29fabc1 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less
@@ -1,5 +1,6 @@
.grid-column {
width: 100%;
+ position: relative;
}
/* gutters between elements in a column */
@@ -8,12 +9,12 @@
}
.dashboard-v2--editing .grid-column:after {
- border: 1px dashed transparent;
+ border: 1px solid transparent;
content: "";
position: absolute;
width: 100%;
height: 100%;
- top: 1px;
+ top: 0;
left: 0;
z-index: 1;
pointer-events: none;
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less
index 956966d..efc93fe 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less
+++ b/superset/assets/javascripts/dashboard/v2/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;
@@ -19,7 +20,7 @@
position: absolute;
width: 100%;
height: 100%;
- top: 1px;
+ top: 0;
left: 0;
z-index: 1;
pointer-events: none;
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/dnd.less b/superset/assets/javascripts/dashboard/v2/stylesheets/dnd.less
index 45a9784..835b62b 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/dnd.less
+++ b/superset/assets/javascripts/dashboard/v2/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/javascripts/dashboard/v2/stylesheets/grid.less b/superset/assets/javascripts/dashboard/v2/stylesheets/grid.less
index 45b8a42..a12ac97 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/grid.less
+++ b/superset/assets/javascripts/dashboard/v2/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/javascripts/dashboard/v2/stylesheets/resizable.less b/superset/assets/javascripts/dashboard/v2/stylesheets/resizable.less
index 7bdd5f8..973daab 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/resizable.less
+++ b/superset/assets/javascripts/dashboard/v2/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/javascripts/dashboard/v2/util/charts/getEffectiveExtraFilters.js b/superset/assets/javascripts/dashboard/v2/util/charts/getEffectiveExtraFilters.js
new file mode 100644
index 0000000..e6b5c5e
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/util/charts/getEffectiveExtraFilters.js
@@ -0,0 +1,41 @@
+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/javascripts/dashboard/v2/util/charts/getFormDataWithExtraFilters.js b/superset/assets/javascripts/dashboard/v2/util/charts/getFormDataWithExtraFilters.js
new file mode 100644
index 0000000..ebb66e3
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/util/charts/getFormDataWithExtraFilters.js
@@ -0,0 +1,40 @@
+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
+let cachedMetadata = null;
+let cachedFormdata = {};
+
+export default function getFormDataWithExtraFilters({
+ chart,
+ dashboardMetadata,
+ filters,
+ sliceId,
+}) {
+ // dashboard metadata has not changed use cache if possible
+ if (cachedMetadata === dashboardMetadata && cachedFormdata[sliceId]) {
+ return cachedFormdata[sliceId];
+ } else if (cachedMetadata !== dashboardMetadata) {
+ // changes to dashboardMetadata should invalidate all caches
+ cachedMetadata = dashboardMetadata;
+ cachedFormdata = {};
+ }
+
+ const extraFilters = getEffectiveExtraFilters({
+ dashboardMetadata,
+ filters,
+ sliceId,
+ });
+
+ const formData = {
+ ...chart.formData,
+ extra_filters: [
+ ...chart.formData.filters,
+ ...extraFilters,
+ ],
+ };
+
+ cachedFormdata[sliceId] = formData;
+
+ return formData;
+}
diff --git a/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js b/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js
index 0fd0c4e..e298719 100644
--- a/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js
+++ b/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js
@@ -1,13 +1,18 @@
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] || {};
@@ -17,7 +22,8 @@ export default function doesChildOverflowParent(dropResult, components) {
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 });
+ ? (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/javascripts/dashboard/v2/util/getDropPosition.js b/superset/assets/javascripts/dashboard/v2/util/getDropPosition.js
index 9605db2..b0a75fb 100644
--- a/superset/assets/javascripts/dashboard/v2/util/getDropPosition.js
+++ b/superset/assets/javascripts/dashboard/v2/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 {
diff --git a/superset/assets/javascripts/explore/components/ExploreChartPanel.jsx b/superset/assets/javascripts/explore/components/ExploreChartPanel.jsx
index f595fb5..6821185 100644
--- a/superset/assets/javascripts/explore/components/ExploreChartPanel.jsx
+++ b/superset/assets/javascripts/explore/components/ExploreChartPanel.jsx
@@ -38,15 +38,15 @@ class ExploreChartPanel extends React.PureComponent {
}
renderChart() {
- debugger
+ const { chart } = this.props;
return (
<ChartContainer
+ sliceId={chart.chartKey}
containerId={this.props.containerId}
datasource={this.props.datasource}
formData={this.props.form_data}
height={this.getHeight()}
slice={this.props.slice}
- chart={this.props.chart}
setControlValue={this.props.actions.setControlValue}
timeout={this.props.timeout}
vizType={this.props.vizType}
@@ -54,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/package.json b/superset/assets/package.json
index c3afd7a..27fe935 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -107,7 +107,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",
diff --git a/superset/assets/stylesheets/dashboard.less b/superset/assets/stylesheets/dashboard.less
index a8973a3..f9c9b3d 100644
--- a/superset/assets/stylesheets/dashboard.less
+++ b/superset/assets/stylesheets/dashboard.less
@@ -109,22 +109,6 @@
display: none;
}
-.slice-grid div.separator.widget {
- border: 1px solid transparent;
- box-shadow: none;
- z-index: 1;
-}
-.slice-grid div.separator.widget:hover {
- border: 1px solid #EEE;
-}
-.slice-grid div.separator.widget .chart-header {
- background-color: transparent;
- color: transparent;
-}
-.slice-grid div.separator.widget h1,h2,h3,h4 {
- margin-top: 0px;
-}
-
.slice-cell {
box-shadow: 0px 0px 20px 5px rgba(0,0,0,0);
transition: box-shadow 1s ease-in;
@@ -142,7 +126,7 @@
height: 100%;
}
-.slice-cell .editable-title input[type="button"] {
+.dashboard-chart .editable-title input[type="button"] {
font-weight: bold;
}
@@ -302,4 +286,4 @@ i.warning {
.ReactVirtualized__Grid.ReactVirtualized__List:focus {
outline: none;
}
-}
\ No newline at end of file
+}
diff --git a/superset/assets/visualizations/nvd3_vis.css b/superset/assets/visualizations/nvd3_vis.css
index fed0d01..6b3b25d 100644
--- a/superset/assets/visualizations/nvd3_vis.css
+++ b/superset/assets/visualizations/nvd3_vis.css
@@ -11,10 +11,6 @@ text.nv-axislabel {
font-size: 14px;
}
-.slice_container.dist_bar {
- overflow-x: auto !important;
-}
-
.dist_bar svg.nvd3-svg {
width: auto;
font-size: 14px;
@@ -63,4 +59,3 @@ g.opacityMedium path, line.opacityMedium {
g.opacityHigh path, line.opacityHigh {
stroke-opacity: .8
}
-
diff --git a/superset/templates/superset/dashboard.html b/superset/templates/superset/dashboard.html
index 1a158d9..5c93d2a 100644
--- a/superset/templates/superset/dashboard.html
+++ b/superset/templates/superset/dashboard.html
@@ -1,10 +1,5 @@
{% extends "superset/basic.html" %}
{% block body %}
-<div
- id="app"
- class="dashboard container-fluid"
- data-bootstrap="{{ bootstrap_data }}"
->
-</div>
+ <div id="app" class="dashboard" data-bootstrap="{{ bootstrap_data }}" />
{% endblock %}
--
To stop receiving notification emails like this one, please contact
ccwilliams@apache.org.