feat(explore): Implement data panel redesign (#19751)

* feat(explore): Redesign of data panel

* Auto calculate chart panel height and width

* Add tests

* Fix e2e tests

* Increase collapsed data panel height
This commit is contained in:
Kamil Gabryjelski
2022-04-19 10:10:40 +02:00
committed by GitHub
parent 34323f9b5f
commit 594523e895
6 changed files with 496 additions and 361 deletions

View File

@@ -19,7 +19,7 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import Split from 'react-split';
import { styled, SupersetClient, useTheme } from '@superset-ui/core';
import { css, styled, SupersetClient, useTheme } from '@superset-ui/core';
import { useResizeDetector } from 'react-resize-detector';
import { chartPropShape } from 'src/dashboard/util/propShapes';
import ChartContainer from 'src/components/Chart/ChartContainer';
@@ -41,8 +41,6 @@ const propTypes = {
dashboardId: PropTypes.number,
column_formats: PropTypes.object,
containerId: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
width: PropTypes.string.isRequired,
isStarred: PropTypes.bool.isRequired,
slice: PropTypes.object,
sliceName: PropTypes.string,
@@ -61,11 +59,8 @@ const propTypes = {
const GUTTER_SIZE_FACTOR = 1.25;
const CHART_PANEL_PADDING_HORIZ = 30;
const CHART_PANEL_PADDING_VERTICAL = 15;
const INITIAL_SIZES = [90, 10];
const MIN_SIZES = [300, 50];
const INITIAL_SIZES = [100, 0];
const MIN_SIZES = [300, 65];
const DEFAULT_SOUTH_PANE_HEIGHT_PERCENT = 40;
const Styles = styled.div`
@@ -109,28 +104,42 @@ const Styles = styled.div`
}
`;
const ExploreChartPanel = props => {
const ExploreChartPanel = ({
chart,
slice,
vizType,
ownState,
triggerRender,
force,
datasource,
errorMessage,
form_data: formData,
onQuery,
refreshOverlayVisible,
actions,
timeout,
standalone,
}) => {
const theme = useTheme();
const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR;
const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR;
const { width: chartPanelWidth, ref: chartPanelRef } = useResizeDetector({
const {
width: chartPanelWidth,
height: chartPanelHeight,
ref: chartPanelRef,
} = useResizeDetector({
refreshMode: 'debounce',
refreshRate: 300,
});
const { height: pillsHeight, ref: pillsRef } = useResizeDetector({
refreshMode: 'debounce',
refreshRate: 1000,
});
const [splitSizes, setSplitSizes] = useState(
getItem(LocalStorageKeys.chart_split_sizes, INITIAL_SIZES),
);
const { slice } = props;
const updateQueryContext = useCallback(
async function fetchChartData() {
if (slice && slice.query_context === null) {
const queryContext = buildV1ChartDataPayload({
formData: slice.form_data,
force: props.force,
force,
resultFormat: 'json',
resultType: 'full',
setDataMask: null,
@@ -154,34 +163,6 @@ const ExploreChartPanel = props => {
updateQueryContext();
}, [updateQueryContext]);
const calcSectionHeight = useCallback(
percent => {
let containerHeight = parseInt(props.height, 10);
if (pillsHeight) {
containerHeight -= pillsHeight;
}
return (
(containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin)
);
},
[gutterHeight, gutterMargin, pillsHeight, props.height, props.standalone],
);
const [tableSectionHeight, setTableSectionHeight] = useState(
calcSectionHeight(INITIAL_SIZES[1]),
);
const recalcPanelSizes = useCallback(
([, southPercent]) => {
setTableSectionHeight(calcSectionHeight(southPercent));
},
[calcSectionHeight],
);
useEffect(() => {
recalcPanelSizes(splitSizes);
}, [recalcPanelSizes, splitSizes]);
useEffect(() => {
setItem(LocalStorageKeys.chart_split_sizes, splitSizes);
}, [splitSizes]);
@@ -191,19 +172,19 @@ const ExploreChartPanel = props => {
};
const refreshCachedQuery = () => {
props.actions.postChartFormData(
props.form_data,
actions.postChartFormData(
formData,
true,
props.timeout,
props.chart.id,
timeout,
chart.id,
undefined,
props.ownState,
ownState,
);
};
const onCollapseChange = openPanelName => {
const onCollapseChange = useCallback(isOpen => {
let splitSizes;
if (!openPanelName) {
if (!isOpen) {
splitSizes = INITIAL_SIZES;
} else {
splitSizes = [
@@ -212,53 +193,84 @@ const ExploreChartPanel = props => {
];
}
setSplitSizes(splitSizes);
};
const renderChart = useCallback(() => {
const { chart, vizType } = props;
const newHeight =
vizType === 'filter_box'
? calcSectionHeight(100) - CHART_PANEL_PADDING_VERTICAL
: calcSectionHeight(splitSizes[0]) - CHART_PANEL_PADDING_VERTICAL;
const chartWidth = chartPanelWidth - CHART_PANEL_PADDING_HORIZ;
return (
chartWidth > 0 && (
<ChartContainer
width={Math.floor(chartWidth)}
height={newHeight}
ownState={props.ownState}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartStackTrace={chart.chartStackTrace}
chartId={chart.id}
chartStatus={chart.chartStatus}
triggerRender={props.triggerRender}
force={props.force}
datasource={props.datasource}
errorMessage={props.errorMessage}
formData={props.form_data}
onQuery={props.onQuery}
queriesResponse={chart.queriesResponse}
refreshOverlayVisible={props.refreshOverlayVisible}
setControlValue={props.actions.setControlValue}
timeout={props.timeout}
triggerQuery={chart.triggerQuery}
vizType={props.vizType}
/>
)
);
}, [calcSectionHeight, chartPanelWidth, props, splitSizes]);
}, []);
const renderChart = useCallback(
() => (
<div
css={css`
min-height: 0;
flex: 1;
`}
ref={chartPanelRef}
>
{chartPanelWidth && chartPanelHeight && (
<ChartContainer
width={Math.floor(chartPanelWidth)}
height={chartPanelHeight}
ownState={ownState}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartStackTrace={chart.chartStackTrace}
chartId={chart.id}
chartStatus={chart.chartStatus}
triggerRender={triggerRender}
force={force}
datasource={datasource}
errorMessage={errorMessage}
formData={formData}
onQuery={onQuery}
queriesResponse={chart.queriesResponse}
refreshOverlayVisible={refreshOverlayVisible}
setControlValue={actions.setControlValue}
timeout={timeout}
triggerQuery={chart.triggerQuery}
vizType={vizType}
/>
)}
</div>
),
[
actions.setControlValue,
chart.annotationData,
chart.chartAlert,
chart.chartStackTrace,
chart.chartStatus,
chart.id,
chart.queriesResponse,
chart.triggerQuery,
chartPanelHeight,
chartPanelRef,
chartPanelWidth,
datasource,
errorMessage,
force,
formData,
onQuery,
ownState,
refreshOverlayVisible,
timeout,
triggerRender,
vizType,
],
);
const panelBody = useMemo(
() => (
<div className="panel-body" ref={chartPanelRef}>
<div
className="panel-body"
css={css`
display: flex;
flex-direction: column;
`}
>
<ChartPills
queriesResponse={props.chart.queriesResponse}
chartStatus={props.chart.chartStatus}
chartUpdateStartTime={props.chart.chartUpdateStartTime}
chartUpdateEndTime={props.chart.chartUpdateEndTime}
queriesResponse={chart.queriesResponse}
chartStatus={chart.chartStatus}
chartUpdateStartTime={chart.chartUpdateStartTime}
chartUpdateEndTime={chart.chartUpdateEndTime}
refreshCachedQuery={refreshCachedQuery}
rowLimit={props.form_data?.row_limit}
ref={pillsRef}
rowLimit={formData?.row_limit}
/>
{renderChart()}
</div>
@@ -266,14 +278,9 @@ const ExploreChartPanel = props => {
[chartPanelRef, renderChart],
);
const standaloneChartBody = useMemo(
() => <div ref={chartPanelRef}>{renderChart()}</div>,
[chartPanelRef, renderChart],
);
const standaloneChartBody = useMemo(() => renderChart(), [renderChart]);
const [queryFormData, setQueryFormData] = useState(
props.chart.latestQueryFormData,
);
const [queryFormData, setQueryFormData] = useState(chart.latestQueryFormData);
useEffect(() => {
// only update when `latestQueryFormData` changes AND `triggerRender`
@@ -281,13 +288,13 @@ const ExploreChartPanel = props => {
// as this can trigger a query downstream based on incomplete form data.
// (`latestQueryFormData` is only updated when a a valid request has been
// triggered).
if (!props.triggerRender) {
setQueryFormData(props.chart.latestQueryFormData);
if (!triggerRender) {
setQueryFormData(chart.latestQueryFormData);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.chart.latestQueryFormData]);
}, [chart.latestQueryFormData]);
if (props.standalone) {
if (standalone) {
// dom manipulation hack to get rid of the boostrap theme's body background
const standaloneClass = 'background-transparent';
const bodyClasses = document.body.className.split(' ');
@@ -302,8 +309,8 @@ const ExploreChartPanel = props => {
});
return (
<Styles className="panel panel-default chart-container" ref={chartPanelRef}>
{props.vizType === 'filter_box' ? (
<Styles className="panel panel-default chart-container">
{vizType === 'filter_box' ? (
panelBody
) : (
<Split
@@ -313,16 +320,16 @@ const ExploreChartPanel = props => {
gutterSize={gutterHeight}
onDragEnd={onDragEnd}
elementStyle={elementStyle}
expandToMin
>
{panelBody}
<DataTablesPane
ownState={props.ownState}
ownState={ownState}
queryFormData={queryFormData}
tableSectionHeight={tableSectionHeight}
onCollapseChange={onCollapseChange}
chartStatus={props.chart.chartStatus}
errorMessage={props.errorMessage}
queriesResponse={props.chart.queriesResponse}
chartStatus={chart.chartStatus}
errorMessage={errorMessage}
queriesResponse={chart.queriesResponse}
/>
</Split>
)}