mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
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:
committed by
GitHub
parent
66cd565bff
commit
41738df77d
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user