superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx
/**
* 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 {
ensureIsArray,
isAdhocColumn,
isPhysicalColumn,
QueryFormMetric,
SMART_DATE_ID,
t,
validateNonEmpty,
} from '@superset-ui/core';
import {
ControlPanelConfig,
D3_TIME_FORMAT_OPTIONS,
sharedControls,
Dataset,
getStandardizedControls,
} from '@superset-ui/chart-controls';
import { MetricsLayoutEnum } from '../types';
const config: ControlPanelConfig = {
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
[
{
name: 'groupbyColumns',
config: {
...sharedControls.groupby,
label: t('Columns'),
description: t('Columns to group by on the columns'),
},
},
],
[
{
name: 'groupbyRows',
config: {
...sharedControls.groupby,
label: t('Rows'),
description: t('Columns to group by on the rows'),
},
},
],
[
{
name: 'time_grain_sqla',
config: {
...sharedControls.time_grain_sqla,
visibility: ({ controls }) => {
const dttmLookup = Object.fromEntries(
ensureIsArray(controls?.groupbyColumns?.options).map(
option => [option.column_name, option.is_dttm],
),
);
return [
...ensureIsArray(controls?.groupbyColumns.value),
...ensureIsArray(controls?.groupbyRows.value),
]
.map(selection => {
if (isAdhocColumn(selection)) {
return true;
}
if (isPhysicalColumn(selection)) {
return !!dttmLookup[selection];
}
return false;
})
.some(Boolean);
},
},
},
'temporal_columns_lookup',
],
[
{
name: 'metrics',
config: {
...sharedControls.metrics,
validators: [validateNonEmpty],
rerender: ['conditional_formatting'],
},
},
],
[
{
name: 'metricsLayout',
config: {
type: 'RadioButtonControl',
renderTrigger: true,
label: t('Apply metrics on'),
default: MetricsLayoutEnum.COLUMNS,
options: [
[MetricsLayoutEnum.COLUMNS, t('Columns')],
[MetricsLayoutEnum.ROWS, t('Rows')],
],
description: t(
'Use metrics as a top level group for columns or for rows',
),
},
},
],
['adhoc_filters'],
['series_limit'],
[
{
name: 'row_limit',
config: {
...sharedControls.row_limit,
label: t('Cell limit'),
description: t('Limits the number of cells that get retrieved.'),
},
},
],
// TODO(kgabryje): add series_columns control after control panel is redesigned to avoid clutter
[
{
name: 'series_limit_metric',
config: {
...sharedControls.series_limit_metric,
description: t(
'Metric used to define how the top series are sorted if a series or cell limit is present. ' +
'If undefined reverts to the first metric (where appropriate).',
),
},
},
],
[
{
name: 'order_desc',
config: {
type: 'CheckboxControl',
label: t('Sort Descending'),
default: true,
description: t('Whether to sort descending or ascending'),
},
},
],
],
},
{
label: t('Options'),
expanded: true,
tabOverride: 'data',
controlSetRows: [
[
{
name: 'aggregateFunction',
config: {
type: 'SelectControl',
label: t('Aggregation function'),
clearable: false,
choices: [
['Count', t('Count')],
['Count Unique Values', t('Count Unique Values')],
['List Unique Values', t('List Unique Values')],
['Sum', t('Sum')],
['Average', t('Average')],
['Median', t('Median')],
['Sample Variance', t('Sample Variance')],
['Sample Standard Deviation', t('Sample Standard Deviation')],
['Minimum', t('Minimum')],
['Maximum', t('Maximum')],
['First', t('First')],
['Last', t('Last')],
['Sum as Fraction of Total', t('Sum as Fraction of Total')],
['Sum as Fraction of Rows', t('Sum as Fraction of Rows')],
['Sum as Fraction of Columns', t('Sum as Fraction of Columns')],
['Count as Fraction of Total', t('Count as Fraction of Total')],
['Count as Fraction of Rows', t('Count as Fraction of Rows')],
[
'Count as Fraction of Columns',
t('Count as Fraction of Columns'),
],
],
default: 'Sum',
description: t(
'Aggregate function to apply when pivoting and computing the total rows and columns',
),
renderTrigger: true,
},
},
],
[
{
name: 'rowTotals',
config: {
type: 'CheckboxControl',
label: t('Show rows total'),
default: false,
renderTrigger: true,
description: t('Display row level total'),
},
},
],
[
{
name: 'rowSubTotals',
config: {
type: 'CheckboxControl',
label: t('Show rows subtotal'),
default: false,
renderTrigger: true,
description: t('Display row level subtotal'),
},
},
],
[
{
name: 'colTotals',
config: {
type: 'CheckboxControl',
label: t('Show columns total'),
default: false,
renderTrigger: true,
description: t('Display column level total'),
},
},
],
[
{
name: 'colSubTotals',
config: {
type: 'CheckboxControl',
label: t('Show columns subtotal'),
default: false,
renderTrigger: true,
description: t('Display column level subtotal'),
},
},
],
[
{
name: 'transposePivot',
config: {
type: 'CheckboxControl',
label: t('Transpose pivot'),
default: false,
description: t('Swap rows and columns'),
renderTrigger: true,
},
},
],
[
{
name: 'combineMetric',
config: {
type: 'CheckboxControl',
label: t('Combine metrics'),
default: false,
description: t(
'Display metrics side by side within each column, as ' +
'opposed to each column being displayed side by side for each metric.',
),
renderTrigger: true,
},
},
],
],
},
{
label: t('Options'),
expanded: true,
controlSetRows: [
[
{
name: 'valueFormat',
config: {
...sharedControls.y_axis_format,
label: t('Value format'),
},
},
],
['currency_format'],
[
{
name: 'date_format',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Date format'),
default: SMART_DATE_ID,
renderTrigger: true,
choices: D3_TIME_FORMAT_OPTIONS,
description: t('D3 time format for datetime columns'),
},
},
],
[
{
name: 'rowOrder',
config: {
type: 'SelectControl',
label: t('Sort rows by'),
default: 'key_a_to_z',
choices: [
// [value, label]
['key_a_to_z', t('key a-z')],
['key_z_to_a', t('key z-a')],
['value_a_to_z', t('value ascending')],
['value_z_to_a', t('value descending')],
],
renderTrigger: true,
description: (
<>
<div>{t('Change order of rows.')}</div>
<div>{t('Available sorting modes:')}</div>
<ul>
<li>{t('By key: use row names as sorting key')}</li>
<li>{t('By value: use metric values as sorting key')}</li>
</ul>
</>
),
},
},
],
[
{
name: 'colOrder',
config: {
type: 'SelectControl',
label: t('Sort columns by'),
default: 'key_a_to_z',
choices: [
// [value, label]
['key_a_to_z', t('key a-z')],
['key_z_to_a', t('key z-a')],
['value_a_to_z', t('value ascending')],
['value_z_to_a', t('value descending')],
],
renderTrigger: true,
description: (
<>
<div>{t('Change order of columns.')}</div>
<div>{t('Available sorting modes:')}</div>
<ul>
<li>{t('By key: use column names as sorting key')}</li>
<li>{t('By value: use metric values as sorting key')}</li>
</ul>
</>
),
},
},
],
[
{
name: 'rowSubtotalPosition',
config: {
type: 'SelectControl',
label: t('Rows subtotal position'),
default: false,
choices: [
// [value, label]
[true, t('Top')],
[false, t('Bottom')],
],
renderTrigger: true,
description: t('Position of row level subtotal'),
},
},
],
[
{
name: 'colSubtotalPosition',
config: {
type: 'SelectControl',
label: t('Columns subtotal position'),
default: false,
choices: [
// [value, label]
[true, t('Left')],
[false, t('Right')],
],
renderTrigger: true,
description: t('Position of column level subtotal'),
},
},
],
[
{
name: 'conditional_formatting',
config: {
type: 'ConditionalFormattingControl',
renderTrigger: true,
label: t('Conditional formatting'),
description: t('Apply conditional color formatting to metrics'),
mapStateToProps(explore, _, chart) {
const values =
(explore?.controls?.metrics?.value as QueryFormMetric[]) ??
[];
const verboseMap = explore?.datasource?.hasOwnProperty(
'verbose_map',
)
? (explore?.datasource as Dataset)?.verbose_map
: (explore?.datasource?.columns ?? {});
const chartStatus = chart?.chartStatus;
const metricColumn = values.map(value => {
if (typeof value === 'string') {
return { value, label: verboseMap[value] ?? value };
}
return { value: value.label, label: value.label };
});
return {
removeIrrelevantConditions: chartStatus === 'success',
columnOptions: metricColumn,
verboseMap,
};
},
},
},
],
[
{
name: 'allow_render_html',
config: {
type: 'CheckboxControl',
label: t('Render columns in HTML format'),
renderTrigger: true,
default: true,
description: t('Render data in HTML format if applicable.'),
},
},
],
],
},
],
formDataOverrides: formData => {
const groupbyColumns = getStandardizedControls().controls.columns.filter(
col => !ensureIsArray(formData.groupbyRows).includes(col),
);
getStandardizedControls().controls.columns =
getStandardizedControls().controls.columns.filter(
col => !groupbyColumns.includes(col),
);
return {
...formData,
metrics: getStandardizedControls().popAllMetrics(),
groupbyColumns,
};
},
};
export default config;