feat: Results table on Explore view (#11854)

* Upgrade react-split

* Implement split on ExploreChartPanel

* Implement tabs with collapse

* Fix run query button

* Fix copy to clipboard button

* Move table controls to separate file

* Make component more generic to handle samples

* Remove samples from DisplayQueryButton

* Move data tables to separate file

* Make split dynamically controlled

* Fix unit test

* Fix arrow not centered

* fixup! Fix arrow not centered

* Change copy button paddings

* Use translations

* Fix grammar in a comment

* Fix imports

* Use grid units

* Convert new files to typescript

* Fix after rebase

* Remove forceRender

* Fix big_number test

* Delay making request until panel is opened

* White background in south panel

* fixup! White background in south panel

* Lint fix

* Lint fix

* Remove redundant prop types

* Remove console log

* Delay loading samples until user switches tab

* Add debounce to filter input

* Use gridUnit for gutter sizes

* Change types object to Record<string, any>
This commit is contained in:
Kamil Gabryjelski
2020-12-05 04:49:24 +01:00
committed by GitHub
parent 66cd565bff
commit 41738df77d
9 changed files with 569 additions and 216 deletions

View File

@@ -16,13 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import Split from 'react-split';
import { ParentSize } from '@vx/responsive';
import { styled } from '@superset-ui/core';
import { chartPropShape } from '../../dashboard/util/propShapes';
import ChartContainer from '../../chart/ChartContainer';
import { styled, useTheme } from '@superset-ui/core';
import debounce from 'lodash/debounce';
import { chartPropShape } from 'src/dashboard/util/propShapes';
import ChartContainer from 'src/chart/ChartContainer';
import ConnectedExploreChartHeader from './ExploreChartHeader';
import { DataTablesPane } from './DataTablesPane';
const propTypes = {
actions: PropTypes.object.isRequired,
@@ -49,22 +52,142 @@ const propTypes = {
triggerRender: PropTypes.bool,
};
const GUTTER_SIZE_FACTOR = 1.25;
const CHART_PANEL_PADDING = 30;
const INITIAL_SIZES = [90, 10];
const MIN_SIZES = [300, 50];
const DEFAULT_SOUTH_PANE_HEIGHT_PERCENT = 40;
const Styles = styled.div`
background-color: ${({ theme }) => theme.colors.grayscale.light5};
height: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
align-content: stretch;
overflow: hidden;
& > div:last-of-type {
flex-basis: 100%;
}
.gutter {
border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
width: ${({ theme }) => theme.gridUnit * 9}px;
margin: ${({ theme }) => theme.gridUnit * GUTTER_SIZE_FACTOR}px auto;
}
.gutter.gutter-vertical {
cursor: row-resize;
}
.ant-collapse {
height: 100%;
background-color: ${({ theme }) => theme.colors.grayscale.light5};
.ant-collapse-item {
height: 100%;
border: 0;
}
.ant-collapse-content,
.ant-collapse-content-box {
height: 100%;
}
.ant-collapse-header {
background-color: ${({ theme }) => theme.colors.grayscale.light5};
padding-top: 0;
padding-bottom: 0;
font-weight: ${({ theme }) => theme.typography.weights.bold};
& > .ant-collapse-arrow {
top: 5px; // not a theme variable, override necessary after setting paddings to 0 to center arrow
}
}
.ant-tabs {
height: 100%;
.ant-tabs-nav {
padding-left: ${({ theme }) => theme.gridUnit * 5}px;
margin: 0;
}
.ant-tabs-content-holder {
overflow: hidden;
.ant-tabs-content {
height: 100%;
}
}
}
}
`;
class ExploreChartPanel extends React.PureComponent {
renderChart() {
const { chart } = this.props;
const headerHeight = this.props.standalone ? 0 : 80;
const ExploreChartPanel = props => {
const theme = useTheme();
const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR;
const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR;
const panelHeadingRef = useRef(null);
const [headerHeight, setHeaderHeight] = useState(props.standalone ? 0 : 50);
const [splitSizes, setSplitSizes] = useState(INITIAL_SIZES);
const calcSectionHeight = percent => {
const containerHeight = parseInt(props.height, 10) - headerHeight - 30;
return (
(containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin)
);
};
const [chartSectionHeight, setChartSectionHeight] = useState(
calcSectionHeight(INITIAL_SIZES[0]) - CHART_PANEL_PADDING,
);
const [tableSectionHeight, setTableSectionHeight] = useState(
calcSectionHeight(INITIAL_SIZES[1]),
);
const [displaySouthPaneBackground, setDisplaySouthPaneBackground] = useState(
false,
);
useEffect(() => {
const calcHeaderSize = debounce(() => {
setHeaderHeight(
props.standalone ? 0 : panelHeadingRef?.current?.offsetHeight,
);
}, 100);
calcHeaderSize();
document.addEventListener('resize', calcHeaderSize);
return () => document.removeEventListener('resize', calcHeaderSize);
}, [props.standalone]);
const recalcPanelSizes = ([northPercent, southPercent]) => {
setChartSectionHeight(
calcSectionHeight(northPercent) - CHART_PANEL_PADDING,
);
setTableSectionHeight(calcSectionHeight(southPercent));
};
const onDragStart = () => {
setDisplaySouthPaneBackground(true);
};
const onDragEnd = sizes => {
recalcPanelSizes(sizes);
setDisplaySouthPaneBackground(false);
};
const onCollapseChange = openPanelName => {
let splitSizes;
if (!openPanelName) {
splitSizes = INITIAL_SIZES;
} else {
splitSizes = [
100 - DEFAULT_SOUTH_PANE_HEIGHT_PERCENT,
DEFAULT_SOUTH_PANE_HEIGHT_PERCENT,
];
}
setSplitSizes(splitSizes);
recalcPanelSizes(splitSizes);
};
const renderChart = () => {
const { chart } = props;
return (
<ParentSize>
@@ -73,67 +196,89 @@ class ExploreChartPanel extends React.PureComponent {
height > 0 && (
<ChartContainer
width={Math.floor(width)}
height={parseInt(this.props.height, 10) - headerHeight}
height={chartSectionHeight}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartStackTrace={chart.chartStackTrace}
chartId={chart.id}
chartStatus={chart.chartStatus}
triggerRender={this.props.triggerRender}
datasource={this.props.datasource}
errorMessage={this.props.errorMessage}
formData={this.props.form_data}
onQuery={this.props.onQuery}
owners={this.props?.slice?.owners}
triggerRender={props.triggerRender}
datasource={props.datasource}
errorMessage={props.errorMessage}
formData={props.form_data}
onQuery={props.onQuery}
owners={props?.slice?.owners}
queryResponse={chart.queryResponse}
refreshOverlayVisible={this.props.refreshOverlayVisible}
setControlValue={this.props.actions.setControlValue}
timeout={this.props.timeout}
refreshOverlayVisible={props.refreshOverlayVisible}
setControlValue={props.actions.setControlValue}
timeout={props.timeout}
triggerQuery={chart.triggerQuery}
vizType={this.props.vizType}
vizType={props.vizType}
/>
)
}
</ParentSize>
);
}
};
render() {
if (this.props.standalone) {
// dom manipulation hack to get rid of the boostrap theme's body background
const standaloneClass = 'background-transparent';
const bodyClasses = document.body.className.split(' ');
if (bodyClasses.indexOf(standaloneClass) === -1) {
document.body.className += ` ${standaloneClass}`;
}
return this.renderChart();
if (props.standalone) {
// dom manipulation hack to get rid of the boostrap theme's body background
const standaloneClass = 'background-transparent';
const bodyClasses = document.body.className.split(' ');
if (!bodyClasses.includes(standaloneClass)) {
document.body.className += ` ${standaloneClass}`;
}
const header = (
<ConnectedExploreChartHeader
actions={this.props.actions}
addHistory={this.props.addHistory}
can_overwrite={this.props.can_overwrite}
can_download={this.props.can_download}
chartHeight={this.props.height}
isStarred={this.props.isStarred}
slice={this.props.slice}
sliceName={this.props.sliceName}
table_name={this.props.table_name}
form_data={this.props.form_data}
timeout={this.props.timeout}
chart={this.props.chart}
/>
);
return (
<Styles className="panel panel-default chart-container">
<div className="panel-heading">{header}</div>
<div className="panel-body">{this.renderChart()}</div>
</Styles>
);
return renderChart();
}
}
const header = (
<ConnectedExploreChartHeader
actions={props.actions}
addHistory={props.addHistory}
can_overwrite={props.can_overwrite}
can_download={props.can_download}
chartHeight={props.height}
isStarred={props.isStarred}
slice={props.slice}
sliceName={props.sliceName}
table_name={props.table_name}
form_data={props.form_data}
timeout={props.timeout}
chart={props.chart}
/>
);
const elementStyle = (dimension, elementSize, gutterSize) => {
return {
[dimension]: `calc(${elementSize}% - ${gutterSize + gutterMargin}px)`,
};
};
return (
<Styles className="panel panel-default chart-container">
<div className="panel-heading" ref={panelHeadingRef}>
{header}
</div>
<Split
sizes={splitSizes}
minSize={MIN_SIZES}
direction="vertical"
gutterSize={gutterHeight}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
elementStyle={elementStyle}
>
<div className="panel-body">{renderChart()}</div>
<DataTablesPane
queryFormData={props.chart.latestQueryFormData}
tableSectionHeight={tableSectionHeight}
onCollapseChange={onCollapseChange}
displayBackground={displaySouthPaneBackground}
/>
</Split>
</Styles>
);
};
ExploreChartPanel.propTypes = propTypes;