diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index 599a588d160..6be68111dd5 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -7,7 +7,7 @@ import { Settings } from 'app/core/config'; import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin'; import { StoreState, ThunkResult } from 'app/types/store'; -import { invalidatePluginInCache } from '../../loader/cache'; +import { clearPluginInfoInCache } from '../../loader/pluginInfoCache'; import { getRemotePlugins, getPluginErrors, @@ -206,7 +206,7 @@ export const install = createAsyncThunk< await updatePanels(); if (installType !== PluginStatus.INSTALL) { - invalidatePluginInCache(id); + clearPluginInfoInCache(id); } return { id, changes }; @@ -231,7 +231,7 @@ export const uninstall = createAsyncThunk, string> await uninstallPlugin(id); await updatePanels(); - invalidatePluginInCache(id); + clearPluginInfoInCache(id); return { id, diff --git a/public/app/features/plugins/importer/importPluginModule.ts b/public/app/features/plugins/importer/importPluginModule.ts index 14e54fda24f..7b73e158881 100644 --- a/public/app/features/plugins/importer/importPluginModule.ts +++ b/public/app/features/plugins/importer/importPluginModule.ts @@ -3,7 +3,7 @@ import { getResolvedLanguage } from '@grafana/i18n/internal'; import { config } from '@grafana/runtime'; import builtInPlugins from '../built_in_plugins'; -import { registerPluginInCache } from '../loader/cache'; +import { registerPluginInfoInCache } from '../loader/pluginInfoCache'; import { SystemJS } from '../loader/systemjs'; import { resolveModulePath } from '../loader/utils'; import { importPluginModuleInSandbox } from '../sandbox/sandboxPluginLoader'; @@ -22,7 +22,7 @@ export async function importPluginModule({ translations, }: PluginImportInfo): Promise { if (version) { - registerPluginInCache({ path, version, loadingStrategy }); + registerPluginInfoInCache({ path, version, loadingStrategy }); } // Add locales to i18n for a plugin if the feature toggle is enabled and the plugin has locales diff --git a/public/app/features/plugins/loader/constants.ts b/public/app/features/plugins/loader/constants.ts index dc90df3f4ba..cb5b3d4182e 100644 --- a/public/app/features/plugins/loader/constants.ts +++ b/public/app/features/plugins/loader/constants.ts @@ -2,3 +2,5 @@ export const SHARED_DEPENDENCY_PREFIX = 'package'; export const LOAD_PLUGIN_CSS_REGEX = /^plugins.+\.css$/i; export const JS_CONTENT_TYPE_REGEX = /^(text|application)\/(x-)?javascript(;|$)/; export const CACHE_INITIALISED_AT = Date.now(); +export const PLUGIN_PATH_REGEX = /\/?public\/plugins\/([^\/]+)\//; +export const DECOUPLED_PLUGIN_REGEX = /\/?public\/app\/plugins\/(?:datasource|panel)\/([^\/]+)\//; diff --git a/public/app/features/plugins/loader/cache.test.ts b/public/app/features/plugins/loader/pluginInfoCache.test.ts similarity index 55% rename from public/app/features/plugins/loader/cache.test.ts rename to public/app/features/plugins/loader/pluginInfoCache.test.ts index 39fa44576be..c6eff383879 100644 --- a/public/app/features/plugins/loader/cache.test.ts +++ b/public/app/features/plugins/loader/pluginInfoCache.test.ts @@ -1,60 +1,61 @@ import { PluginLoadingStrategy } from '@grafana/data'; import { - registerPluginInCache, - invalidatePluginInCache, - resolveWithCache, - getPluginFromCache, + registerPluginInfoInCache, + clearPluginInfoInCache, + resolvePluginUrlWithCache, + getPluginInfoFromCache, extractCacheKeyFromPath, -} from './cache'; +} from './pluginInfoCache'; jest.mock('./constants', () => ({ + ...jest.requireActual('./constants'), CACHE_INITIALISED_AT: 123456, })); describe('Cache Functions', () => { - describe('registerPluginInCache', () => { - it('should register a plugin in the cache', () => { + describe('registerPluginInfoInCache', () => { + it('should register pluginInfo in the cache', () => { const plugin = { version: '1.0.0', loadingStrategy: PluginLoadingStrategy.script }; - registerPluginInCache({ path: 'public/plugins/plugin1/module.js', ...plugin }); - expect(getPluginFromCache('plugin1')).toEqual(plugin); + registerPluginInfoInCache({ path: 'public/plugins/plugin1/module.js', ...plugin }); + expect(getPluginInfoFromCache('plugin1')).toEqual(plugin); }); - it('should not register a plugin if it already exists in the cache', () => { + it('should not register pluginInfo if it already exists in the cache', () => { const path = 'public/plugins/plugin2/module.js'; const plugin = { path, version: '2.0.0', loadingStrategy: PluginLoadingStrategy.script }; - registerPluginInCache(plugin); + registerPluginInfoInCache(plugin); const plugin2 = { path, version: '2.5.0', loadingStrategy: PluginLoadingStrategy.script }; - registerPluginInCache(plugin2); - expect(getPluginFromCache(path)?.version).toBe('2.0.0'); + registerPluginInfoInCache(plugin2); + expect(getPluginInfoFromCache(path)?.version).toBe('2.0.0'); }); }); - describe('invalidatePluginInCache', () => { - it('should invalidate a plugin in the cache', () => { + describe('clearPluginInfoInCache', () => { + it('should clear pluginInfo in the cache', () => { const path = 'public/plugins/plugin2/module.js'; const plugin = { path, version: '3.0.0', loadingStrategy: PluginLoadingStrategy.script }; - registerPluginInCache(plugin); - invalidatePluginInCache('plugin2'); - expect(getPluginFromCache('plugin2')).toBeUndefined(); + registerPluginInfoInCache(plugin); + clearPluginInfoInCache('plugin2'); + expect(getPluginInfoFromCache('plugin2')).toBeUndefined(); }); - it('should not throw an error if the plugin does not exist in the cache', () => { - expect(() => invalidatePluginInCache('nonExistentPlugin')).not.toThrow(); + it('should not throw an error if the pluginInfo does not exist in the cache', () => { + expect(() => clearPluginInfoInCache('nonExistentPlugin')).not.toThrow(); }); }); - describe('resolveWithCache', () => { - it('should resolve URL with timestamp cache bust parameter if plugin is not available in the cache', () => { + describe('resolvePluginUrlWithCache', () => { + it('should resolve URL with timestamp cache bust parameter if pluginInfo is not available in the cache', () => { const url = 'http://localhost:3000/public/plugins/plugin4/module.js'; - expect(resolveWithCache(url)).toContain('_cache=123456'); + expect(resolvePluginUrlWithCache(url)).toContain('_cache=123456'); }); it('should resolve URL with plugin version as cache bust parameter if available', () => { const url = 'http://localhost:3000/public/plugins/plugin5/module.js'; const plugin = { path: url, version: '5.0.0', loadingStrategy: PluginLoadingStrategy.script }; - registerPluginInCache(plugin); - expect(resolveWithCache(url)).toContain('_cache=5.0.0'); + registerPluginInfoInCache(plugin); + expect(resolvePluginUrlWithCache(url)).toContain('_cache=5.0.0'); }); }); @@ -84,15 +85,15 @@ describe('Cache Functions', () => { }); }); - describe('getPluginFromCache', () => { - it('should return plugin from cache if exists', () => { + describe('getPluginInfoFromCache', () => { + it('should return pluginInfo from cache if exists', () => { const plugin = { version: '6.0.0', loadingStrategy: PluginLoadingStrategy.script }; - registerPluginInCache({ path: 'public/plugins/plugin6/module.js', ...plugin }); - expect(getPluginFromCache('plugin6')).toEqual(plugin); + registerPluginInfoInCache({ path: 'public/plugins/plugin6/module.js', ...plugin }); + expect(getPluginInfoFromCache('plugin6')).toEqual(plugin); }); - it('should return undefined if plugin does not exist in cache', () => { - expect(getPluginFromCache('nonExistentPlugin')).toBeUndefined(); + it('should return undefined if pluginInfo does not exist in cache', () => { + expect(getPluginInfoFromCache('nonExistentPlugin')).toBeUndefined(); }); }); }); diff --git a/public/app/features/plugins/loader/cache.ts b/public/app/features/plugins/loader/pluginInfoCache.ts similarity index 52% rename from public/app/features/plugins/loader/cache.ts rename to public/app/features/plugins/loader/pluginInfoCache.ts index 877d7fc485b..3dd3d9b4f56 100644 --- a/public/app/features/plugins/loader/cache.ts +++ b/public/app/features/plugins/loader/pluginInfoCache.ts @@ -2,19 +2,19 @@ import { PluginLoadingStrategy } from '@grafana/data'; import { clearPluginSettingsCache } from '../pluginSettings'; -import { CACHE_INITIALISED_AT } from './constants'; +import { CACHE_INITIALISED_AT, DECOUPLED_PLUGIN_REGEX, PLUGIN_PATH_REGEX } from './constants'; -const cache: Record = {}; +const cache: Record = {}; -type CacheablePlugin = { +type RegisterPluginInfo = { path: string; version: string; loadingStrategy: PluginLoadingStrategy; }; -type CachedPlugin = Omit; +type PluginInfo = Omit; -export function registerPluginInCache({ path, version, loadingStrategy }: CacheablePlugin): void { +export function registerPluginInfoInCache({ path, version, loadingStrategy }: RegisterPluginInfo): void { const key = extractCacheKeyFromPath(path); if (key && !cache[key]) { @@ -25,7 +25,7 @@ export function registerPluginInCache({ path, version, loadingStrategy }: Cachea } } -export function invalidatePluginInCache(pluginId: string): void { +export function clearPluginInfoInCache(pluginId: string): void { const path = pluginId; if (cache[path]) { delete cache[path]; @@ -33,7 +33,7 @@ export function invalidatePluginInCache(pluginId: string): void { clearPluginSettingsCache(pluginId); } -export function resolveWithCache(url: string, defaultBust = CACHE_INITIALISED_AT): string { +export function resolvePluginUrlWithCache(url: string, defaultBust = CACHE_INITIALISED_AT): string { const path = getCacheKey(url); if (!path) { return `${url}?_cache=${defaultBust}`; @@ -43,7 +43,7 @@ export function resolveWithCache(url: string, defaultBust = CACHE_INITIALISED_AT return `${url}?_cache=${bust}`; } -export function getPluginFromCache(path: string): CachedPlugin | undefined { +export function getPluginInfoFromCache(path: string): PluginInfo | undefined { const key = getCacheKey(path); if (!key) { return; @@ -51,17 +51,15 @@ export function getPluginFromCache(path: string): CachedPlugin | undefined { return cache[key]; } -export function extractCacheKeyFromPath(path: string) { - const regex = /\/?public\/plugins\/([^\/]+)\//; - const match = path.match(regex); +export function extractCacheKeyFromPath(path: string): string | null { + const match = path.match(PLUGIN_PATH_REGEX); if (match) { return match[1]; } // Decoupled core plugins can be loaded by alternative paths - const decoupledPluginRegex = /\/?public\/app\/plugins\/(?:datasource|panel)\/([^\/]+)\//; - const decoupledPluginMatch = path.match(decoupledPluginRegex); + const decoupledPluginMatch = path.match(DECOUPLED_PLUGIN_REGEX); if (decoupledPluginMatch) { return decoupledPluginMatch[1]; @@ -70,8 +68,8 @@ export function extractCacheKeyFromPath(path: string) { return null; } -function getCacheKey(address: string): string | undefined { - const key = Object.keys(cache).find((key) => address.includes(key)); +function getCacheKey(path: string): string | undefined { + const key = Object.keys(cache).find((key) => path.includes(key)); if (!key) { return; } diff --git a/public/app/features/plugins/loader/systemjsHooks.test.ts b/public/app/features/plugins/loader/systemjsHooks.test.ts index eae508b0e71..0d24d18c325 100644 --- a/public/app/features/plugins/loader/systemjsHooks.test.ts +++ b/public/app/features/plugins/loader/systemjsHooks.test.ts @@ -1,7 +1,7 @@ import { config } from '@grafana/runtime'; -jest.mock('./cache', () => ({ - resolveWithCache: (url: string) => `${url}?_cache=1234`, +jest.mock('./pluginInfoCache', () => ({ + resolvePluginUrlWithCache: (url: string) => `${url}?_cache=1234`, })); import { server } from './pluginLoader.mock'; diff --git a/public/app/features/plugins/loader/systemjsHooks.ts b/public/app/features/plugins/loader/systemjsHooks.ts index e460c170ae9..cf896e97aea 100644 --- a/public/app/features/plugins/loader/systemjsHooks.ts +++ b/public/app/features/plugins/loader/systemjsHooks.ts @@ -2,8 +2,8 @@ import { config } from '@grafana/runtime'; import { transformPluginSourceForCDN } from '../cdn/utils'; -import { resolveWithCache } from './cache'; import { LOAD_PLUGIN_CSS_REGEX, JS_CONTENT_TYPE_REGEX, SHARED_DEPENDENCY_PREFIX } from './constants'; +import { resolvePluginUrlWithCache } from './pluginInfoCache'; import { SystemJS } from './systemjs'; import { SystemJSWithLoaderHooks } from './types'; import { isHostedOnCDN } from './utils'; @@ -44,13 +44,13 @@ export function decorateSystemJSResolve( (cleanedUrl.endsWith('.js') || cleanedUrl.endsWith('.css')) && !isHostedOnCDN(cleanedUrl); // Add a cache query param for filesystem module.js requests // CDN hosted plugins contain the version in the path so skip - return isFileSystemModule ? resolveWithCache(cleanedUrl) : cleanedUrl; + return isFileSystemModule ? resolvePluginUrlWithCache(cleanedUrl) : cleanedUrl; } catch (err) { // Provide fallback for plugins that use `loadPluginCss` to load theme styles. if (LOAD_PLUGIN_CSS_REGEX.test(id)) { const resolvedUrl = getLoadPluginCssUrl(id); const url = originalResolve.apply(this, [resolvedUrl, parentUrl]); - return resolveWithCache(url); + return resolvePluginUrlWithCache(url); } console.warn(`SystemJS: failed to resolve '${id}'`); return id; diff --git a/public/app/features/plugins/pluginLoader.ts b/public/app/features/plugins/pluginLoader.ts index 2a5e50a93e5..377874a0a5f 100644 --- a/public/app/features/plugins/pluginLoader.ts +++ b/public/app/features/plugins/pluginLoader.ts @@ -21,7 +21,7 @@ import { } from './extensions/registry/setup'; import { importPluginModule } from './importer/importPluginModule'; import { pluginImporter } from './importer/pluginImporter'; -import { getPluginFromCache } from './loader/cache'; +import { getPluginInfoFromCache } from './loader/pluginInfoCache'; // SystemJS has to be imported before the sharedDependenciesMap import { SystemJS } from './loader/systemjs'; // eslint-disable-next-line import/order @@ -40,7 +40,7 @@ const systemJSPrototype: SystemJSWithLoaderHooks = SystemJS.constructor.prototyp // it will load the plugin using a script tag. The logic that sets loadingStrategy comes from the backend. // See: pkg/services/pluginsintegration/pluginassets/pluginassets.go systemJSPrototype.shouldFetch = function (url) { - const pluginInfo = getPluginFromCache(url); + const pluginInfo = getPluginInfoFromCache(url); const jsTypeRegEx = /^[^#?]+\.(js)([?#].*)?$/; if (!jsTypeRegEx.test(url)) { diff --git a/public/app/features/plugins/sandbox/codeLoader.ts b/public/app/features/plugins/sandbox/codeLoader.ts index 72d8a524d7f..67c3c4ec516 100644 --- a/public/app/features/plugins/sandbox/codeLoader.ts +++ b/public/app/features/plugins/sandbox/codeLoader.ts @@ -2,7 +2,7 @@ import { PluginType, patchArrayVectorProrotypeMethods } from '@grafana/data'; import { config } from '@grafana/runtime'; import { transformPluginSourceForCDN } from '../cdn/utils'; -import { resolveWithCache } from '../loader/cache'; +import { resolvePluginUrlWithCache } from '../loader/pluginInfoCache'; import { isHostedOnCDN, resolveModulePath } from '../loader/utils'; import { SandboxEnvironment, SandboxPluginMeta } from './types'; @@ -49,7 +49,7 @@ export async function loadScriptIntoSandbox(url: string, sandboxEnv: SandboxEnvi export async function getPluginCode(meta: SandboxPluginMeta): Promise { if (isHostedOnCDN(meta.module)) { - // Load plugin from CDN, no need for "resolveWithCache" as CDN URLs already include the version + // Load plugin from CDN, no need for "resolvePluginUrlWithCache" as CDN URLs already include the version const url = meta.module; const response = await fetch(url); @@ -67,9 +67,9 @@ export async function getPluginCode(meta: SandboxPluginMeta): Promise { return pluginCode; } else { let modulePath = resolveModulePath(meta.module); - // resolveWithCache will append a query parameter with its version + // resolvePluginUrlWithCache will append a query parameter with its version // to ensure correct cached version is served for local plugins - const pluginCodeUrl = resolveWithCache(modulePath); + const pluginCodeUrl = resolvePluginUrlWithCache(modulePath); const response = await fetch(pluginCodeUrl); let pluginCode = await response.text();