mirror of
https://github.com/apache/superset.git
synced 2026-05-10 10:25:51 +00:00
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:
committed by
GitHub
parent
34323f9b5f
commit
594523e895
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user