跳至主要内容

创建扩展点

扩展点 是您插件 UI 或 Grafana UI 的一部分,其他插件可以通过钩子在其中添加链接或 React 组件。您可以使用它们根据扩展点公开的上下文扩展用户体验。

关键概念 下阅读有关扩展的更多信息。

类型描述
链接链接具有 pathonClick() 属性。

何时使用?
如果您希望为插件提供一种方法来为您的 UI 的一部分定义自定义用户操作,请使用链接。这些操作可以只是指向插件的交叉链接,或者使用 onClick() 方法,它们可以实现更具交互性的页面内体验,例如模态窗口。

API 参考
- addLink() - 从插件注册链接
- usePluginLinks() - 获取为扩展点注册的链接
组件组件是可以用来渲染自定义用户体验的 React 组件。

何时使用?
如果您想让插件有更大的自由来扩展您的 UI,例如使用自定义部分扩展配置表单,请使用组件。

API 参考
- addComponent() - 从插件注册组件
- usePluginComponents() - 获取为扩展点注册的组件
  • 确保您的 UI 处理多个链接
    多个插件可能会向您的扩展点添加链接。确保您的扩展点可以处理这种情况,并仍然提供良好的用户体验。请参阅如何 限制插件显示的扩展数量

  • 共享上下文信息
    考虑哪些上下文信息可能对其他插件有用,并将这些信息添加到 context 对象中。例如,面板菜单扩展点共享 panelIdtimeRange。请注意,context{} 对象在传递到链接之前始终会被冻结,因此无法对其进行变异。

  • 避免不必要的重新渲染

    • 静态上下文

      // Define the `context` object outside of the component if it only has static values
      const context { foo: 'bar' };

      export const InstanceToolbar = () => {
      const { links, isLoading } = usePluginLinks({ extensionPointId, context });
    • 动态上下文

      export const InstanceToolbar = ({ instanceId }) => {
      // Always use `useMemo()` when the `context` object has "dynamic" values
      const context = useMemo(() => ({ instanceId }), [instanceId]);
      const { links, isLoading } = usePluginLinks({ extensionPointId, context });
import { usePluginLinks } from '@grafana/runtime';

export const InstanceToolbar = () => {
// The `extensionPointId` must be prefixed.
// - Core Grafana -> prefix with "grafana/"
// - Plugin -> prefix with "{your-plugin-id}/"
//
// This is also what plugins use when they call `addLink()`
const extensionPointId = 'myorg-foo-app/toolbar/v1';
const { links, isLoading } = usePluginLinks({ extensionPointId });

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div>
{/* Loop through the links added by plugins */}
{links.map(({ id, title, path, onClick }) => (
<a href={path} title={title} key={id} onClick={onClick}>
{title}
</a>
))}
</div>
);
};
import { usePluginLinks } from '@grafana/runtime';

export const InstanceToolbar = ({ instanceId }) => {
const extensionPointId = 'myorg-foo-app/toolbar/v1';
// Heads up! Always use `useMemo()` in case the `context` object has any "dynamic" properties
// to prevent unnecessary re-renders (Otherwise a new object would be created on every render, that could
// result in a new links{} object, that could trigger a new re-render, and so on.)
const context = useMemo(() => ({ instanceId }), [instanceId]);
const { links, isLoading } = usePluginLinks({ extensionPointId, context });

// ...
};

限制插件的扩展数量

您的 UI 上的空间可能有限,您希望限制插件可以注册到您的扩展点的扩展数量。默认情况下,没有限制

import { usePluginLinks } from '@grafana/runtime';

export const InstanceToolbar = () => {
// Only one link per plugin is allowed.
// (If a plugin registers more than one links, then the rest will be ignored
// and won't be returned by the hook.)
const { links, isLoading } = usePluginLinks({ extensionPointId, limitPerPlugin: 1 });

// ...
};
import { usePluginLinks } from '@grafana/runtime';

export const InstanceToolbar = () => {
const { links, isLoading } = usePluginLinks({ extensionPointId, limitPerPlugin: 1 });

// You can rely on the `link.pluginId` prop to filter based on the plugin
// that has registered the extension.
const allowedLinks = useMemo(() => {
const allowedPluginIds = ['myorg-a-app', 'myorg-b-app'];
return links.filter(({ pluginId }) => allowedPluginIds.includes(pluginId));
}, [links]);

// ...
};

组件

渲染组件的最佳实践

  • 确保您的 UI 控制行为
    组件扩展可以渲染不同的布局,并可以响应各种用户交互。确保您的 UI 为渲染其他插件定义的组件定义了明确的边界。
  • 共享上下文信息
    考虑哪些上下文信息可能对其他插件有用,并将这些信息作为 props 传递给组件。

创建组件的扩展点

import { usePluginComponents } from '@grafana/runtime';

export const InstanceToolbar = () => {
// The `extensionPointId` must be prefixed.
// - Core Grafana -> prefix with "grafana/"
// - Plugin -> prefix with "{your-plugin-id}/"
//
// This is also what plugins use when they call `addComponent()`
const extensionPointId = 'myorg-foo-app/toolbar/v1';
const { components, isLoading } = usePluginComponents({ extensionPointId });

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div>
{/* Loop through the components added by plugins */}
{components.map(({ id, component: Component }) => (
<Component key={id} />
))}
</div>
);
};

将数据传递到组件

import { usePluginComponents } from '@grafana/runtime';

// Types for the props (passed as a generic to the hook in the following code block)
type ComponentProps = {
instanceId: string;
};

export const InstanceToolbar = ({ instanceId }) => {
const extensionPointId = 'myorg-foo-app/toolbar/v1';
const { components, isLoading } = usePluginComponents<ComponentProps>({ extensionPointId });

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div>
{/* Sharing contextual information using component props */}
{components.map(({ id, component: Component }) => (
<Component key={id} instanceId={instanceId} />
))}
</div>
);
};

限制哪些插件可以注册组件

import { usePluginComponents } from '@grafana/runtime';

export const InstanceToolbar = () => {
const extensionPointId = 'myorg-foo-app/toolbar/v1';
const { components, isLoading } = usePluginComponents<ComponentProps>({ extensionPointId });

// You can rely on the `component.pluginId` prop to filter based on the plugin
// that has registered the extension.
const allowedComponents = useMemo(() => {
const allowedPluginIds = ['myorg-a-app', 'myorg-b-app'];
return components.filter(({ pluginId }) => allowedPluginIds.includes(pluginId));
}, [components]);

// ...
};