跳转到主要内容

创建扩展点

扩展点是插件 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]);

// ...
};