通过运行时检查管理向后兼容性
插件中使用的大多数 Grafana NPM 依赖项在运行时与 Grafana 应用程序共享,如管理 NPM 依赖项文章中所详述。为了在利用插件中新的 Grafana 功能的同时保持与旧版本的兼容性,插件作者需要实施条件逻辑,以检查运行时功能的可用性。未能考虑向后兼容性可能会导致插件崩溃和糟糕的用户体验。
执行这些运行时检查的方法因功能及其向插件开发者提供的可用方式而异。以下示例演示了有效处理这些场景的最佳实践。
示例:有条件地调用函数
Grafana 10.1.0 在 @grafana/data
包中引入了 createDataFrame
函数,弃用了 MutableDataFrame
类。为了保持与 Grafana 10.1.0 之前版本的兼容性,插件必须实施条件逻辑,以确定这些 API 在运行时是否可用。
import { createDataFrame, DataFrameDTO, MutableDataFrame } from '@grafana/data';
function getDataFrame(data: DataFrameDTO) {
if (typeof createDataFrame === 'undefined') {
// fallback to the deprecated class for older versions
return new MutableDataFrame(data);
} else {
// use the new API if available
return createDataFrame(data);
}
}
示例:有条件地使用 React Hooks
在 Grafana 11.1.0 中,同步的 getPluginLinkExtensions
函数已被弃用,并被反应式的 usePluginLinks
Hook 取代。以下示例演示了如何根据两个 API 的可用性动态地在它们之间切换。
import { useMemo } from 'react';
import { PluginExtensionLink } from '@grafana/data';
import {
GetPluginExtensionsOptions,
getPluginLinkExtensions,
usePluginLinks as usePluginLinksOriginal,
} from '@grafana/runtime';
function useLegacyLinkExtensions({ context, extensionPointId }: GetPluginExtensionsOptions): {
links: PluginExtensionLink[];
isLoading: boolean;
} {
const { extensions } = useMemo(
() =>
getPluginLinkExtensions({
extensionPointId,
context,
}),
[context, extensionPointId]
);
return {
links: extensions,
isLoading: false,
};
}
// dynamically decide which API to use
const usePluginLinks = usePluginLinksOriginal !== undefined ? usePluginLinksOriginal : useLegacyLinkExtensions;
export function ToolbarExtensionPoint() {
const { links, isLoading } = usePluginLinks({ extensionPointId: 'myorg-foo-app/toolbar/v1' });
// Your implementation here
...
}
示例:有条件地渲染 React 组件
UserIcon
组件在 Grafana 10.1.0 中引入,在早期版本中没有等效组件。为了保持兼容性,仅当 UserIcon
组件在当前运行时环境中可用时才渲染它。
import React from 'react';
import { Card, UserIcon, UserView } from '@grafana/ui';
export const Profile = ({ userView }: { userView: UserView }) => {
return (
<Card>
<Card.Heading>Profile</Card.Heading>
<Card.Meta>{['Tag 1']}</Card.Meta>
{/* Conditionally render the UserIcon component if it exists */}
{UserIcon && <UserIcon userView={userView} />}
</Card>
);
};
示例:在端到端测试中覆盖条件渲染
当某个功能仅在某些 Grafana 版本中可用时,最好通过端到端 (E2E) 测试来验证其条件渲染。这些测试确保插件在功能存在的新环境中和功能不可用的旧环境中都能正确运行。
以下示例测试 UserIcon
组件是否仅在 Grafana 10.1.0 或更高版本中渲染,同时确保用户配置文件的其余部分始终显示。
import * as semver from 'semver';
import { test, expect } from '@grafana/plugin-e2e';
test('should render profile', async ({ page, grafanaVersion }) => {
const userProfile = page.getByTestId('user-profile');
// verify the visibility of shared components
await expect(userProfile.getByText('Heading')).toBeVisible();
await expect(userProfile.getByText('Tag 1')).toBeVisible();
// conditionally validate the rendering of the UserIcon component
if (semver.gte(grafanaVersion, '10.1.0')) {
await expect(userProfile.getByText('Jane Doe')).toBeVisible();
}
});