Skip to content

Commit ad3eff9

Browse files
mistercrunchclaudemsyavuz
authored
feat(matrixify): replace single toggle with separate horizontal/vertical layout controls (#35067)
Co-authored-by: Claude <[email protected]> Co-authored-by: Mehmet Salih Yavuz <[email protected]>
1 parent 3e55467 commit ad3eff9

File tree

20 files changed

+493
-231
lines changed

20 files changed

+493
-231
lines changed

superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ describe('Charts list', () => {
175175
interceptDelete();
176176
cy.getBySel('sort-header').contains('Name').click();
177177

178-
// Modal closes immediatly without this
178+
// Modal closes immediately without this
179179
cy.wait(2000);
180180

181181
cy.getBySel('table-row').eq(0).contains('3 - Sample chart');

superset-frontend/packages/superset-ui-chart-controls/src/sections/matrixify.tsx

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,32 @@ import { t } from '@superset-ui/core';
2020
import { ControlPanelSectionConfig } from '../types';
2121

2222
export const matrixifyEnableSection: ControlPanelSectionConfig = {
23-
label: t('Enable matrixify'),
23+
label: t('Matrixify'),
2424
expanded: true,
2525
controlSetRows: [
2626
[
2727
{
28-
name: 'matrixify_enabled',
28+
name: 'matrixify_enable_horizontal_layout',
2929
config: {
3030
type: 'CheckboxControl',
31-
label: t('Enable matrixify'),
32-
default: false,
33-
renderTrigger: true,
31+
label: t('Enable horizontal layout (columns)'),
3432
description: t(
35-
'Transform this chart into a matrix/grid of charts based on dimensions or metrics',
33+
'Create matrix columns by placing charts side-by-side',
3634
),
35+
default: false,
36+
renderTrigger: true,
37+
},
38+
},
39+
],
40+
[
41+
{
42+
name: 'matrixify_enable_vertical_layout',
43+
config: {
44+
type: 'CheckboxControl',
45+
label: t('Enable vertical layout (rows)'),
46+
description: t('Create matrix rows by stacking charts vertically'),
47+
default: false,
48+
renderTrigger: true,
3749
},
3850
},
3951
],
@@ -42,9 +54,11 @@ export const matrixifyEnableSection: ControlPanelSectionConfig = {
4254
};
4355

4456
export const matrixifySection: ControlPanelSectionConfig = {
45-
label: t('Matrixify'),
57+
label: t('Cell layout & styling'),
4658
expanded: false,
47-
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
59+
visibility: ({ controls }) =>
60+
controls?.matrixify_enable_vertical_layout?.value === true ||
61+
controls?.matrixify_enable_horizontal_layout?.value === true,
4862
controlSetRows: [
4963
[
5064
{
@@ -105,9 +119,10 @@ export const matrixifySection: ControlPanelSectionConfig = {
105119
};
106120

107121
export const matrixifyRowSection: ControlPanelSectionConfig = {
108-
label: t('Vertical layout'),
122+
label: t('Vertical layout (rows)'),
109123
expanded: false,
110-
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
124+
visibility: ({ controls }) =>
125+
controls?.matrixify_enable_vertical_layout?.value === true,
111126
controlSetRows: [
112127
['matrixify_show_row_labels'],
113128
['matrixify_mode_rows'],
@@ -118,13 +133,14 @@ export const matrixifyRowSection: ControlPanelSectionConfig = {
118133
['matrixify_topn_metric_rows'],
119134
['matrixify_topn_order_rows'],
120135
],
121-
tabOverride: 'data',
136+
tabOverride: 'matrixify',
122137
};
123138

124139
export const matrixifyColumnSection: ControlPanelSectionConfig = {
125-
label: t('Horizontal layout'),
140+
label: t('Horizontal layout (columns)'),
126141
expanded: false,
127-
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
142+
visibility: ({ controls }) =>
143+
controls?.matrixify_enable_horizontal_layout?.value === true,
128144
controlSetRows: [
129145
['matrixify_show_column_headers'],
130146
['matrixify_mode_columns'],
@@ -135,5 +151,5 @@ export const matrixifyColumnSection: ControlPanelSectionConfig = {
135151
['matrixify_topn_metric_columns'],
136152
['matrixify_topn_order_columns'],
137153
],
138-
tabOverride: 'data',
154+
tabOverride: 'matrixify',
139155
};

superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.tsx

Lines changed: 124 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,49 @@
1818
* under the License.
1919
*/
2020

21-
import { t } from '@superset-ui/core';
21+
import { t, validateNonEmpty } from '@superset-ui/core';
2222
import { SharedControlConfig } from '../types';
2323
import { dndAdhocMetricControl } from './dndControls';
24+
import { defineSavedMetrics } from '../utils';
2425

2526
/**
2627
* Matrixify control definitions
2728
* Controls for transforming charts into matrix/grid layouts
2829
*/
2930

31+
// Utility function to check if matrixify controls should be visible
32+
const isMatrixifyVisible = (
33+
controls: any,
34+
axis: 'rows' | 'columns',
35+
mode?: 'metrics' | 'dimensions',
36+
selectionMode?: 'members' | 'topn',
37+
) => {
38+
const layoutControl = `matrixify_enable_${axis === 'rows' ? 'vertical' : 'horizontal'}_layout`;
39+
const modeControl = `matrixify_mode_${axis}`;
40+
const selectionModeControl = `matrixify_dimension_selection_mode_${axis}`;
41+
42+
const isLayoutEnabled = controls?.[layoutControl]?.value === true;
43+
44+
if (!isLayoutEnabled) return false;
45+
46+
if (mode) {
47+
const isModeMatch = controls?.[modeControl]?.value === mode;
48+
if (!isModeMatch) return false;
49+
50+
if (selectionMode && mode === 'dimensions') {
51+
return controls?.[selectionModeControl]?.value === selectionMode;
52+
}
53+
}
54+
55+
return true;
56+
};
57+
3058
// Initialize the controls object that will be populated dynamically
3159
const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
3260

3361
// Dynamically add axis-specific controls (rows and columns)
34-
['columns', 'rows'].forEach(axisParam => {
35-
const axis = axisParam; // Capture the value in a local variable
62+
(['columns', 'rows'] as const).forEach(axisParam => {
63+
const axis: 'rows' | 'columns' = axisParam;
3664

3765
matrixifyControls[`matrixify_mode_${axis}`] = {
3866
type: 'RadioButtonControl',
@@ -43,17 +71,18 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
4371
['dimensions', t('Dimension members')],
4472
],
4573
renderTrigger: true,
74+
tabOverride: 'matrixify',
75+
visibility: ({ controls }) => isMatrixifyVisible(controls, axis),
4676
};
4777

4878
matrixifyControls[`matrixify_${axis}`] = {
4979
...dndAdhocMetricControl,
5080
label: t(`Metrics`),
5181
multi: true,
52-
validators: [], // Not required
53-
// description: t(`Select metrics for ${axis}`),
82+
validators: [], // No validation - rely on visibility
5483
renderTrigger: true,
55-
visibility: ({ controls }) =>
56-
controls?.[`matrixify_mode_${axis}`]?.value === 'metrics',
84+
tabOverride: 'matrixify',
85+
visibility: ({ controls }) => isMatrixifyVisible(controls, axis, 'metrics'),
5786
};
5887

5988
// Combined dimension and values control
@@ -62,8 +91,9 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
6291
label: t(`Dimension selection`),
6392
description: t(`Select dimension and values`),
6493
default: { dimension: '', values: [] },
65-
validators: [], // Not required
94+
validators: [], // No validation - rely on visibility
6695
renderTrigger: true,
96+
tabOverride: 'matrixify',
6797
shouldMapStateToProps: (prevState, state) => {
6898
// Recalculate when any relevant form_data field changes
6999
const fieldsToCheck = [
@@ -82,24 +112,40 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
82112
const getValue = (key: string, defaultValue?: any) =>
83113
form_data?.[key] ?? controls?.[key]?.value ?? defaultValue;
84114

115+
const selectionMode = getValue(
116+
`matrixify_dimension_selection_mode_${axis}`,
117+
'members',
118+
);
119+
120+
const isVisible = isMatrixifyVisible(controls, axis, 'dimensions');
121+
122+
// Validate dimension is selected when visible
123+
const dimensionValidator = (value: any) => {
124+
if (!value?.dimension) {
125+
return t('Dimension is required');
126+
}
127+
return false;
128+
};
129+
130+
// Additional validation for topN mode
131+
const validators = isVisible
132+
? [dimensionValidator, validateNonEmpty]
133+
: [];
134+
85135
return {
86136
datasource,
87-
selectionMode: getValue(
88-
`matrixify_dimension_selection_mode_${axis}`,
89-
'members',
90-
),
137+
selectionMode,
91138
topNMetric: getValue(`matrixify_topn_metric_${axis}`),
92139
topNValue: getValue(`matrixify_topn_value_${axis}`),
93140
topNOrder: getValue(`matrixify_topn_order_${axis}`),
94141
formData: form_data,
142+
validators,
95143
};
96144
},
97145
visibility: ({ controls }) =>
98-
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions',
146+
isMatrixifyVisible(controls, axis, 'dimensions'),
99147
};
100148

101-
// Dimension picker for TopN mode (just dimension, no values)
102-
// NOTE: This is now handled by matrixify_dimension control, so hiding it
103149
matrixifyControls[`matrixify_topn_dimension_${axis}`] = {
104150
type: 'SelectControl',
105151
label: t('Dimension'),
@@ -127,33 +173,67 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
127173
['topn', t('Top n')],
128174
],
129175
renderTrigger: true,
176+
tabOverride: 'matrixify',
130177
visibility: ({ controls }) =>
131-
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions',
178+
isMatrixifyVisible(controls, axis, 'dimensions'),
132179
};
133180

134181
// TopN controls
135182
matrixifyControls[`matrixify_topn_value_${axis}`] = {
136-
type: 'TextControl',
183+
type: 'NumberControl',
137184
label: t(`Number of top values`),
138185
description: t(`How many top values to select`),
139186
default: 10,
140187
isInt: true,
188+
validators: [],
189+
renderTrigger: true,
190+
tabOverride: 'matrixify',
141191
visibility: ({ controls }) =>
142-
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
143-
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
192+
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
193+
mapStateToProps: ({ controls }) => {
194+
const isVisible = isMatrixifyVisible(
195+
controls,
196+
axis,
197+
'dimensions',
144198
'topn',
199+
);
200+
201+
return {
202+
validators: isVisible ? [validateNonEmpty] : [],
203+
};
204+
},
145205
};
146206

147207
matrixifyControls[`matrixify_topn_metric_${axis}`] = {
148208
...dndAdhocMetricControl,
149209
label: t(`Metric for ordering`),
150210
multi: false,
151-
validators: [], // Not required
211+
validators: [],
152212
description: t(`Metric to use for ordering Top N values`),
213+
tabOverride: 'matrixify',
153214
visibility: ({ controls }) =>
154-
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
155-
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
215+
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
216+
mapStateToProps: (state, controlState) => {
217+
const { controls, datasource } = state;
218+
const isVisible = isMatrixifyVisible(
219+
controls,
220+
axis,
221+
'dimensions',
156222
'topn',
223+
);
224+
225+
const originalProps =
226+
dndAdhocMetricControl.mapStateToProps?.(state, controlState) || {};
227+
228+
return {
229+
...originalProps,
230+
columns: datasource?.columns || [],
231+
savedMetrics: defineSavedMetrics(datasource),
232+
datasource,
233+
datasourceType: datasource?.type,
234+
validators: isVisible ? [validateNonEmpty] : [],
235+
};
236+
},
157237
};
158238

159239
matrixifyControls[`matrixify_topn_order_${axis}`] = {
@@ -164,10 +244,10 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
164244
['asc', t('Ascending')],
165245
['desc', t('Descending')],
166246
],
247+
renderTrigger: true,
248+
tabOverride: 'matrixify',
167249
visibility: ({ controls }) =>
168-
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
169-
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
170-
'topn',
250+
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
171251
};
172252
});
173253

@@ -213,15 +293,22 @@ matrixifyControls.matrixify_charts_per_row = {
213293
!controls?.matrixify_fit_columns_dynamically?.value,
214294
};
215295

216-
// Main enable control
217-
matrixifyControls.matrixify_enabled = {
296+
matrixifyControls.matrixify_enable_vertical_layout = {
218297
type: 'CheckboxControl',
219-
label: t('Enable matrixify'),
220-
description: t(
221-
'Transform this chart into a matrix/grid of charts based on dimensions or metrics',
222-
),
298+
label: t('Enable vertical layout (rows)'),
299+
description: t('Create matrix rows by stacking charts vertically'),
300+
default: false,
301+
renderTrigger: true,
302+
tabOverride: 'matrixify',
303+
};
304+
305+
matrixifyControls.matrixify_enable_horizontal_layout = {
306+
type: 'CheckboxControl',
307+
label: t('Enable horizontal layout (columns)'),
308+
description: t('Create matrix columns by placing charts side-by-side'),
223309
default: false,
224310
renderTrigger: true,
311+
tabOverride: 'matrixify',
225312
};
226313

227314
// Cell title control for Matrixify
@@ -234,8 +321,8 @@ matrixifyControls.matrixify_cell_title_template = {
234321
default: '',
235322
renderTrigger: true,
236323
visibility: ({ controls }) =>
237-
(controls?.matrixify_mode_rows?.value ||
238-
controls?.matrixify_mode_columns?.value) !== undefined,
324+
controls?.matrixify_enable_vertical_layout?.value === true ||
325+
controls?.matrixify_enable_horizontal_layout?.value === true,
239326
};
240327

241328
// Matrix display controls
@@ -245,9 +332,9 @@ matrixifyControls.matrixify_show_row_labels = {
245332
description: t('Display labels for each row on the left side of the matrix'),
246333
default: true,
247334
renderTrigger: true,
335+
tabOverride: 'matrixify',
248336
visibility: ({ controls }) =>
249-
(controls?.matrixify_mode_rows?.value ||
250-
controls?.matrixify_mode_columns?.value) !== undefined,
337+
controls?.matrixify_enable_vertical_layout?.value === true,
251338
};
252339

253340
matrixifyControls.matrixify_show_column_headers = {
@@ -256,9 +343,9 @@ matrixifyControls.matrixify_show_column_headers = {
256343
description: t('Display headers for each column at the top of the matrix'),
257344
default: true,
258345
renderTrigger: true,
346+
tabOverride: 'matrixify',
259347
visibility: ({ controls }) =>
260-
(controls?.matrixify_mode_rows?.value ||
261-
controls?.matrixify_mode_columns?.value) !== undefined,
348+
controls?.matrixify_enable_horizontal_layout?.value === true,
262349
};
263350

264351
export { matrixifyControls };

0 commit comments

Comments
 (0)