mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
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:
@@ -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}
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
)
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user