This is an automated email from the ASF dual-hosted git repository.
asoare 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 f6106cd26f1 fix(timeshiftcolor): Time shift color to match the
original color (#38473)
f6106cd26f1 is described below
commit f6106cd26f1b2da27e2157dc0034cf9933ef68bc
Author: Alexandru Soare <[email protected]>
AuthorDate: Fri Mar 13 15:24:56 2026 +0200
fix(timeshiftcolor): Time shift color to match the original color (#38473)
---
.../src/operators/utils/timeOffset.ts | 12 ++-
.../test/operators/utils/timeOffset.test.ts | 92 +++++++++++++++++++++-
.../src/Timeseries/transformProps.ts | 17 +++-
3 files changed, 114 insertions(+), 7 deletions(-)
diff --git
a/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts
b/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts
index 852b4eb1ab8..9aae5445bc8 100644
---
a/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts
+++
b/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts
@@ -28,7 +28,9 @@ export const getTimeOffset = (
// offset is represented as <offset>, group by list
series.name.includes(`${timeOffset},`) ||
// offset is represented as <metric>__<offset>
- series.name.includes(`__${timeOffset}`),
+ series.name.includes(`__${timeOffset}`) ||
+ // offset is represented as <metric>, <offset>
+ series.name.includes(`, ${timeOffset}`),
);
export const hasTimeOffset = (
@@ -45,10 +47,14 @@ export const getOriginalSeries = (
): string => {
let result = seriesName;
timeCompare.forEach(compare => {
- // offset is represented as <offset>, group by list
+ // offset in the middle: <metric>, <offset>, <dimension>
+ result = result.replace(`, ${compare},`, ',');
+ // offset at start: <offset>, <dimension>
result = result.replace(`${compare},`, '');
- // offset is represented as <metric>__<offset>
+ // offset with double underscore: <metric>__<offset>
result = result.replace(`__${compare}`, '');
+ // offset at end: <metric>, <offset>
+ result = result.replace(`, ${compare}`, '');
});
return result.trim();
};
diff --git
a/superset-frontend/packages/superset-ui-chart-controls/test/operators/utils/timeOffset.test.ts
b/superset-frontend/packages/superset-ui-chart-controls/test/operators/utils/timeOffset.test.ts
index bee8c5bbbd2..bec42af6c9b 100644
---
a/superset-frontend/packages/superset-ui-chart-controls/test/operators/utils/timeOffset.test.ts
+++
b/superset-frontend/packages/superset-ui-chart-controls/test/operators/utils/timeOffset.test.ts
@@ -16,15 +16,101 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { getOriginalSeries } from '@superset-ui/chart-controls';
+import {
+ getOriginalSeries,
+ getTimeOffset,
+ hasTimeOffset,
+} from '@superset-ui/chart-controls';
-test('returns the series name when time compare is empty', () => {
+test('getOriginalSeries returns the series name when time compare is empty',
() => {
const seriesName = 'sum';
expect(getOriginalSeries(seriesName, [])).toEqual(seriesName);
});
-test('returns the original series name', () => {
+test('getOriginalSeries returns the original series name with __ pattern', ()
=> {
const seriesName = 'sum__1_month_ago';
const timeCompare = ['1_month_ago'];
expect(getOriginalSeries(seriesName, timeCompare)).toEqual('sum');
});
+
+test('getOriginalSeries returns the original series name with <offset>,
pattern', () => {
+ const seriesName = '1 year ago, groupby_value';
+ const timeCompare = ['1 year ago'];
+ expect(getOriginalSeries(seriesName, timeCompare)).toEqual('groupby_value');
+});
+
+test('getOriginalSeries returns the original series name with , <offset>
pattern', () => {
+ const seriesName = 'AVG(price_each), 1 year ago';
+ const timeCompare = ['1 year ago'];
+ expect(getOriginalSeries(seriesName,
timeCompare)).toEqual('AVG(price_each)');
+});
+
+test('getOriginalSeries handles multiple time compares', () => {
+ const seriesName = 'count, 1 year ago';
+ const timeCompare = ['1 month ago', '1 year ago'];
+ expect(getOriginalSeries(seriesName, timeCompare)).toEqual('count');
+});
+
+test('getOriginalSeries strips offset in the middle with dimension', () => {
+ const seriesName = 'SUM(sales), 28 days ago, Medium';
+ const timeCompare = ['28 days ago'];
+ expect(getOriginalSeries(seriesName, timeCompare)).toEqual(
+ 'SUM(sales), Medium',
+ );
+});
+
+test('getOriginalSeries strips offset in the middle with multiple dimensions',
() => {
+ const seriesName = 'SUM(sales), 1 year ago, Medium, 11';
+ const timeCompare = ['1 year ago'];
+ expect(getOriginalSeries(seriesName, timeCompare)).toEqual(
+ 'SUM(sales), Medium, 11',
+ );
+});
+
+test('getTimeOffset returns undefined when no time offset pattern matches', ()
=> {
+ const series = { name: 'count' };
+ const timeCompare = ['1 year ago'];
+ expect(getTimeOffset(series, timeCompare)).toBeUndefined();
+});
+
+test('getTimeOffset detects __ pattern', () => {
+ const series = { name: 'count__1 year ago' };
+ const timeCompare = ['1 year ago'];
+ expect(getTimeOffset(series, timeCompare)).toEqual('1 year ago');
+});
+
+test('getTimeOffset detects <offset>, pattern', () => {
+ const series = { name: '1 year ago, groupby_value' };
+ const timeCompare = ['1 year ago'];
+ expect(getTimeOffset(series, timeCompare)).toEqual('1 year ago');
+});
+
+test('getTimeOffset detects , <offset> pattern', () => {
+ const series = { name: 'AVG(price_each), 1 year ago' };
+ const timeCompare = ['1 year ago'];
+ expect(getTimeOffset(series, timeCompare)).toEqual('1 year ago');
+});
+
+test('getTimeOffset detects , <offset>, pattern (offset in middle)', () => {
+ const series = { name: 'SUM(sales), 28 days ago, Medium' };
+ const timeCompare = ['28 days ago'];
+ expect(getTimeOffset(series, timeCompare)).toEqual('28 days ago');
+});
+
+test('hasTimeOffset returns false for original series', () => {
+ const series = { name: 'count' };
+ const timeCompare = ['1 year ago'];
+ expect(hasTimeOffset(series, timeCompare)).toBe(false);
+});
+
+test('hasTimeOffset returns true for derived series with , <offset> pattern',
() => {
+ const series = { name: 'AVG(price_each), 1 year ago' };
+ const timeCompare = ['1 year ago'];
+ expect(hasTimeOffset(series, timeCompare)).toBe(true);
+});
+
+test('hasTimeOffset returns false when series name is not a string', () => {
+ const series = { name: 123 };
+ const timeCompare = ['1 year ago'];
+ expect(hasTimeOffset(series, timeCompare)).toBe(false);
+});
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 6eff01ca80b..f7d90cc3f5a 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -336,6 +336,7 @@ export default function transformProps(
chartProps.rawFormData,
seriesName,
);
+
const lineStyle: LineStyleOption = {};
if (derivedSeries) {
// Get the time offset for this series to assign different dash patterns
@@ -370,7 +371,21 @@ export default function transformProps(
let colorScaleKey = getOriginalSeries(seriesName, array);
- // If series name exactly matches a time offset (single metric case),
+ // When there's a single metric with dimensions, the backend replaces the
metric
+ // with the time offset in derived series (e.g., "28 days ago, Medium"
instead of
+ // "SUM(sales), 28 days ago, Medium"). To match colors, strip the metric
label
+ // from original series so both produce the same key (e.g., "Medium").
+ if (
+ groupby &&
+ groupby.length > 0 &&
+ array.length > 0 &&
+ metrics?.length === 1
+ ) {
+ const metricLabel = getMetricLabel(metrics[0]);
+ colorScaleKey = colorScaleKey.replace(`${metricLabel}, `, '');
+ }
+
+ // If series name exactly matches a time offset (single metric case, no
dimensions),
// find the original series for color matching
if (derivedSeries && array.includes(seriesName)) {
const originalSeries = rawSeries.find(