feat: Resizable dataset and controls panels on Explore view (#12411)

* Implement resizable panels on explore view

* Optimize chart rendering while resizing

* Make dataset column narrower

Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
Kamil Gabryjelski
2021-01-12 19:39:56 +01:00
committed by GitHub
parent d8f4443c10
commit ed53b0090e
5 changed files with 116 additions and 57 deletions

View File

@@ -141,6 +141,24 @@ const DatasourceContainer = styled.div`
}
`;
const LabelContainer = styled.div`
overflow: hidden;
text-overflow: ellipsis;
& > span {
white-space: nowrap;
}
.option-label {
display: inline;
}
.metric-option > .option-label {
overflow: hidden;
text-overflow: ellipsis;
}
`;
const DataSourcePanel = ({
datasource,
controls: { datasource: datasourceControl },
@@ -200,9 +218,9 @@ const DataSourcePanel = ({
{t(`Showing %s of %s`, metricSlice.length, metrics.length)}
</div>
{metricSlice.map(m => (
<div key={m.metric_name} className="column">
<LabelContainer key={m.metric_name} className="column">
<MetricOption metric={m} showType />
</div>
</LabelContainer>
))}
</Collapse.Panel>
<Collapse.Panel
@@ -213,9 +231,9 @@ const DataSourcePanel = ({
{t(`Showing %s of %s`, columnSlice.length, columns.length)}
</div>
{columnSlice.map(col => (
<div key={col.column_name} className="column">
<LabelContainer key={col.column_name} className="column">
<ColumnOption column={col} showType />
</div>
</LabelContainer>
))}
</Collapse.Panel>
</Collapse>

View File

@@ -16,12 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState, useEffect, useRef, useCallback } from 'react';
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import Split from 'react-split';
import { ParentSize } from '@vx/responsive';
import { styled, useTheme } from '@superset-ui/core';
import debounce from 'lodash/debounce';
import { useResizeDetector } from 'react-resize-detector';
import { chartPropShape } from 'src/dashboard/util/propShapes';
import ChartContainer from 'src/chart/ChartContainer';
import ConnectedExploreChartHeader from './ExploreChartHeader';
@@ -55,6 +54,7 @@ const propTypes = {
const GUTTER_SIZE_FACTOR = 1.25;
const CHART_PANEL_PADDING = 30;
const HEADER_PADDING = 15;
const INITIAL_SIZES = [90, 10];
const MIN_SIZES = [300, 50];
@@ -104,20 +104,32 @@ const ExploreChartPanel = props => {
const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR;
const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR;
const panelHeadingRef = useRef(null);
const { height: hHeight, ref: headerRef } = useResizeDetector({
refreshMode: 'debounce',
refreshRate: 300,
});
const { width: chartWidth, ref: chartRef } = useResizeDetector({
refreshMode: 'debounce',
refreshRate: 300,
});
const [splitSizes, setSplitSizes] = useState(INITIAL_SIZES);
const calcSectionHeight = useCallback(
percent => {
const headerHeight = props.standalone
? 0
: panelHeadingRef?.current?.offsetHeight ?? 50;
let headerHeight;
if (props.standalone) {
headerHeight = 0;
} else if (hHeight) {
headerHeight = hHeight + HEADER_PADDING;
} else {
headerHeight = 50;
}
const containerHeight = parseInt(props.height, 10) - headerHeight;
return (
(containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin)
);
},
[gutterHeight, gutterMargin, props.height, props.standalone],
[gutterHeight, gutterMargin, props.height, props.standalone, hHeight],
);
const [tableSectionHeight, setTableSectionHeight] = useState(
@@ -132,15 +144,11 @@ const ExploreChartPanel = props => {
);
useEffect(() => {
const recalcSizes = debounce(() => recalcPanelSizes(splitSizes), 200);
window.addEventListener('resize', recalcSizes);
return () => window.removeEventListener('resize', recalcSizes);
}, [props.standalone, recalcPanelSizes, splitSizes]);
recalcPanelSizes(splitSizes);
}, [recalcPanelSizes, splitSizes]);
const onDragEnd = sizes => {
setSplitSizes(sizes);
recalcPanelSizes(sizes);
};
const onCollapseChange = openPanelName => {
@@ -154,42 +162,46 @@ const ExploreChartPanel = props => {
];
}
setSplitSizes(splitSizes);
recalcPanelSizes(splitSizes);
};
const renderChart = () => {
const renderChart = useCallback(() => {
const { chart } = props;
const newHeight = calcSectionHeight(splitSizes[0]) - CHART_PANEL_PADDING;
return (
<ParentSize>
{({ width }) =>
width > 0 && (
<ChartContainer
width={Math.floor(width)}
height={newHeight}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartStackTrace={chart.chartStackTrace}
chartId={chart.id}
chartStatus={chart.chartStatus}
triggerRender={props.triggerRender}
datasource={props.datasource}
errorMessage={props.errorMessage}
formData={props.form_data}
onQuery={props.onQuery}
owners={props?.slice?.owners}
queriesResponse={chart.queriesResponse}
refreshOverlayVisible={props.refreshOverlayVisible}
setControlValue={props.actions.setControlValue}
timeout={props.timeout}
triggerQuery={chart.triggerQuery}
vizType={props.vizType}
/>
)
}
</ParentSize>
chartWidth > 0 && (
<ChartContainer
width={Math.floor(chartWidth)}
height={newHeight}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartStackTrace={chart.chartStackTrace}
chartId={chart.id}
chartStatus={chart.chartStatus}
triggerRender={props.triggerRender}
datasource={props.datasource}
errorMessage={props.errorMessage}
formData={props.form_data}
onQuery={props.onQuery}
owners={props?.slice?.owners}
queriesResponse={chart.queriesResponse}
refreshOverlayVisible={props.refreshOverlayVisible}
setControlValue={props.actions.setControlValue}
timeout={props.timeout}
triggerQuery={chart.triggerQuery}
vizType={props.vizType}
/>
)
);
};
}, [calcSectionHeight, chartWidth, props, splitSizes]);
const panelBody = useMemo(
() => (
<div className="panel-body" ref={chartRef}>
{renderChart()}
</div>
),
[chartRef, renderChart],
);
if (props.standalone) {
// dom manipulation hack to get rid of the boostrap theme's body background
@@ -222,14 +234,12 @@ const ExploreChartPanel = props => {
[dimension]: `calc(${elementSize}% - ${gutterSize + gutterMargin}px)`,
});
const panelBody = <div className="panel-body">{renderChart()}</div>;
return (
<Styles
className="panel panel-default chart-container"
style={{ height: props.height }}
>
<div className="panel-heading" ref={panelHeadingRef}>
<div className="panel-heading" ref={headerRef}>
{header}
</div>
{props.vizType === 'filter_box' ? (

View File

@@ -23,6 +23,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { styled, t, supersetTheme, css } from '@superset-ui/core';
import { debounce } from 'lodash';
import { Resizable } from 're-resizable';
import { useDynamicPluginContext } from 'src/components/DynamicPlugins';
import { Global } from '@emotion/core';
@@ -81,10 +82,8 @@ const Styles = styled.div`
border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
.explore-column {
display: flex;
flex: 0 0 ${({ theme }) => theme.gridUnit * 95}px;
flex-direction: column;
padding: ${({ theme }) => 2 * theme.gridUnit}px 0;
max-width: ${({ theme }) => theme.gridUnit * 95}px;
max-height: 100%;
}
.data-source-selection {
@@ -404,7 +403,11 @@ function ExploreViewContainer(props) {
dashboardId={props.dashboardId}
/>
)}
<div
<Resizable
defaultSize={{ width: 300 }}
minWidth={300}
maxWidth="33%"
enable={{ right: true }}
className={
isCollapsed ? 'no-show' : 'explore-column data-source-selection'
}
@@ -430,7 +433,7 @@ function ExploreViewContainer(props) {
controls={props.controls}
actions={props.actions}
/>
</div>
</Resizable>
{isCollapsed ? (
<div
className="sidebar"
@@ -452,7 +455,13 @@ function ExploreViewContainer(props) {
<Icon name="dataset-physical" width={16} />
</div>
) : null}
<div className="col-sm-3 explore-column controls-column">
<Resizable
defaultSize={{ width: 320 }}
minWidth={320}
maxWidth="33%"
enable={{ right: true }}
className="col-sm-3 explore-column controls-column"
>
<QueryAndSaveBtns
canAdd={!!(props.can_add || props.can_overwrite)}
onQuery={onQuery}
@@ -470,7 +479,7 @@ function ExploreViewContainer(props) {
datasource_type={props.datasource_type}
isDatasourceMetaLoading={props.isDatasourceMetaLoading}
/>
</div>
</Resizable>
<div
className={`main-explore-content ${
isCollapsed ? 'col-sm-9' : 'col-sm-7'