PluginExtensions: Exposing registry meta for components returned via `usePluginComponents` (#100587)

Exposing meta information from registry via Component.meta.
single-top-nav-means-single
Marcus Andersson 5 months ago committed by GitHub
parent 187a18ea25
commit 84d22e176b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      packages/grafana-data/src/index.ts
  2. 2
      packages/grafana-data/src/types/pluginExtensions.ts
  3. 3
      packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts
  4. 44
      public/app/features/plugins/extensions/usePluginComponents.test.tsx
  5. 38
      public/app/features/plugins/extensions/usePluginComponents.tsx

@ -548,6 +548,7 @@ export {
type PluginExtension,
type PluginExtensionLink,
type PluginExtensionComponent,
type PluginExtensionComponentMeta,
type PluginExtensionConfig,
type PluginExtensionFunction,
type PluginExtensionLinkConfig,

@ -32,6 +32,8 @@ export type PluginExtensionLink = PluginExtensionBase & {
category?: string;
};
export type PluginExtensionComponentMeta = Omit<PluginExtensionComponent, 'component'>;
export type PluginExtensionComponent<Props = {}> = PluginExtensionBase & {
type: PluginExtensionTypes.component;
component: React.ComponentType<Props>;

@ -3,6 +3,7 @@ import type {
PluginExtensionLink,
PluginExtensionComponent,
PluginExtensionFunction,
PluginExtensionComponentMeta,
} from '@grafana/data';
import { isPluginExtensionComponent, isPluginExtensionLink } from './utils';
@ -42,7 +43,7 @@ export type UsePluginComponentResult<Props = {}> = {
};
export type UsePluginComponentsResult<Props = {}> = {
components: Array<React.ComponentType<Props>>;
components: Array<React.ComponentType<Props> & { meta: PluginExtensionComponentMeta }>;
isLoading: boolean;
};

@ -159,6 +159,50 @@ describe('usePluginComponents()', () => {
expect(screen.queryByText('Hello World3')).toBeNull();
});
it('should return component with meta information attached to it', async () => {
registries.addedComponentsRegistry.register({
pluginId,
configs: [
{
targets: extensionPointId,
title: '1',
description: '1',
component: () => <div>Hello World1</div>,
},
{
targets: extensionPointId,
title: '2',
description: '2',
component: () => <div>Hello World2</div>,
},
{
targets: 'plugins/another-extension/v1',
title: '3',
description: '3',
component: () => <div>Hello World3</div>,
},
],
});
const { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper });
expect(result.current.components.length).toBe(2);
expect(result.current.components[0].meta).toEqual({
pluginId,
title: '1',
description: '1',
id: '-1921123020',
type: 'component',
});
expect(result.current.components[1].meta).toEqual({
pluginId,
title: '2',
description: '2',
id: '-1921123019',
type: 'component',
});
});
it('should dynamically update the extensions registered for a certain extension point', () => {
let { result, rerender } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper });

@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { useObservable } from 'react-use';
import { usePluginContext } from '@grafana/data';
import { PluginExtensionComponentMeta, PluginExtensionTypes, usePluginContext } from '@grafana/data';
import {
UsePluginComponentOptions,
UsePluginComponentsResult,
@ -10,8 +10,9 @@ import {
import { useAddedComponentsRegistry } from './ExtensionRegistriesContext';
import * as errors from './errors';
import { log } from './logs/log';
import { AddedComponentRegistryItem } from './registry/AddedComponentsRegistry';
import { useLoadAppPlugins } from './useLoadAppPlugins';
import { getExtensionPointPluginDependencies, isGrafanaDevMode } from './utils';
import { generateExtensionId, getExtensionPointPluginDependencies, isGrafanaDevMode } from './utils';
import { isExtensionPointIdValid, isExtensionPointMetaInfoMissing } from './validators';
// Returns an array of component extensions for the given extension point
@ -27,7 +28,7 @@ export function usePluginComponents<Props extends object = {}>({
return useMemo(() => {
// For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana.
const enableRestrictions = isGrafanaDevMode() && pluginContext;
const components: Array<React.ComponentType<Props>> = [];
const components: Array<React.ComponentType<Props> & { meta: PluginExtensionComponentMeta }> = [];
const extensionsByPlugin: Record<string, number> = {};
const pluginId = pluginContext?.meta.id ?? '';
const pointLog = log.child({
@ -66,7 +67,12 @@ export function usePluginComponents<Props extends object = {}>({
extensionsByPlugin[pluginId] = 0;
}
components.push(registryItem.component as React.ComponentType<Props>);
const component = createComponentWithMeta<Props>(
registryItem as AddedComponentRegistryItem<Props>,
extensionPointId
);
components.push(component);
extensionsByPlugin[pluginId] += 1;
}
@ -76,3 +82,27 @@ export function usePluginComponents<Props extends object = {}>({
};
}, [extensionPointId, limitPerPlugin, pluginContext, registryState, isLoadingAppPlugins]);
}
function createComponentWithMeta<Props extends JSX.IntrinsicAttributes>(
registryItem: AddedComponentRegistryItem<Props>,
extensionPointId: string
): React.ComponentType<Props> & { meta: PluginExtensionComponentMeta } {
const { component: Component, ...config } = registryItem;
function ComponentWithMeta(props: Props) {
return <Component {...props} />;
}
ComponentWithMeta.displayName = Component.displayName;
ComponentWithMeta.defaultProps = Component.defaultProps;
ComponentWithMeta.propTypes = Component.propTypes;
ComponentWithMeta.contextTypes = Component.contextTypes;
ComponentWithMeta.meta = {
pluginId: config.pluginId,
title: config.title ?? '',
description: config.description ?? '',
id: generateExtensionId(config.pluginId, extensionPointId, config.title),
type: PluginExtensionTypes.component,
} satisfies PluginExtensionComponentMeta;
return ComponentWithMeta;
}

Loading…
Cancel
Save