diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/package.json b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/package.json
index 251d0388bc7ed..705daf72f1521 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/package.json
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/package.json
@@ -27,6 +27,7 @@
},
"peerDependencies": {
"@superset-ui/color": "^0.13.3",
+ "@superset-ui/query": "^0.13.27",
"@superset-ui/translation": "^0.13",
"@superset-ui/validator": "^0.13",
"react": "^16.13.1"
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/ColumnOption.tsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/ColumnOption.tsx
index f39763e85b864..158e70309bbe3 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/ColumnOption.tsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/ColumnOption.tsx
@@ -16,25 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-/* eslint-disable camelcase */
import React from 'react';
import { ColumnTypeLabel } from './ColumnTypeLabel';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
-
-export type ColumnOptionColumn = {
- column_name: string;
- groupby?: string;
- verbose_name?: string;
- description?: string;
- expression?: string;
- is_dttm?: boolean;
- type?: string;
- filterable?: boolean;
-};
+import { ColumnMeta } from './types';
export type ColumnOptionProps = {
- column: ColumnOptionColumn;
+ column: ColumnMeta;
showType?: boolean;
};
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/index.ts
index 22726d7cb9162..2c8fe383f5e5f 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/index.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/index.ts
@@ -8,8 +8,10 @@ export const internalSharedControls = sharedControlsModule;
export const sections = sectionModules;
export { D3_FORMAT_DOCS, D3_FORMAT_OPTIONS, D3_TIME_FORMAT_OPTIONS } from './D3Formatting';
export { formatSelectOptions, formatSelectOptionsForRange } from './selectOptions';
+
export * from './InfoTooltipWithTrigger';
export * from './ColumnOption';
export * from './ColumnTypeLabel';
export * from './mainMetric';
export * from './MetricOption';
+export * from './types';
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/shared-controls.tsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/shared-controls.tsx
index 526ec7d999ea6..f4784c2cc6e8b 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/shared-controls.tsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/shared-controls.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -17,8 +18,6 @@
* under the License.
*/
-/* eslint-disable camelcase */
-
/**
* This file exports all controls available for use in chart plugins internal to Superset.
* It is not recommended to use the controls here for any third-party plugins.
@@ -32,33 +31,7 @@
*
* While the keys defined in the control itself get passed to the controlType as props,
* here's a list of the keys that are common to all controls, and as a result define the
- * control interface:
- *
- * - type: the control type, referencing a React component of the same name
- * - label: the label as shown in the control's header
- * - description: shown in the info tooltip of the control's header
- * - default: the default value when opening a new chart, or changing visualization type
- * - renderTrigger: a bool that defines whether the visualization should be re-rendered
- when changed. This should `true` for controls that only affect the rendering (client side)
- and don't affect the query or backend data processing as those require to re run a query
- and fetch the data
- * - validators: an array of functions that will receive the value of the component and
- should return error messages when the value is not valid. The error message gets
- bubbled up to the control header, section header and query panel header.
- * - warning: text shown as a tooltip on a warning icon in the control's header
- * - error: text shown as a tooltip on a error icon in the control's header
- * - mapStateToProps: a function that receives the App's state and return an object of k/v
- to overwrite configuration at runtime. This is useful to alter a component based on
- anything external to it, like another control's value. For instance it's possible to
- show a warning based on the value of another component. It's also possible to bind
- arbitrary data from the redux store to the component this way.
- * - tabOverride: set to 'data' if you want to force a renderTrigger to show up on the `Data`
- tab, otherwise `renderTrigger: true` components will show up on the `Style` tab.
- *
- * Note that the keys defined in controls in this file that are not listed above represent
- * props specific for the React component defined as `type`. Also note that this module work
- * in tandem with `controlPanels/index.js` that defines how controls are composed into sections for
- * each and every visualization type.
+ * control interface.
*/
import React from 'react';
import { t } from '@superset-ui/translation';
@@ -71,8 +44,15 @@ import { legacyValidateInteger, validateNonEmpty } from '@superset-ui/validator'
import { formatSelectOptions } from './selectOptions';
import { mainMetric, Metric } from './mainMetric';
-import { ColumnOption, ColumnOptionColumn } from './ColumnOption';
+import { ColumnOption } from './ColumnOption';
import { TIME_FILTER_LABELS } from './constants';
+import {
+ ControlConfig,
+ ColumnMeta,
+ DatasourceMeta,
+ ExtraControlProps,
+ SelectControlConfig,
+} from './types';
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
@@ -97,13 +77,12 @@ export const D3_FORMAT_OPTIONS = [
];
const ROW_LIMIT_OPTIONS = [10, 50, 100, 250, 500, 1000, 5000, 10000, 50000];
-
const SERIES_LIMITS = [0, 5, 10, 25, 50, 100, 500];
-export const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
+export const D3_FORMAT_DOCS = t('D3 format syntax: https://github.com/d3/d3-format');
export const D3_TIME_FORMAT_OPTIONS = [
- ['smart_date', 'Adaptative formating'],
+ ['smart_date', t('Adaptative formating')],
['%d/%m/%Y', '%d/%m/%Y | 14/01/2019'],
['%m/%d/%Y', '%m/%d/%Y | 01/14/2019'],
['%Y-%m-%d', '%Y-%m-%d | 2019-01-14'],
@@ -118,31 +97,12 @@ const timeColumnOption = {
description: t('A reference to the [Time] configuration, taking granularity into account'),
};
-type StateDatasource = {
- columns: ColumnOptionColumn[];
- metrics: unknown[];
- type: unknown;
- main_dttm_col: unknown;
- time_grain_sqla: unknown;
-};
-
-type State = {
- form_data: { [key: string]: unknown };
- datasource?: StateDatasource | null;
- options?: ColumnOptionColumn[];
- controls?: {
- comparison_type?: {
- value: string;
- };
- };
-};
-
type Control = {
savedMetrics?: Metric[] | null;
default?: unknown;
};
-const groupByControl = {
+const groupByControl: ControlConfig = {
type: 'SelectControl',
queryField: 'groupby',
multi: true,
@@ -151,28 +111,29 @@ const groupByControl = {
default: [],
includeTime: false,
description: t('One or many controls to group by'),
- optionRenderer: (c: ColumnOptionColumn) => ,
- valueRenderer: (c: ColumnOptionColumn) => ,
+ optionRenderer: (c: ColumnMeta) => ,
+ valueRenderer: (c: ColumnMeta) => ,
valueKey: 'column_name',
allowAll: true,
- filterOption: (opt: ColumnOptionColumn, text: string) =>
+ filterOption: (opt: ColumnMeta, text: string) =>
(opt.column_name && opt.column_name.toLowerCase().includes(text.toLowerCase())) ||
(opt.verbose_name && opt.verbose_name.toLowerCase().includes(text.toLowerCase())),
promptTextCreator: (label: unknown) => label,
- mapStateToProps: (state: State, control?: { includeTime: boolean }) => {
- const newState: State = {} as any;
+ mapStateToProps(state, { includeTime }) {
+ const newState: ExtraControlProps = {};
if (state.datasource) {
- newState.options = state.datasource.columns.filter(c => c.groupby);
- if (control?.includeTime) {
- newState.options.push(timeColumnOption);
+ const options = state.datasource.columns.filter(c => c.groupby);
+ if (includeTime) {
+ options.push(timeColumnOption);
}
+ newState.options = options;
}
return newState;
},
commaChoosesOption: false,
};
-const metrics = {
+const metrics: ControlConfig = {
type: 'MetricsControl',
queryField: 'metrics',
multi: true,
@@ -182,7 +143,7 @@ const metrics = {
const metric = mainMetric(c.savedMetrics);
return metric ? [metric] : null;
},
- mapStateToProps: ({ datasource }: State) => {
+ mapStateToProps: ({ datasource }) => {
return {
columns: datasource ? datasource.columns : [],
savedMetrics: datasource ? datasource.metrics : [],
@@ -191,7 +152,7 @@ const metrics = {
},
description: t('One or many metrics to display'),
};
-const metric = {
+const metric: ControlConfig = {
...metrics,
multi: false,
label: t('Metric'),
@@ -199,7 +160,7 @@ const metric = {
default: (c: Control) => mainMetric(c.savedMetrics),
};
-export function columnChoices(datasource: StateDatasource) {
+export function columnChoices(datasource: DatasourceMeta) {
if (datasource?.columns) {
return datasource.columns
.map(col => [col.column_name, col.verbose_name || col.column_name])
@@ -208,315 +169,335 @@ export function columnChoices(datasource: StateDatasource) {
return [];
}
-export const controls = {
- metrics,
-
- metric,
-
- datasource: {
- type: 'DatasourceControl',
- label: t('Datasource'),
- default: null,
- description: null,
- mapStateToProps: (state: State, control: unknown, actions: { setDatasource: unknown }) => ({
- datasource: state.datasource,
- onDatasourceSave: actions ? actions.setDatasource : () => {},
- }),
- },
-
- viz_type: {
- type: 'VizTypeControl',
- label: t('Visualization Type'),
- default: 'table',
- description: t('The type of visualization to display'),
- },
+const datasourceControl: ControlConfig = {
+ type: 'DatasourceControl',
+ label: t('Datasource'),
+ default: null,
+ description: null,
+ mapStateToProps: (state, control, { setDatasource }) => ({
+ datasource: state.datasource,
+ onDatasourceSave: setDatasource,
+ }),
+};
- color_picker: {
- label: t('Fixed Color'),
- description: t('Use this to define a static color for all circles'),
- type: 'ColorPickerControl',
- default: PRIMARY_COLOR,
- renderTrigger: true,
- },
+const viz_type: ControlConfig = {
+ type: 'VizTypeControl',
+ label: t('Visualization Type'),
+ default: 'table',
+ description: t('The type of visualization to display'),
+};
- metric_2: {
- ...metric,
- label: t('Right Axis Metric'),
- clearable: true,
- description: t('Choose a metric for right axis'),
- },
+const color_picker: ControlConfig = {
+ label: t('Fixed Color'),
+ description: t('Use this to define a static color for all circles'),
+ type: 'ColorPickerControl',
+ default: PRIMARY_COLOR,
+ renderTrigger: true,
+};
- linear_color_scheme: {
- type: 'ColorSchemeControl',
- label: t('Linear Color Scheme'),
- choices: () =>
- (sequentialSchemeRegistry.values() as SequentialScheme[]).map(value => [
- value.id,
- value.label,
- ]),
- default: sequentialSchemeRegistry.getDefaultKey(),
- clearable: false,
- description: '',
- renderTrigger: true,
- schemes: () => sequentialSchemeRegistry.getMap(),
- isLinear: true,
- },
+const metric_2: ControlConfig = {
+ ...metric,
+ label: t('Right Axis Metric'),
+ clearable: true,
+ description: t('Choose a metric for right axis'),
+};
- secondary_metric: {
- ...metric,
- label: t('Color Metric'),
- default: null,
- validators: [],
- description: t('A metric to use for color'),
- },
+const linear_color_scheme: ControlConfig = {
+ type: 'ColorSchemeControl',
+ label: t('Linear Color Scheme'),
+ choices: () =>
+ (sequentialSchemeRegistry.values() as SequentialScheme[]).map(value => [value.id, value.label]),
+ default: sequentialSchemeRegistry.getDefaultKey(),
+ clearable: false,
+ description: '',
+ renderTrigger: true,
+ schemes: () => sequentialSchemeRegistry.getMap(),
+ isLinear: true,
+};
- groupby: groupByControl,
+const secondary_metric: ControlConfig = {
+ ...metric,
+ label: t('Color Metric'),
+ default: null,
+ validators: [],
+ description: t('A metric to use for color'),
+};
- columns: {
- ...groupByControl,
- label: t('Columns'),
- description: t('One or many controls to pivot as columns'),
- },
+const columnsControl: ControlConfig = {
+ ...groupByControl,
+ label: t('Columns'),
+ description: t('One or many controls to pivot as columns'),
+};
- druid_time_origin: {
- type: 'SelectControl',
- freeForm: true,
- label: TIME_FILTER_LABELS.druid_time_origin,
- choices: [
- ['', 'default'],
- ['now', 'now'],
- ],
- default: null,
- description: t(
- 'Defines the origin where time buckets start, ' +
- 'accepts natural dates as in `now`, `sunday` or `1970-01-01`',
- ),
- },
+const druid_time_origin: ControlConfig = {
+ type: 'SelectControl',
+ freeForm: true,
+ label: TIME_FILTER_LABELS.druid_time_origin,
+ choices: [
+ ['', 'default'],
+ ['now', 'now'],
+ ],
+ default: null,
+ description: t(
+ 'Defines the origin where time buckets start, ' +
+ 'accepts natural dates as in `now`, `sunday` or `1970-01-01`',
+ ),
+};
- granularity: {
- type: 'SelectControl',
- freeForm: true,
- label: TIME_FILTER_LABELS.granularity,
- default: 'one day',
- choices: [
- [null, 'all'],
- ['PT5S', '5 seconds'],
- ['PT30S', '30 seconds'],
- ['PT1M', '1 minute'],
- ['PT5M', '5 minutes'],
- ['PT30M', '30 minutes'],
- ['PT1H', '1 hour'],
- ['PT6H', '6 hour'],
- ['P1D', '1 day'],
- ['P7D', '7 days'],
- ['P1W', 'week'],
- ['week_starting_sunday', 'week starting Sunday'],
- ['week_ending_saturday', 'week ending Saturday'],
- ['P1M', 'month'],
- ['P3M', 'quarter'],
- ['P1Y', 'year'],
- ],
- description: t(
- 'The time granularity for the visualization. Note that you ' +
- 'can type and use simple natural language as in `10 seconds`, ' +
- '`1 day` or `56 weeks`',
- ),
- },
+const granularity: ControlConfig = {
+ type: 'SelectControl',
+ freeForm: true,
+ label: TIME_FILTER_LABELS.granularity,
+ default: 'one day',
+ choices: [
+ [null, 'all'],
+ ['PT5S', '5 seconds'],
+ ['PT30S', '30 seconds'],
+ ['PT1M', '1 minute'],
+ ['PT5M', '5 minutes'],
+ ['PT30M', '30 minutes'],
+ ['PT1H', '1 hour'],
+ ['PT6H', '6 hour'],
+ ['P1D', '1 day'],
+ ['P7D', '7 days'],
+ ['P1W', 'week'],
+ ['week_starting_sunday', 'week starting Sunday'],
+ ['week_ending_saturday', 'week ending Saturday'],
+ ['P1M', 'month'],
+ ['P3M', 'quarter'],
+ ['P1Y', 'year'],
+ ],
+ description: t(
+ 'The time granularity for the visualization. Note that you ' +
+ 'can type and use simple natural language as in `10 seconds`, ' +
+ '`1 day` or `56 weeks`',
+ ),
+};
- granularity_sqla: {
- type: 'SelectControl',
- label: TIME_FILTER_LABELS.granularity_sqla,
- description: t(
- 'The time column for the visualization. Note that you ' +
- 'can define arbitrary expression that return a DATETIME ' +
- 'column in the table. Also note that the ' +
- 'filter below is applied against this column or ' +
- 'expression',
- ),
- default: (c: Control) => c.default,
- clearable: false,
- optionRenderer: (c: ColumnOptionColumn) => ,
- valueRenderer: (c: ColumnOptionColumn) => ,
- valueKey: 'column_name',
- mapStateToProps: (state: State) => {
- const props: any = {};
- if (state.datasource) {
- props.options = state.datasource.columns.filter(c => c.is_dttm);
- props.default = null;
- if (state.datasource.main_dttm_col) {
- props.default = state.datasource.main_dttm_col;
- } else if (props.options && props.options.length > 0) {
- props.default = props.options[0].column_name;
- }
+const granularity_sqla: ControlConfig = {
+ type: 'SelectControl',
+ label: TIME_FILTER_LABELS.granularity_sqla,
+ description: t(
+ 'The time column for the visualization. Note that you ' +
+ 'can define arbitrary expression that return a DATETIME ' +
+ 'column in the table. Also note that the ' +
+ 'filter below is applied against this column or ' +
+ 'expression',
+ ),
+ default: (c: Control) => c.default,
+ clearable: false,
+ optionRenderer: (c: ColumnMeta) => ,
+ valueRenderer: (c: ColumnMeta) => ,
+ valueKey: 'column_name',
+ mapStateToProps: state => {
+ const props: Partial> = {};
+ if (state.datasource) {
+ props.options = state.datasource.columns.filter(c => c.is_dttm);
+ props.default = null;
+ if (state.datasource.main_dttm_col) {
+ props.default = state.datasource.main_dttm_col;
+ } else if (props.options && props.options.length > 0) {
+ props.default = props.options[0].column_name;
}
- return props;
- },
+ }
+ return props;
},
+};
- time_grain_sqla: {
- type: 'SelectControl',
- label: TIME_FILTER_LABELS.time_grain_sqla,
- default: 'P1D',
- description: t(
- 'The time granularity for the visualization. This ' +
- 'applies a date transformation to alter ' +
- 'your time column and defines a new time granularity. ' +
- 'The options here are defined on a per database ' +
- 'engine basis in the Superset source code.',
- ),
- mapStateToProps: (state: State) => ({
- choices: state.datasource ? state.datasource.time_grain_sqla : null,
- }),
- },
+const time_grain_sqla: ControlConfig = {
+ type: 'SelectControl',
+ label: TIME_FILTER_LABELS.time_grain_sqla,
+ default: 'P1D',
+ description: t(
+ 'The time granularity for the visualization. This ' +
+ 'applies a date transformation to alter ' +
+ 'your time column and defines a new time granularity. ' +
+ 'The options here are defined on a per database ' +
+ 'engine basis in the Superset source code.',
+ ),
+ mapStateToProps: ({ datasource }) => ({
+ choices: datasource?.time_grain_sqla || null,
+ }),
+};
- time_range: {
- type: 'DateFilterControl',
- freeForm: true,
- label: TIME_FILTER_LABELS.time_range,
- default: t('Last week'), // this value is translated, but the backend wouldn't understand a translated value?
- description: t(
- 'The time range for the visualization. All relative times, e.g. "Last month", ' +
- '"Last 7 days", "now", etc. are evaluated on the server using the server\'s ' +
- 'local time (sans timezone). All tooltips and placeholder times are expressed ' +
- 'in UTC (sans timezone). The timestamps are then evaluated by the database ' +
- "using the engine's local timezone. Note one can explicitly set the timezone " +
- 'per the ISO 8601 format if specifying either the start and/or end time.',
- ),
- mapStateToProps: (state: State) => ({
- endpoints: state.form_data ? state.form_data.time_range_endpoints : null,
- }),
- },
+const time_range: ControlConfig = {
+ type: 'DateFilterControl',
+ freeForm: true,
+ label: TIME_FILTER_LABELS.time_range,
+ default: t('Last week'), // this value is translated, but the backend wouldn't understand a translated value?
+ description: t(
+ 'The time range for the visualization. All relative times, e.g. "Last month", ' +
+ '"Last 7 days", "now", etc. are evaluated on the server using the server\'s ' +
+ 'local time (sans timezone). All tooltips and placeholder times are expressed ' +
+ 'in UTC (sans timezone). The timestamps are then evaluated by the database ' +
+ "using the engine's local timezone. Note one can explicitly set the timezone " +
+ 'per the ISO 8601 format if specifying either the start and/or end time.',
+ ),
+ mapStateToProps: ({ form_data }) => ({
+ endpoints: form_data?.time_range_endpoints || null,
+ }),
+};
- row_limit: {
- type: 'SelectControl',
- freeForm: true,
- label: t('Row limit'),
- validators: [legacyValidateInteger],
- default: 10000,
- choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
- },
+const row_limit: ControlConfig = {
+ type: 'SelectControl',
+ freeForm: true,
+ label: t('Row limit'),
+ validators: [legacyValidateInteger],
+ default: 10000,
+ choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
+};
- limit: {
- type: 'SelectControl',
- freeForm: true,
- label: t('Series limit'),
- validators: [legacyValidateInteger],
- choices: formatSelectOptions(SERIES_LIMITS),
- description: t(
- 'Limits the number of time series that get displayed. A sub query ' +
- '(or an extra phase where sub queries are not supported) is applied to limit ' +
- 'the number of time series that get fetched and displayed. This feature is useful ' +
- 'when grouping by high cardinality dimension(s).',
- ),
- },
+const limit: ControlConfig = {
+ type: 'SelectControl',
+ freeForm: true,
+ label: t('Series limit'),
+ validators: [legacyValidateInteger],
+ choices: formatSelectOptions(SERIES_LIMITS),
+ description: t(
+ 'Limits the number of time series that get displayed. A sub query ' +
+ '(or an extra phase where sub queries are not supported) is applied to limit ' +
+ 'the number of time series that get fetched and displayed. This feature is useful ' +
+ 'when grouping by high cardinality dimension(s).',
+ ),
+};
- timeseries_limit_metric: {
- type: 'MetricsControl',
- label: t('Sort By'),
- default: null,
- description: t('Metric used to define the top series'),
- mapStateToProps: (state: State) => ({
- columns: state.datasource ? state.datasource.columns : [],
- savedMetrics: state.datasource ? state.datasource.metrics : [],
- datasourceType: state.datasource && state.datasource.type,
- }),
- },
+const timeseries_limit_metric: ControlConfig = {
+ type: 'MetricsControl',
+ label: t('Sort By'),
+ default: null,
+ description: t('Metric used to define the top series'),
+ mapStateToProps: ({ datasource }) => ({
+ columns: datasource?.columns || [],
+ savedMetrics: datasource?.metrics || [],
+ datasourceType: datasource?.type,
+ }),
+};
- series: {
- ...groupByControl,
- label: t('Series'),
- multi: false,
- default: null,
- description: t(
- 'Defines the grouping of entities. ' +
- 'Each series is shown as a specific color on the chart and ' +
- 'has a legend toggle',
- ),
- },
+const series: ControlConfig = {
+ ...groupByControl,
+ label: t('Series'),
+ multi: false,
+ default: null,
+ description: t(
+ 'Defines the grouping of entities. ' +
+ 'Each series is shown as a specific color on the chart and ' +
+ 'has a legend toggle',
+ ),
+};
- entity: {
- ...groupByControl,
- label: t('Entity'),
- default: null,
- multi: false,
- validators: [validateNonEmpty],
- description: t('This defines the element to be plotted on the chart'),
- },
+const entity: ControlConfig = {
+ ...groupByControl,
+ label: t('Entity'),
+ default: null,
+ multi: false,
+ validators: [validateNonEmpty],
+ description: t('This defines the element to be plotted on the chart'),
+};
- x: {
- ...metric,
- label: t('X Axis'),
- description: t('Metric assigned to the [X] axis'),
- default: null,
- },
+const x: ControlConfig = {
+ ...metric,
+ label: t('X Axis'),
+ description: t('Metric assigned to the [X] axis'),
+ default: null,
+};
- y: {
- ...metric,
- label: t('Y Axis'),
- default: null,
- description: t('Metric assigned to the [Y] axis'),
- },
+const y: ControlConfig = {
+ ...metric,
+ label: t('Y Axis'),
+ default: null,
+ description: t('Metric assigned to the [Y] axis'),
+};
- size: {
- ...metric,
- label: t('Bubble Size'),
- default: null,
- },
+const size: ControlConfig = {
+ ...metric,
+ label: t('Bubble Size'),
+ default: null,
+};
- y_axis_format: {
- type: 'SelectControl',
- freeForm: true,
- label: t('Y Axis Format'),
- renderTrigger: true,
- default: 'SMART_NUMBER',
- choices: D3_FORMAT_OPTIONS,
- description: D3_FORMAT_DOCS,
- mapStateToProps: (state: State) => {
- const showWarning = state.controls?.comparison_type?.value === 'percentage';
- return {
- warning: showWarning
- ? t(
- 'When `Calculation type` is set to "Percentage change", the Y ' +
- 'Axis Format is forced to `.1%`',
- )
- : null,
- disabled: showWarning,
- };
- },
+const y_axis_format: ControlConfig = {
+ type: 'SelectControl',
+ freeForm: true,
+ label: t('Y Axis Format'),
+ renderTrigger: true,
+ default: 'SMART_NUMBER',
+ choices: D3_FORMAT_OPTIONS,
+ description: D3_FORMAT_DOCS,
+ mapStateToProps: state => {
+ const showWarning = state.controls?.comparison_type?.value === 'percentage';
+ return {
+ warning: showWarning
+ ? t(
+ 'When `Calculation type` is set to "Percentage change", the Y ' +
+ 'Axis Format is forced to `.1%`',
+ )
+ : null,
+ disabled: showWarning,
+ };
},
+};
- adhoc_filters: {
- type: 'AdhocFilterControl',
- label: t('Filters'),
- default: null,
- description: '',
- mapStateToProps: (state: State) => ({
- columns: state.datasource?.columns.filter(c => c.filterable) || [],
- savedMetrics: state.datasource?.metrics || [],
- datasource: state.datasource,
- }),
- provideFormDataToProps: true,
- },
+const adhoc_filters: ControlConfig = {
+ type: 'AdhocFilterControl',
+ label: t('Filters'),
+ default: null,
+ description: '',
+ mapStateToProps: ({ datasource }) => ({
+ columns: datasource?.columns.filter(c => c.filterable) || [],
+ savedMetrics: datasource?.metrics || [],
+ datasource,
+ }),
+ provideFormDataToProps: true,
+};
- color_scheme: {
- type: 'ColorSchemeControl',
- label: t('Color Scheme'),
- default: categoricalSchemeRegistry.getDefaultKey(),
- renderTrigger: true,
- choices: () => categoricalSchemeRegistry.keys().map(s => [s, s]),
- description: t('The color scheme for rendering chart'),
- schemes: () => categoricalSchemeRegistry.getMap(),
- },
+const color_scheme: ControlConfig = {
+ type: 'ColorSchemeControl',
+ label: t('Color Scheme'),
+ default: categoricalSchemeRegistry.getDefaultKey(),
+ renderTrigger: true,
+ choices: () => categoricalSchemeRegistry.keys().map(s => [s, s]),
+ description: t('The color scheme for rendering chart'),
+ schemes: () => categoricalSchemeRegistry.getMap(),
+};
- label_colors: {
- type: 'ColorMapControl',
- label: t('Color Map'),
- default: {},
- renderTrigger: true,
- mapStateToProps: (state: State) => ({
- colorNamespace: state.form_data.color_namespace,
- colorScheme: state.form_data.color_scheme,
- }),
- },
+const label_colors: ControlConfig = {
+ type: 'ColorMapControl',
+ label: t('Color Map'),
+ default: {},
+ renderTrigger: true,
+ mapStateToProps: ({
+ form_data: { color_namespace: colorNamespace, color_scheme: colorScheme },
+ }) => ({
+ colorNamespace,
+ colorScheme,
+ }),
+};
+
+export default {
+ metrics,
+ metric,
+ datasource: datasourceControl,
+ viz_type,
+ color_picker,
+ metric_2,
+ linear_color_scheme,
+ secondary_metric,
+ groupby: groupByControl,
+ columns: columnsControl,
+ druid_time_origin,
+ granularity,
+ granularity_sqla,
+ time_grain_sqla,
+ time_range,
+ row_limit,
+ limit,
+ timeseries_limit_metric,
+ series,
+ entity,
+ x,
+ y,
+ size,
+ y_axis_format,
+ adhoc_filters,
+ color_scheme,
+ label_colors,
};
-export default controls;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/types.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/types.ts
new file mode 100644
index 0000000000000..4e0e7d09ad4fa
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-control-utils/src/types.ts
@@ -0,0 +1,267 @@
+/* eslint-disable camelcase */
+/**
+ * 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 React, { ReactNode, ReactText } from 'react';
+import { QueryFormData } from '@superset-ui/query';
+import sharedControls from './shared-controls';
+
+type AnyDict = Record;
+interface Action {
+ type: string;
+}
+interface AnyAction extends Action, AnyDict {}
+
+/** ----------------------------------------------
+ * Input data/props while rendering
+ * ---------------------------------------------*/
+export interface ColumnMeta extends AnyDict {
+ column_name: string;
+ groupby?: string;
+ verbose_name?: string;
+ description?: string;
+ expression?: string;
+ is_dttm?: boolean;
+ type?: string;
+ filterable?: boolean;
+}
+
+export interface DatasourceMeta {
+ columns: ColumnMeta[];
+ metrics: unknown[];
+ type: unknown;
+ main_dttm_col: unknown;
+ time_grain_sqla: unknown;
+ order_by_choices?: [] | null;
+}
+
+export interface ControlPanelState {
+ form_data: QueryFormData;
+ datasource?: DatasourceMeta | null;
+ options?: ColumnMeta[];
+ controls?: {
+ comparison_type?: {
+ value: string;
+ };
+ };
+}
+
+/**
+ * The action dispather will call Redux `dispatch` internally and return what's
+ * returned from `dispatch`, which by default is the original or another action.
+ */
+export interface ActionDispatcher {
+ (...args: ARGS): A;
+}
+
+/**
+ * Mapping of action dispatchers
+ */
+export interface ControlPanelActionDispathers {
+ setDatasource: ActionDispatcher<[DatasourceMeta]>;
+}
+
+/**
+ * Additional control props obtained from `mapStateToProps`.
+ */
+export type ExtraControlProps = AnyDict;
+
+// Ref:superset-frontend/src/explore/store.js
+export type ControlState = ControlConfig &
+ ExtraControlProps;
+
+export interface ControlStateMapping {
+ [key: string]: ControlState;
+}
+
+// Ref: superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+export interface ControlPanelsContainerProps extends AnyDict {
+ actions: ControlPanelActionDispathers;
+ controls: ControlStateMapping;
+ exportState: AnyDict;
+ form_data: QueryFormData;
+}
+
+/** ----------------------------------------------
+ * Config for a chart Control
+ * ---------------------------------------------*/
+
+// Ref: superset-frontend/src/explore/components/controls/index.js
+export type InternalControlType =
+ | 'AnnotationLayerControl'
+ | 'BoundsControl'
+ | 'CheckboxControl'
+ | 'CollectionControl'
+ | 'ColorMapControl'
+ | 'ColorPickerControl'
+ | 'ColorSchemeControl'
+ | 'DatasourceControl'
+ | 'DateFilterControl'
+ | 'FixedOrMetricControl'
+ | 'HiddenControl'
+ | 'SelectAsyncControl'
+ | 'SelectControl'
+ | 'SliderControl'
+ | 'SpatialControl'
+ | 'TextAreaControl'
+ | 'TextControl'
+ | 'TimeSeriesColumnControl'
+ | 'ViewportControl'
+ | 'VizTypeControl'
+ | 'MetricsControl'
+ | 'AdhocFilterControl'
+ | 'FilterBoxItemControl'
+ | 'MetricsControlVerifiedOptions'
+ | 'SelectControlVerifiedOptions'
+ | 'AdhocFilterControlVerifiedOptions';
+
+export interface Validator {
+ (value: unknown): boolean | string;
+}
+
+export type TabOverride = 'data' | boolean;
+
+/**
+ * Control config specifying how chart controls appear in the control panel, all
+ * these configs will be passed to the UI component for control as props.
+ *
+ * - type: the control type, referencing a React component of the same name
+ * - label: the label as shown in the control's header
+ * - description: shown in the info tooltip of the control's header
+ * - default: the default value when opening a new chart, or changing visualization type
+ * - renderTrigger: a bool that defines whether the visualization should be re-rendered
+ * when changed. This should `true` for controls that only affect the rendering (client side)
+ * and don't affect the query or backend data processing as those require to re run a query
+ * and fetch the data
+ * - validators: an array of functions that will receive the value of the component and
+ * should return error messages when the value is not valid. The error message gets
+ * bubbled up to the control header, section header and query panel header.
+ * - warning: text shown as a tooltip on a warning icon in the control's header
+ * - error: text shown as a tooltip on a error icon in the control's header
+ * - mapStateToProps: a function that receives the App's state and return an object of k/v
+ * to overwrite configuration at runtime. This is useful to alter a component based on
+ * anything external to it, like another control's value. For instance it's possible to
+ * show a warning based on the value of another component. It's also possible to bind
+ * arbitrary data from the redux store to the component this way.
+ * - tabOverride: set to 'data' if you want to force a renderTrigger to show up on the `Data`
+ * tab, otherwise `renderTrigger: true` components will show up on the `Style` tab.
+ * - visibility: a function that uses control panel props to check whether a control should
+ * be visibile.
+ */
+export interface GeneralControlConfig {
+ type: InternalControlType | React.ComponentType;
+ label?: ReactNode;
+ description?: ReactNode;
+ default?: unknown;
+ renderTrigger?: boolean;
+ validators?: Validator[];
+ warning?: ReactNode;
+ error?: ReactNode;
+ // override control panel state props
+ mapStateToProps?: (
+ state: ControlPanelState,
+ control: ControlConfig,
+ actions: ControlPanelActionDispathers,
+ ) => ExtraControlProps;
+ tabOverride?: TabOverride;
+ visibility?: (props: ControlPanelsContainerProps) => boolean;
+ [key: string]: unknown;
+}
+/** --------------------------------------------
+ * Additional Config for specific control Types
+ * --------------------------------------------- */
+type SelectOption = AnyDict | string | [ReactText, ReactNode];
+type SelectControlType =
+ | 'SelectControl'
+ | 'SelectAsyncControl'
+ | 'SelectControl'
+ | 'MetricsControl'
+ | 'FixedOrMetricControl'
+ | 'AdhocFilterControl'
+ | 'FilterBoxItemControl'
+ | 'MetricsControlVerifiedOptions'
+ | 'SelectControlVerifiedOptions'
+ | 'AdhocFilterControlVerifiedOptions';
+
+export interface SelectControlConfig
+ extends GeneralControlConfig {
+ type: SelectControlType;
+ options?: T[];
+ clearable?: boolean;
+ freeForm?: boolean;
+ multi?: boolean;
+ optionRenderer?: (option: T) => ReactNode;
+ valueRenderer?: (option: T) => ReactNode;
+ valueKey?: string;
+ labelKey?: string;
+}
+
+export type ControlConfig =
+ | GeneralControlConfig
+ | SelectControlConfig;
+
+/** --------------------------------------------
+ * Chart plugin control panel config
+ * --------------------------------------------- */
+export type SharedControlAlias = keyof typeof sharedControls;
+
+export type SharedSectionAlias =
+ | 'annotations'
+ | 'colorScheme'
+ | 'datasourceAndVizType'
+ | 'druidTimeSeries'
+ | 'sqlaTimeSeries'
+ | 'NVD3TimeSeries';
+
+export interface ControlItem {
+ name: SharedControlAlias;
+ config: Partial;
+}
+
+export interface CustomControlItem {
+ name: string;
+ config: ControlConfig;
+}
+
+export type ControlSetItem = SharedControlAlias | ControlItem | CustomControlItem | ReactNode;
+export type ControlSetRow = ControlSetItem[];
+
+// Ref:
+// - superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+// - superset-frontend/src/explore/components/ControlPanelSection.jsx
+export interface ControlPanelSectionConfig {
+ label: ReactNode;
+ controlSetRows: ControlSetRow[];
+ description?: ReactNode;
+ expanded?: boolean;
+ tabOverride?: TabOverride;
+}
+
+export interface ControlPanelConfig {
+ controlPanelSections: ControlPanelSectionConfig[];
+ controlOverrides?: ControlOverrides;
+ sectionOverrides?: SectionOverrides;
+}
+
+export type ControlOverrides = {
+ [P in SharedControlAlias]?: Partial;
+};
+
+export type SectionOverrides = {
+ [P in SharedSectionAlias]?: Partial;
+};
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-plugin-chart-table/src/controlPanel.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-plugin-chart-table/src/controlPanel.tsx
index 527d76b88ad19..345ffab2d1b6e 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-plugin-chart-table/src/controlPanel.tsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-plugin-chart-table/src/controlPanel.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -22,10 +23,11 @@ import {
formatSelectOptions,
D3_TIME_FORMAT_OPTIONS,
ColumnOption,
+ ControlPanelConfig,
} from '@superset-ui/control-utils';
import { validateNonEmpty } from '@superset-ui/validator';
-export default {
+const config: ControlPanelConfig = {
controlPanelSections: [
{
label: t('GROUP BY'),
@@ -40,13 +42,11 @@ export default {
config: {
type: 'MetricsControl',
multi: true,
- mapStateToProps: (state: never) => {
- const { datasource } = state;
- const { columns, metrics, type } = datasource;
+ mapStateToProps: ({ datasource }) => {
return {
- columns: datasource ? columns : [],
- savedMetrics: datasource ? metrics : [],
- datasourceType: datasource && type,
+ columns: datasource?.columns || [],
+ savedMetrics: datasource?.metrics || [],
+ datasourceType: datasource?.type,
};
},
default: [],
@@ -99,8 +99,8 @@ export default {
valueRenderer: (c: never) => ,
valueKey: 'column_name',
allowAll: true,
- mapStateToProps: (state: { datasource: { columns: unknown } }) => ({
- options: state.datasource ? state.datasource.columns : [],
+ mapStateToProps: ({ datasource }) => ({
+ options: datasource?.columns || [],
}),
commaChoosesOption: false,
freeForm: true,
@@ -116,9 +116,8 @@ export default {
label: t('Ordering'),
default: [],
description: t('One or many metrics to display'),
- // eslint-disable-next-line camelcase
- mapStateToProps: (state: { datasource: { order_by_choices: never } }) => ({
- choices: state.datasource ? state.datasource.order_by_choices : [],
+ mapStateToProps: ({ datasource }) => ({
+ choices: datasource?.order_by_choices || [],
}),
},
},
@@ -240,3 +239,5 @@ export default {
},
},
};
+
+export default config;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/BigNumber/controlPanel.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/BigNumber/controlPanel.tsx
index a98696121df98..1f2968aa30a5d 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/BigNumber/controlPanel.tsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/BigNumber/controlPanel.tsx
@@ -17,16 +17,11 @@
* under the License.
*/
import { t } from '@superset-ui/translation';
-import { formatSelectOptions } from '@superset-ui/control-utils';
+import { formatSelectOptions, ControlPanelConfig } from '@superset-ui/control-utils';
import React from 'react';
import { headerFontSize, subheaderFontSize } from '../sharedControls';
-type VisibilityProps = {
- // eslint-disable-next-line camelcase
- form_data: { time_range: string };
-};
-
-export default {
+const config: ControlPanelConfig = {
controlPanelSections: [
{
label: t('Query'),
@@ -92,10 +87,10 @@ export default {
'Fix the trend line to the full time range specified in case filtered results do not include the start or end dates',
),
renderTrigger: true,
- visibility(props: VisibilityProps) {
+ visibility(props) {
const { time_range: timeRange } = props.form_data;
// only display this option when a time range is selected
- return timeRange && timeRange !== 'No filter';
+ return !!timeRange && timeRange !== 'No filter';
},
},
},
@@ -172,3 +167,5 @@ export default {
},
},
};
+
+export default config;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/sharedControls.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/sharedControls.ts
index a4df9bcede6d8..1165155a618f5 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/sharedControls.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/legacy-preset-chart-big-number/src/sharedControls.ts
@@ -19,8 +19,9 @@
// These are control configurations that are shared ONLY within the BigNumber viz plugin repo.
import { t } from '@superset-ui/translation';
+import { CustomControlItem } from '@superset-ui/control-utils';
-export const headerFontSize = {
+export const headerFontSize: CustomControlItem = {
name: 'header_font_size',
config: {
type: 'SelectControl',
@@ -54,7 +55,7 @@ export const headerFontSize = {
},
};
-export const subheaderFontSize = {
+export const subheaderFontSize: CustomControlItem = {
name: 'subheader_font_size',
config: {
type: 'SelectControl',