diff --git a/public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts b/public/app/features/plugins/cdn/utils.test.ts
similarity index 77%
rename from public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts
rename to public/app/features/plugins/cdn/utils.test.ts
index d2c2bcfa8d3..b2fda523950 100644
--- a/public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts
+++ b/public/app/features/plugins/cdn/utils.test.ts
@@ -1,29 +1,19 @@
import { config } from '@grafana/runtime';
-import { translateForCDN, extractPluginIdVersionFromUrl } from './pluginCDN';
-describe('Plugin CDN', () => {
- describe('translateForCDN', () => {
- const load = {
- name: 'http://localhost:3000/public/plugin-cdn/grafana-worldmap-panel/0.3.3/grafana-worldmap-panel/module.js',
- address: 'http://my-host.com/grafana-worldmap-panel/0.3.3/grafana-worldmap-panel/module.js',
- source: 'public/plugins/grafana-worldmap-panel/template.html',
- metadata: {
- extension: '',
- deps: [],
- format: 'amd',
- loader: 'cdn-loader',
- encapsulateGlobal: false,
- cjsRequireDetection: true,
- cjsDeferDepsExecute: false,
- esModule: true,
- authorization: false,
- },
- };
+import { extractPluginIdVersionFromUrl, transformPluginSourceForCDN } from './utils';
+
+describe('Plugin CDN Utils', () => {
+ describe('transformPluginSourceForCdn', () => {
+ // const localUrl =
+ // 'http://localhost:3000/public/plugin-cdn/grafana-worldmap-panel/0.3.3/grafana-worldmap-panel/module.js';
+ const pluginId = 'grafana-worldmap-panel';
+ const version = '0.3.3';
config.pluginsCDNBaseURL = 'http://my-host.com';
it('should update the default local path to use the CDN path', () => {
- const translatedLoad = translateForCDN({
- ...load,
+ const translatedLoad = transformPluginSourceForCDN({
+ pluginId,
+ version,
source: 'public/plugins/grafana-worldmap-panel/template.html',
});
expect(translatedLoad).toBe(
@@ -40,7 +30,7 @@ describe('Plugin CDN', () => {
const a = "http://my-host.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/template.html";
const img = "
";
`;
- const translatedLoad = translateForCDN({ ...load, source });
+ const translatedLoad = transformPluginSourceForCDN({ pluginId, version, source });
expect(translatedLoad).toBe(expectedSource);
});
@@ -53,7 +43,7 @@ describe('Plugin CDN', () => {
const a = "http://my-host.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/template.html";
const img = "
";
`;
- const translatedLoad = translateForCDN({ ...load, source });
+ const translatedLoad = transformPluginSourceForCDN({ pluginId, version, source });
expect(translatedLoad).toBe(expectedSource);
});
@@ -72,7 +62,8 @@ describe('Plugin CDN', () => {
".json"
)
`;
- const translatedLoad = translateForCDN({ ...load, source });
+ const translatedLoad = transformPluginSourceForCDN({ pluginId, version, source });
+
expect(translatedLoad).toBe(expectedSource);
});
@@ -85,18 +76,19 @@ describe('Plugin CDN', () => {
Zn(t,e)},t.Rectangle=ui,t.rectangle=function(t,e){return new ui(t,e)},t.Map=He,t.map=function(t,e){return new He(t,e)}}(e)}])});
//# sourceMappingURL=http://my-host.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/module.js.map
`;
- const translatedLoad = translateForCDN({ ...load, source });
+ const translatedLoad = transformPluginSourceForCDN({ pluginId, version, source });
+
expect(translatedLoad).toBe(expectedSource);
});
it('should replace css paths', () => {
const source = `(0,o.loadPluginCss)({dark:"plugins/grafana-worldmap-panel/css/worldmap.dark.css",light:"plugins/grafana-worldmap-panel/css/worldmap.light.css"}),`;
const expectedSource = `(0,o.loadPluginCss)({dark:"http://my-host.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/css/worldmap.dark.css",light:"http://my-host.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/css/worldmap.light.css"}),`;
- const translatedLoad = translateForCDN({ ...load, source });
+ const translatedLoad = transformPluginSourceForCDN({ pluginId, version, source });
+
expect(translatedLoad).toBe(expectedSource);
});
});
-
describe('extractPluginIdVersionFromUrl', () => {
it('should extract the plugin id and version from a path', () => {
const source =
diff --git a/public/app/features/plugins/cdn/utils.ts b/public/app/features/plugins/cdn/utils.ts
new file mode 100644
index 00000000000..04e8b7d4b6d
--- /dev/null
+++ b/public/app/features/plugins/cdn/utils.ts
@@ -0,0 +1,45 @@
+import { config } from '@grafana/runtime';
+
+import { PLUGIN_CDN_URL_KEY } from '../constants';
+/*
+ Given an "expected" address of `http://localhost/public/plugin-cdn/{pluginId}/{version}/public/plugins/{pluginId}`
+ this function will return the plugin id and version.
+ */
+export function extractPluginIdVersionFromUrl(address: string) {
+ const path = new URL(address).pathname;
+ const match = path.split('/');
+ return { id: match[3], version: match[4] };
+}
+
+/*
+ Transforms plugin's source for CDNs loa.
+ Plugins that require loading via a CDN need to have their asset paths translated to point to the configured CDN.
+ e.g. public/plugins/my-plugin/data/ -> http://my-host.com/my-plugin/0.3.3/public/plugins/my-plugin/data/
+ */
+export function transformPluginSourceForCDN({
+ pluginId,
+ version,
+ source,
+}: {
+ pluginId: string;
+ version: string;
+ source: string;
+}): string {
+ const baseAddress = `${config.pluginsCDNBaseURL}/${pluginId}/${version}`;
+ // handle basic asset paths that include public/plugins
+ let newSource = source;
+ newSource = source.replace(/(\/?)(public\/plugins)/g, `${baseAddress}/$2`);
+ // handle custom plugin css (light and dark themes)
+ newSource = newSource.replace(/(["|'])(plugins\/.+?.css)(["|'])/g, `$1${baseAddress}/public/$2$3`);
+ // handle external sourcemap links
+ newSource = newSource.replace(
+ /(\/\/#\ssourceMappingURL=)(.+)\.map/g,
+ `$1${baseAddress}/public/plugins/${pluginId}/$2.map`
+ );
+ return newSource;
+}
+
+export function getPluginCdnResourceUrl(localPath: string): string {
+ const pluginPath = localPath.split(`/public/${PLUGIN_CDN_URL_KEY}/`);
+ return `${config.pluginsCDNBaseURL}/${pluginPath[1]}`;
+}
diff --git a/public/app/features/plugins/constants.ts b/public/app/features/plugins/constants.ts
new file mode 100644
index 00000000000..5a49d1e3845
--- /dev/null
+++ b/public/app/features/plugins/constants.ts
@@ -0,0 +1 @@
+export const PLUGIN_CDN_URL_KEY = 'plugin-cdn';
diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts
index 1857c4dd6ea..471263a85cd 100644
--- a/public/app/features/plugins/plugin_loader.ts
+++ b/public/app/features/plugins/plugin_loader.ts
@@ -33,6 +33,7 @@ import * as ticks from 'app/core/utils/ticks';
import { GenericDataSourcePlugin } from '../datasources/types';
import builtInPlugins from './built_in_plugins';
+import { PLUGIN_CDN_URL_KEY } from './constants';
import { sandboxPluginDependencies } from './sandbox/plugin_dependencies';
import { importPluginModuleInSandbox } from './sandbox/sandbox_plugin_loader';
import { locateFromCDN, translateForCDN } from './systemjsPlugins/pluginCDN';
@@ -78,7 +79,7 @@ grafanaRuntime.SystemJS.config({
'*.css': {
loader: 'css',
},
- 'plugin-cdn/*': {
+ [`${PLUGIN_CDN_URL_KEY}/*`]: {
esModule: true,
authorization: false,
loader: 'cdn-loader',
diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts
index 89c3746307a..9405ccf2d81 100644
--- a/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts
+++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts
@@ -3,6 +3,8 @@ import { ProxyTarget } from '@locker/near-membrane-shared';
import { PluginMeta } from '@grafana/data';
+import { extractPluginIdVersionFromUrl, getPluginCdnResourceUrl, transformPluginSourceForCDN } from '../cdn/utils';
+import { PLUGIN_CDN_URL_KEY } from '../constants';
import { getPluginSettings } from '../pluginSettings';
import { getGeneralSandboxDistortionMap } from './distortion_map';
@@ -120,11 +122,16 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise
},
});
- // fetch and evalute the plugin code inside the sandbox
+ // fetch plugin's code
+ let pluginCode = '';
try {
- let pluginCode = await getPluginCode(meta.module);
- pluginCode = patchPluginSourceMap(meta, pluginCode);
+ pluginCode = await getPluginCode(meta);
+ } catch (e) {
+ throw new Error(`Could not load plugin ${meta.id}: ` + e);
+ reject(new Error(`Could not load plugin ${meta.id}: ` + e));
+ }
+ try {
// runs the code inside the sandbox environment
// this evaluate will eventually run the `define` function inside
// of endowments.
@@ -135,9 +142,26 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise
});
}
-async function getPluginCode(modulePath: string) {
- const response = await fetch('public/' + modulePath + '.js');
- return await response.text();
+async function getPluginCode(meta: PluginMeta): Promise {
+ if (meta.module.includes(`${PLUGIN_CDN_URL_KEY}/`)) {
+ // should load plugin from a CDN
+ const pluginUrl = getPluginCdnResourceUrl(`/public/${meta.module}`) + '.js';
+ const response = await fetch(pluginUrl);
+ let pluginCode = await response.text();
+ const { version } = extractPluginIdVersionFromUrl(pluginUrl);
+ pluginCode = transformPluginSourceForCDN({
+ pluginId: meta.id,
+ version,
+ source: pluginCode,
+ });
+ return pluginCode;
+ } else {
+ //local plugin loading
+ const response = await fetch('public/' + meta.module + '.js');
+ let pluginCode = await response.text();
+ pluginCode = patchPluginSourceMap(meta, pluginCode);
+ return pluginCode;
+ }
}
function getActivityErrorHandler(pluginId: string) {
diff --git a/public/app/features/plugins/systemjsPlugins/pluginCDN.ts b/public/app/features/plugins/systemjsPlugins/pluginCDN.ts
index 27d2b5382f4..8e3784e8d0d 100644
--- a/public/app/features/plugins/systemjsPlugins/pluginCDN.ts
+++ b/public/app/features/plugins/systemjsPlugins/pluginCDN.ts
@@ -1,17 +1,7 @@
-import { config } from '@grafana/runtime';
+import { extractPluginIdVersionFromUrl, getPluginCdnResourceUrl, transformPluginSourceForCDN } from '../cdn/utils';
import type { SystemJSLoad } from './types';
-/*
- Given an "expected" address of `http://localhost/public/plugin-cdn/{pluginId}/{version}/public/plugins/{pluginId}`
- this function will return the plugin id and version.
- */
-export function extractPluginIdVersionFromUrl(address: string) {
- const path = new URL(address).pathname;
- const match = path.split('/');
- return { id: match[3], version: match[4] };
-}
-
/*
Locate: Overrides the location of the plugin resource
Plugins loaded via CDN fall into this plugin via the `plugin-cdn` keyword.
@@ -21,27 +11,13 @@ export function extractPluginIdVersionFromUrl(address: string) {
*/
export function locateFromCDN(load: SystemJSLoad) {
const { address } = load;
- const pluginPath = address.split('/public/plugin-cdn/');
- return `${config.pluginsCDNBaseURL}/${pluginPath[1]}`;
+ return getPluginCdnResourceUrl(address);
}
/*
- Translate: Returns the translated source from load.source, can also set load.metadata.sourceMap for full source maps support.
- Plugins that require loading via a CDN need to have their asset paths translated to point to the configured CDN.
- e.g. public/plugins/my-plugin/data/ -> http://my-host.com/my-plugin/0.3.3/public/plugins/my-plugin/data/
+ Translate: Returns the translated source from load.source;
*/
export function translateForCDN(load: SystemJSLoad) {
const { id, version } = extractPluginIdVersionFromUrl(load.name);
- const baseAddress = `${config.pluginsCDNBaseURL}/${id}/${version}`;
- // handle basic asset paths that include public/plugins
- load.source = load.source.replace(/(\/?)(public\/plugins)/g, `${baseAddress}/$2`);
- // handle custom plugin css (light and dark themes)
- load.source = load.source.replace(/(["|'])(plugins\/.+?.css)(["|'])/g, `$1${baseAddress}/public/$2$3`);
- // handle external sourcemap links
- load.source = load.source.replace(
- /(\/\/#\ssourceMappingURL=)(.+)\.map/g,
- `$1${baseAddress}/public/plugins/${id}/$2.map`
- );
-
- return load.source;
+ return transformPluginSourceForCDN({ pluginId: id, version, source: load.source });
}