diff --git a/public/app/features/plugins/extensions/getPluginExtensions.test.ts b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx
similarity index 93%
rename from public/app/features/plugins/extensions/getPluginExtensions.test.ts
rename to public/app/features/plugins/extensions/getPluginExtensions.test.tsx
index 52408ecf6de..85c6304154e 100644
--- a/public/app/features/plugins/extensions/getPluginExtensions.test.ts
+++ b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx
@@ -1,4 +1,6 @@
-import { PluginExtensionLinkConfig, PluginExtensionTypes } from '@grafana/data';
+import React from 'react';
+
+import { PluginExtensionComponentConfig, PluginExtensionLinkConfig, PluginExtensionTypes } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { createPluginExtensionRegistry } from './createPluginExtensionRegistry';
@@ -16,8 +18,10 @@ jest.mock('@grafana/runtime', () => {
describe('getPluginExtensions()', () => {
const extensionPoint1 = 'grafana/dashboard/panel/menu';
const extensionPoint2 = 'plugins/myorg-basic-app/start';
+ const extensionPoint3 = 'grafana/datasources/config';
const pluginId = 'grafana-basic-app';
- let link1: PluginExtensionLinkConfig, link2: PluginExtensionLinkConfig;
+ // Sample extension configs that are used in the tests below
+ let link1: PluginExtensionLinkConfig, link2: PluginExtensionLinkConfig, component1: PluginExtensionComponentConfig;
beforeEach(() => {
link1 = {
@@ -36,6 +40,15 @@ describe('getPluginExtensions()', () => {
extensionPointId: extensionPoint2,
configure: jest.fn().mockImplementation((context) => ({ title: context?.title })),
};
+ component1 = {
+ type: PluginExtensionTypes.component,
+ title: 'Component 1',
+ description: 'Component 1 description',
+ extensionPointId: extensionPoint3,
+ component: (context) => {
+ return
Hello world!
;
+ },
+ };
global.console.warn = jest.fn();
jest.mocked(reportInteraction).mockReset();
@@ -409,4 +422,20 @@ describe('getPluginExtensions()', () => {
category: extension.category,
});
});
+
+ test('should be possible to register and get component type extensions', () => {
+ const extension = component1;
+ const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [extension] }]);
+ const { extensions } = getPluginExtensions({ registry, extensionPointId: extension.extensionPointId });
+
+ expect(extensions).toHaveLength(1);
+ expect(extensions[0]).toEqual(
+ expect.objectContaining({
+ pluginId,
+ type: PluginExtensionTypes.component,
+ title: extension.title,
+ description: extension.description,
+ })
+ );
+ });
});
diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts
index 85b35929969..2db726b5e2a 100644
--- a/public/app/features/plugins/extensions/getPluginExtensions.ts
+++ b/public/app/features/plugins/extensions/getPluginExtensions.ts
@@ -18,6 +18,7 @@ import {
generateExtensionId,
getEventHelpers,
isPluginExtensionComponentConfig,
+ wrapWithPluginContext,
} from './utils';
import {
assertIsReactComponent,
@@ -101,7 +102,7 @@ export const getPluginExtensions: GetExtensions = ({ context, extensionPointId,
title: extensionConfig.title,
description: extensionConfig.description,
- component: extensionConfig.component,
+ component: wrapWithPluginContext(pluginId, extensionConfig.component),
};
extensions.push(extension);
diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx
index c7cc1169d8e..ee4b742f5f4 100644
--- a/public/app/features/plugins/extensions/utils.test.tsx
+++ b/public/app/features/plugins/extensions/utils.test.tsx
@@ -6,7 +6,14 @@ import { type PluginExtensionLinkConfig, PluginExtensionTypes, dateTime, usePlug
import appEvents from 'app/core/app_events';
import { ShowModalReactEvent } from 'app/types/events';
-import { deepFreeze, isPluginExtensionLinkConfig, handleErrorsInFn, getReadOnlyProxy, getEventHelpers } from './utils';
+import {
+ deepFreeze,
+ isPluginExtensionLinkConfig,
+ handleErrorsInFn,
+ getReadOnlyProxy,
+ getEventHelpers,
+ wrapWithPluginContext,
+} from './utils';
jest.mock('app/features/plugins/pluginSettings', () => ({
...jest.requireActual('app/features/plugins/pluginSettings'),
@@ -436,4 +443,26 @@ describe('Plugin Extensions / Utils', () => {
});
});
});
+
+ describe('wrapExtensionComponentWithContext()', () => {
+ const ExampleComponent = () => {
+ const { meta } = usePluginContext();
+
+ return (
+
+
Hello Grafana!
Version: {meta.info.version}
+
+ );
+ };
+
+ it('should make the plugin context available for the wrapped component', async () => {
+ const pluginId = 'grafana-worldmap-panel';
+ const Component = wrapWithPluginContext(pluginId, ExampleComponent);
+
+ render();
+
+ expect(await screen.findByText('Hello Grafana!')).toBeVisible();
+ expect(screen.getByText('Version: 1.0.0')).toBeVisible();
+ });
+ });
});
diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx
index be8fbb6762b..82ff3c34df9 100644
--- a/public/app/features/plugins/extensions/utils.tsx
+++ b/public/app/features/plugins/extensions/utils.tsx
@@ -1,6 +1,7 @@
import { css } from '@emotion/css';
import { isArray, isObject } from 'lodash';
import React from 'react';
+import { useAsync } from 'react-use';
import {
type PluginExtensionLinkConfig,
@@ -12,7 +13,6 @@ import {
isDateTime,
dateTime,
PluginContextProvider,
- PluginMeta,
} from '@grafana/data';
import { Modal } from '@grafana/ui';
import appEvents from 'app/core/app_events';
@@ -51,11 +51,10 @@ export function handleErrorsInFn(fn: Function, errorMessagePrefix = '') {
export function getEventHelpers(pluginId: string, context?: Readonly