diff --git a/packages/grafana-data/src/context/plugins/usePluginContext.tsx b/packages/grafana-data/src/context/plugins/usePluginContext.tsx
index 51723446b81..e2ea1056629 100644
--- a/packages/grafana-data/src/context/plugins/usePluginContext.tsx
+++ b/packages/grafana-data/src/context/plugins/usePluginContext.tsx
@@ -1,5 +1,7 @@
import { useContext } from 'react';
+import { PluginMeta } from '../../types';
+
import { Context, PluginContextType } from './PluginContext';
export function usePluginContext(): PluginContextType {
@@ -9,3 +11,21 @@ export function usePluginContext(): PluginContextType {
}
return context;
}
+
+export function usePluginMeta(): PluginMeta {
+ const context = usePluginContext();
+
+ return context.meta;
+}
+
+export function usePluginJsonData() {
+ const context = usePluginContext();
+
+ return context.meta.jsonData;
+}
+
+export function usePluginVersion() {
+ const context = usePluginContext();
+
+ return context.meta.info.version;
+}
diff --git a/public/app/features/plugins/components/AppRootPage.tsx b/public/app/features/plugins/components/AppRootPage.tsx
index cf4abcd403f..bd22739aa1a 100644
--- a/public/app/features/plugins/components/AppRootPage.tsx
+++ b/public/app/features/plugins/components/AppRootPage.tsx
@@ -3,7 +3,16 @@ import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
import { useLocation, useRouteMatch } from 'react-router-dom';
-import { AppEvents, AppPlugin, AppPluginMeta, NavModel, NavModelItem, OrgRole, PluginType } from '@grafana/data';
+import {
+ AppEvents,
+ AppPlugin,
+ AppPluginMeta,
+ NavModel,
+ NavModelItem,
+ OrgRole,
+ PluginType,
+ PluginContextProvider,
+} from '@grafana/data';
import { config, locationSearchToObject } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
@@ -76,13 +85,15 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) {
}
const pluginRoot = plugin.root && (
-
+
+
+
);
// Because of the fallback at plugin routes, we need to check
@@ -93,7 +104,7 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) {
return true;
}
- const pluginInclude = plugin.meta?.includes.find((include) => include.path === pluginRoot.props.path);
+ const pluginInclude = plugin.meta?.includes.find((include) => include.path === location.pathname);
// Check if include configuration contains current path
if (!pluginInclude) {
return true;
diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts
index 069635dea3d..85b35929969 100644
--- a/public/app/features/plugins/extensions/getPluginExtensions.ts
+++ b/public/app/features/plugins/extensions/getPluginExtensions.ts
@@ -189,7 +189,7 @@ function getLinkExtensionOnClick(
category: config.category,
});
- const result = onClick(event, getEventHelpers(context));
+ const result = onClick(event, getEventHelpers(pluginId, context));
if (isPromise(result)) {
result.catch((e) => {
diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx
index 60738f3a3ba..c7cc1169d8e 100644
--- a/public/app/features/plugins/extensions/utils.test.tsx
+++ b/public/app/features/plugins/extensions/utils.test.tsx
@@ -2,12 +2,17 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { type Unsubscribable } from 'rxjs';
-import { type PluginExtensionLinkConfig, PluginExtensionTypes, dateTime } from '@grafana/data';
+import { type PluginExtensionLinkConfig, PluginExtensionTypes, dateTime, usePluginContext } from '@grafana/data';
import appEvents from 'app/core/app_events';
import { ShowModalReactEvent } from 'app/types/events';
import { deepFreeze, isPluginExtensionLinkConfig, handleErrorsInFn, getReadOnlyProxy, getEventHelpers } from './utils';
+jest.mock('app/features/plugins/pluginSettings', () => ({
+ ...jest.requireActual('app/features/plugins/pluginSettings'),
+ getPluginSettings: () => Promise.resolve({ info: { version: '1.0.0' } }),
+}));
+
describe('Plugin Extensions / Utils', () => {
describe('deepFreeze()', () => {
test('should not fail when called with primitive values', () => {
@@ -341,27 +346,29 @@ describe('Plugin Extensions / Utils', () => {
});
it('should open modal with provided title and body', async () => {
- const { openModal } = getEventHelpers();
+ const pluginId = 'grafana-worldmap-panel';
+ const { openModal } = getEventHelpers(pluginId);
openModal({
title: 'Title in modal',
body: () =>
Text in body
,
});
- expect(screen.getByRole('dialog')).toBeVisible();
+ expect(await screen.findByRole('dialog')).toBeVisible();
expect(screen.getByRole('heading')).toHaveTextContent('Title in modal');
expect(screen.getByText('Text in body')).toBeVisible();
});
it('should open modal with default width if not specified', async () => {
- const { openModal } = getEventHelpers();
+ const pluginId = 'grafana-worldmap-panel';
+ const { openModal } = getEventHelpers(pluginId);
openModal({
title: 'Title in modal',
body: () => Text in body
,
});
- const modal = screen.getByRole('dialog');
+ const modal = await screen.findByRole('dialog');
const style = window.getComputedStyle(modal);
expect(style.width).toBe('750px');
@@ -369,7 +376,8 @@ describe('Plugin Extensions / Utils', () => {
});
it('should open modal with specified width', async () => {
- const { openModal } = getEventHelpers();
+ const pluginId = 'grafana-worldmap-panel';
+ const { openModal } = getEventHelpers(pluginId);
openModal({
title: 'Title in modal',
@@ -377,14 +385,15 @@ describe('Plugin Extensions / Utils', () => {
width: '70%',
});
- const modal = screen.getByRole('dialog');
+ const modal = await screen.findByRole('dialog');
const style = window.getComputedStyle(modal);
expect(style.width).toBe('70%');
});
it('should open modal with specified height', async () => {
- const { openModal } = getEventHelpers();
+ const pluginId = 'grafana-worldmap-panel';
+ const { openModal } = getEventHelpers(pluginId);
openModal({
title: 'Title in modal',
@@ -392,17 +401,37 @@ describe('Plugin Extensions / Utils', () => {
height: 600,
});
- const modal = screen.getByRole('dialog');
+ const modal = await screen.findByRole('dialog');
const style = window.getComputedStyle(modal);
expect(style.height).toBe('600px');
});
+
+ it('should open modal with the plugin context being available', async () => {
+ const pluginId = 'grafana-worldmap-panel';
+ const { openModal } = getEventHelpers(pluginId);
+
+ const ModalContent = () => {
+ const context = usePluginContext();
+
+ return Version: {context.meta.info.version}
;
+ };
+
+ openModal({
+ title: 'Title in modal',
+ body: ModalContent,
+ });
+
+ const modal = await screen.findByRole('dialog');
+ expect(modal).toHaveTextContent('Version: 1.0.0');
+ });
});
describe('context', () => {
it('should return same object as passed to getEventHelpers', () => {
+ const pluginId = 'grafana-worldmap-panel';
const source = {};
- const { context } = getEventHelpers(source);
+ const { context } = getEventHelpers(pluginId, source);
expect(context).toBe(source);
});
});
diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx
index 42dc799494f..be8fbb6762b 100644
--- a/public/app/features/plugins/extensions/utils.tsx
+++ b/public/app/features/plugins/extensions/utils.tsx
@@ -11,9 +11,12 @@ import {
type PluginExtensionOpenModalOptions,
isDateTime,
dateTime,
+ PluginContextProvider,
+ PluginMeta,
} from '@grafana/data';
import { Modal } from '@grafana/ui';
import appEvents from 'app/core/app_events';
+import { getPluginSettings } from 'app/features/plugins/pluginSettings';
import { ShowModalReactEvent } from 'app/types/events';
export function logWarning(message: string) {
@@ -45,13 +48,14 @@ export function handleErrorsInFn(fn: Function, errorMessagePrefix = '') {
}
// Event helpers are designed to make it easier to trigger "core actions" from an extension event handler, e.g. opening a modal or showing a notification.
-export function getEventHelpers(context?: Readonly