blob: 39c32e7b8df3edc7652d7bbb4e909134ad1e9ed1 [file] [log] [blame]
/* 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 { t, validateNonEmpty } from '@superset-ui/core';
import { SharedControlConfig } from '../types';
import { dndAdhocMetricControl } from './dndControls';
import { defineSavedMetrics } from '../utils';
/**
* Matrixify control definitions
* Controls for transforming charts into matrix/grid layouts
*/
// Utility function to check if matrixify controls should be visible
const isMatrixifyVisible = (
controls: any,
axis: 'rows' | 'columns',
mode?: 'metrics' | 'dimensions',
selectionMode?: 'members' | 'topn',
) => {
const layoutControl = `matrixify_enable_${axis === 'rows' ? 'vertical' : 'horizontal'}_layout`;
const modeControl = `matrixify_mode_${axis}`;
const selectionModeControl = `matrixify_dimension_selection_mode_${axis}`;
const isLayoutEnabled = controls?.[layoutControl]?.value === true;
if (!isLayoutEnabled) return false;
if (mode) {
const isModeMatch = controls?.[modeControl]?.value === mode;
if (!isModeMatch) return false;
if (selectionMode && mode === 'dimensions') {
return controls?.[selectionModeControl]?.value === selectionMode;
}
}
return true;
};
// Initialize the controls object that will be populated dynamically
const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
// Dynamically add axis-specific controls (rows and columns)
(['columns', 'rows'] as const).forEach(axisParam => {
const axis: 'rows' | 'columns' = axisParam;
matrixifyControls[`matrixify_mode_${axis}`] = {
type: 'RadioButtonControl',
label: t(`Metrics / Dimensions`),
default: 'metrics',
options: [
['metrics', t('Metrics')],
['dimensions', t('Dimension members')],
],
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) => isMatrixifyVisible(controls, axis),
};
matrixifyControls[`matrixify_${axis}`] = {
...dndAdhocMetricControl,
label: t(`Metrics`),
multi: true,
validators: [], // No validation - rely on visibility
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) => isMatrixifyVisible(controls, axis, 'metrics'),
};
// Combined dimension and values control
matrixifyControls[`matrixify_dimension_${axis}`] = {
type: 'MatrixifyDimensionControl',
label: t(`Dimension selection`),
description: t(`Select dimension and values`),
default: { dimension: '', values: [] },
validators: [], // No validation - rely on visibility
renderTrigger: true,
tabOverride: 'matrixify',
shouldMapStateToProps: (prevState, state) => {
// Recalculate when any relevant form_data field changes
const fieldsToCheck = [
`matrixify_topn_value_${axis}`,
`matrixify_topn_metric_${axis}`,
`matrixify_topn_order_${axis}`,
`matrixify_dimension_selection_mode_${axis}`,
];
return fieldsToCheck.some(
field => prevState?.form_data?.[field] !== state?.form_data?.[field],
);
},
mapStateToProps: ({ datasource, controls, form_data }) => {
// Helper to get value from form_data or controls
const getValue = (key: string, defaultValue?: any) =>
form_data?.[key] ?? controls?.[key]?.value ?? defaultValue;
const selectionMode = getValue(
`matrixify_dimension_selection_mode_${axis}`,
'members',
);
const isVisible = isMatrixifyVisible(controls, axis, 'dimensions');
// Validate dimension is selected when visible
const dimensionValidator = (value: any) => {
if (!value?.dimension) {
return t('Dimension is required');
}
return false;
};
// Additional validation for topN mode
const validators = isVisible
? [dimensionValidator, validateNonEmpty]
: [];
return {
datasource,
selectionMode,
topNMetric: getValue(`matrixify_topn_metric_${axis}`),
topNValue: getValue(`matrixify_topn_value_${axis}`),
topNOrder: getValue(`matrixify_topn_order_${axis}`),
formData: form_data,
validators,
};
},
visibility: ({ controls }) =>
isMatrixifyVisible(controls, axis, 'dimensions'),
};
matrixifyControls[`matrixify_topn_dimension_${axis}`] = {
type: 'SelectControl',
label: t('Dimension'),
description: t(`Select dimension for Top N`),
default: null,
mapStateToProps: ({ datasource }) => ({
choices:
datasource?.columns?.map((col: any) => [
col.column_name,
col.column_name,
]) || [],
}),
renderTrigger: true,
// Hide this control - now handled by matrixify_dimension control
visibility: () => false,
};
// Add selection mode control (Dimension Members vs TopN)
matrixifyControls[`matrixify_dimension_selection_mode_${axis}`] = {
type: 'RadioButtonControl',
label: t(`Selection method`),
default: 'members',
options: [
['members', t('Dimension members')],
['topn', t('Top n')],
],
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) =>
isMatrixifyVisible(controls, axis, 'dimensions'),
};
// TopN controls
matrixifyControls[`matrixify_topn_value_${axis}`] = {
type: 'NumberControl',
label: t(`Number of top values`),
description: t(`How many top values to select`),
default: 10,
isInt: true,
validators: [],
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) =>
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
mapStateToProps: ({ controls }) => {
const isVisible = isMatrixifyVisible(
controls,
axis,
'dimensions',
'topn',
);
return {
validators: isVisible ? [validateNonEmpty] : [],
};
},
};
matrixifyControls[`matrixify_topn_metric_${axis}`] = {
...dndAdhocMetricControl,
label: t(`Metric for ordering`),
multi: false,
validators: [],
description: t(`Metric to use for ordering Top N values`),
tabOverride: 'matrixify',
visibility: ({ controls }) =>
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
mapStateToProps: (state, controlState) => {
const { controls, datasource } = state;
const isVisible = isMatrixifyVisible(
controls,
axis,
'dimensions',
'topn',
);
const originalProps =
dndAdhocMetricControl.mapStateToProps?.(state, controlState) || {};
return {
...originalProps,
columns: datasource?.columns || [],
savedMetrics: defineSavedMetrics(datasource),
datasource,
datasourceType: datasource?.type,
validators: isVisible ? [validateNonEmpty] : [],
};
},
};
matrixifyControls[`matrixify_topn_order_${axis}`] = {
type: 'RadioButtonControl',
label: t(`Sort order`),
default: 'desc',
options: [
['asc', t('Ascending')],
['desc', t('Descending')],
],
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) =>
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
};
});
// Grid layout controls (added once, not per axis)
matrixifyControls.matrixify_row_height = {
type: 'TextControl',
label: t('Row height'),
description: t('Height of each row in pixels'),
default: 300,
isInt: true,
validators: [],
renderTrigger: true,
};
matrixifyControls.matrixify_fit_columns_dynamically = {
type: 'CheckboxControl',
label: t('Fit columns dynamically'),
description: t('Automatically adjust column width based on available space'),
default: true,
renderTrigger: true,
};
matrixifyControls.matrixify_charts_per_row = {
type: 'SelectControl',
label: t('Charts per row'),
description: t('Number of charts to display per row'),
default: 4,
choices: [
[1, '1'],
[2, '2'],
[3, '3'],
[4, '4'],
[5, '5'],
[6, '6'],
[8, '8'],
[10, '10'],
[12, '12'],
],
freeForm: true,
clearable: false,
renderTrigger: true,
visibility: ({ controls }) =>
!controls?.matrixify_fit_columns_dynamically?.value,
};
matrixifyControls.matrixify_enable_vertical_layout = {
type: 'CheckboxControl',
label: t('Enable vertical layout (rows)'),
description: t('Create matrix rows by stacking charts vertically'),
default: false,
renderTrigger: true,
tabOverride: 'matrixify',
};
matrixifyControls.matrixify_enable_horizontal_layout = {
type: 'CheckboxControl',
label: t('Enable horizontal layout (columns)'),
description: t('Create matrix columns by placing charts side-by-side'),
default: false,
renderTrigger: true,
tabOverride: 'matrixify',
};
// Cell title control for Matrixify
matrixifyControls.matrixify_cell_title_template = {
type: 'TextControl',
label: t('Title'),
description: t(
'Customize cell titles using Handlebars template syntax. Available variables: {{rowLabel}}, {{colLabel}}',
),
default: '',
renderTrigger: true,
visibility: ({ controls }) =>
controls?.matrixify_enable_vertical_layout?.value === true ||
controls?.matrixify_enable_horizontal_layout?.value === true,
};
// Matrix display controls
matrixifyControls.matrixify_show_row_labels = {
type: 'CheckboxControl',
label: t('Show row labels'),
description: t('Display labels for each row on the left side of the matrix'),
default: true,
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) =>
controls?.matrixify_enable_vertical_layout?.value === true,
};
matrixifyControls.matrixify_show_column_headers = {
type: 'CheckboxControl',
label: t('Show column headers'),
description: t('Display headers for each column at the top of the matrix'),
default: true,
renderTrigger: true,
tabOverride: 'matrixify',
visibility: ({ controls }) =>
controls?.matrixify_enable_horizontal_layout?.value === true,
};
export { matrixifyControls };