diff --git a/packages/scenes/src/components/VizPanel/VizPanel.test.tsx b/packages/scenes/src/components/VizPanel/VizPanel.test.tsx index a19ec45cb..ee9a548d3 100644 --- a/packages/scenes/src/components/VizPanel/VizPanel.test.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanel.test.tsx @@ -48,6 +48,7 @@ jest.mock('react-use', () => ({ interface OptionsPlugin1 { showThresholds: boolean; option2?: string; + option3?: string; sortBy?: string[]; } @@ -58,6 +59,21 @@ interface FieldConfigPlugin1 { junkProp?: boolean; } +interface OptionsPlugin2 { + showThresholds: boolean; + option2?: string; + option3?: string; + option4?: string; + sortBy?: string[]; +} + +interface FieldConfigPlugin2 { + customPropInOtherPlugin?: boolean; + customProp2InOtherPlugin?: boolean; + customProp3?: string; + hideFrom?: boolean; +} + let panelProps: PanelProps | undefined; let panelRenderCount = 0; @@ -91,6 +107,11 @@ function getTestPlugin1(dataSupport?: PanelPluginDataSupport) { path: 'option2', defaultValue: undefined, }); + builder.addTextInput({ + name: 'option3', + path: 'option3', + defaultValue: 'option3 value plugin 1', + }); }); pluginToLoad.useFieldConfig({ @@ -165,6 +186,16 @@ function getTestPlugin2(dataSupport?: PanelPluginDataSupport) { path: 'option2', defaultValue: undefined, }); + builder.addTextInput({ + name: 'option3', + path: 'option3', + defaultValue: 'option3 value plugin 2', + }); + builder.addTextInput({ + name: 'option4', + path: 'option4', + defaultValue: 'option4 value plugin 2', + }); }); pluginToLoad.useFieldConfig({ @@ -515,16 +546,34 @@ describe('VizPanel', () => { expect(panel.state.options.option2).not.toBeDefined(); }); - test('should allow to call getPanelOptionsWithDefaults to compute new color options for plugin', () => { + test('should allow to override default options with empty value', () => { + panel.onOptionsChange({ option3: undefined }); + + expect(panel.state.options.option3).toBe(undefined); + + panel.onOptionsChange({ option3: '' }); + + expect(panel.state.options.option3).toBe(''); + }); + + test('should allow to call getPanelOptionsWithDefaults to compute new plugin options', async () => { + expect(panel.state.pluginId).toBe('custom-plugin-id'); + expect(panel.state.pluginVersion).toBe('1.0.0'); + panel.onOptionsChange({ option3: undefined }); + + pluginToLoad = getTestPlugin2(); + const spy = jest.spyOn(grafanaData, 'getPanelOptionsWithDefaults'); - pluginToLoad = getTestPlugin1(); - panel.activate(); - panel.onOptionsChange({}, false, true); + await panel.changePluginType('custom2-plugin-id', undefined, panel.state.fieldConfig); expect(spy).toHaveBeenCalledTimes(1); - // Marked as after plugin change to readjust to prefered field color setting + // Marked as after a plugin change to readjust to preferred field color setting expect(spy.mock.calls[0][0].isAfterPluginChange).toBe(true); + + const newPanelOptions = (panel as VizPanel).state.options; + expect(newPanelOptions.option3).toEqual('option3 value plugin 2'); + expect(newPanelOptions.option4).toEqual('option4 value plugin 2'); }); }); diff --git a/packages/scenes/src/components/VizPanel/VizPanel.tsx b/packages/scenes/src/components/VizPanel/VizPanel.tsx index 9fda04e30..3eb125209 100644 --- a/packages/scenes/src/components/VizPanel/VizPanel.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanel.tsx @@ -363,7 +363,15 @@ export class VizPanel extends Scene const updatedOptions = this._plugin?.onPanelTypeChanged?.(panel, prevPluginId, prevOptions, prevFieldConfig); if (updatedOptions && !isEmpty(updatedOptions)) { - this.onOptionsChange(updatedOptions, true, true); + const { options } = getPanelOptionsWithDefaults({ + plugin: this._plugin!, + currentOptions: updatedOptions, + currentFieldConfig: this.state.fieldConfig, + isAfterPluginChange: isAfterPluginChange, + }); + + // We need to merge the update with the defaults before overwriting the options given we use replace + this.onOptionsChange(options as DeepPartial, true); } } @@ -385,36 +393,30 @@ export class VizPanel extends Scene }); }; - public onOptionsChange = (optionsUpdate: DeepPartial, replace = false, isAfterPluginChange = false) => { - const { fieldConfig, options } = this.state; + public onOptionsChange = (options: DeepPartial, replace = false) => { + const { options: prevOptions } = this.state; - // When replace is true, we want to replace the entire options object. Default will be applied. - const nextOptions = replace - ? optionsUpdate - : mergeWith(cloneDeep(options), optionsUpdate, (objValue, srcValue, key, obj) => { - if (isArray(srcValue)) { - return srcValue; - } - // If customizer returns undefined, merging is handled by the method instead - // so we need to override the value in the object instead - if (objValue !== srcValue && typeof srcValue === 'undefined') { - obj[key] = srcValue; - return; - } - return; - }); + const _renderCounter = (this.state._renderCounter ?? 0) + 1; - const withDefaults = getPanelOptionsWithDefaults({ - plugin: this._plugin!, - currentOptions: nextOptions, - currentFieldConfig: fieldConfig, - isAfterPluginChange: isAfterPluginChange, - }); + if (replace) { + return this.setState({ options, _renderCounter }); + } - this.setState({ - options: withDefaults.options as DeepPartial, - _renderCounter: (this.state._renderCounter ?? 0) + 1, + options = mergeWith(cloneDeep(prevOptions), options, (objValue, srcValue, key, obj) => { + if (isArray(srcValue)) { + return srcValue; + } + + // If the customizer returns undefined, the method handles merging instead, + // So we need to override the value in the object instead + if (objValue !== srcValue && typeof srcValue === 'undefined') { + obj[key] = srcValue; + return; + } + return; }); + + this.setState({ options, _renderCounter }); }; public onFieldConfigChange = (fieldConfigUpdate: FieldConfigSource>, replace?: boolean) => {