创建扩展点
扩展点是插件 UI 或 Grafana UI 的一个部分,其他插件可以通过钩子添加链接或 React 组件。您可以使用它们根据扩展点暴露的上下文扩展用户体验。
在 关键概念 下阅读有关扩展的更多信息。
类型 | 描述 |
---|---|
链接 | 链接具有 path 或 onClick() 属性。何时使用? 使用链接如果希望为插件提供定义自定义用户动作的方式,例如为 UI 的某个部分创建交叉链接,或者使用 onClick() 方法实现具有模态的更互动的页面体验。API 参考 - addLink() - 从插件注册链接- usePluginLinks() - 获取为扩展点注册的链接 |
组件 | 组件是可以用于渲染自定义用户体验的 React 组件。 何时使用? 使用组件如果希望为插件提供更多自由来扩展 UI,例如使用自定义部分扩展配置表单。 API 参考 - addComponent() - 从插件注册组件- usePluginComponents() - 获取为扩展点注册的组件 |
链接
渲染链接的最佳实践
-
确保您的UI能够处理多个链接
多个插件可能会向您的扩展点添加链接。请确保您的扩展点能够处理这种情况,并仍提供良好的用户体验。查看如何限制插件显示的扩展数量。 -
共享上下文信息
考虑哪些上下文信息可能对其他插件有用,并将其添加到context
对象中。例如,面板菜单扩展点共享panelId
和timeRange
。请注意,在传递给链接之前,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]);
// ...
};