This is an automated email from the ASF dual-hosted git repository.

jli 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 4140261797 fix: subheader should show as subtitle (#33172)
4140261797 is described below

commit 41402617970368f0b14ff4e7acc1d271d5bcdd89
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Thu Apr 17 22:03:20 2025 -0700

    fix: subheader should show as subtitle (#33172)
---
 .../e2e/explore/visualizations/big_number.test.js  |   4 +-
 .../BigNumber/BigNumberTotal/controlPanel.test.ts  |  97 +++++++++
 .../BigNumberTotal/transformProps.test.ts          | 220 +++++++++++++++++++++
 .../src/BigNumber/BigNumberTotal/transformProps.ts |  11 +-
 .../src/BigNumber/BigNumberViz.tsx                 |  48 -----
 .../src/BigNumber/sharedControls.ts                |   3 +
 .../plugin-chart-echarts/src/BigNumber/types.ts    |   2 -
 7 files changed, 327 insertions(+), 58 deletions(-)

diff --git 
a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/big_number.test.js
 
b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/big_number.test.js
index 2882f6ab4a..72d9e92710 100644
--- 
a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/big_number.test.js
+++ 
b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/big_number.test.js
@@ -56,7 +56,6 @@ describe('Visualization > Big Number with Trendline', () => {
   it('should work', () => {
     verify(BIG_NUMBER_FORM_DATA);
     cy.get('.chart-container .header-line');
-    cy.get('.chart-container .subheader-line');
     cy.get('.chart-container canvas');
   });
 
@@ -66,7 +65,7 @@ describe('Visualization > Big Number with Trendline', () => {
       compare_lag: null,
     });
     cy.get('.chart-container .header-line');
-    cy.get('.chart-container .subheader-line').should('not.exist');
+    cy.get('.chart-container .subtitle-line').should('not.exist');
     cy.get('.chart-container canvas');
   });
 
@@ -76,7 +75,6 @@ describe('Visualization > Big Number with Trendline', () => {
       show_trend_line: false,
     });
     cy.get('[data-test="chart-container"] .header-line');
-    cy.get('[data-test="chart-container"] .subheader-line');
     cy.get('[data-test="chart-container"] canvas').should('not.exist');
   });
 });
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.test.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.test.ts
new file mode 100644
index 0000000000..4be15b9e4b
--- /dev/null
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.test.ts
@@ -0,0 +1,97 @@
+/**
+ * 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 { SqlaFormData } from '@superset-ui/core';
+import * as ChartControls from '@superset-ui/chart-controls';
+import controlPanel from './controlPanel';
+
+const { __mockShiftMetric } = ChartControls as any;
+
+jest.mock('@superset-ui/core', () => ({
+  GenericDataType: { Numeric: 'numeric' },
+  SMART_DATE_ID: 'SMART_DATE_ID',
+  t: (str: string) => str,
+}));
+
+jest.mock('@superset-ui/chart-controls', () => {
+  // Define the mock function inside the factory
+  const mockShiftMetric = jest.fn(() => 'shiftedMetric');
+  return {
+    ControlPanelConfig: {},
+    D3_FORMAT_DOCS: 'Format docs',
+    D3_TIME_FORMAT_OPTIONS: [['', 'default']],
+    getStandardizedControls: () => ({
+      shiftMetric: mockShiftMetric,
+    }),
+    // Optional export to let tests access the mock
+    __mockShiftMetric: mockShiftMetric,
+  };
+});
+
+describe('BigNumber Total Control Panel Config', () => {
+  it('should have the required control panel sections', () => {
+    expect(controlPanel).toHaveProperty('controlPanelSections');
+    const sections = controlPanel.controlPanelSections;
+    expect(Array.isArray(sections)).toBe(true);
+    expect(sections.length).toBe(2);
+
+    // First section should have label 'Query' and contain rows with metric 
and adhoc_filters
+    expect(sections[0]!.label).toBe('Query');
+    expect(Array.isArray(sections[0]!.controlSetRows)).toBe(true);
+    expect(sections[0]!.controlSetRows[0]).toEqual(['metric']);
+    expect(sections[0]!.controlSetRows[1]).toEqual(['adhoc_filters']);
+
+    // Second section should contain a control named subtitle
+    const secondSectionRow = sections[1]!.controlSetRows[1];
+    expect(secondSectionRow[0]).toHaveProperty('name', 'subtitle');
+
+    // Second section should include controls for time_format and 
conditional_formatting
+    const thirdSection = sections[1]!.controlSetRows;
+    // Check time_format control exists in one of the rows
+    const timeFormatRow = thirdSection.find(row =>
+      row.some((control: any) => control.name === 'time_format'),
+    );
+    expect(timeFormatRow).toBeTruthy();
+    // Check conditional_formatting control exists in one of the rows
+    const conditionalFormattingRow = thirdSection.find(row =>
+      row.some((control: any) => control.name === 'conditional_formatting'),
+    );
+    expect(conditionalFormattingRow).toBeTruthy();
+  });
+
+  it('should have y_axis_format override with correct label', () => {
+    expect(controlPanel).toHaveProperty('controlOverrides');
+    expect(controlPanel.controlOverrides).toHaveProperty('y_axis_format');
+    expect(controlPanel.controlOverrides!.y_axis_format!.label).toBe(
+      'Number format',
+    );
+  });
+
+  it('should override formData metric using getStandardizedControls', () => {
+    const dummyFormData = { someProp: 'test' } as unknown as SqlaFormData;
+    const newFormData = controlPanel.formDataOverrides!(dummyFormData);
+
+    // The original properties are spread correctly.
+    expect(newFormData.someProp).toBe('test');
+    // The metric property should be replaced by the output of shiftMetric.
+    expect(newFormData.metric).toBe('shiftedMetric');
+
+    // Ensure that the mockShiftMetric function was called.
+    expect(__mockShiftMetric).toHaveBeenCalled();
+  });
+});
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.test.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.test.ts
new file mode 100644
index 0000000000..f41008eb60
--- /dev/null
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.test.ts
@@ -0,0 +1,220 @@
+/**
+ * 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 { GenericDataType } from '@superset-ui/core';
+import { getColorFormatters } from '@superset-ui/chart-controls';
+import { BigNumberTotalChartProps } from '../types';
+import transformProps from './transformProps';
+
+jest.mock('@superset-ui/chart-controls', () => ({
+  getColorFormatters: jest.fn(),
+}));
+
+jest.mock('@superset-ui/core', () => ({
+  GenericDataType: { Temporal: 2, String: 1 },
+  getMetricLabel: jest.fn(metric => metric),
+  extractTimegrain: jest.fn(() => 'P1D'),
+  getValueFormatter: jest.fn(() => (v: any) => `$${v}`),
+}));
+
+jest.mock('../utils', () => ({
+  getDateFormatter: jest.fn(() => (v: any) => `${v}pm`),
+  parseMetricValue: jest.fn(val => Number(val)),
+}));
+
+describe('BigNumberTotal transformProps', () => {
+  const onContextMenu = jest.fn();
+  const baseFormData = {
+    headerFontSize: 20,
+    metric: 'value',
+    subheader: 'sub header text',
+    subheaderFontSize: 14,
+    forceTimestampFormatting: false,
+    timeFormat: 'YYYY-MM-DD',
+    yAxisFormat: 'SMART_NUMBER',
+    conditionalFormatting: [{ color: 'red', op: '>', value: 0 }],
+    currencyFormat: { symbol: '$', symbolPosition: 'prefix' },
+  };
+
+  const baseDatasource = {
+    currencyFormats: { value: '$0,0.00' },
+    columnFormats: { value: '$0,0.00' },
+    metrics: [{ metric_name: 'value', d3format: '.2f' }],
+  };
+
+  const baseHooks = { onContextMenu };
+
+  const baseRawFormData = { dummy: 'raw' };
+
+  it('should return null bigNumber when no data is provided', () => {
+    const chartProps = {
+      width: 400,
+      height: 300,
+      queriesData: [{ data: [], coltypes: [] }],
+      formData: baseFormData,
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+
+    const result = transformProps(
+      chartProps as unknown as BigNumberTotalChartProps,
+    );
+    expect(result.bigNumber).toBeNull();
+    expect(result.width).toBe(400);
+    expect(result.height).toBe(300);
+    expect(result.subtitle).toBe(baseFormData.subheader);
+    expect(result.onContextMenu).toBe(onContextMenu);
+    expect(result.refs).toEqual({});
+    // headerFormatter should be set even if there's no data
+    expect(typeof result.headerFormatter).toBe('function');
+    // colorThresholdFormatters fallback to empty array when 
getColorFormatters returns falsy
+    expect(result.colorThresholdFormatters).toEqual([]);
+  });
+  it('should convert subheader to subtitle', () => {
+    const chartProps = {
+      width: 400,
+      height: 300,
+      queriesData: [{ data: [], coltypes: [] }],
+      formData: { ...baseFormData, subheader: 'test' },
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+    const result = transformProps(
+      chartProps as unknown as BigNumberTotalChartProps,
+    );
+    expect(result.subtitle).toBe('test');
+  });
+
+  it('should compute bigNumber using parseMetricValue when data exists', () => 
{
+    const chartProps = {
+      width: 500,
+      height: 400,
+      queriesData: [
+        { data: [{ value: '456' }], coltypes: [GenericDataType.String] },
+      ],
+      formData: { ...baseFormData, forceTimestampFormatting: false },
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+      sortBy: 'value',
+    };
+
+    const result = transformProps(
+      chartProps as unknown as BigNumberTotalChartProps,
+    );
+    // parseMetricValue converts '456' to number 456 by our mock
+    expect(result.bigNumber).toEqual(456);
+  });
+
+  it('should use formatTime as headerFormatter for Temporal or String types or 
forced formatting', () => {
+    // Case 1: Temporal type
+    const chartPropsTemporal = {
+      width: 600,
+      height: 450,
+      queriesData: [
+        { data: [{ value: '789' }], coltypes: [GenericDataType.Temporal] },
+      ],
+      formData: { ...baseFormData, forceTimestampFormatting: false },
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+
+    const resultTemporal = transformProps(
+      chartPropsTemporal as unknown as BigNumberTotalChartProps,
+    );
+    expect(resultTemporal.headerFormatter(5)).toBe('5pm');
+
+    // Case 2: String type regardless of forcing formatting
+    const chartPropsString = {
+      width: 600,
+      height: 450,
+      queriesData: [
+        { data: [{ value: '789' }], coltypes: [GenericDataType.String] },
+      ],
+      formData: { ...baseFormData, forceTimestampFormatting: false },
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+
+    const resultString = transformProps(
+      chartPropsString as unknown as BigNumberTotalChartProps,
+    );
+    expect(resultString.headerFormatter(5)).toBe('5pm');
+
+    // Case 3: Forced timestamp formatting
+    const chartPropsForced = {
+      width: 600,
+      height: 450,
+      queriesData: [{ data: [{ value: '789' }], coltypes: [0] }], // 
non-temporal/non-string
+      formData: { ...baseFormData, forceTimestampFormatting: true },
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+
+    const resultForced = transformProps(
+      chartPropsForced as unknown as BigNumberTotalChartProps,
+    );
+    expect(resultForced.headerFormatter(5)).toBe('5pm');
+  });
+
+  it('should use numberFormatter as headerFormatter when not Temporal/String 
and no forced formatting', () => {
+    const chartProps = {
+      width: 700,
+      height: 500,
+      queriesData: [{ data: [{ value: '321' }], coltypes: [0] }], // 
non-temporal/non-string
+      formData: { ...baseFormData, forceTimestampFormatting: false },
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+
+    const result = transformProps(
+      chartProps as unknown as BigNumberTotalChartProps,
+    );
+    expect(result.headerFormatter(500)).toBe('$500');
+  });
+
+  it('should propagate colorThresholdFormatters from getColorFormatters', () 
=> {
+    // Override the getColorFormatters mock to return specific value
+    const mockFormatters = [{ formatter: 'red' }];
+    (getColorFormatters as jest.Mock).mockReturnValueOnce(mockFormatters);
+
+    const chartProps = {
+      width: 800,
+      height: 600,
+      queriesData: [
+        { data: [{ value: '100' }], coltypes: [GenericDataType.Temporal] },
+      ],
+      formData: baseFormData,
+      rawFormData: baseRawFormData,
+      hooks: baseHooks,
+      datasource: baseDatasource,
+    };
+
+    const result = transformProps(
+      chartProps as unknown as BigNumberTotalChartProps,
+    );
+    expect(result.colorThresholdFormatters).toEqual(mockFormatters);
+  });
+});
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 c673ebd9f5..e892ca33f7 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,19 +47,22 @@ export default function transformProps(
   const {
     headerFontSize,
     metric = 'value',
-    subtitle = '',
+    subtitle,
     subtitleFontSize,
     forceTimestampFormatting,
     timeFormat,
     yAxisFormat,
     conditionalFormatting,
     currencyFormat,
+    subheader,
+    subheaderFontSize,
   } = formData;
   const refs: Refs = {};
   const { data = [], coltypes = [] } = queriesData[0];
   const granularity = extractTimegrain(rawFormData as QueryFormData);
   const metricName = getMetricLabel(metric);
-  const formattedSubtitle = subtitle;
+  const formattedSubtitle = subtitle || subheader || '';
+  const formattedSubtitleFontSize = subheaderFontSize || subtitleFontSize;
   const bigNumber =
     data.length === 0 ? null : parseMetricValue(data[0][metricName]);
 
@@ -105,10 +108,8 @@ export default function transformProps(
     bigNumber,
     headerFormatter,
     headerFontSize,
-    subtitleFontSize,
+    subtitleFontSize: formattedSubtitleFontSize,
     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 fddebc93a5..540efa4908 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx
@@ -188,47 +188,6 @@ class BigNumberVis extends 
PureComponent<BigNumberVizProps> {
     );
   }
 
-  renderSubheader(maxHeight: number) {
-    const { bigNumber, subheader, width, bigNumberFallback } = this.props;
-    let fontSize = 0;
-
-    const NO_DATA_OR_HASNT_LANDED = t(
-      'No data after filtering or data is NULL for the latest time record',
-    );
-    const NO_DATA = t(
-      'Try applying different filters or ensuring your datasource has data',
-    );
-    let text = subheader;
-    if (bigNumber === null) {
-      text = bigNumberFallback ? NO_DATA : NO_DATA_OR_HASNT_LANDED;
-    }
-    if (text) {
-      const container = this.createTemporaryContainer();
-      document.body.append(container);
-      fontSize = computeMaxFontSize({
-        text,
-        maxWidth: width * 0.9, // max width reduced
-        maxHeight,
-        className: 'subheader-line',
-        container,
-      });
-      container.remove();
-
-      return (
-        <div
-          className="subheader-line"
-          style={{
-            fontSize,
-            height: maxHeight,
-          }}
-        >
-          {text}
-        </div>
-      );
-    }
-    return null;
-  }
-
   renderSubtitle(maxHeight: number) {
     const { subtitle, width } = this.props;
     let fontSize = 0;
@@ -315,7 +274,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> 
{
       height,
       kickerFontSize,
       headerFontSize,
-      subheaderFontSize,
       subtitleFontSize,
     } = this.props;
     const className = this.getClassName();
@@ -336,11 +294,6 @@ class BigNumberVis extends 
PureComponent<BigNumberVizProps> {
             {this.renderHeader(
               Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height),
             )}
-            {this.renderSubheader(
-              Math.ceil(
-                subheaderFontSize * (1 - PROPORTION.TRENDLINE) * height,
-              ),
-            )}
             {this.renderSubtitle(
               Math.ceil(subtitleFontSize * (1 - PROPORTION.TRENDLINE) * 
height),
             )}
@@ -355,7 +308,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> 
{
         {this.renderFallbackWarning()}
         {this.renderKicker((kickerFontSize || 0) * height)}
         {this.renderHeader(Math.ceil(headerFontSize * height))}
-        {this.renderSubheader(Math.ceil(subheaderFontSize * height))}
         {this.renderSubtitle(Math.ceil(subtitleFontSize * height))}
       </div>
     );
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 09766ed4bf..9de4065b67 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/sharedControls.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/sharedControls.ts
@@ -129,5 +129,8 @@ export const subtitleControl: CustomControlItem = {
     label: t('Subtitle'),
     renderTrigger: true,
     description: t('Description text that shows up below your Big Number'),
+    mapStateToProps: state => ({
+      value: state.form_data.subheader || state.form_data.subtitle,
+    }),
   },
 };
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 d843ee1fd4..8b5a5abddb 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/types.ts
@@ -77,9 +77,7 @@ export type BigNumberVizProps = {
   formatTime?: TimeFormatter;
   headerFontSize: number;
   kickerFontSize?: number;
-  subheader: string;
   subtitle: string;
-  subheaderFontSize: number;
   subtitleFontSize: number;
   showTimestamp?: boolean;
   showTrendLine?: boolean;

Reply via email to