feat(viz): add query mode switch to table chart (#10113)

1, Replace table chart rendering from jquery.DataTables to react-table: apache-superset/superset-ui#623
2. Rearrange the control panel, replace GROUP BY and NOT GROUP BY with a "Query Mode" switch: apache-superset/superset-ui#609
This commit is contained in:
Jesse Yang
2020-06-28 21:37:04 -07:00
committed by GitHub
parent 3414f35792
commit 9bdfa055ac
34 changed files with 5549 additions and 9234 deletions

View File

@@ -27,7 +27,10 @@ const controlTypes = Object.keys(controlMap);
const propTypes = {
actions: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.oneOf(controlTypes).isRequired,
type: PropTypes.oneOfType([
PropTypes.oneOf(controlTypes).isRequired,
PropTypes.func.isRequired,
]),
hidden: PropTypes.bool,
label: PropTypes.string.isRequired,
choices: PropTypes.oneOfType([
@@ -62,6 +65,8 @@ export default class Control extends React.PureComponent {
super(props);
this.state = { hovered: false };
this.onChange = this.onChange.bind(this);
this.onMouseEnter = this.setHover.bind(this, true);
this.onMouseLeave = this.setHover.bind(this, false);
}
onChange(value, errors) {
this.props.actions.setControlValue(this.props.name, value, errors);
@@ -70,18 +75,18 @@ export default class Control extends React.PureComponent {
this.setState({ hovered });
}
render() {
if (!this.props.type) return null; // this catches things like <hr/> elements (not a control!) shoved into the control panel configs.
const ControlType = controlMap[this.props.type];
const divStyle = this.props.hidden ? { display: 'none' } : null;
const { type, hidden } = this.props;
if (!type) return null;
const ControlComponent = typeof type === 'string' ? controlMap[type] : type;
return (
<div
className="Control"
data-test={this.props.name}
style={divStyle}
onMouseEnter={this.setHover.bind(this, true)}
onMouseLeave={this.setHover.bind(this, false)}
style={hidden ? { display: 'none' } : undefined}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<ControlType
<ControlComponent
onChange={this.onChange}
hovered={this.state.hovered}
{...this.props}

View File

@@ -25,11 +25,11 @@ import { Alert, Tab, Tabs } from 'react-bootstrap';
import { isPlainObject } from 'lodash';
import { t } from '@superset-ui/translation';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { sharedControls } from '@superset-ui/chart-controls';
import ControlPanelSection from './ControlPanelSection';
import ControlRow from './ControlRow';
import Control from './Control';
import controlConfigs from '../controls';
import { sectionsToRender } from '../controlUtils';
import * as exploreActions from '../actions/exploreActions';
@@ -47,42 +47,11 @@ class ControlPanelsContainer extends React.Component {
constructor(props) {
super(props);
this.getControlData = this.getControlData.bind(this);
this.removeAlert = this.removeAlert.bind(this);
this.renderControl = this.renderControl.bind(this);
this.renderControlPanelSection = this.renderControlPanelSection.bind(this);
}
getControlData(controlName) {
if (React.isValidElement(controlName)) {
return controlName;
}
const control = this.props.controls[controlName];
// Identifying mapStateToProps function to apply (logic can't be in store)
let mapF = controlConfigs[controlName].mapStateToProps;
// Looking to find mapStateToProps override for this viz type
const controlPanelConfig =
getChartControlPanelRegistry().get(this.props.controls.viz_type.value) ||
{};
const controlOverrides = controlPanelConfig.controlOverrides || {};
if (
controlOverrides[controlName] &&
controlOverrides[controlName].mapStateToProps
) {
mapF = controlOverrides[controlName].mapStateToProps;
}
// Applying mapStateToProps if needed
if (mapF) {
return {
...control,
...mapF(this.props.exploreState, control, this.props.actions),
};
}
return control;
}
sectionsToRender() {
return sectionsToRender(
this.props.form_data.viz_type,
@@ -94,48 +63,45 @@ class ControlPanelsContainer extends React.Component {
this.props.actions.removeControlPanelAlert();
}
renderControl(name, config, lookupControlData) {
renderControl({ name, config }) {
const { actions, controls, exploreState, form_data: formData } = this.props;
const { visibility } = config;
// if visibility check says the config is not visible, don't render it
if (visibility && !visibility.call(config, this.props)) {
return null;
}
// Looking to find mapStateToProps override for this viz type
const controlPanelConfig =
getChartControlPanelRegistry().get(controls.viz_type.value) || {};
const controlOverrides = controlPanelConfig.controlOverrides || {};
const overrides = controlOverrides[name];
// Identifying mapStateToProps function to apply (logic can't be in store)
const mapFn =
overrides && overrides.mapStateToProps
? overrides.mapStateToProps
: config.mapStateToProps;
// If the control item is not an object, we have to look up the control data from
// the centralized controls file.
// When it is an object we read control data straight from `config` instead
const controlData = lookupControlData ? controls[name] : config;
const controlData = {
...controls[name],
...config,
name,
// apply current value in formData
value: formData[name],
};
const { mapStateToProps: mapFn } = controlData;
if (mapFn) {
Object.assign(
controlData,
mapFn(exploreState, controlData, actions) || {},
);
}
const {
validationErrors,
provideFormDataToProps,
...restProps
} = controlData;
// Applying mapStateToProps if needed
const additionalProps = mapFn
? { ...controlData, ...mapFn(exploreState, controlData, actions) }
: controlData;
const { validationErrors, provideFormDataToProps } = controlData;
// if visibility check says the config is not visible, don't render it
if (visibility && !visibility.call(config, this.props, controlData)) {
return null;
}
return (
<Control
name={name}
key={`control-${name}`}
value={formData[name]}
validationErrors={validationErrors}
actions={actions}
formData={provideFormDataToProps ? formData : null}
{...additionalProps}
{...restProps}
/>
);
}
@@ -160,54 +126,51 @@ class ControlPanelsContainer extends React.Component {
hasErrors={hasErrors}
description={section.description}
>
{section.controlSetRows.map((controlSets, i) => (
<ControlRow
key={`controlsetrow-${i}`}
className="control-row"
controls={controlSets.map(controlItem => {
{section.controlSetRows.map((controlSets, i) => {
const renderedControls = controlSets
.map(controlItem => {
if (!controlItem) {
// When the item is invalid
return null;
} else if (React.isValidElement(controlItem)) {
// When the item is a React element
return controlItem;
} else if (
isPlainObject(controlItem) &&
controlItem.name &&
controlItem.config
) {
const { name, config } = controlItem;
return this.renderControl(name, config, false);
} else if (controls[controlItem]) {
// When the item is string name, meaning the control config
// is not specified directly. Have to look up the config from
// centralized configs.
const name = controlItem;
return this.renderControl(name, controlConfigs[name], true);
} else if (controlItem.name && controlItem.config) {
return this.renderControl(controlItem);
}
return null;
})}
/>
))}
})
.filter(x => x !== null);
// don't show the row if it is empty
if (renderedControls.length === 0) {
return null;
}
return (
<ControlRow
key={`controlsetrow-${i}`}
className="control-row"
controls={renderedControls}
/>
);
})}
</ControlPanelSection>
);
}
render() {
const allSectionsToRender = this.sectionsToRender();
const querySectionsToRender = [];
const displaySectionsToRender = [];
allSectionsToRender.forEach(section => {
// if at least one control in the secion is not `renderTrigger`
this.sectionsToRender().forEach(section => {
// if at least one control in the section is not `renderTrigger`
// or asks to be displayed at the Data tab
if (
section.tabOverride === 'data' ||
section.controlSetRows.some(rows =>
rows.some(
control =>
controlConfigs[control] &&
(!controlConfigs[control].renderTrigger ||
controlConfigs[control].tabOverride === 'data'),
control &&
control.config &&
(!control.config.renderTrigger ||
control.config.tabOverride === 'data'),
),
)
) {