diff --git a/superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts b/superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts index 9c2dc91d0b8d..93e91d125a86 100644 --- a/superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts +++ b/superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts @@ -276,6 +276,18 @@ describe('should collect control values and create SFD', () => { metrics: formData.standardizedFormData.controls.metrics, }), }); + // a target viz whose "Time shift" control offers inherit/custom choices + getChartControlPanelRegistry().registerValue('target_viz_inherit', { + controlPanelSections: [ + sections.timeComparisonControls({}), + { + label: 'transform controls', + controlSetRows: publicControlFields + .filter(c => c !== 'time_compare') + .map(control => [control]), + }, + ], + }); }); test('should avoid to overlap', () => { @@ -344,6 +356,52 @@ describe('should collect control values and create SFD', () => { ]); }); + test('strips inherit/custom time shifts when target viz does not support them', () => { + // Table/Big Number period-over-period offer "inherit" and "custom" time + // shifts; the timeseries advanced-analytics target (target_viz) does not. + // Carrying them over leaves un-removable tags on the new chart (SC-99170). + const store = { + ...sourceMockStore, + form_data: { + ...sourceMockFormData, + time_compare: ['1 year ago', 'inherit', 'custom'], + }, + }; + const sfd = new StandardizedFormData(store.form_data); + const { formData } = sfd.transform('target_viz', store); + // the free-form delta survives, the special markers are dropped + expect(formData.time_compare).toEqual(['1 year ago']); + }); + + test('preserves inherit/custom time shifts when target viz supports them', () => { + const store = { + ...sourceMockStore, + form_data: { + ...sourceMockFormData, + time_compare: ['1 year ago', 'inherit'], + }, + }; + const sfd = new StandardizedFormData(store.form_data); + const { formData } = sfd.transform('target_viz_inherit', store); + expect(formData.time_compare).toEqual(['1 year ago', 'inherit']); + }); + + test('clears time_compare to null when every shift is unsupported', () => { + // When only special markers carry over and none survive, the control value + // must serialize to null (not an empty array) so the target chart shows an + // empty "Time shift" rather than a stray tag (SC-99170). + const store = { + ...sourceMockStore, + form_data: { + ...sourceMockFormData, + time_compare: ['inherit', 'custom'], + }, + }; + const sfd = new StandardizedFormData(store.form_data); + const { formData } = sfd.transform('target_viz', store); + expect(formData.time_compare).toBeNull(); + }); + test('should inherit standardizedFormData and memorizedFormData is LIFO', () => { // from source_viz to target_viz const sfd = new StandardizedFormData(sourceMockFormData); diff --git a/superset-frontend/src/explore/controlUtils/standardizedFormData.ts b/superset-frontend/src/explore/controlUtils/standardizedFormData.ts index 0462b9e3a1c6..1adf00d1ee7a 100644 --- a/superset-frontend/src/explore/controlUtils/standardizedFormData.ts +++ b/superset-frontend/src/explore/controlUtils/standardizedFormData.ts @@ -156,6 +156,41 @@ export class StandardizedFormData { return controls; } + // Time shifts only meaningful for viz types whose "Time shift" control offers + // them (Big Number / Table period-over-period). Other viz types reuse the same + // `time_compare` key without these choices. + private static specialTimeShifts = ['inherit', 'custom']; + + // Drop `time_compare` markers the target viz can't honor so they don't carry + // over as un-removable tags when switching chart types. + static dropUnsupportedTimeShifts( + controlsState: Record, + ): void { + const control = controlsState?.time_compare as + | { value?: unknown; choices?: unknown } + | undefined; + if (!control || !Array.isArray(control.value)) { + return; + } + const supportedChoices = new Set( + ensureIsArray(control.choices) + .filter( + (choice): choice is [string, string] => + Array.isArray(choice) && typeof choice[0] === 'string', + ) + .map(choice => choice[0]), + ); + const filtered = control.value.filter( + (shift: unknown) => + typeof shift !== 'string' || + !StandardizedFormData.specialTimeShifts.includes(shift) || + supportedChoices.has(shift), + ); + if (filtered.length !== control.value.length) { + control.value = filtered.length ? filtered : null; + } + } + private getLatestFormData(vizType: string): QueryFormData { if (this.has(vizType)) { return this.get(vizType); @@ -215,6 +250,7 @@ export class StandardizedFormData { ...publicFormData, viz_type: targetVizType, }); + StandardizedFormData.dropUnsupportedTimeShifts(targetControlsState); const targetFormData = { // eslint-disable-next-line @typescript-eslint/no-explicit-any ...getFormDataFromControls(targetControlsState as any),