frontend-plugin-api: use react element for header action
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -14,16 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { lazy as reactLazy } from 'react';
|
||||
import { ExtensionBoundary } from '../components';
|
||||
import {
|
||||
coreExtensionData,
|
||||
createExtensionBlueprint,
|
||||
createExtensionBlueprintParams,
|
||||
createExtensionDataRef,
|
||||
} from '../wiring';
|
||||
|
||||
const actionDataRef = createExtensionDataRef<() => Promise<JSX.Element>>().with(
|
||||
{ id: 'core.header-action.loader' },
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates extensions that provide plugin-scoped header actions.
|
||||
*
|
||||
@@ -37,14 +35,18 @@ const actionDataRef = createExtensionDataRef<() => Promise<JSX.Element>>().with(
|
||||
export const HeaderActionBlueprint = createExtensionBlueprint({
|
||||
kind: 'header-action',
|
||||
attachTo: { id: 'api:app/header-actions', input: 'actions' },
|
||||
output: [actionDataRef],
|
||||
dataRefs: {
|
||||
action: actionDataRef,
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
defineParams(params: { loader: () => Promise<JSX.Element> }) {
|
||||
return createExtensionBlueprintParams(params);
|
||||
},
|
||||
*factory(params) {
|
||||
yield actionDataRef(params.loader);
|
||||
*factory(params, { node }) {
|
||||
const LazyAction = reactLazy(() =>
|
||||
params.loader().then(element => ({ default: () => element })),
|
||||
);
|
||||
yield coreExtensionData.reactElement(
|
||||
<ExtensionBoundary node={node} errorPresentation="error-api">
|
||||
<LazyAction />
|
||||
</ExtensionBoundary>,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,17 +18,15 @@ import { render, screen } from '@testing-library/react';
|
||||
import { DefaultHeaderActionsApi } from './DefaultHeaderActionsApi';
|
||||
|
||||
describe('DefaultHeaderActionsApi', () => {
|
||||
it('should return actions for a specific plugin', async () => {
|
||||
it('should return actions for a specific plugin', () => {
|
||||
const api = DefaultHeaderActionsApi.fromActions([
|
||||
{
|
||||
loader: async () => <button>Action A</button>,
|
||||
element: <button>Action A</button>,
|
||||
pluginId: 'plugin-a',
|
||||
nodeId: 'plugin-header-action:plugin-a/action-a',
|
||||
},
|
||||
{
|
||||
loader: async () => <button>Action B</button>,
|
||||
element: <button>Action B</button>,
|
||||
pluginId: 'plugin-b',
|
||||
nodeId: 'plugin-header-action:plugin-b/action-b',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -36,34 +34,31 @@ describe('DefaultHeaderActionsApi', () => {
|
||||
expect(api.getHeaderActions('plugin-b')).toHaveLength(1);
|
||||
|
||||
render(<>{api.getHeaderActions('plugin-a')}</>);
|
||||
await expect(
|
||||
screen.findByRole('button', { name: 'Action A' }),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Action A' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return an empty array for unknown plugins', () => {
|
||||
const api = DefaultHeaderActionsApi.fromActions([
|
||||
{
|
||||
loader: async () => <span>Action</span>,
|
||||
element: <span>Action</span>,
|
||||
pluginId: 'plugin-a',
|
||||
nodeId: 'plugin-header-action:plugin-a/action',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(api.getHeaderActions('unknown-plugin')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should group multiple actions by plugin', async () => {
|
||||
it('should group multiple actions by plugin', () => {
|
||||
const api = DefaultHeaderActionsApi.fromActions([
|
||||
{
|
||||
loader: async () => <button>First</button>,
|
||||
element: <button>First</button>,
|
||||
pluginId: 'plugin-a',
|
||||
nodeId: 'plugin-header-action:plugin-a/first',
|
||||
},
|
||||
{
|
||||
loader: async () => <button>Second</button>,
|
||||
element: <button>Second</button>,
|
||||
pluginId: 'plugin-a',
|
||||
nodeId: 'plugin-header-action:plugin-a/second',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -71,11 +66,7 @@ describe('DefaultHeaderActionsApi', () => {
|
||||
expect(actions).toHaveLength(2);
|
||||
|
||||
render(<>{actions}</>);
|
||||
await expect(
|
||||
screen.findByRole('button', { name: 'First' }),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByRole('button', { name: 'Second' }),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'First' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Second' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Suspense, ReactNode, lazy } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { type HeaderActionsApi } from '@backstage/frontend-plugin-api';
|
||||
|
||||
type ActionInput = {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
element: JSX.Element;
|
||||
pluginId: string;
|
||||
nodeId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -45,16 +44,7 @@ export class DefaultHeaderActionsApi implements HeaderActionsApi {
|
||||
actionsByPlugin.set(action.pluginId, pluginActions);
|
||||
}
|
||||
|
||||
const LazyAction = lazy(async () => {
|
||||
const element = await action.loader();
|
||||
return { default: () => element };
|
||||
});
|
||||
|
||||
pluginActions.push(
|
||||
<Suspense key={action.nodeId} fallback={null}>
|
||||
<LazyAction />
|
||||
</Suspense>,
|
||||
);
|
||||
pluginActions.push(action.element);
|
||||
}
|
||||
|
||||
return new DefaultHeaderActionsApi(actionsByPlugin);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
HeaderActionBlueprint,
|
||||
coreExtensionData,
|
||||
headerActionsApiRef,
|
||||
createExtensionInput,
|
||||
ApiBlueprint,
|
||||
@@ -28,7 +28,7 @@ import { DefaultHeaderActionsApi } from '../apis/HeaderActionsApi';
|
||||
export const HeaderActionsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'header-actions',
|
||||
inputs: {
|
||||
actions: createExtensionInput([HeaderActionBlueprint.dataRefs.action]),
|
||||
actions: createExtensionInput([coreExtensionData.reactElement]),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
return originalFactory(defineParams =>
|
||||
@@ -38,9 +38,8 @@ export const HeaderActionsApi = ApiBlueprint.makeWithOverrides({
|
||||
factory: () => {
|
||||
return DefaultHeaderActionsApi.fromActions(
|
||||
inputs.actions.map(actionInput => ({
|
||||
loader: actionInput.get(HeaderActionBlueprint.dataRefs.action),
|
||||
element: actionInput.get(coreExtensionData.reactElement),
|
||||
pluginId: actionInput.node.spec.plugin.pluginId,
|
||||
nodeId: actionInput.node.spec.id,
|
||||
})),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user