ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Backstage 插件级分析(Plugin Analytics):事件模型、自定义集成与埋点实践指南

Backstage 插件级分析(Plugin Analytics):事件模型、自定义集成与埋点实践指南 Backstage 插件级分析Plugin Analytics事件模型、自定义集成与埋点实践指南【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文基于 Backstage 仓库中的官方插件分析文档系统讲解 Backstage 基于事件的 Analytics API从 Events / Attributes / Context 三大核心概念出发覆盖现成分析工具接入、自定义 AnalyticsApi 集成、useAnalytics()埋点、AnalyticsContext上下文注入、事件命名规范与单元测试帮助开发者度量 Backstage 实例的真实使用情况并为自己的插件标准化埋点。读完本文你将能够为任意分析平台编写backstage/analytics-module-*集成并在插件中正确捕获可聚合、可下钻的事件。说明本文对应的官方文档属于旧版前端系统的插件文档新版前端系统的对应版本参见 Plugin Analytics。本文描述的概念与事件在新旧两套前端系统中同样适用。为什么 Backstage 需要 Analytics API搭建、维护并持续迭代一个 Backstage 实例是一笔不小的投入。为了衡量这笔投入的回报Backstage 内置了一个基于事件event-based的 Analytics API它一方面给予应用集成方充分的灵活性让他们可以把 Backstage 的使用数据收集到任意自选的分析工具中另一方面又为插件开发者提供了一套标准接口用来对关键用户交互进行埋点。这套设计的核心在于事件组合composition of events它允许分析同时回答两类问题细粒度问题例如某个特定路由上被点击最多的元素是什么宏观问题例如我的 Backstage 实例中哪个插件使用率最高。在源码层面这个能力由 AnalyticsApi 定义文件 提供analyticsApiRef是 ID 为core.analytics的ApiRef而AnalyticsApi接口只有一个方法captureEvent(event: AnalyticsEvent): void所有事件最终都会汇聚到这个唯一的入口。核心概念Events、Attributes 与 Context概念含义示例Events事件至少包含一个action如click和一个subject如被点击的东西clickDeployAttributes属性事件级的额外维度数据键/值对形式点击跳转的目标 URL{ to: /a/page }Context上下文事件发生的更广阔背景默认提供pluginId、extension、routeRef等信息{ pluginId: catalog, routeRef: catalogIndexRouteRef }从源码类型定义看事件对象的完整结构如下见 AnalyticsApi.tsexport type AnalyticsEvent { action: string; // 事件动作如 view / click / filter / search subject: string; // 动作作用的对象如页面路径、链接 URL value?: number; // 可选数值如排名、进度百分比、耗时 attributes?: AnalyticsEventAttributes; // 可选维度数据 { [key]: string | boolean | number } context: AnalyticsContextValue; // 上下文元数据 };其中context的类型来自 analytics/types.ts它由CommonAnalyticsContextpluginId、routeRef、extension三个必填字段与任意自定义键值组成。pluginId指事件被捕获处最近的父插件routeRef是事件捕获时激活的路由引用 IDextension是最近的父扩展。官方支持的分析工具消费并转发这些事件只需要一个 AnalyticsApi 的具体实现不过常用集成已经被打包成插件提供可直接选用分析工具支持状态Google Analytics 4官方支持 ✅New Relic Browser社区支持 ✅Matomo社区支持 ✅Quantum Metric社区支持 ✅Generic HTTP社区支持 ✅想为你的组织使用的工具新增集成可以提交 issue 建议也可以直接阅读下文 Writing Integrations 章节自己动手贡献一个集成。关键事件一览下表汇总了取决于你所安装的插件可能被捕获的标准事件。这些事件是各插件埋点的事实标准也是你自定义事件命名时的参照基准。动作Action主体Subject其他说明navigate被导航到的页面 URL路由位置变化时立即触发若关联的插件/路由数据不明确则会在插件/路由数据就绪后、下一条事件或文档卸载前触发。当前路由的参数会作为 attributes 一并上报click被点击链接的文本to属性表示点击跳转到的 URLcreate被创建软件的名称name若对应软件模板没有请求name属性则使用字符串new {templateName}context 中包含entityRef模板引用如template:default/template-namevalue表示运行该模板节省的分钟数基于模板的backstage.io/time-saved注解若有search在任意搜索栏组件中输入的搜索词context 中包含searchTypes约束搜索范围的typesvalue为该查询的总结果数若启用权限框架该值可能不可见discover被点击的搜索结果标题value为结果排名同时提供to属性not-found导致 404 页面出现的资源路径至少由 TechDocs 触发navigate事件的时序细节在 Tracker 实现 中有对应的源码逻辑当路由变化发生在gathered mountpoint一个路由节点下聚合多个插件的场景时navigate事件会被暂存到全局存储mostRecentGatheredNavigation待插件/路由数据确定后、或在下一条真实事件触发前、或页面beforeunload时补发从而保证导航事件的准确性。相关行为在 Tracker.test.ts 中有系统性的单元测试覆盖例如绝不立即捕获_routeNodeType为gathered的 navigate 事件。Writing Integrations编写自己的分析集成分析事件转发在 Backstage 中实现为一个Utility API。就像你可以为错误处理或 SCM 认证提供自定义 API 实现一样你也可以为分析提供自定义实现。所需的 API 只需提供一个方法captureEvent接收一个AnalyticsEvent对象。最小实现旧版前端系统中通过createApiFactory将自定义实现注册到analyticsApiRefimport { analyticsApiRef, AnalyticsEvent, AnyApiFactory, createApiFactory, } from backstage/core-plugin-api; export const apis: AnyApiFactory[] [ createApiFactory(analyticsApiRef, { captureEvent: (event: AnalyticsEvent) { window._AcmeAnalyticsQ.push(event); }, }), ];如果是在新版前端系统中构建则使用AnalyticsImplementationBlueprint其源码定义见 AnalyticsImplementationBlueprint.tsimport { AnalyticsImplementationBlueprint } from backstage/frontend-plugin-api; export const acmeAnalyticsImplementation AnalyticsImplementationBlueprint.make({ name: acme, params: define define({ deps: {}, factory() { return { captureEvent: event { 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), }), ];新版前端系统的等价写法import { AnalyticsImplementationBlueprint } from backstage/frontend-plugin-api; export const acmeAnalyticsImplementation AnalyticsImplementationBlueprint.make({ name: acme, params: define define({ deps: { configApi: configApiRef }, factory: ({ configApi }) AcmeAnalytics.fromConfig(configApi), }), });命名约定如果你是在与一个分析服务而非内部工具做集成请考虑把该 API 实现作为插件贡献出来。按惯例此类包命名为backstage/analytics-module-[name]相关配置键统一放在app.analytics.[name]之下。处理用户身份User Identity如果你所集成的分析平台具备用户身份的一等概念可以可选地按如下约定支持它允许实现通过fromConfig静态方法、以identityApi作为其中一个选项来实例化使用identityApi的getBackstageIdentity()方法解析出的userEntityRef作为发送到分析平台的用户 ID 基础。import { AnalyticsApi, analyticsApiRef, AnyApiFactory, configApiRef, createApiFactory, identityApiRef, IdentityApi, } from backstage/core-plugin-api; // 可选用 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); } } // 你的实现应这样实例化 export const apis: AnyApiFactory[] [ createApiFactory({ api: analyticsApiRef, deps: { configApi: configApiRef, identityApi: identityApiRef }, factory: ({ configApi, identityApi }) AcmeAnalytics.fromConfig(configApi, { identityApi, }), }), ];Capturing Events在组件中捕获事件要在组件中埋点首先通过backstage/core-plugin-api提供的useAnalytics()钩子获取一个分析追踪器tracker。追踪器包含captureEvent方法接收action和subject两个参数import { useAnalytics } from backstage/core-plugin-api; const analytics useAnalytics(); analytics.captureEvent(deploy, serviceName);从 useAnalytics 实现 可以看到该钩子通过useAnalyticsContext()读取当前 React 树中的上下文并把它交给一个可复用的Tracker实例即使analyticsApiRef未被注册例如在测试环境中它也会回退到空实现的{ captureEvent: () {} }保证 API 对任何消费代码都是真正可选的。提供额外属性attributes与数值value在第三个options参数上可以附带额外的维度attributes以及数值型valueanalytics.captureEvent(merge, pullRequestName, { value: pullRequestAgeInMinutes, attributes: { org, repo, }, });上面的示例最终会捕获到如下事件对象{ action: merge, subject: Name of Pull Request, value: 60, attributes: { org: some-org, repo: some-repo } }其中action、subject、value、attributes、context的字段约束均可在 AnalyticsApi.ts 的类型定义中找到对应依据。为事件提供上下文AnalyticsContextattributes选项适合捕获组件内部就能拿到的细节。若要捕获 React 树更高层才可获得的元数据或者希望帮助应用集成方按某个公共值聚合不同的事件请使用AnalyticsContextimport { 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 核心提供的 context 键pluginId、extension、routeRef实际上报时这些细节会与自定义 context 一并携带。AnalyticsContext 可以嵌套其值会沿 React 树向下合并允许下层覆盖上层的键。这一点在 AnalyticsContext 源码 中有明确的实现它基于createVersionedContext创建版本化上下文useMemo把父级值与当前 attributes 合并为{ ...parentValues, ...attributes }从而实现向下合并、可覆盖。当组件在根节点之下、没有任何 Provider 时默认上下文为冻结的{ routeRef: unknown, pluginId: root, extension: App }。事件命名注意事项事件被拆分为多个组成部分正是为了在分析阶段支持不同粒度的分析。为了在分析时保持这种灵活性务必让各层细节保持未聚合状态避免使用过于具体的action。例如不要用filterEntityTable而应使用filter作为 action让EntityTable作为事件context的一部分通常会自动由捕获filter事件时所在的extension提供。保持 attributes / context 语义一致。在向事件添加attributes或围绕事件添加context时先看看现有事件判断你要捕获的数据是否与它们的attributes/context在意图、类型甚至内容上匹配。例如涉及 Catalog 的事件通常会包含entityRef上下文键——在自己的事件中使用相同的键和值可以确保跨插件埋点的事件容易被聚合。单元测试事件捕获backstage/test-utils包内置了MockAnalyticsApi实现你可以在单元测试中用它来监视并对捕获到的任何分析事件做断言。其源码实现见 MockAnalyticsApi.ts它内部维护一个事件数组captureEvent时仅保留非空字段getEvents()返回全部捕获的事件。注意该实现已标记为 deprecated官方推荐改用backstage/test-utils中mockApis.analytics命名空间下的新版本当前文档示例仍以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, () { // 使用 Mock Analytics API 监视事件捕获。 const apiSpy new MockAnalyticsApi(); // 渲染被测组件 const { getByText } render( wrapInTestApp( TestApiProvider apis{[[analyticsApiRef, apiSpy]]} SomeComponentUnderTest / /TestApiProvider, ), ); // 触发会捕获事件的动作。 fireEvent.click(getByText(some component text)); // 断言事件以预期数据被捕获。 await waitFor(() { expect(apiSpy.getEvents()[0]).toMatchObject({ action: expected action, subject: expected subject, attributes: { foo: bar, }, }); }); }); });事件在底层是如何流转的理解事件流转的完整链路有助于你写出更符合 Backstage 语义的埋点代码埋点组件调用useAnalytics()拿到Tracker再调用tracker.captureEvent(action, subject, options)上下文合并useAnalytics通过useAnalyticsContext()读取当前 React 树含各层AnalyticsContext合并出的上下文并通过tracker.setContext(context)注入见 useAnalytics.tsx特殊事件处理Tracker.captureEvent会剥离内部_routeNodeType键、拦截_ROUTABLE-EXTENSION-RENDERED内部事件、并在需要时把延迟的navigate事件优先补发见 Tracker.ts转发最终组装出完整的AnalyticsEvent含context调用analyticsApiRef所注册实现的captureEvent(event)由该实现转发到具体分析平台。结语Backstage 的 Analytics API 通过标准事件 可插拔实现的方式把埋点接口与数据分析平台解耦插件开发者只需遵循action/subject/attributes/context的约定即可贡献标准化事件应用集成方则只需实现一个captureEvent方法就能把全部数据接入任意工具。无论你是想度量整个实例的插件使用率还是想追踪某个路由上的细粒度交互这套事件模型都能给出结构化的答案。相关的类型定义、Tracker 实现与 Mock 工具分别位于 AnalyticsApi.ts、Tracker.ts 与 MockAnalyticsApi.ts可作为进一步深挖的起点。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表