创建扩展点
扩展点 是您插件 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]);
// ...
};