跳到主要内容

通过运行时检查管理向后兼容性

插件中使用的大多数 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();
}
});

进一步阅读

  • 插件的端到端测试:有关编写和运行 Grafana 插件 E2E 测试的全面指南,请参阅文档
  • 跨多个 Grafana 版本运行端到端测试:要了解如何配置工作流程以针对不同的 Grafana 版本测试插件,请参阅示例工作流程