Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions packages/scenes/src/components/VizPanel/VizPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jest.mock('react-use', () => ({
interface OptionsPlugin1 {
showThresholds: boolean;
option2?: string;
option3?: string;
sortBy?: string[];
}

Expand All @@ -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;

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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<OptionsPlugin2, FieldConfigPlugin2>).state.options;
expect(newPanelOptions.option3).toEqual('option3 value plugin 2');
expect(newPanelOptions.option4).toEqual('option4 value plugin 2');
});
});

Expand Down
56 changes: 29 additions & 27 deletions packages/scenes/src/components/VizPanel/VizPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,15 @@ export class VizPanel<TOptions = {}, TFieldConfig extends {} = {}> 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<TOptions>, true);
}
}

Expand All @@ -385,36 +393,30 @@ export class VizPanel<TOptions = {}, TFieldConfig extends {} = {}> extends Scene
});
};

public onOptionsChange = (optionsUpdate: DeepPartial<TOptions>, replace = false, isAfterPluginChange = false) => {
const { fieldConfig, options } = this.state;
public onOptionsChange = (options: DeepPartial<TOptions>, 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<TOptions>,
_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<DeepPartial<TFieldConfig>>, replace?: boolean) => {
Expand Down