This is an automated email from the ASF dual-hosted git repository.
rusackas 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 e0103fa8896 fix(plugin-chart-echarts): honor per-metric formats in
Timeseries tooltips (#43113)
e0103fa8896 is described below
commit e0103fa8896acb8026b1ba8b49bde41fee5689c9
Author: Ankit <[email protected]>
AuthorDate: Wed Sep 9 23:26:59 2026 +0530
fix(plugin-chart-echarts): honor per-metric formats in Timeseries tooltips
(#43113)
---
.../src/Timeseries/transformProps.ts | 79 ++-
.../test/Timeseries/transformProps.test.ts | 579 +++++++++++++++++++++
2 files changed, 657 insertions(+), 1 deletion(-)
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
index f655cece310..276db9e6325 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -25,6 +25,7 @@ import {
AxisType,
buildCustomFormatters,
CategoricalColorNamespace,
+ ComparisonType,
CurrencyFormatter,
DataRecordValue,
DTTM_ALIAS,
@@ -419,6 +420,11 @@ export default function transformProps(
const refs: Refs = {};
const groupBy = ensureIsArray(groupby);
+ // Series whose `label_map` entry led with a time offset, recorded before
the shift
+ // below drops it. That leading column is the only structural marker
distinguishing a
+ // derived comparison row from a base row whose dimension value happens to
read like
+ // the offset, and it is gone from `labelMap` by the time the formatters run.
+ const derivedComparisonSeries = new Set<string>();
const labelMap: { [key: string]: string[] } = Object.entries(
label_map,
).reduce((acc, entry) => {
@@ -427,6 +433,7 @@ export default function transformProps(
Array.isArray(timeCompare) &&
timeCompare.includes(entry[1][0])
) {
+ derivedComparisonSeries.add(entry[0]);
entry[1].shift();
}
return { ...acc, [entry[0]]: entry[1] };
@@ -681,6 +688,51 @@ export default function transformProps(
const array = ensureIsArray(chartProps.rawFormData?.time_compare);
const inverted = invert(verboseMap);
+ // A Percentage or Ratio time comparison replaces the derived series' values
with a
+ // dimensionless number, so that row is no longer in the source metric's
units and
+ // must not inherit its currency/D3 format.
+ //
+ // `label_map` carries the structured identity behind a rendered series
name, and
+ // `renameOperator` puts the offset at the front of a derived row's entry:
+ //
+ // derived '1 week ago, East' -> ['1 week ago', 'East']
+ // derived 'count, 1 year ago' -> ['1 year ago', 'count']
+ // base 'sum__num, East' -> ['sum__num', 'East']
+ //
+ // so the leading column says which it is. Matching the rendered name
instead would
+ // misread a base series whose dimension value happens to equal the offset —
a region
+ // literally named "1 week ago" gives 'sum__num, 1 week ago', which reads as
derived.
+ const isDerivedComparisonSeries = (seriesKey: string) => {
+ // Recorded above, before the offset was shifted off the `label_map` entry.
+ if (derivedComparisonSeries.has(seriesKey)) {
+ return true;
+ }
+ const columns = labelMap?.[seriesKey];
+ // The shift only runs when `timeCompare` is populated; otherwise the
entry still
+ // leads with the offset and can be read directly.
+ return columns?.length
+ ? array.includes(columns[0])
+ : array.includes(seriesKey);
+ };
+
+ // Percentage yields `(s - c) / c`, which reads as a percentage. Ratio
yields `s / c`,
+ // a plain multiplier, so it takes a unitless number format rather than a
percent one.
+ const ratioFormatter = getNumberFormatter(NumberFormats.SMART_NUMBER);
+
+ const getComparisonFormatter = (seriesKey: string) => {
+ if (!isDerivedComparisonSeries(seriesKey)) {
+ return undefined;
+ }
+ switch (chartProps.rawFormData?.comparison_type) {
+ case ComparisonType.Percentage:
+ return percentFormatter;
+ case ComparisonType.Ratio:
+ return ratioFormatter;
+ default:
+ return undefined;
+ }
+ };
+
// With the "full range" time-shift option, offset series are outer-joined
onto
// the main series, which inserts null rows into the main series wherever the
// comparison period has data the current period lacks. Connect nulls so the
@@ -1522,6 +1574,31 @@ export default function transformProps(
value.forecastTrend || value.forecastLower || value.forecastUpper,
);
+ // Resolve the value formatter per series so each metric keeps its own
+ // D3/currency format, matching how the series labels are formatted.
+ // Without the series key, `getCustomFormatter` returns undefined for
+ // multi-metric charts and every row falls back to `defaultFormatter`,
+ // rendering the y-axis/currency format for all metrics.
+ //
+ // The tooltip key is the rendered series name, so resolve it through
+ // `labelMap`, whose values lead with the raw metric label. Series
+ // renamed by a verbose_name are absent from that map, so fall back to
+ // the verbose-name inversion, as MixedTimeseries does. A Percentage or
+ // Ratio comparison row is dimensionless rather than a value in the
+ // metric's units, so it takes its own formatter instead of the
metric's.
+ const getSeriesFormatter = (seriesKey: string) =>
+ forcePercentFormatter
+ ? percentFormatter
+ : (getComparisonFormatter(seriesKey) ??
+ getCustomFormatter(
+ customFormatters,
+ metrics,
+ labelMap?.[seriesKey]?.[0] ?? inverted[seriesKey],
+ ) ??
+ defaultFormatter);
+
+ // The total row aggregates every series, so it keeps the chart-level
+ // formatter rather than any single metric's format.
const formatter = forcePercentFormatter
? percentFormatter
: (getCustomFormatter(customFormatters, metrics) ??
defaultFormatter);
@@ -1552,7 +1629,7 @@ export default function transformProps(
const row = formatForecastTooltipSeries({
...value,
seriesName: key,
- formatter,
+ formatter: getSeriesFormatter(key),
marker,
truncation: tooltipTruncation,
});
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
index d0fc6ca482a..43166287193 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
@@ -3576,3 +3576,582 @@ test('boundary label alignment is dropped when the
orientation moves the time ax
expect(horizontal.axisLabel.showMinLabel).toBe(true);
expect(horizontal.axisLabel.showMaxLabel).toBe(true);
});
+
+test('tooltip formats each series with its own metric format instead of the
default formatter', () => {
+ // Two saved metrics with different formats: `pct_change` carries a
percentage
+ // D3 format, `count` carries a currency format. The series labels already
+ // honor each metric's format; the tooltip must do the same.
+ const chartProps = createTestChartProps({
+ formData: {
+ metrics: ['count', 'pct_change'],
+ richTooltip: true,
+ },
+ queriesData: [
+ createTestQueryData(
+ [{ count: 1000, pct_change: 0.1234, __timestamp: BASE_TIMESTAMP }],
+ { label_map: { count: ['count'], pct_change: ['pct_change'] } },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: { pct_change: '.2%' },
+ currencyFormats: { count: { symbol: 'USD', symbolPosition: 'prefix' } },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ { seriesId: 'count', seriesName: 'count', value: [BASE_TIMESTAMP, 1000] },
+ {
+ seriesId: 'pct_change',
+ seriesName: 'pct_change',
+ value: [BASE_TIMESTAMP, 0.1234],
+ },
+ ]);
+
+ expect(result).toContain('12.34%');
+ expect(result).toContain('$');
+});
+
+test('tooltip resolves per-metric formats for series renamed by verbose_name',
() => {
+ // With a verbose_name configured, the rendered series name (and so the
+ // tooltip key) is the verbose label, while `label_map` stays keyed by the
+ // raw metric label. The formatter lookup has to bridge that gap.
+ const chartProps = createTestChartProps({
+ formData: {
+ metrics: ['count', 'pct_change'],
+ richTooltip: true,
+ },
+ queriesData: [
+ createTestQueryData(
+ [{ count: 1000, pct_change: 0.1234, __timestamp: BASE_TIMESTAMP }],
+ { label_map: { count: ['count'], pct_change: ['pct_change'] } },
+ ),
+ ],
+ datasource: {
+ verboseMap: { count: 'Total Count', pct_change: 'Percent Change' },
+ columnFormats: { pct_change: '.2%' },
+ currencyFormats: { count: { symbol: 'USD', symbolPosition: 'prefix' } },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'Total Count',
+ seriesName: 'Total Count',
+ value: [BASE_TIMESTAMP, 1000],
+ },
+ {
+ seriesId: 'Percent Change',
+ seriesName: 'Percent Change',
+ value: [BASE_TIMESTAMP, 0.1234],
+ },
+ ]);
+
+ expect(result).toContain('12.34%');
+ expect(result).toContain('$');
+});
+
+test('tooltip keeps per-metric formats on time-comparison (time-shifted)
series', () => {
+ // A time-shifted series renders under a name carrying the offset, and its
+ // `label_map` entry leads with that offset rather than the metric. The
+ // formatter lookup has to land on the underlying metric so the shifted row
is
+ // formatted like the series it is compared against.
+ const chartProps = createTestChartProps({
+ formData: {
+ metrics: ['count', 'pct_change'],
+ richTooltip: true,
+ timeCompare: ['1 year ago'],
+ },
+ queriesData: [
+ createTestQueryData(
+ [
+ {
+ count: 1000,
+ pct_change: 0.1234,
+ 'count, 1 year ago': 900,
+ __timestamp: BASE_TIMESTAMP,
+ },
+ ],
+ {
+ label_map: {
+ count: ['count'],
+ pct_change: ['pct_change'],
+ 'count, 1 year ago': ['1 year ago', 'count'],
+ },
+ },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: { pct_change: '.2%' },
+ currencyFormats: { count: { symbol: 'USD', symbolPosition: 'prefix' } },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ { seriesId: 'count', seriesName: 'count', value: [BASE_TIMESTAMP, 1000] },
+ {
+ seriesId: 'count, 1 year ago',
+ seriesName: 'count, 1 year ago',
+ value: [BASE_TIMESTAMP, 900],
+ },
+ ]);
+
+ // The base series and its time-shifted counterpart keep the currency format.
+ expect(result).toContain('$ 1k');
+ expect(result).toContain('$ 900');
+});
+
+test('tooltip does not apply a metric currency format to a Percentage time
comparison', () => {
+ // Reported on #33757: a Time Comparison set to Percentage change on a
+ // currency metric kept rendering the derived row in dollars. That row holds
a
+ // ratio rather than a value in the metric's units, so it must not inherit
the
+ // metric's saved CurrencyFormatter.
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ richTooltip: true,
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Percentage,
+ },
+ queriesData: [
+ createTestQueryData(
+ [{ sum__num: 100, '1 week ago': 0.25, __timestamp: BASE_TIMESTAMP }],
+ { label_map: { sum__num: ['sum__num'], '1 week ago': ['1 week ago'] }
},
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num',
+ seriesName: 'sum__num',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago',
+ seriesName: '1 week ago',
+ value: [BASE_TIMESTAMP, 0.25],
+ },
+ ]);
+
+ // The source metric keeps its currency; the percentage-change row does not.
+ expect(result).toContain('$ 100');
+ expect(result).toContain('25.00%');
+ expect(result).not.toContain('$ 0.25');
+});
+
+test('tooltip does not apply a metric currency format to a grouped Percentage
time comparison', () => {
+ // A groupby appends the dimension values to the derived series name
+ // ("1 week ago, East"), so matching the dimensionless names alone left the
+ // grouped rows resolving back to the source metric's CurrencyFormatter.
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ groupby: ['region'],
+ richTooltip: true,
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Percentage,
+ },
+ queriesData: [
+ createTestQueryData(
+ [
+ {
+ 'sum__num, East': 100,
+ '1 week ago, East': 0.25,
+ __timestamp: BASE_TIMESTAMP,
+ },
+ ],
+ {
+ label_map: {
+ 'sum__num, East': ['sum__num', 'East'],
+ '1 week ago, East': ['1 week ago', 'East'],
+ },
+ },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num, East',
+ seriesName: 'sum__num, East',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago, East',
+ seriesName: '1 week ago, East',
+ value: [BASE_TIMESTAMP, 0.25],
+ },
+ ]);
+
+ expect(result).toContain('$ 100');
+ expect(result).toContain('25.00%');
+ expect(result).not.toContain('$ 0.25');
+});
+
+test('tooltip does not apply a metric currency format to a Ratio time
comparison', () => {
+ // A Ratio comparison is `source / compare`, a plain multiplier, so the
derived row is
+ // no more in the metric's currency than a Percentage one is — but it is not
a
+ // percentage either, so it takes a unitless number format rather than the
percent one.
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ richTooltip: true,
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Ratio,
+ },
+ queriesData: [
+ createTestQueryData(
+ [{ sum__num: 100, '1 week ago': 1.25, __timestamp: BASE_TIMESTAMP }],
+ { label_map: { sum__num: ['sum__num'], '1 week ago': ['1 week ago'] }
},
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num',
+ seriesName: 'sum__num',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago',
+ seriesName: '1 week ago',
+ value: [BASE_TIMESTAMP, 1.25],
+ },
+ ]);
+
+ // The source metric keeps its currency; the ratio row renders as a plain
number.
+ expect(result).toContain('$ 100');
+ expect(result).toContain('1.25');
+ expect(result).not.toContain('$ 1.25');
+});
+
+test('tooltip does not apply a metric currency format to a grouped Ratio time
comparison', () => {
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ groupby: ['region'],
+ richTooltip: true,
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Ratio,
+ },
+ queriesData: [
+ createTestQueryData(
+ [
+ {
+ 'sum__num, East': 100,
+ '1 week ago, East': 1.25,
+ __timestamp: BASE_TIMESTAMP,
+ },
+ ],
+ {
+ label_map: {
+ 'sum__num, East': ['sum__num', 'East'],
+ '1 week ago, East': ['1 week ago', 'East'],
+ },
+ },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num, East',
+ seriesName: 'sum__num, East',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago, East',
+ seriesName: '1 week ago, East',
+ value: [BASE_TIMESTAMP, 1.25],
+ },
+ ]);
+
+ expect(result).toContain('$ 100');
+ expect(result).toContain('1.25');
+ expect(result).not.toContain('$ 1.25');
+});
+
+test('tooltip formats derived rows when timeCompare normalization strips the
offset', () => {
+ // With `timeCompare` populated, `labelMap` has its leading offset shifted
off before
+ // the formatters run, so the derived identity has to be captured during
that pass —
+ // reading `labelMap[key][0]` afterwards sees the dimension value instead.
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ groupby: ['region'],
+ richTooltip: true,
+ timeCompare: ['1 week ago'],
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Percentage,
+ },
+ queriesData: [
+ createTestQueryData(
+ [
+ {
+ 'sum__num, East': 100,
+ '1 week ago, East': 0.25,
+ __timestamp: BASE_TIMESTAMP,
+ },
+ ],
+ {
+ label_map: {
+ 'sum__num, East': ['sum__num', 'East'],
+ '1 week ago, East': ['1 week ago', 'East'],
+ },
+ },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num, East',
+ seriesName: 'sum__num, East',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago, East',
+ seriesName: '1 week ago, East',
+ value: [BASE_TIMESTAMP, 0.25],
+ },
+ ]);
+
+ expect(result).toContain('$ 100');
+ expect(result).toContain('25.00%');
+ expect(result).not.toContain('$ 0.25');
+});
+
+test('tooltip gives a Ratio row a unitless format when timeCompare is set', ()
=> {
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ groupby: ['region'],
+ richTooltip: true,
+ timeCompare: ['1 week ago'],
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Ratio,
+ },
+ queriesData: [
+ createTestQueryData(
+ [
+ {
+ 'sum__num, East': 100,
+ '1 week ago, East': 1.25,
+ __timestamp: BASE_TIMESTAMP,
+ },
+ ],
+ {
+ label_map: {
+ 'sum__num, East': ['sum__num', 'East'],
+ '1 week ago, East': ['1 week ago', 'East'],
+ },
+ },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num, East',
+ seriesName: 'sum__num, East',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago, East',
+ seriesName: '1 week ago, East',
+ value: [BASE_TIMESTAMP, 1.25],
+ },
+ ]);
+
+ expect(result).toContain('$ 100');
+ expect(result).toContain('1.25');
+ expect(result).not.toContain('$ 1.25');
+});
+
+test('tooltip keeps the metric format when a dimension value equals the
offset', () => {
+ // A groupby value can legitimately read like the configured offset, giving
a *base*
+ // series called `sum__num, 1 week ago`. Matching the rendered name would
classify it
+ // as derived and strip its currency; `label_map` leads with the metric, not
the
+ // offset, so it stays a base row.
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ groupby: ['region'],
+ richTooltip: true,
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Percentage,
+ },
+ queriesData: [
+ createTestQueryData(
+ [
+ {
+ 'sum__num, 1 week ago': 100,
+ '1 week ago, 1 week ago': 0.25,
+ __timestamp: BASE_TIMESTAMP,
+ },
+ ],
+ {
+ label_map: {
+ // The region is named "1 week ago"; the metric still leads the
base entry.
+ 'sum__num, 1 week ago': ['sum__num', '1 week ago'],
+ // Its derived counterpart leads with the offset.
+ '1 week ago, 1 week ago': ['1 week ago', '1 week ago'],
+ },
+ },
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num, 1 week ago',
+ seriesName: 'sum__num, 1 week ago',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago, 1 week ago',
+ seriesName: '1 week ago, 1 week ago',
+ value: [BASE_TIMESTAMP, 0.25],
+ },
+ ]);
+
+ // The base row keeps its currency even though its name ends in the offset,
and the
+ // genuinely derived row is still formatted as a percentage.
+ expect(result).toContain('$ 100');
+ expect(result).toContain('25.00%');
+});
+
+test('tooltip keeps the metric format on a Difference time comparison', () => {
+ // Difference is `source - compare`, which stays in the metric's units, so
unlike
+ // Percentage and Ratio it must keep the currency format.
+ const chartProps = createTestChartProps({
+ formData: {
+ metric: 'sum__num',
+ metrics: ['sum__num'],
+ richTooltip: true,
+ time_compare: ['1 week ago'],
+ comparison_type: ComparisonType.Difference,
+ },
+ queriesData: [
+ createTestQueryData(
+ [{ sum__num: 100, '1 week ago': 25, __timestamp: BASE_TIMESTAMP }],
+ { label_map: { sum__num: ['sum__num'], '1 week ago': ['1 week ago'] }
},
+ ),
+ ],
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {
+ sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
+ },
+ },
+ });
+
+ const { echartOptions } = transformProps(chartProps);
+ const { tooltip } = echartOptions as unknown as TooltipFormatterOptions;
+
+ const result = tooltip.formatter([
+ {
+ seriesId: 'sum__num',
+ seriesName: 'sum__num',
+ value: [BASE_TIMESTAMP, 100],
+ },
+ {
+ seriesId: '1 week ago',
+ seriesName: '1 week ago',
+ value: [BASE_TIMESTAMP, 25],
+ },
+ ]);
+
+ expect(result).toContain('$ 100');
+ expect(result).toContain('$ 25');
+});