Skip to main content

插件分析

建立、维护和迭代 Backstage 实例是一项巨大的投资。 为了帮助衡量这项投资的回报,Backstage 配备了基于事件的分析 API,使应用程序集成商可以灵活地在自己选择的分析工具中收集和分析 Backstage 的使用情况,同时为插件开发商提供了一个标准接口,用于检测关键的用户交互。

概念

  • 事件***至少包括一个 "动作"(如 "点击")和一个 "主题"(如 "被点击的东西")。 属性代表附加的维度数据(以键/值对的形式),可根据事件逐一提供。 继续上面的例子,用户点击的 URL 可能是"{ "to": "/a/page" }"。 上下文代表事件发生的更广泛的上下文。 默认情况下,会提供 "插件 ID"、"扩展名 "和 "路由参考 "等信息。

这种事件组成的目的是在不同的细节层面上进行分析,既能回答非常细化的问题(如 "在某条路线上点击次数最多的是什么"),也能回答非常高层次的问题(如 "我的Backstage实例中使用最多的插件是什么")。

支持的分析工具

虽然将这些事件消耗并转发给分析工具所需的只是AnalyticsApi您可以在下面找到您所选择的分析工具。

| 分析工具 | 支持状态 | | ------------------------------------- | -------------- | | | |谷歌分析| 是 ✅ | |谷歌分析 4| 是 ✅ | |新 Relic 浏览器| 社区 ✅ | |Matomo| 社区 ✅ |

如需提出整合建议,请打开问题或跳转到写作整合以了解如何自己进行整合!

关键事件

下表总结了可能捕获的事件,具体取决于您安装的插件。

| Action | Subject | Other Notes | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |navigate| 路径位置发生变化时立即触发(除非相关插件/路径数据不明确,在这种情况下,事件会在已知插件/路径数据后,紧接着在下一个事件或文档卸载前触发)。 当前路径的参数将作为属性包含在内。click| 点击的链接文本。to属性表示点击到的 URL。create| 《......name如果没有name属性,则字符串new {templateName}上下文会保存一个entityRef,设置为模板的 ref(例如template:default/template-name). | |search| 在任何搜索栏组件中输入的搜索词。searchTypes代表types限制搜索。value表示查询的搜索结果总数。 如果正在使用权限框架,则可能不可见。discover| 被点击的搜索结果的标题value是结果等级。to属性。not-found| 导致未找到页面的资源路径 | 至少由 TechDocs 启动。

如果您希望看到某个活动的照片,请打开问题或跳转到捕捉事件学习如何自己制作乐器!

_OSS 插件维护者:请随时在上表中记录您的事件。

写作集成

分析事件转发是作为Backstage实用 API 实现的。 就像为错误或 SCM 身份验证提供自定义 API 实现一样,您也可以为分析提供自定义 API 实现。

所提供的应用程序接口只需提供一个方法captureEvent,它需要一个AnalyticsEvent反对

import {
analyticsApiRef,
AnalyticsEvent,
AnyApiFactory,
createApiFactory,
} from '@backstage/core-plugin-api';

export const apis: AnyApiFactory[] = [
createApiFactory(analyticsApiRef, {
captureEvent: (event: AnalyticsEvent) => {
window._AcmeAnalyticsQ.push(event);
},
}),
];

实际上,您可能希望封装实例化逻辑,并从配置中获取一些细节。 一个更完整的示例可能是这样的:

import {
AnalyticsApi,
analyticsApiRef,
AnalyticsEvent,
AnyApiFactory,
configApiRef,
createApiFactory,
} from '@backstage/core-plugin-api';
import { AcmeAnalytics } from 'acme-analytics';

class AcmeAnalytics implements AnalyticsApi {
private constructor(accountId: number) {
AcmeAnalytics.init(accountId);
}

static fromConfig(config) {
const accountId = config.getString('app.analytics.acme.id');
return new AcmeAnalytics(accountId);
}

captureEvent(event: AnalyticsEvent) {
const { action, ...rest } = event;
AcmeAnalytics.send(action, rest);
}
}

export const apis: AnyApiFactory[] = [
createApiFactory({
api: analyticsApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => AcmeAnalytics.fromConfig(configApi),
}),
];

如果您要集成分析服务(而不是内部工具),请考虑将您的 API 实现作为插件提供!

按照惯例,此类软件包应命名为@backstage/analytics-module-[name]任何配置都应在app.analytics.[name].

处理用户身份

如果您要集成的分析平台有用户身份的一级概念,您可以(选择性地)通过遵循此约定来支持这一概念:

  • 使用identityApigetBackstageIdentity()方法解析的userEntityRef作为发送到分析平台的用户 ID 的基础。

例如

import {
AnalyticsApi,
analyticsApiRef,
AnyApiFactory,
configApiRef,
createApiFactory,
identityApiRef,
IdentityApi,
} from '@backstage/core-plugin-api';

// Implementation that optionally initializes with a userId.
class AcmeAnalytics implements AnalyticsApi {
private constructor(accountId: number, identityApi?: IdentityApi) {
if (identityApi) {
identityApi.getBackstageIdentity().then(identity => {
AcmeAnalytics.init(accountId, {
userId: identity.userEntityRef,
});
});
} else {
AcmeAnalytics.init(accountId);
}
}

static fromConfig(config, options) {
const accountId = config.getString('app.analytics.acme.id');
return new AcmeAnalytics(accountId, options.identityApi);
}
}

// Your implementation should be instantiated like this:
export const apis: AnyApiFactory[] = [
createApiFactory({
api: analyticsApiRef,
deps: { configApi: configApiRef, identityApi: identityApiRef },
factory: ({ configApi, identityApi }) =>
AcmeAnalytics.fromConfig(configApi, {
identityApi,
}),
}),
];

捕捉事件

要在组件中检测事件,首先要使用useAnalytics()@backstage/core-plugin-api跟踪器包括一个captureEvent方法,该方法接收一个action和一个subject作为参数。

import { useAnalytics } from '@backstage/core-plugin-api';

const analytics = useAnalytics();
analytics.captureEvent('deploy', serviceName);

提供额外属性

附加尺寸attributes以及数字value可在第三options如果/当与事件相关时,可使用该参数:

analytics.captureEvent('merge', pullRequestName, {
value: pullRequestAgeInMinutes,
attributes: {
org,
repo,
},
});

在上述示例中,将捕捉到类似以下对象的事件:

{
"action": "merge",
"subject": "Name of Pull Request",
"value": 60,
"attributes": {
"org": "some-org",
"repo": "some-repo"
}
}

为事件提供背景

attributes选项可用于捕获您正在检测的组件中的详细信息。 如果要捕获只有在 React 树的更上层才有的元数据,或帮助应用程序集成人员通过一些通用值来聚合不同的事件,可使用<AnalyticsContext>.

import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';

const MyComponent = ({ value }) => {
const analytics = useAnalytics();
const handleClick = () => analytics.captureEvent('check', value);
return <SomeThing value={value} onClick={handleClick} />;
};

const MyWrapper = () => {
return (
<AnalyticsContext attributes={{ segment: 'xyz' }}>
<MyComponent value={'Some Value'} />
</AnalyticsContext>
);
};

在上例中,点击<SomeThing />将导致类似于分析事件的发生:

{
"action": "check",
"subject": "Some Value",
"context": {
"segment": "xyz"
}
}

请注意,为简洁起见,上例中Backstage核心提供的上下文键 (pluginId,extensionrouteRef实际上,这些细节将与您提供的任何其他背景信息一起包括在内。

分析上下文可以嵌套;它们的值在 React 树中向下合并,允许键被覆盖。

事件命名注意事项

为了在分析时保持这种灵活性,必须对每个细节级别进行分解。

  • Avoid providing an overly specific action. For example, instead of filterEntityTable, consider just using filter as the action, and allowing EntityTable to be specified as part of the event's context (most likely automatically as part of the extension in which the filter event was captured). * On the flip side, when adding attributes to or context around an event, look at existing events and see if the data you are capturing matches the intention, type, or even the content of their attributes or context. For instance, it's common for events that involve the Catalog to include an entityRef contextual key. Using the same keys and values in your event will ensure that events instrumented across plugins can easily be aggregated.

###单元测试事件捕捉

@backstage/test-utilsPackages包括MockAnalyticsApi实现,您可以在单元测试中使用它来监视捕获到的任何分析事件并对其进行断言。

这样使用

import { render, fireEvent, waitFor } from '@testing-library/react';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import {
MockAnalyticsApi,
TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';

describe('SomeComponent', () => {
it('should capture event on click', () => {
// Use the Mock Analytics API to spy on event captures.
const apiSpy = new MockAnalyticsApi();

// Render the component being tested
const { getByText } = render(
wrapInTestApp(
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
<SomeComponentUnderTest />
</TestApiProvider>,
),
);

// Fire the event that triggers event capture.
fireEvent.click(getByText('some component text'));

// Assert that the event was captured with the expected data.
await waitFor(() => {
expect(apiSpy.getEvents()[0]).toMatchObject({
action: 'expected action',
subject: 'expected subject',
attributes: {
foo: 'bar',
},
});
});
});
});