feat: read control panel configs from registry (#8173)

* feat: read control panel configs from registry

* fix: order imports

* fix: remove index.js and get items on-the-fly, remove extraOverrides

* fix: lint

* fix: unit tests

* fix: unit tests

* fix: lint

* fix: unit tests
This commit is contained in:
Krist Wongsuphasawat 2019-09-11 08:58:24 -07:00 committed by GitHub
parent 30483cee31
commit c566141f25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 312 additions and 215 deletions

View File

@ -18,39 +18,91 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { t } from '@superset-ui/translation';
import { defaultControls } from 'src/explore/store';
import { getFormDataFromControls } from 'src/explore/controlUtils';
import { ControlPanelsContainer } from 'src/explore/components/ControlPanelsContainer';
import ControlPanelSection from 'src/explore/components/ControlPanelSection';
import * as featureFlags from 'src/featureFlags';
const defaultProps = {
datasource_type: 'table',
actions: {},
controls: defaultControls,
form_data: getFormDataFromControls(defaultControls),
isDatasourceMetaLoading: false,
exploreState: {},
};
describe('ControlPanelsContainer', () => {
let wrapper;
let scopedFilterOn = false;
const isFeatureEnabledMock = jest.spyOn(featureFlags, 'isFeatureEnabled')
.mockImplementation(() => scopedFilterOn);
beforeAll(() => {
getChartControlPanelRegistry().registerValue('table', {
controlPanelSections: [
{
label: t('GROUP BY'),
description: t('Use this section if you want a query that aggregates'),
expanded: true,
controlSetRows: [
['groupby'],
['metrics'],
['percent_metrics'],
['timeseries_limit_metric', 'row_limit'],
['include_time', 'order_desc'],
],
},
{
label: t('NOT GROUPED BY'),
description: t('Use this section if you want to query atomic rows'),
expanded: true,
controlSetRows: [
['all_columns'],
['order_by_cols'],
['row_limit', null],
],
},
{
label: t('Query'),
expanded: true,
controlSetRows: [
['adhoc_filters'],
],
},
{
label: t('Options'),
expanded: true,
controlSetRows: [
['table_timestamp_format'],
['page_length', null],
['include_search', 'table_filter'],
['align_pn', 'color_pn'],
],
},
],
});
});
afterAll(() => {
getChartControlPanelRegistry().remove('table');
isFeatureEnabledMock.mockRestore();
});
function getDefaultProps() {
return {
datasource_type: 'table',
actions: {},
controls: defaultControls,
// Note: default viz_type is table
form_data: getFormDataFromControls(defaultControls),
isDatasourceMetaLoading: false,
exploreState: {},
};
}
it('renders ControlPanelSections', () => {
wrapper = shallow(<ControlPanelsContainer {...defaultProps} />);
wrapper = shallow(<ControlPanelsContainer {...getDefaultProps()} />);
expect(wrapper.find(ControlPanelSection)).toHaveLength(6);
});
it('renders filter panel when SCOPED_FILTER flag is on', () => {
scopedFilterOn = true;
wrapper = shallow(<ControlPanelsContainer {...defaultProps} />);
wrapper = shallow(<ControlPanelsContainer {...getDefaultProps()} />);
expect(wrapper.find(ControlPanelSection)).toHaveLength(7);
});
});

View File

@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { t } from '@superset-ui/translation';
import {
getControlConfig,
@ -36,27 +37,73 @@ describe('controlUtils', () => {
},
};
beforeAll(() => {
getChartControlPanelRegistry().registerValue('test-chart', {
requiresTime: true,
controlPanelSections: [
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
['color_scheme', {
name: 'rose_area_proportion',
config: {
type: 'CheckboxControl',
label: t('Use Area Proportions'),
description: t(
'Check if the Rose Chart should use segment area instead of ' +
'segment radius for proportioning',
),
default: false,
renderTrigger: true,
},
}],
],
},
],
}).registerValue('test-chart-override', {
requiresTime: true,
controlPanelSections: [
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
['color_scheme'],
],
},
],
controlOverrides: {
color_scheme: {
label: t('My beautiful colors'),
},
},
});
});
afterAll(() => {
getChartControlPanelRegistry()
.remove('test-chart')
.remove('test-chart-override');
});
describe('getControlConfig', () => {
it('returns a valid spatial controlConfig', () => {
const spatialControl = getControlConfig('spatial', 'deck_grid');
expect(spatialControl.type).toEqual('SpatialControl');
expect(spatialControl.validators).toHaveLength(1);
const spatialControl = getControlConfig('color_scheme', 'test-chart');
expect(spatialControl.type).toEqual('ColorSchemeControl');
});
it('overrides according to vizType', () => {
let control = getControlConfig('metric', 'line');
expect(control.type).toEqual('MetricsControl');
expect(control.validators).toHaveLength(1);
let control = getControlConfig('color_scheme', 'test-chart');
expect(control.label).toEqual('Color Scheme');
// deck_polygon overrides and removes validators
control = getControlConfig('metric', 'deck_polygon');
expect(control.type).toEqual('MetricsControl');
expect(control.validators).toHaveLength(0);
control = getControlConfig('color_scheme', 'test-chart-override');
expect(control.label).toEqual('My beautiful colors');
});
it('returns correct control config when control config is defined ' +
'in the control panel definition', () => {
const roseAreaProportionControlConfig = getControlConfig('rose_area_proportion', 'rose');
const roseAreaProportionControlConfig = getControlConfig('rose_area_proportion', 'test-chart');
expect(roseAreaProportionControlConfig).toEqual({
type: 'CheckboxControl',
label: t('Use Area Proportions'),

View File

@ -16,9 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { applyDefaultFormData } from '../../../src/explore/store';
describe('store', () => {
beforeAll(() => {
getChartControlPanelRegistry().registerValue('test-chart', {
controlPanelSections: [
{
label: 'Test section',
expanded: true,
controlSetRows: [['row_limit']],
},
],
});
});
afterAll(() => {
getChartControlPanelRegistry().remove('test-chart');
});
describe('applyDefaultFormData', () => {
@ -29,7 +45,7 @@ describe('store', () => {
it('applies default to formData if the key is missing', () => {
const inputFormData = {
datasource: '11_table',
viz_type: 'table',
viz_type: 'test-chart',
};
let outputFormData = applyDefaultFormData(inputFormData);
expect(outputFormData.row_limit).toEqual(10000);
@ -45,7 +61,7 @@ describe('store', () => {
it('keeps null if key is defined with null', () => {
const inputFormData = {
datasource: '11_table',
viz_type: 'table',
viz_type: 'test-chart',
row_limit: null,
};
const outputFormData = applyDefaultFormData(inputFormData);

View File

@ -24,12 +24,13 @@ import { connect } from 'react-redux';
import { Alert, Tab, Tabs } from 'react-bootstrap';
import { isPlainObject } from 'lodash';
import { t } from '@superset-ui/translation';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import controlPanelConfigs, { sectionsToRender } from '../controlPanels';
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';
const propTypes = {
@ -62,8 +63,9 @@ class ControlPanelsContainer extends React.Component {
let mapF = controlConfigs[controlName].mapStateToProps;
// Looking to find mapStateToProps override for this viz type
const config = controlPanelConfigs[this.props.controls.viz_type.value] || {};
const controlOverrides = config.controlOverrides || {};
const controlPanelConfig = getChartControlPanelRegistry()
.get(this.props.controls.viz_type.value) || {};
const controlOverrides = controlPanelConfig.controlOverrides || {};
if (controlOverrides[controlName] && controlOverrides[controlName].mapStateToProps) {
mapF = controlOverrides[controlName].mapStateToProps;
}
@ -86,8 +88,9 @@ class ControlPanelsContainer extends React.Component {
const { actions, controls, exploreState, form_data: formData } = this.props;
// Looking to find mapStateToProps override for this viz type
const controlPanelConfig = controlPanelConfigs[controls.viz_type.value].controlOverrides || {};
const overrides = controlPanelConfig[name];
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)

View File

@ -1,153 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* This file defines how controls (defined in controls.js) are structured into sections
* and associated with each and every visualization type.
*/
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import * as sections from './sections';
import extraOverrides from './extraOverrides';
import Area from './Area';
import Bar from './Bar';
import BigNumber from './BigNumber';
import BigNumberTotal from './BigNumberTotal';
import BoxPlot from './BoxPlot';
import Bubble from './Bubble';
import Bullet from './Bullet';
import CalHeatmap from './CalHeatmap';
import Chord from './Chord';
import Compare from './Compare';
import CountryMap from './CountryMap';
import DirectedForce from './DirectedForce';
import DistBar from './DistBar';
import DualLine from './DualLine';
import EventFlow from './EventFlow';
import FilterBox from './FilterBox';
import Heatmap from './Heatmap';
import Histogram from './Histogram';
import Horizon from './Horizon';
import Iframe from './Iframe';
import Line from './Line';
import LineMulti from './LineMulti';
import Mapbox from './Mapbox';
import Markup from './Markup';
import PairedTtest from './PairedTtest';
import Para from './Para';
import Partition from './Partition';
import Pie from './Pie';
import PivotTable from './PivotTable';
import Rose from './Rose';
import Sankey from './Sankey';
import Sunburst from './Sunburst';
import Separator from './Separator';
import Table from './Table';
import TimePivot from './TimePivot';
import TimeTable from './TimeTable';
import Treemap from './Treemap';
import WordCloud from './WordCloud';
import WorldMap from './WorldMap';
import DeckArc from './DeckArc';
import DeckGeojson from './DeckGeojson';
import DeckGrid from './DeckGrid';
import DeckHex from './DeckHex';
import DeckMulti from './DeckMulti';
import DeckPath from './DeckPath';
import DeckPolygon from './DeckPolygon';
import DeckScatter from './DeckScatter';
import DeckScreengrid from './DeckScreengrid';
export const controlPanelConfigs = extraOverrides({
area: Area,
bar: Bar,
big_number: BigNumber,
big_number_total: BigNumberTotal,
box_plot: BoxPlot,
bubble: Bubble,
bullet: Bullet,
cal_heatmap: CalHeatmap,
chord: Chord,
compare: Compare,
country_map: CountryMap,
directed_force: DirectedForce,
dist_bar: DistBar,
dual_line: DualLine,
event_flow: EventFlow,
filter_box: FilterBox,
heatmap: Heatmap,
histogram: Histogram,
horizon: Horizon,
iframe: Iframe,
line: Line,
line_multi: LineMulti,
mapbox: Mapbox,
markup: Markup,
paired_ttest: PairedTtest,
para: Para,
partition: Partition,
pie: Pie,
pivot_table: PivotTable,
rose: Rose,
sankey: Sankey,
separator: Separator,
sunburst: Sunburst,
table: Table,
time_pivot: TimePivot,
time_table: TimeTable,
treemap: Treemap,
word_cloud: WordCloud,
world_map: WorldMap,
deck_arc: DeckArc,
deck_geojson: DeckGeojson,
deck_grid: DeckGrid,
deck_hex: DeckHex,
deck_multi: DeckMulti,
deck_path: DeckPath,
deck_polygon: DeckPolygon,
deck_scatter: DeckScatter,
deck_screengrid: DeckScreengrid,
});
export default controlPanelConfigs;
export function sectionsToRender(vizType, datasourceType) {
const { sectionOverrides = {}, controlPanelSections = [] } = controlPanelConfigs[vizType] || {};
const sectionsCopy = { ...sections };
Object.entries(sectionOverrides).forEach(([section, overrides]) => {
if (typeof overrides === 'object' && overrides.constructor === Object) {
sectionsCopy[section] = {
...sectionsCopy[section],
...overrides,
};
} else {
sectionsCopy[section] = overrides;
}
});
const { datasourceAndVizType, sqlaTimeSeries, druidTimeSeries, filters } = sectionsCopy;
return [].concat(
datasourceAndVizType,
datasourceType === 'table' ? sqlaTimeSeries : druidTimeSeries,
isFeatureEnabled(FeatureFlag.SCOPED_FILTER) ? filters : undefined,
controlPanelSections,
).filter(section => section);
}

View File

@ -16,8 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import controlPanelConfigs, { sectionsToRender } from './controlPanels';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import controls from './controls';
import * as sections from './controlPanels/sections';
export function getFormDataFromControls(controlsState) {
const formData = {};
@ -51,11 +53,11 @@ function isGlobalControl(controlKey) {
export function getControlConfig(controlKey, vizType) {
// Gets the control definition, applies overrides, and executes
// the mapStatetoProps
const vizConf = controlPanelConfigs[vizType] || {};
const controlOverrides = vizConf.controlOverrides || {};
const controlPanelConfig = getChartControlPanelRegistry().get(vizType) || {};
const { controlOverrides = {}, controlPanelSections = [] } = controlPanelConfig;
if (!isGlobalControl(controlKey)) {
for (const section of vizConf.controlPanelSections) {
for (const section of controlPanelSections) {
for (const controlArr of section.controlSetRows) {
for (const control of controlArr) {
if (control != null && typeof control === 'object') {
@ -131,6 +133,33 @@ export function getControlState(controlKey, vizType, state, value) {
return validateControl(controlState);
}
export function sectionsToRender(vizType, datasourceType) {
const controlPanelConfig = getChartControlPanelRegistry().get(vizType) || {};
const { sectionOverrides = {}, controlPanelSections = [] } = controlPanelConfig;
const sectionsCopy = { ...sections };
Object.entries(sectionOverrides).forEach(([section, overrides]) => {
if (typeof overrides === 'object' && overrides.constructor === Object) {
sectionsCopy[section] = {
...sectionsCopy[section],
...overrides,
};
} else {
sectionsCopy[section] = overrides;
}
});
const { datasourceAndVizType, sqlaTimeSeries, druidTimeSeries, filters } = sectionsCopy;
return [].concat(
datasourceAndVizType,
datasourceType === 'table' ? sqlaTimeSeries : druidTimeSeries,
isFeatureEnabled(FeatureFlag.SCOPED_FILTER) ? filters : undefined,
controlPanelSections,
).filter(section => section);
}
export function getAllControlsState(vizType, datasourceType, state, formData) {
const controlsState = {};
sectionsToRender(vizType, datasourceType).forEach(

View File

@ -17,12 +17,12 @@
* under the License.
*/
/* eslint camelcase: 0 */
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import {
getAllControlsState,
getFormDataFromControls,
} from './controlUtils';
import controls from './controls';
import controlPanelConfigs from './controlPanels';
function handleDeprecatedControls(formData) {
// Reacffectation / handling of deprecated controls
@ -56,9 +56,9 @@ export function getControlsState(state, inputFormData) {
formData,
);
const viz = controlPanelConfigs[vizType] || {};
if (viz.onInit) {
return viz.onInit(controlsState);
const controlPanelConfig = getChartControlPanelRegistry().get(vizType) || {};
if (controlPanelConfig.onInit) {
return controlPanelConfig.onInit(controlsState);
}
return controlsState;

View File

@ -1,23 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import MainPreset from '../visualizations/presets/MainPreset';
export default function setupPlugins() {
new MainPreset().register();
}

View File

@ -0,0 +1,127 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import MainPreset from '../visualizations/presets/MainPreset';
import setupPluginsExtra from './setupPluginsExtra';
import Area from '../explore/controlPanels/Area';
import Bar from '../explore/controlPanels/Bar';
import BigNumber from '../explore/controlPanels/BigNumber';
import BigNumberTotal from '../explore/controlPanels/BigNumberTotal';
import BoxPlot from '../explore/controlPanels/BoxPlot';
import Bubble from '../explore/controlPanels/Bubble';
import Bullet from '../explore/controlPanels/Bullet';
import CalHeatmap from '../explore/controlPanels/CalHeatmap';
import Chord from '../explore/controlPanels/Chord';
import Compare from '../explore/controlPanels/Compare';
import CountryMap from '../explore/controlPanels/CountryMap';
import DeckArc from '../explore/controlPanels/DeckArc';
import DeckGeojson from '../explore/controlPanels/DeckGeojson';
import DeckGrid from '../explore/controlPanels/DeckGrid';
import DeckHex from '../explore/controlPanels/DeckHex';
import DeckMulti from '../explore/controlPanels/DeckMulti';
import DeckPath from '../explore/controlPanels/DeckPath';
import DeckPolygon from '../explore/controlPanels/DeckPolygon';
import DeckScatter from '../explore/controlPanels/DeckScatter';
import DeckScreengrid from '../explore/controlPanels/DeckScreengrid';
import DirectedForce from '../explore/controlPanels/DirectedForce';
import DistBar from '../explore/controlPanels/DistBar';
import DualLine from '../explore/controlPanels/DualLine';
import EventFlow from '../explore/controlPanels/EventFlow';
import FilterBox from '../explore/controlPanels/FilterBox';
import Heatmap from '../explore/controlPanels/Heatmap';
import Histogram from '../explore/controlPanels/Histogram';
import Horizon from '../explore/controlPanels/Horizon';
import Iframe from '../explore/controlPanels/Iframe';
import Line from '../explore/controlPanels/Line';
import LineMulti from '../explore/controlPanels/LineMulti';
import Mapbox from '../explore/controlPanels/Mapbox';
import Markup from '../explore/controlPanels/Markup';
import PairedTtest from '../explore/controlPanels/PairedTtest';
import Para from '../explore/controlPanels/Para';
import Partition from '../explore/controlPanels/Partition';
import Pie from '../explore/controlPanels/Pie';
import PivotTable from '../explore/controlPanels/PivotTable';
import Rose from '../explore/controlPanels/Rose';
import Sankey from '../explore/controlPanels/Sankey';
import Separator from '../explore/controlPanels/Separator';
import Sunburst from '../explore/controlPanels/Sunburst';
import Table from '../explore/controlPanels/Table';
import TimePivot from '../explore/controlPanels/TimePivot';
import TimeTable from '../explore/controlPanels/TimeTable';
import Treemap from '../explore/controlPanels/Treemap';
import WordCloud from '../explore/controlPanels/WordCloud';
import WorldMap from '../explore/controlPanels/WorldMap';
export default function setupPlugins() {
new MainPreset().register();
// TODO: Remove these shims once the control panel configs are moved into the plugin package.
getChartControlPanelRegistry()
.registerValue('area', Area)
.registerValue('bar', Bar)
.registerValue('big_number', BigNumber)
.registerValue('big_number_total', BigNumberTotal)
.registerValue('box_plot', BoxPlot)
.registerValue('bubble', Bubble)
.registerValue('bullet', Bullet)
.registerValue('cal_heatmap', CalHeatmap)
.registerValue('chord', Chord)
.registerValue('compare', Compare)
.registerValue('country_map', CountryMap)
.registerValue('directed_force', DirectedForce)
.registerValue('dist_bar', DistBar)
.registerValue('dual_line', DualLine)
.registerValue('event_flow', EventFlow)
.registerValue('filter_box', FilterBox)
.registerValue('heatmap', Heatmap)
.registerValue('histogram', Histogram)
.registerValue('horizon', Horizon)
.registerValue('iframe', Iframe)
.registerValue('line', Line)
.registerValue('line_multi', LineMulti)
.registerValue('mapbox', Mapbox)
.registerValue('markup', Markup)
.registerValue('paired_ttest', PairedTtest)
.registerValue('para', Para)
.registerValue('partition', Partition)
.registerValue('pie', Pie)
.registerValue('pivot_table', PivotTable)
.registerValue('rose', Rose)
.registerValue('sankey', Sankey)
.registerValue('separator', Separator)
.registerValue('sunburst', Sunburst)
.registerValue('table', Table)
.registerValue('time_pivot', TimePivot)
.registerValue('time_table', TimeTable)
.registerValue('treemap', Treemap)
.registerValue('word_cloud', WordCloud)
.registerValue('world_map', WorldMap)
.registerValue('deck_arc', DeckArc)
.registerValue('deck_geojson', DeckGeojson)
.registerValue('deck_grid', DeckGrid)
.registerValue('deck_hex', DeckHex)
.registerValue('deck_multi', DeckMulti)
.registerValue('deck_path', DeckPath)
.registerValue('deck_polygon', DeckPolygon)
.registerValue('deck_scatter', DeckScatter)
.registerValue('deck_screengrid', DeckScreengrid);
setupPluginsExtra();
}

View File

@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
// For individual deployments to add custom overrides
export default function extraOverrides(controlPanelConfigs) {
return controlPanelConfigs;
}
export default function setupPluginsExtra() {}