Plugin extensions: Introduce new registry for added links (#92343)

* add added component registry

* fix broken test

* add tests for usePluginComponents hook

* readd expose components

* add type assertion exceptions to betterer results

* use new addedComponent registry in legacy endpoints

* remove unused code

* cleanup

* revert test code

* remove commented code

* initial commit

* refactor sync method and hook

* fix tests

* subscribe to the correct registry

* remove old registry

* cleanup types

* add use usePluginLinks hook

* add more tests

* fix import order

* fix typo

* fix and temporarly skip failing tests

* wip

* add hook tests

* add more tests

* remove old hook

* fix versioning

* add version to all extension point ids

* remove cleanup

* remove unused imports

* revert touched file

* fix test

* test: remove hook creation

* catch init error

* send error to faro

* fix broken hook

* comment out call hook initialization

* use the right import ofr isString

* remove unused import

* remove registryState type

* pr feedback

* Update public/app/features/plugins/extensions/validators.test.tsx

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* Update public/app/features/plugins/extensions/validators.test.tsx

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* remove no longer relevant comment

* fix broken tests

* Fixed test to verify that the memotization works properly.

* simplify hooks

---------

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
This commit is contained in:
Erik Sundell
2024-08-30 10:09:01 +02:00
committed by GitHub
co-authored by Levente Balogh Marcus Andersson
parent 16c618f4d3
commit db0cc24f2b
40 changed files with 1498 additions and 1639 deletions
@@ -1,25 +1,22 @@
import { PluginPage, usePluginLinks } from '@grafana/runtime';
import { Stack } from '@grafana/ui';
import { ActionButton } from '../components/ActionButton';
import { testIds } from '../testIds';
export const LINKS_EXTENSION_POINT_ID = 'plugins/grafana-extensionstest-app/use-plugin-links/v1';
export function AddedLinks() {
const { links, isLoading } = usePluginLinks({ extensionPointId: LINKS_EXTENSION_POINT_ID });
const { links } = usePluginLinks({ extensionPointId: LINKS_EXTENSION_POINT_ID });
return (
<PluginPage>
<div data-testid={testIds.addedLinksPage.container}>
{isLoading ? (
<div>Loading...</div>
) : (
links.map(({ id, title, path, onClick }) => (
<a href={path} title={title} key={id} onClick={onClick}>
{title}
</a>
))
)}
</div>
<Stack direction={'column'} gap={4} data-testid={testIds.addedLinksPage.container}>
<section data-testid={testIds.addedLinksPage.section1}>
<h3>Link extensions defined with addLink and retrieved using usePluginLinks</h3>
<ActionButton extensions={links} />
</section>
</Stack>
</PluginPage>
);
}
@@ -4,7 +4,6 @@ import { LINKS_EXTENSION_POINT_ID } from '../../pages/AddedLinks';
import { testIds } from '../../testIds';
import { App } from './components/App';
import pluginJson from './plugin.json';
export const plugin = new AppPlugin<{}>()
.setRootPage(App)
@@ -24,5 +23,11 @@ export const plugin = new AppPlugin<{}>()
title: 'Basic link',
description: '...',
targets: [LINKS_EXTENSION_POINT_ID],
path: `/a/${pluginJson.id}/`,
path: '/a/grafana-extensionexample1-app/',
})
.addLink({
title: 'Go to A',
description: 'Navigating to pluging A',
targets: [LINKS_EXTENSION_POINT_ID],
path: '/a/grafana-extensionexample1-app/',
});
@@ -1,5 +1,6 @@
import { AppPlugin } from '@grafana/data';
import { LINKS_EXTENSION_POINT_ID } from '../../pages/AddedLinks';
import { testIds } from '../../testIds';
import { App } from './components/App';
@@ -30,4 +31,15 @@ export const plugin = new AppPlugin<{}>()
component: ({ name }: { name: string }) => (
<div data-testid={testIds.appB.reusableAddedComponent}>Hello {name}!</div>
),
})
.addLink({
title: 'Open from B',
description: 'Open a modal from plugin B',
targets: [LINKS_EXTENSION_POINT_ID],
onClick: (_, { openModal }) => {
openModal({
title: 'Modal from app B',
body: () => <div data-testid={testIds.appB.modal}>From plugin B</div>,
});
},
});
@@ -36,5 +36,6 @@ export const testIds = {
},
addedLinksPage: {
container: 'data-testid pg-added-links-container',
section1: 'use-plugin-links',
},
};
@@ -3,8 +3,28 @@ import { test, expect } from '@grafana/plugin-e2e';
import pluginJson from '../plugin.json';
import { testIds } from '../testIds';
test('path link', async ({ page }) => {
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => {
await page.goto(`/a/${pluginJson.id}/added-links`);
await page.getByTestId(testIds.addedLinksPage.container).getByText('Basic link').click();
await expect(page.getByTestId(testIds.appA.container)).toHaveText('Hello Grafana!');
const section = await page.getByTestId(testIds.addedLinksPage.section1);
await section.getByTestId(testIds.actions.button).click();
await page.getByTestId(testIds.container).getByText('Go to A').click();
await page.getByTestId(testIds.modal.open).click();
await expect(page.getByTestId(testIds.appA.container)).toBeVisible();
});
test('should extend main app with link extension from app B', async ({ page }) => {
await page.goto(`/a/${pluginJson.id}/added-links`);
const section = await page.getByTestId(testIds.addedLinksPage.section1);
await section.getByTestId(testIds.actions.button).click();
await page.getByTestId(testIds.container).getByText('Open from B').click();
await expect(page.getByTestId(testIds.appB.modal)).toBeVisible();
});
test('should extend main app with basic link extension from app A', async ({ page }) => {
await page.goto(`/a/${pluginJson.id}/added-links`);
const section = await page.getByTestId(testIds.addedLinksPage.section1);
await section.getByTestId(testIds.actions.button).click();
await page.getByTestId(testIds.container).getByText('Basic link').click();
await page.getByTestId(testIds.modal.open).click();
await expect(page.getByTestId(testIds.appA.container)).toBeVisible();
});