From 8717bc7ef4ccf867035247f92a66eb5d4b14de4b Mon Sep 17 00:00:00 2001 From: Timur Olzhabayev Date: Fri, 7 Jan 2022 09:54:02 +0100 Subject: [PATCH] fix(40639): datasource should not be visible after uninstall (#43625) * fix(40639): datasource should not be visible after uninstall --- .../app/features/plugins/pluginCacheBuster.ts | 3 + .../features/plugins/pluginSettings.test.ts | 116 ++++++++++++++++++ public/app/features/plugins/pluginSettings.ts | 8 ++ .../plugins/tests/pluginCacheBuster.test.ts | 15 +++ 4 files changed, 142 insertions(+) create mode 100644 public/app/features/plugins/pluginSettings.test.ts diff --git a/public/app/features/plugins/pluginCacheBuster.ts b/public/app/features/plugins/pluginCacheBuster.ts index d8d3860bd7c..5df396c1562 100644 --- a/public/app/features/plugins/pluginCacheBuster.ts +++ b/public/app/features/plugins/pluginCacheBuster.ts @@ -1,3 +1,5 @@ +import { clearPluginSettingsCache } from './pluginSettings'; + const cache: Record = {}; const initializedAt: number = Date.now(); @@ -17,6 +19,7 @@ export function invalidatePluginInCache(pluginId: string): void { if (cache[path]) { delete cache[path]; } + clearPluginSettingsCache(pluginId); } export function locateWithCache(load: { address: string }, defaultBust = initializedAt): string { diff --git a/public/app/features/plugins/pluginSettings.test.ts b/public/app/features/plugins/pluginSettings.test.ts new file mode 100644 index 00000000000..08d6b67a5fc --- /dev/null +++ b/public/app/features/plugins/pluginSettings.test.ts @@ -0,0 +1,116 @@ +import { getPluginSettings, clearPluginSettingsCache } from './pluginSettings'; +import { getBackendSrv } from '@grafana/runtime'; + +jest.mock('@grafana/runtime', () => ({ + getBackendSrv: jest.fn().mockReturnValue({ + get: jest.fn(), + }), +})); + +describe('PluginSettings', () => { + beforeEach(() => { + jest.clearAllMocks(); + clearPluginSettingsCache(); + }); + + it('should fetch settings when cache is empty', async () => { + // arrange + const testPluginResponse = { + name: 'TestPlugin', + type: 'datasource', + id: 'test-plugin', + enabled: true, + }; + getBackendSrv().get = jest.fn().mockResolvedValue(testPluginResponse); + const getRequestSpy = jest.spyOn(getBackendSrv(), 'get'); + // act + const response = await getPluginSettings('test'); + // assert + expect(response).toEqual(testPluginResponse); + expect(getRequestSpy).toHaveBeenCalledTimes(1); + expect(getRequestSpy).toHaveBeenCalledWith('/api/plugins/test/settings'); + }); + + it('should fetch settings from cache when it has a hit', async () => { + // arrange + const testPluginResponse = { + name: 'TestPlugin', + type: 'datasource', + id: 'test-plugin', + enabled: true, + }; + getBackendSrv().get = jest.fn().mockResolvedValue(testPluginResponse); + const getRequestSpy = jest.spyOn(getBackendSrv(), 'get'); + // act + const response1 = await getPluginSettings('test'); + const response2 = await getPluginSettings('test'); + + // assert + expect(response1).toEqual(testPluginResponse); + expect(response2).toEqual(testPluginResponse); + expect(getRequestSpy).toHaveBeenCalledTimes(1); + }); + + it('should refetch from backend when cache is cleared', async () => { + // arrange + const testPluginResponse = { + name: 'TestPlugin', + type: 'datasource', + id: 'test-plugin', + enabled: true, + }; + getBackendSrv().get = jest.fn().mockResolvedValue(testPluginResponse); + const getRequestSpy = jest.spyOn(getBackendSrv(), 'get'); + + // act + const response1 = await getPluginSettings('test'); + await clearPluginSettingsCache('test'); + const response2 = await getPluginSettings('test'); + + // assert + expect(response1).toEqual(testPluginResponse); + expect(response2).toEqual(testPluginResponse); + expect(getRequestSpy).toHaveBeenCalledTimes(2); + }); + it('should fetch from cache when it is cleared for another plugin setting', async () => { + // arrange + const testPluginResponse = { + name: 'TestPlugin', + type: 'datasource', + id: 'test-plugin', + enabled: true, + }; + getBackendSrv().get = jest.fn().mockResolvedValue(testPluginResponse); + const getRequestSpy = jest.spyOn(getBackendSrv(), 'get'); + // act + const response1 = await getPluginSettings('test'); + await clearPluginSettingsCache('another-test'); + const response2 = await getPluginSettings('test'); + + // assert + expect(response1).toEqual(testPluginResponse); + expect(response2).toEqual(testPluginResponse); + expect(getRequestSpy).toHaveBeenCalledTimes(1); + }); + it('should clear all cache when no plugin id is provided to the clear function', async () => { + // arrange + const testPluginResponse = { + name: 'TestPlugin', + type: 'datasource', + id: 'test-plugin', + enabled: true, + }; + getBackendSrv().get = jest.fn().mockResolvedValue(testPluginResponse); + const getRequestSpy = jest.spyOn(getBackendSrv(), 'get'); + + // act + const response1 = await getPluginSettings('test'); + await clearPluginSettingsCache(); + const response2 = await getPluginSettings('test'); + + // assert + expect(response1).toEqual(testPluginResponse); + expect(response2).toEqual(testPluginResponse); + expect(getRequestSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/public/app/features/plugins/pluginSettings.ts b/public/app/features/plugins/pluginSettings.ts index 0061919769e..29a9f041cef 100644 --- a/public/app/features/plugins/pluginSettings.ts +++ b/public/app/features/plugins/pluginSettings.ts @@ -22,3 +22,11 @@ export function getPluginSettings(pluginId: string): Promise { return Promise.reject(new Error('Unknown Plugin')); }); } + +export const clearPluginSettingsCache = (pluginId?: string) => { + if (pluginId) { + return delete pluginInfoCache[pluginId]; + } + // clear all + return Object.keys(pluginInfoCache).forEach((key) => delete pluginInfoCache[key]); +}; diff --git a/public/app/features/plugins/tests/pluginCacheBuster.test.ts b/public/app/features/plugins/tests/pluginCacheBuster.test.ts index 7954cef0278..f263cbd3d04 100644 --- a/public/app/features/plugins/tests/pluginCacheBuster.test.ts +++ b/public/app/features/plugins/tests/pluginCacheBuster.test.ts @@ -1,4 +1,5 @@ import { invalidatePluginInCache, locateWithCache, registerPluginInCache } from '../pluginCacheBuster'; +import * as pluginSettings from '../pluginSettings'; describe('PluginCacheBuster', () => { const now = 12345; @@ -35,6 +36,20 @@ describe('PluginCacheBuster', () => { const url = `${address}?_cache=${encodeURI(String(now))}`; expect(locateWithCache({ address }, now)).toBe(url); }); + + it('should also clear plugin settings cache', () => { + const slug = 'bubble-chart-3'; + const version = 'v1.0.0'; + const path = resolvePath(slug); + + const clearPluginSettingsCacheSpy = jest.spyOn(pluginSettings, 'clearPluginSettingsCache'); + + registerPluginInCache({ path, version }); + invalidatePluginInCache(slug); + + expect(clearPluginSettingsCacheSpy).toBeCalledTimes(1); + expect(clearPluginSettingsCacheSpy).toBeCalledWith('bubble-chart-3'); + }); }); function resolvePath(slug: string): string {