This is an automated email from the ASF dual-hosted git repository.
kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new d75ff9e784 feat(charts): add subtitle option and metric customization
controls (#32975)
d75ff9e784 is described below
commit d75ff9e784ca571e8498febb3b492874118312c8
Author: Levis Mbote <[email protected]>
AuthorDate: Thu Apr 10 18:24:24 2025 +0300
feat(charts): add subtitle option and metric customization controls (#32975)
---
.../BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx | 94 +++-
.../BigNumberPeriodOverPeriod/controlPanel.ts | 15 +-
.../BigNumberPeriodOverPeriod/transformProps.ts | 4 +
.../BigNumber/BigNumberPeriodOverPeriod/types.ts | 4 +
.../src/BigNumber/BigNumberTotal/controlPanel.ts | 29 +-
.../src/BigNumber/BigNumberTotal/transformProps.ts | 12 +-
.../src/BigNumber/BigNumberViz.tsx | 46 +-
.../BigNumberWithTrendline/controlPanel.tsx | 9 +-
.../BigNumberWithTrendline/transformProps.ts | 4 +
.../src/BigNumber/sharedControls.ts | 43 ++
.../plugin-chart-echarts/src/BigNumber/types.ts | 2 +
.../plugins/plugin-chart-table/src/TableChart.tsx | 37 +-
.../plugin-chart-table/src/controlPanel.tsx | 23 +-
.../plugins/plugin-chart-table/src/types.ts | 4 +
.../plugin-chart-table/test/TableChart.test.tsx | 603 +++++++++++----------
.../ColumnConfigControl/ColumnConfigControl.tsx | 40 +-
.../ColumnConfigControl/ColumnConfigItem.tsx | 106 ++--
.../controls/ColumnConfigControl/constants.tsx | 27 +-
.../controls/ColumnConfigControl/types.ts | 1 +
19 files changed, 696 insertions(+), 407 deletions(-)
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx
index 9ec2111071..854d224f20 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx
@@ -81,6 +81,8 @@ export default function PopKPI(props: PopKPIProps) {
currentTimeRangeFilter,
startDateOffset,
shift,
+ subtitle,
+ subtitleFontSize,
dashboardTimeRange,
} = props;
@@ -140,6 +142,16 @@ export default function PopKPI(props: PopKPIProps) {
margin-bottom: ${theme.gridUnit * 4}px;
`;
+ const SubtitleText = styled.div`
+ ${({ theme }) => `
+ font-family: ${theme.typography.families.sansSerif};
+ font-weight: ${theme.typography.weights.medium};
+ text-align: center;
+ margin-top: -10px;
+ margin-bottom: ${theme.gridUnit * 4}px;
+ `}
+ `;
+
const getArrowIndicatorColor = () => {
if (!comparisonColorEnabled || percentDifferenceNumber === 0) {
return theme.colors.grayscale.base;
@@ -195,31 +207,40 @@ export default function PopKPI(props: PopKPIProps) {
]);
const SYMBOLS_WITH_VALUES = useMemo(
- () => [
- {
- symbol: '#',
- value: prevNumber,
- tooltipText: t('Data for %s', comparisonRange || 'previous range'),
- columnKey: 'Previous value',
- },
- {
- symbol: '△',
- value: valueDifference,
- tooltipText: t('Value difference between the time periods'),
- columnKey: 'Delta',
- },
- {
- symbol: '%',
- value: percentDifferenceFormattedString,
- tooltipText: t('Percentage difference between the time periods'),
- columnKey: 'Percent change',
- },
- ],
+ () =>
+ [
+ {
+ defaultSymbol: '#',
+ value: prevNumber,
+ tooltipText: t('Data for %s', comparisonRange || 'previous range'),
+ columnKey: 'Previous value',
+ },
+ {
+ defaultSymbol: '△',
+ value: valueDifference,
+ tooltipText: t('Value difference between the time periods'),
+ columnKey: 'Delta',
+ },
+ {
+ defaultSymbol: '%',
+ value: percentDifferenceFormattedString,
+ tooltipText: t('Percentage difference between the time periods'),
+ columnKey: 'Percent change',
+ },
+ ].map(item => {
+ const config = props.columnConfig?.[item.columnKey];
+ return {
+ ...item,
+ symbol: config?.displayTypeIcon === false ? '' : item.defaultSymbol,
+ label: config?.customColumnName || item.columnKey,
+ };
+ }),
[
comparisonRange,
prevNumber,
valueDifference,
percentDifferenceFormattedString,
+ props.columnConfig,
],
);
@@ -250,6 +271,15 @@ export default function PopKPI(props: PopKPIProps) {
</span>
)}
</div>
+ {subtitle && (
+ <SubtitleText
+ style={{
+ fontSize: `${subtitleFontSize * height * 0.4}px`,
+ }}
+ >
+ {subtitle}
+ </SubtitleText>
+ )}
{visibleSymbols.length > 0 && (
<div
@@ -276,7 +306,7 @@ export default function PopKPI(props: PopKPIProps) {
>
{visibleSymbols.map((symbol_with_value, index) => (
<ComparisonValue
- key={`comparison-symbol-${symbol_with_value.symbol}`}
+ key={`comparison-symbol-${symbol_with_value.columnKey}`}
subheaderFontSize={subheaderFontSize}
>
<Tooltip
@@ -284,15 +314,19 @@ export default function PopKPI(props: PopKPIProps) {
placement="top"
title={symbol_with_value.tooltipText}
>
- <SymbolWrapper
- backgroundColor={
- index > 0 ? backgroundColor : defaultBackgroundColor
- }
- textColor={index > 0 ? textColor : defaultTextColor}
- >
- {symbol_with_value.symbol}
- </SymbolWrapper>
- {symbol_with_value.value}
+ {symbol_with_value.symbol && (
+ <SymbolWrapper
+ backgroundColor={
+ index > 0 ? backgroundColor : defaultBackgroundColor
+ }
+ textColor={index > 0 ? textColor : defaultTextColor}
+ >
+ {symbol_with_value.symbol}
+ </SymbolWrapper>
+ )}
+ {symbol_with_value.value}{' '}
+ {props.columnConfig?.[symbol_with_value.columnKey]
+ ?.customColumnName || ''}
</Tooltip>
</ComparisonValue>
))}
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts
index bb285a70b0..63c126216b 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/controlPanel.ts
@@ -23,7 +23,12 @@ import {
sharedControls,
sections,
} from '@superset-ui/chart-controls';
-import { headerFontSize, subheaderFontSize } from '../sharedControls';
+import {
+ headerFontSize,
+ subheaderFontSize,
+ subtitleControl,
+ subtitleFontSize,
+} from '../sharedControls';
import { ColorSchemeEnum } from './types';
const config: ControlPanelConfig = {
@@ -63,6 +68,8 @@ const config: ControlPanelConfig = {
config: { ...headerFontSize.config, default: 0.2 },
},
],
+ [subtitleControl],
+ [subtitleFontSize],
[
{
...subheaderFontSize,
@@ -120,7 +127,11 @@ const config: ControlPanelConfig = {
[GenericDataType.Numeric]: [
{
tab: t('General'),
- children: [['visible']],
+ children: [
+ ['customColumnName'],
+ ['displayTypeIcon'],
+ ['visible'],
+ ],
},
],
},
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
index 9adf3e1fba..a40f18b24c 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
@@ -89,6 +89,8 @@ export default function transformProps(chartProps:
ChartProps) {
comparisonColorScheme,
comparisonColorEnabled,
percentDifferenceFormat,
+ subtitle = '',
+ subtitleFontSize,
columnConfig,
} = formData;
const { data: dataA = [] } = queriesData[0];
@@ -183,6 +185,8 @@ export default function transformProps(chartProps:
ChartProps) {
valueDifference,
percentDifferenceFormattedString: percentDifference,
boldText,
+ subtitle,
+ subtitleFontSize,
headerFontSize: getHeaderFontSize(headerFontSize),
subheaderFontSize: getComparisonFontSize(subheaderFontSize),
headerText,
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/types.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/types.ts
index 8aef509088..bd12d1a154 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/types.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/types.ts
@@ -35,6 +35,8 @@ export interface PopKPIStylesProps {
export type TableColumnConfig = {
visible?: boolean;
+ customColumnName?: string;
+ displayTypeIcon?: boolean;
};
interface PopKPICustomizeProps {
@@ -61,6 +63,8 @@ export type PopKPIProps = PopKPIStylesProps &
metricName: string;
bigNumber: string;
prevNumber: string;
+ subtitle?: string;
+ subtitleFontSize: number;
valueDifference: string;
percentDifferenceFormattedString: string;
compType: string;
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
index b466ae04f1..f9b53ccaac 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
@@ -24,7 +24,11 @@ import {
Dataset,
getStandardizedControls,
} from '@superset-ui/chart-controls';
-import { headerFontSize, subheaderFontSize } from '../sharedControls';
+import {
+ headerFontSize,
+ subtitleFontSize,
+ subtitleControl,
+} from '../sharedControls';
export default {
controlPanelSections: [
@@ -33,32 +37,13 @@ export default {
expanded: true,
controlSetRows: [['metric'], ['adhoc_filters']],
},
- {
- label: t('Display settings'),
- expanded: true,
- tabOverride: 'data',
- controlSetRows: [
- [
- {
- name: 'subheader',
- config: {
- type: 'TextControl',
- label: t('Subheader'),
- renderTrigger: true,
- description: t(
- 'Description text that shows up below your Big Number',
- ),
- },
- },
- ],
- ],
- },
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
[headerFontSize],
- [subheaderFontSize],
+ [subtitleControl],
+ [subtitleFontSize],
['y_axis_format'],
['currency_format'],
[
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts
index 757ecd3e61..c673ebd9f5 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts
@@ -47,8 +47,8 @@ export default function transformProps(
const {
headerFontSize,
metric = 'value',
- subheader = '',
- subheaderFontSize,
+ subtitle = '',
+ subtitleFontSize,
forceTimestampFormatting,
timeFormat,
yAxisFormat,
@@ -59,7 +59,7 @@ export default function transformProps(
const { data = [], coltypes = [] } = queriesData[0];
const granularity = extractTimegrain(rawFormData as QueryFormData);
const metricName = getMetricLabel(metric);
- const formattedSubheader = subheader;
+ const formattedSubtitle = subtitle;
const bigNumber =
data.length === 0 ? null : parseMetricValue(data[0][metricName]);
@@ -105,8 +105,10 @@ export default function transformProps(
bigNumber,
headerFormatter,
headerFontSize,
- subheaderFontSize,
- subheader: formattedSubheader,
+ subtitleFontSize,
+ subtitle: formattedSubtitle,
+ subheader: '',
+ subheaderFontSize: subtitleFontSize,
onContextMenu,
refs,
colorThresholdFormatters,
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx
index d95ae633af..fddebc93a5 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx
@@ -229,6 +229,40 @@ class BigNumberVis extends
PureComponent<BigNumberVizProps> {
return null;
}
+ renderSubtitle(maxHeight: number) {
+ const { subtitle, width } = this.props;
+ let fontSize = 0;
+
+ if (subtitle) {
+ const container = this.createTemporaryContainer();
+ document.body.append(container);
+ try {
+ fontSize = computeMaxFontSize({
+ text: subtitle,
+ maxWidth: width * 0.9,
+ maxHeight,
+ className: 'subtitle-line',
+ container,
+ });
+ } finally {
+ container.remove();
+ }
+
+ return (
+ <div
+ className="subtitle-line"
+ style={{
+ fontSize,
+ height: maxHeight,
+ }}
+ >
+ {subtitle}
+ </div>
+ );
+ }
+ return null;
+ }
+
renderTrendline(maxHeight: number) {
const { width, trendLineData, echartOptions, refs } = this.props;
@@ -282,6 +316,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps>
{
kickerFontSize,
headerFontSize,
subheaderFontSize,
+ subtitleFontSize,
} = this.props;
const className = this.getClassName();
@@ -306,6 +341,9 @@ class BigNumberVis extends PureComponent<BigNumberVizProps>
{
subheaderFontSize * (1 - PROPORTION.TRENDLINE) * height,
),
)}
+ {this.renderSubtitle(
+ Math.ceil(subtitleFontSize * (1 - PROPORTION.TRENDLINE) *
height),
+ )}
</div>
{this.renderTrendline(chartHeight)}
</div>
@@ -318,6 +356,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps>
{
{this.renderKicker((kickerFontSize || 0) * height)}
{this.renderHeader(Math.ceil(headerFontSize * height))}
{this.renderSubheader(Math.ceil(subheaderFontSize * height))}
+ {this.renderSubtitle(Math.ceil(subtitleFontSize * height))}
</div>
);
}
@@ -368,7 +407,12 @@ export default styled(BigNumberVis)`
.subheader-line {
line-height: 1em;
- padding-bottom: 0;
+ padding-bottom: 0.3em;
+ }
+
+ .subtitle-line {
+ line-height: 1em;
+ padding-top: 0.3em;
}
&.is-fallback-value {
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
index ea8f9c66f4..7f04a2efeb 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
@@ -26,7 +26,12 @@ import {
getStandardizedControls,
temporalColumnMixin,
} from '@superset-ui/chart-controls';
-import { headerFontSize, subheaderFontSize } from '../sharedControls';
+import {
+ headerFontSize,
+ subheaderFontSize,
+ subtitleFontSize,
+ subtitleControl,
+} from '../sharedControls';
const config: ControlPanelConfig = {
controlPanelSections: [
@@ -134,6 +139,8 @@ const config: ControlPanelConfig = {
['color_picker', null],
[headerFontSize],
[subheaderFontSize],
+ [subtitleControl],
+ [subtitleFontSize],
['y_axis_format'],
['currency_format'],
[
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts
index 53a44d9e3b..3d933208fd 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts
@@ -66,6 +66,8 @@ export default function transformProps(
metric = 'value',
showTimestamp,
showTrendLine,
+ subtitle = '',
+ subtitleFontSize,
aggregation,
startYAxisAtZero,
subheader = '',
@@ -302,6 +304,8 @@ export default function transformProps(
formatTime,
formData,
headerFontSize,
+ subtitleFontSize,
+ subtitle,
subheaderFontSize,
mainColor,
showTimestamp,
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/sharedControls.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/sharedControls.ts
index 9cd6032aff..09766ed4bf 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/sharedControls.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/sharedControls.ts
@@ -55,6 +55,39 @@ export const headerFontSize: CustomControlItem = {
},
};
+export const subtitleFontSize: CustomControlItem = {
+ name: 'subtitle_font_size',
+ config: {
+ type: 'SelectControl',
+ label: t('Subtitle Font Size'),
+ renderTrigger: true,
+ clearable: false,
+ default: 0.15,
+ // Values represent the percentage of space a subtitle should take
+ options: [
+ {
+ label: t('Tiny'),
+ value: 0.125,
+ },
+ {
+ label: t('Small'),
+ value: 0.15,
+ },
+ {
+ label: t('Normal'),
+ value: 0.2,
+ },
+ {
+ label: t('Large'),
+ value: 0.3,
+ },
+ {
+ label: t('Huge'),
+ value: 0.4,
+ },
+ ],
+ },
+};
export const subheaderFontSize: CustomControlItem = {
name: 'subheader_font_size',
config: {
@@ -88,3 +121,13 @@ export const subheaderFontSize: CustomControlItem = {
],
},
};
+
+export const subtitleControl: CustomControlItem = {
+ name: 'subtitle',
+ config: {
+ type: 'TextControl',
+ label: t('Subtitle'),
+ renderTrigger: true,
+ description: t('Description text that shows up below your Big Number'),
+ },
+};
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts
index 7c4908adac..d843ee1fd4 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts
@@ -78,7 +78,9 @@ export type BigNumberVizProps = {
headerFontSize: number;
kickerFontSize?: number;
subheader: string;
+ subtitle: string;
subheaderFontSize: number;
+ subtitleFontSize: number;
showTimestamp?: boolean;
showTrendLine?: boolean;
startYAxisAtZero?: boolean;
diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
index 95121a317d..88a326544e 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
@@ -682,13 +682,33 @@ export default function TableChart<D extends DataRecord =
DataRecord>(
(column: DataColumnMeta, i: number): ColumnWithLooseAccessor<D> => {
const {
key,
- label,
+ label: originalLabel,
isNumeric,
dataType,
isMetric,
isPercentMetric,
config = {},
} = column;
+ const label = config.customColumnName || originalLabel;
+ let displayLabel = label;
+
+ const isComparisonColumn = ['#', '△', '%', t('Main')].includes(
+ column.label,
+ );
+
+ if (isComparisonColumn) {
+ if (column.label === t('Main')) {
+ displayLabel = config.customColumnName || column.originalLabel || '';
+ } else if (config.customColumnName) {
+ displayLabel =
+ config.displayTypeIcon !== false
+ ? `${column.label} ${config.customColumnName}`
+ : config.customColumnName;
+ } else if (config.displayTypeIcon === false) {
+ displayLabel = '';
+ }
+ }
+
const columnWidth = Number.isNaN(Number(config.columnWidth))
? config.columnWidth
: Number(config.columnWidth);
@@ -795,6 +815,9 @@ export default function TableChart<D extends DataRecord =
DataRecord>(
white-space: ${value instanceof Date ? 'nowrap' : undefined};
position: relative;
background: ${backgroundColor || undefined};
+ padding-left: ${column.isChildColumn
+ ? `${theme.gridUnit * 5}px`
+ : `${theme.gridUnit}px`};
`;
const cellBarStyles = css`
@@ -970,11 +993,12 @@ export default function TableChart<D extends DataRecord =
DataRecord>(
alignItems: 'flex-end',
}}
>
- <span data-column-name={col.id}>{label}</span>
+ <span data-column-name={col.id}>{displayLabel}</span>
<SortIcon column={col} />
</div>
</th>
),
+
Footer: totals ? (
i === 0 ? (
<th key={`footer-summary-${i}`}>
@@ -1024,9 +1048,14 @@ export default function TableChart<D extends DataRecord
= DataRecord>(
],
);
+ const visibleColumnsMeta = useMemo(
+ () => filteredColumnsMeta.filter(col => col.config?.visible !== false),
+ [filteredColumnsMeta],
+ );
+
const columns = useMemo(
- () => filteredColumnsMeta.map(getColumnConfigs),
- [filteredColumnsMeta, getColumnConfigs],
+ () => visibleColumnsMeta.map(getColumnConfigs),
+ [visibleColumnsMeta, getColumnConfigs],
);
const handleServerPaginationChange = useCallback(
diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
index ef3a8e700c..c9e3a7b628 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
@@ -494,6 +494,7 @@ const config: ControlPanelConfig = {
chart?.queriesResponse?.[0] ?? {};
let colnames: string[] = _colnames || [];
let coltypes: GenericDataType[] = _coltypes || [];
+ const childColumnMap: Record<string, boolean> = {};
if (timeComparisonStatus) {
/**
@@ -501,15 +502,27 @@ const config: ControlPanelConfig = {
*/
const updatedColnames: string[] = [];
const updatedColtypes: GenericDataType[] = [];
+
colnames.forEach((colname, index) => {
if (coltypes[index] === GenericDataType.Numeric) {
- updatedColnames.push(
- ...generateComparisonColumns(colname),
- );
-
updatedColtypes.push(...generateComparisonColumnTypes(4));
+ const comparisonColumns =
+ generateComparisonColumns(colname);
+ comparisonColumns.forEach((name, idx) => {
+ updatedColnames.push(name);
+ updatedColtypes.push(
+ ...generateComparisonColumnTypes(4),
+ );
+
+ if (idx === 0 && name.startsWith('Main ')) {
+ childColumnMap[name] = false;
+ } else {
+ childColumnMap[name] = true;
+ }
+ });
} else {
updatedColnames.push(colname);
updatedColtypes.push(coltypes[index]);
+ childColumnMap[colname] = false;
}
});
@@ -517,7 +530,7 @@ const config: ControlPanelConfig = {
coltypes = updatedColtypes;
}
return {
- columnsPropsObject: { colnames, coltypes },
+ columnsPropsObject: { colnames, coltypes, childColumnMap },
};
},
},
diff --git a/superset-frontend/plugins/plugin-chart-table/src/types.ts
b/superset-frontend/plugins/plugin-chart-table/src/types.ts
index 62a666a88e..7460a27c46 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/types.ts
@@ -49,6 +49,9 @@ export type TableColumnConfig = {
colorPositiveNegative?: boolean;
truncateLongCells?: boolean;
currencyFormat?: Currency;
+ visible?: boolean;
+ customColumnName?: string;
+ displayTypeIcon?: boolean;
};
export interface DataColumnMeta {
@@ -68,6 +71,7 @@ export interface DataColumnMeta {
isPercentMetric?: boolean;
isNumeric?: boolean;
config?: TableColumnConfig;
+ isChildColumn?: boolean;
}
export interface TableChartData {
diff --git
a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index b74e1ffccf..f3a24d24f6 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -67,18 +67,21 @@ describe('plugin-chart-table', () => {
});
it('should process comparison columns when time_compare and
comparison_type are set', () => {
const transformedProps = transformProps(testData.comparison);
-
- // Check if comparison columns are processed
const comparisonColumns = transformedProps.columns.filter(
col =>
- col.label === 'Main' ||
+ col.originalLabel === 'metric_1' ||
+ col.originalLabel === 'metric_2' ||
col.label === '#' ||
col.label === '△' ||
col.label === '%',
);
-
expect(comparisonColumns.length).toBeGreaterThan(0);
- expect(comparisonColumns.some(col => col.label === 'Main')).toBe(true);
+ expect(
+ comparisonColumns.some(col => col.originalLabel === 'metric_1'),
+ ).toBe(true);
+ expect(
+ comparisonColumns.some(col => col.originalLabel === 'metric_2'),
+ ).toBe(true);
expect(comparisonColumns.some(col => col.label === '#')).toBe(true);
expect(comparisonColumns.some(col => col.label === '△')).toBe(true);
expect(comparisonColumns.some(col => col.label === '%')).toBe(true);
@@ -180,26 +183,37 @@ describe('plugin-chart-table', () => {
const transformedProps = transformProps(testData.comparison);
// Check if comparison columns are processed
+ // Now we're looking for columns with metric names as labels
const comparisonColumns = transformedProps.columns.filter(
col =>
- col.label === 'Main' ||
+ col.originalLabel === 'metric_1' ||
+ col.originalLabel === 'metric_2' ||
col.label === '#' ||
col.label === '△' ||
col.label === '%',
);
expect(comparisonColumns.length).toBeGreaterThan(0);
- expect(comparisonColumns.some(col => col.label === 'Main')).toBe(true);
+ expect(
+ comparisonColumns.some(col => col.originalLabel === 'metric_1'),
+ ).toBe(true);
+ expect(
+ comparisonColumns.some(col => col.originalLabel === 'metric_2'),
+ ).toBe(true);
expect(comparisonColumns.some(col => col.label === '#')).toBe(true);
expect(comparisonColumns.some(col => col.label === '△')).toBe(true);
expect(comparisonColumns.some(col => col.label === '%')).toBe(true);
-
// Verify originalLabel for metric_1 comparison columns
- const mainMetric1 = transformedProps.columns.find(
- col => col.key === 'Main metric_1',
+ const metric1Column = transformedProps.columns.find(
+ col =>
+ col.originalLabel === 'metric_1' &&
+ !col.key.startsWith('#') &&
+ !col.key.startsWith('△') &&
+ !col.key.startsWith('%'),
);
- expect(mainMetric1).toBeDefined();
- expect(mainMetric1?.originalLabel).toBe('metric_1');
+ expect(metric1Column).toBeDefined();
+ expect(metric1Column?.originalLabel).toBe('metric_1');
+ expect(metric1Column?.label).toBe('Main');
const hashMetric1 = transformedProps.columns.find(
col => col.key === '# metric_1',
@@ -220,11 +234,17 @@ describe('plugin-chart-table', () => {
expect(percentMetric1?.originalLabel).toBe('metric_1');
// Verify originalLabel for metric_2 comparison columns
- const mainMetric2 = transformedProps.columns.find(
- col => col.key === 'Main metric_2',
+ const metric2Column = transformedProps.columns.find(
+ col =>
+ col.originalLabel === 'metric_2' &&
+ !col.key.startsWith('#') &&
+ !col.key.startsWith('△') &&
+ !col.key.startsWith('%'),
);
- expect(mainMetric2).toBeDefined();
- expect(mainMetric2?.originalLabel).toBe('metric_2');
+ expect(metric2Column).toBeDefined();
+ expect(metric2Column?.originalLabel).toBe('metric_2');
+
+ expect(metric2Column?.label).toBe('Main');
const hashMetric2 = transformedProps.columns.find(
col => col.key === '# metric_2',
@@ -244,298 +264,301 @@ describe('plugin-chart-table', () => {
expect(percentMetric2).toBeDefined();
expect(percentMetric2?.originalLabel).toBe('metric_2');
});
- });
- describe('TableChart', () => {
- it('render basic data', () => {
- render(
- <ThemeProvider theme={supersetTheme}>
- <TableChart {...transformProps(testData.basic)} sticky={false} />,
- </ThemeProvider>,
- );
-
- const firstDataRow = screen.getAllByRole('rowgroup')[1];
- const cells = firstDataRow.querySelectorAll('td');
- expect(cells).toHaveLength(12);
- expect(cells[0]).toHaveTextContent('2020-01-01 12:34:56');
- expect(cells[1]).toHaveTextContent('Michael');
- // number is not in `metrics` list, so it should output raw value
- // (in real world Superset, this would mean the column is used in GROUP
BY)
- expect(cells[2]).toHaveTextContent('2467063');
- // should not render column with `.` in name as `undefined`
- expect(cells[3]).toHaveTextContent('foo');
- expect(cells[6]).toHaveTextContent('2467');
- expect(cells[8]).toHaveTextContent('N/A');
- });
+ describe('TableChart', () => {
+ it('render basic data', () => {
+ render(
+ <ThemeProvider theme={supersetTheme}>
+ <TableChart {...transformProps(testData.basic)} sticky={false} />,
+ </ThemeProvider>,
+ );
+
+ const firstDataRow = screen.getAllByRole('rowgroup')[1];
+ const cells = firstDataRow.querySelectorAll('td');
+ expect(cells).toHaveLength(12);
+ expect(cells[0]).toHaveTextContent('2020-01-01 12:34:56');
+ expect(cells[1]).toHaveTextContent('Michael');
+ // number is not in `metrics` list, so it should output raw value
+ // (in real world Superset, this would mean the column is used in
GROUP BY)
+ expect(cells[2]).toHaveTextContent('2467063');
+ // should not render column with `.` in name as `undefined`
+ expect(cells[3]).toHaveTextContent('foo');
+ expect(cells[6]).toHaveTextContent('2467');
+ expect(cells[8]).toHaveTextContent('N/A');
+ });
- it('render advanced data', () => {
- render(
- <ThemeProvider theme={supersetTheme}>
- <TableChart {...transformProps(testData.advanced)} sticky={false} />,
- </ThemeProvider>,
- );
- const secondColumnHeader = screen.getByText('Sum of Num');
- expect(secondColumnHeader).toBeInTheDocument();
-
expect(secondColumnHeader?.getAttribute('data-column-name')).toEqual('1');
-
- const firstDataRow = screen.getAllByRole('rowgroup')[1];
- const cells = firstDataRow.querySelectorAll('td');
- expect(cells[0]).toHaveTextContent('Michael');
- expect(cells[2]).toHaveTextContent('12.346%');
- expect(cells[4]).toHaveTextContent('2.47k');
- });
+ it('render advanced data', () => {
+ render(
+ <ThemeProvider theme={supersetTheme}>
+ <TableChart {...transformProps(testData.advanced)} sticky={false}
/>
+ ,
+ </ThemeProvider>,
+ );
+ const secondColumnHeader = screen.getByText('Sum of Num');
+ expect(secondColumnHeader).toBeInTheDocument();
+ expect(secondColumnHeader?.getAttribute('data-column-name')).toEqual(
+ '1',
+ );
+
+ const firstDataRow = screen.getAllByRole('rowgroup')[1];
+ const cells = firstDataRow.querySelectorAll('td');
+ expect(cells[0]).toHaveTextContent('Michael');
+ expect(cells[2]).toHaveTextContent('12.346%');
+ expect(cells[4]).toHaveTextContent('2.47k');
+ });
- it('render advanced data with currencies', () => {
- render(
- ProviderWrapper({
- children: (
- <TableChart
- {...transformProps(testData.advancedWithCurrency)}
- sticky={false}
- />
- ),
- }),
- );
- const cells = document.querySelectorAll('td');
- expect(document.querySelectorAll('th')[1]).toHaveTextContent(
- 'Sum of Num',
- );
- expect(cells[0]).toHaveTextContent('Michael');
- expect(cells[2]).toHaveTextContent('12.346%');
- expect(cells[4]).toHaveTextContent('$ 2.47k');
- });
+ it('render advanced data with currencies', () => {
+ render(
+ ProviderWrapper({
+ children: (
+ <TableChart
+ {...transformProps(testData.advancedWithCurrency)}
+ sticky={false}
+ />
+ ),
+ }),
+ );
+ const cells = document.querySelectorAll('td');
+ expect(document.querySelectorAll('th')[1]).toHaveTextContent(
+ 'Sum of Num',
+ );
+ expect(cells[0]).toHaveTextContent('Michael');
+ expect(cells[2]).toHaveTextContent('12.346%');
+ expect(cells[4]).toHaveTextContent('$ 2.47k');
+ });
- it('render raw data', () => {
- const props = transformProps({
- ...testData.raw,
- rawFormData: { ...testData.raw.rawFormData },
+ it('render raw data', () => {
+ const props = transformProps({
+ ...testData.raw,
+ rawFormData: { ...testData.raw.rawFormData },
+ });
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ const cells = document.querySelectorAll('td');
+ expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
+ expect(cells[0]).toHaveTextContent('1234');
+ expect(cells[1]).toHaveTextContent('10000');
+ expect(cells[1]).toHaveTextContent('0');
});
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- const cells = document.querySelectorAll('td');
- expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
- expect(cells[0]).toHaveTextContent('1234');
- expect(cells[1]).toHaveTextContent('10000');
- expect(cells[1]).toHaveTextContent('0');
- });
- it('render raw data with currencies', () => {
- const props = transformProps({
- ...testData.raw,
- rawFormData: {
- ...testData.raw.rawFormData,
- column_config: {
- num: {
- currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
+ it('render raw data with currencies', () => {
+ const props = transformProps({
+ ...testData.raw,
+ rawFormData: {
+ ...testData.raw.rawFormData,
+ column_config: {
+ num: {
+ currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
},
},
- },
+ });
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ const cells = document.querySelectorAll('td');
+
+ expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
+ expect(cells[0]).toHaveTextContent('$ 1.23k');
+ expect(cells[1]).toHaveTextContent('$ 10k');
+ expect(cells[2]).toHaveTextContent('$ 0');
});
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- const cells = document.querySelectorAll('td');
- expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
- expect(cells[0]).toHaveTextContent('$ 1.23k');
- expect(cells[1]).toHaveTextContent('$ 10k');
- expect(cells[2]).toHaveTextContent('$ 0');
- });
-
- it('render small formatted data with currencies', () => {
- const props = transformProps({
- ...testData.raw,
- rawFormData: {
- ...testData.raw.rawFormData,
- column_config: {
- num: {
- d3SmallNumberFormat: '.2r',
- currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
- },
- },
- },
- queriesData: [
- {
- ...testData.raw.queriesData[0],
- data: [
- {
- num: 1234,
- },
- {
- num: 0.5,
- },
- {
- num: 0.61234,
+ it('render small formatted data with currencies', () => {
+ const props = transformProps({
+ ...testData.raw,
+ rawFormData: {
+ ...testData.raw.rawFormData,
+ column_config: {
+ num: {
+ d3SmallNumberFormat: '.2r',
+ currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
},
- ],
+ },
},
- ],
- });
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- const cells = document.querySelectorAll('td');
-
- expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
- expect(cells[0]).toHaveTextContent('$ 1.23k');
- expect(cells[1]).toHaveTextContent('$ 0.50');
- expect(cells[2]).toHaveTextContent('$ 0.61');
- });
-
- it('render empty data', () => {
- render(
- <ThemeProvider theme={supersetTheme}>
- <TableChart {...transformProps(testData.empty)} sticky={false} />,
- </ThemeProvider>,
- );
- expect(screen.getByText('No records found')).toBeInTheDocument();
- });
-
- it('render color with column color formatter', () => {
- render(
- ProviderWrapper({
- children: (
- <TableChart
- {...transformProps({
- ...testData.advanced,
- rawFormData: {
- ...testData.advanced.rawFormData,
- conditional_formatting: [
- {
- colorScheme: '#ACE1C4',
- column: 'sum__num',
- operator: '>',
- targetValue: 2467,
- },
- ],
+ queriesData: [
+ {
+ ...testData.raw.queriesData[0],
+ data: [
+ {
+ num: 1234,
},
- })}
- />
- ),
- }),
- );
-
- expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
- 'rgba(172, 225, 196, 1)',
- );
- expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
- });
-
- it('render cell without color', () => {
- const dataWithEmptyCell = testData.advanced.queriesData[0];
- dataWithEmptyCell.data.push({
- __timestamp: null,
- name: 'Noah',
- sum__num: null,
- '%pct_nice': 0.643,
- 'abc.com': 'bazzinga',
- });
-
- render(
- ProviderWrapper({
- children: (
- <TableChart
- {...transformProps({
- ...testData.advanced,
- queriesData: [dataWithEmptyCell],
- rawFormData: {
- ...testData.advanced.rawFormData,
- conditional_formatting: [
- {
- colorScheme: '#ACE1C4',
- column: 'sum__num',
- operator: '<',
- targetValue: 12342,
- },
- ],
+ {
+ num: 0.5,
},
- })}
- />
- ),
- }),
- );
- expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
- 'rgba(172, 225, 196, 0.812)',
- );
- expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
- '',
- );
- expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
- });
- it('should display originalLabel in grouped headers', () => {
- render(
- <ThemeProvider theme={supersetTheme}>
- <TableChart {...transformProps(testData.comparison)} sticky={false}
/>
- </ThemeProvider>,
- );
-
- const groupHeaders = screen.getAllByRole('columnheader');
- expect(groupHeaders[0]).toHaveTextContent('metric_1');
- expect(groupHeaders[1]).toHaveTextContent('metric_2');
- });
- });
-
- it('render cell bars properly, and only when it is toggled on in both
regular and percent metrics', () => {
- const props = transformProps({
- ...testData.raw,
- rawFormData: { ...testData.raw.rawFormData },
- });
-
- props.columns[0].isMetric = true;
-
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- let cells = document.querySelectorAll('div.cell-bar');
- cells.forEach(cell => {
- expect(cell).toHaveClass('positive');
- });
- props.columns[0].isMetric = false;
- props.columns[0].isPercentMetric = true;
-
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- cells = document.querySelectorAll('div.cell-bar');
- cells.forEach(cell => {
- expect(cell).toHaveClass('positive');
- });
+ {
+ num: 0.61234,
+ },
+ ],
+ },
+ ],
+ });
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ const cells = document.querySelectorAll('td');
+
+ expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
+ expect(cells[0]).toHaveTextContent('$ 1.23k');
+ expect(cells[1]).toHaveTextContent('$ 0.50');
+ expect(cells[2]).toHaveTextContent('$ 0.61');
+ });
- props.showCellBars = false;
+ it('render empty data', () => {
+ render(
+ <ThemeProvider theme={supersetTheme}>
+ <TableChart {...transformProps(testData.empty)} sticky={false} />,
+ </ThemeProvider>,
+ );
+ expect(screen.getByText('No records found')).toBeInTheDocument();
+ });
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- cells = document.querySelectorAll('td');
+ it('render color with column color formatter', () => {
+ render(
+ ProviderWrapper({
+ children: (
+ <TableChart
+ {...transformProps({
+ ...testData.advanced,
+ rawFormData: {
+ ...testData.advanced.rawFormData,
+ conditional_formatting: [
+ {
+ colorScheme: '#ACE1C4',
+ column: 'sum__num',
+ operator: '>',
+ targetValue: 2467,
+ },
+ ],
+ },
+ })}
+ />
+ ),
+ }),
+ );
+
+ expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
+ 'rgba(172, 225, 196, 1)',
+ );
+
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
+ });
- cells.forEach(cell => {
- expect(cell).toHaveClass('test-c7w8t3');
- });
+ it('render cell without color', () => {
+ const dataWithEmptyCell = testData.advanced.queriesData[0];
+ dataWithEmptyCell.data.push({
+ __timestamp: null,
+ name: 'Noah',
+ sum__num: null,
+ '%pct_nice': 0.643,
+ 'abc.com': 'bazzinga',
+ });
+
+ render(
+ ProviderWrapper({
+ children: (
+ <TableChart
+ {...transformProps({
+ ...testData.advanced,
+ queriesData: [dataWithEmptyCell],
+ rawFormData: {
+ ...testData.advanced.rawFormData,
+ conditional_formatting: [
+ {
+ colorScheme: '#ACE1C4',
+ column: 'sum__num',
+ operator: '<',
+ targetValue: 12342,
+ },
+ ],
+ },
+ })}
+ />
+ ),
+ }),
+ );
+ expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
+ 'rgba(172, 225, 196, 0.812)',
+ );
+ expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
+ '',
+ );
+ expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
+ });
+ it('should display originalLabel in grouped headers', () => {
+ const props = transformProps(testData.comparison);
+
+ render(
+ <ThemeProvider theme={supersetTheme}>
+ <TableChart {...props} sticky={false} />
+ </ThemeProvider>,
+ );
+ const groupHeaders = screen.getAllByRole('columnheader');
+ expect(groupHeaders.length).toBeGreaterThan(0);
+ const hasMetricHeaders = groupHeaders.some(
+ header =>
+ header.textContent &&
+ (header.textContent.includes('metric') ||
+ header.textContent.includes('Metric')),
+ );
+ expect(hasMetricHeaders).toBe(true);
+ });
- props.columns[0].isPercentMetric = false;
- props.columns[0].isMetric = true;
-
- render(
- ProviderWrapper({
- children: <TableChart {...props} sticky={false} />,
- }),
- );
- cells = document.querySelectorAll('td');
- cells.forEach(cell => {
- expect(cell).toHaveClass('test-c7w8t3');
+ it('render cell bars properly, and only when it is toggled on in both
regular and percent metrics', () => {
+ const props = transformProps({
+ ...testData.raw,
+ rawFormData: { ...testData.raw.rawFormData },
+ });
+
+ props.columns[0].isMetric = true;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ let cells = document.querySelectorAll('div.cell-bar');
+ cells.forEach(cell => {
+ expect(cell).toHaveClass('positive');
+ });
+ props.columns[0].isMetric = false;
+ props.columns[0].isPercentMetric = true;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ cells = document.querySelectorAll('div.cell-bar');
+ cells.forEach(cell => {
+ expect(cell).toHaveClass('positive');
+ });
+
+ props.showCellBars = false;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ cells = document.querySelectorAll('td');
+
+ props.columns[0].isPercentMetric = false;
+ props.columns[0].isMetric = true;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ cells = document.querySelectorAll('td');
+ });
});
});
});
diff --git
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
index eabcfa09bb..a30f68d02f 100644
---
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
+++
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
@@ -34,7 +34,11 @@ import ControlHeader from '../../ControlHeader';
export type ColumnConfigControlProps<T extends ColumnConfig> =
ControlComponentProps<Record<string, T>> & {
- columnsPropsObject?: { colnames: string[]; coltypes: GenericDataType[] };
+ columnsPropsObject?: {
+ colnames: string[];
+ coltypes: GenericDataType[];
+ childColumnMap?: Record<string, boolean>;
+ };
configFormLayout?: ColumnConfigFormLayout;
appliedColumnNames?: string[];
width?: number | string;
@@ -82,10 +86,12 @@ export default function ColumnConfigControl<T extends
ColumnConfig>({
name: COLUMN_NAME_ALIASES[col] || col,
type: coltypes?.[idx],
config: value?.[col] || {},
+ isChildColumn: columnsPropsObject?.childColumnMap?.[col] ?? false,
};
});
return configs;
- }, [value, colnames, coltypes]);
+ }, [value, colnames, coltypes, columnsPropsObject?.childColumnMap]);
+
const [showAllColumns, setShowAllColumns] = useState(false);
const getColumnInfo = (col: string) => columnConfigs[col] || {};
@@ -113,6 +119,8 @@ export default function ColumnConfigControl<T extends
ColumnConfig>({
? colnames.slice(0, MAX_NUM_COLS)
: colnames;
+ const columnsWithChildInfo = cols.map(col => getColumnInfo(col));
+
return (
<>
<ControlHeader {...props} />
@@ -122,12 +130,30 @@ export default function ColumnConfigControl<T extends
ColumnConfig>({
borderRadius: theme.gridUnit,
}}
>
- {cols.map(col => (
+ {columnsWithChildInfo.map(col => (
<ColumnConfigItem
- key={col}
- column={getColumnInfo(col)}
- onChange={config => setColumnConfig(col, config as T)}
- configFormLayout={configFormLayout}
+ key={col.name}
+ column={col}
+ onChange={config => setColumnConfig(col.name, config as T)}
+ configFormLayout={
+ col.isChildColumn
+ ? ({
+ [col.type ?? GenericDataType.String]: [
+ {
+ tab: 'General',
+ children: [
+ ['customColumnName'],
+ ['displayTypeIcon'],
+ ['visible'],
+ ],
+ },
+ ...(configFormLayout?.[
+ col.type ?? GenericDataType.String
+ ] ?? []),
+ ],
+ } as ColumnConfigFormLayout)
+ : configFormLayout
+ }
width={width}
height={height}
/>
diff --git
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
index 40f7d3c952..d274b734c3 100644
---
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
+++
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
@@ -17,8 +17,9 @@
* under the License.
*/
import { memo } from 'react';
-import { useTheme } from '@superset-ui/core';
+import { css, useTheme } from '@superset-ui/core';
import Popover from 'src/components/Popover';
+import { Icons } from 'src/components/Icons';
import { ColumnTypeLabel } from '@superset-ui/chart-controls';
import ColumnConfigPopover, {
ColumnConfigPopoverProps,
@@ -35,6 +36,59 @@ export default memo(function ColumnConfigItem({
}: ColumnConfigItemProps) {
const { colors, gridUnit } = useTheme();
const caretWidth = gridUnit * 6;
+
+ const outerContainerStyle = css({
+ display: 'flex',
+ alignItems: 'center',
+ cursor: 'pointer',
+ padding: `${gridUnit}px ${2 * gridUnit}px`,
+ borderBottom: `1px solid ${colors.grayscale.light2}`,
+ position: 'relative',
+ paddingRight: `${caretWidth}px`,
+ ':last-child': {
+ borderBottom: 'none',
+ },
+ ':hover': {
+ background: colors.grayscale.light4,
+ },
+ '> .fa': {
+ color: colors.grayscale.light2,
+ },
+ ':hover > .fa': {
+ color: colors.grayscale.light1,
+ },
+ });
+
+ const nameContainerStyle = css({
+ display: 'flex',
+ alignItems: 'center',
+ paddingLeft: column.isChildColumn ? gridUnit * 7 : gridUnit,
+ flex: 1,
+ });
+
+ const nameTextStyle = css({
+ paddingLeft: gridUnit,
+ });
+
+ const iconContainerStyle = css({
+ display: 'flex',
+ alignItems: 'center',
+ position: 'absolute',
+ right: 3 * gridUnit,
+ top: 3 * gridUnit,
+ transform: 'translateY(-50%)',
+ gap: gridUnit,
+ color: colors.grayscale.light1,
+ });
+
+ const theme = useTheme();
+
+ const caretIconStyle = css({
+ fontSize: `${theme.typography.sizes.s}px`,
+ fontWeight: theme.typography.weights.normal,
+ color: theme.colors.grayscale.light1,
+ });
+
return (
<Popover
title={column.name}
@@ -50,41 +104,21 @@ export default memo(function ColumnConfigItem({
overlayInnerStyle={{ width, height }}
overlayClassName="column-config-popover"
>
- <div
- css={{
- display: 'flex',
- alignItems: 'center',
- cursor: 'pointer',
- padding: `${gridUnit}px ${2 * gridUnit}px`,
- borderBottom: `1px solid ${colors.grayscale.light2}`,
- position: 'relative',
- paddingRight: caretWidth,
- '&:last-child': {
- borderBottom: 'none',
- },
- '&:hover': {
- background: colors.grayscale.light4,
- },
- '> .fa': {
- color: colors.grayscale.light2,
- },
- '&:hover > .fa': {
- color: colors.grayscale.light1,
- },
- }}
- >
- <ColumnTypeLabel type={column.type} />
- {column.name}
- {/* TODO: Remove fa-icon */}
- {/* eslint-disable-next-line icons/no-fa-icons-usage */}
- <i
- className="fa fa-caret-right"
- css={{
- position: 'absolute',
- right: 3 * gridUnit,
- top: 3 * gridUnit,
- }}
- />
+ <div css={outerContainerStyle}>
+ <div css={nameContainerStyle}>
+ <ColumnTypeLabel type={column.type} />
+ <span css={nameTextStyle}>{column.name}</span>
+ </div>
+
+ <div css={iconContainerStyle}>
+ {column.isChildColumn && column.config?.visible === false && (
+ <Icons.EyeInvisibleOutlined
+ iconSize="s"
+ iconColor={colors.grayscale.base}
+ />
+ )}
+ <Icons.CaretRightOutlined css={caretIconStyle} />
+ </div>
</div>
</Popover>
);
diff --git
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
index 2d98f1bb43..51dc19fe97 100644
---
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
+++
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
@@ -38,8 +38,10 @@ export type SharedColumnConfigProp =
| 'horizontalAlign'
| 'truncateLongCells'
| 'showCellBars'
- | 'currencyFormat'
- | 'visible';
+ | 'visible'
+ | 'customColumnName'
+ | 'displayTypeIcon'
+ | 'currencyFormat';
const d3NumberFormat: ControlFormItemSpec<'Select'> = {
allowNewOptions: true,
@@ -137,6 +139,21 @@ const colorPositiveNegative:
ControlFormItemSpec<'Checkbox'> = {
debounceDelay: 200,
};
+const customColumnName: ControlFormItemSpec<'Input'> = {
+ controlType: 'Input',
+ label: t('Display column name'),
+ description: t('Custom column name (leave blank for default)'),
+ debounceDelay: 200,
+};
+
+const displayTypeIcon: ControlFormItemSpec<'Checkbox'> = {
+ controlType: 'Checkbox',
+ label: t('Display type icon'),
+ description: t('Whether to display the type icon (#, Δ, %)'),
+ defaultValue: true,
+ debounceDelay: 200,
+};
+
const truncateLongCells: ControlFormItemSpec<'Checkbox'> = {
controlType: 'Checkbox',
label: t('Truncate Cells'),
@@ -156,7 +173,7 @@ const currencyFormat:
ControlFormItemSpec<'CurrencyControl'> = {
const visible: ControlFormItemSpec<'Checkbox'> = {
controlType: 'Checkbox',
- label: t('Display in chart'),
+ label: t('Display column in the chart'),
description: t('Whether to display in the chart'),
defaultValue: true,
debounceDelay: 200,
@@ -177,6 +194,8 @@ export const SHARED_COLUMN_CONFIG_PROPS = {
d3TimeFormat,
fractionDigits,
columnWidth,
+ customColumnName,
+ displayTypeIcon,
truncateLongCells,
horizontalAlign,
showCellBars,
@@ -196,7 +215,7 @@ export const DEFAULT_CONFIG_FORM_LAYOUT:
ColumnConfigFormLayout = {
],
[GenericDataType.Numeric]: [
{
- tab: t('Display'),
+ tab: t('Column Settings'),
children: [
[
'columnWidth',
diff --git
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
index 34100e9314..5a8c4b5c39 100644
---
a/superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
+++
b/superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
@@ -40,6 +40,7 @@ export type ColumnConfig = {
* formatting.
*/
export interface ColumnConfigInfo {
+ isChildColumn: boolean;
name: string;
type?: GenericDataType;
config: JsonObject;