Metabase 前端分析事件跟踪实战trackSimpleEvent、SimpleEventSchema 与类型化 Snowplow 事件体系【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase本文基于 Metabase 仓库内的前端分析事件技能文档.claude/skills/analytics-events/SKILL.md并结合frontend/src/metabase/analytics等核心源码完整讲解 Metabase 前端产品分析Snowplow事件的类型化事件体系如何为一次用户交互命名、在哪个文件里声明跟踪函数、trackSimpleEvent的泛型校验如何在编译期约束事件载荷以及如何通过shouldLogAnalytics在控制台验证事件真实发出。读完本文你可以在 Metabase 代码库中为任意功能正确地新增、复用和调试一条分析事件。1. 体系总览Snowplow 类型化事件 SchemaMetabase 前端使用Snowplow作为事件收集管道所有事件都带有类型化的 Iglu schema。核心设计原则是简单事件在使用点声明——trackSimpleEvent是泛型函数在调用点就完成载荷校验因此无需再集中声明事件类型。体系中的四类关键文件均来自技能文档并已在源码中逐一确认存在文件职责frontend/src/metabase/analytics/event.ts核心跟踪函数trackSimpleEvent/trackSchemaEvent统一从metabase/analytics导入frontend/src/metabase-types/analytics/event.ts共享的SimpleEventSchema定义。不要在此添加事件类型frontend/src/metabase-types/analytics/schema.tsSchema 注册表仅限自定义/遗留 schema各功能目录下的analytics.ts你的跟踪函数与本地字段类型应居住的位置新增一个分析事件时的检查清单继承自技能文档的 Quick Checklist选一个事件名snake_case、过去式在功能自己的analytics.ts中新增跟踪函数内部调用trackSimpleEvent()字段值域联合如success | failure作为本地类型放在同一文件在交互点导入并调用该跟踪函数不要往metabase-types/analytics/event.ts或任何联合类型里添加事件类型。2. SimpleEventSchema 与 trackSimpleEvent 的泛型校验2.1 标准字段绝大多数事件使用SimpleEventSchema。它在 frontend/src/metabase-types/analytics/event.ts 中的完整定义export type SimpleEventSchema { event: string; // 必填事件名snake_case target_id?: number | null; // 可选受影响实体的 ID triggered_from?: string | null; // 可选UI 位置/上下文 duration_ms?: number | null; // 可选耗时毫秒 result?: string | null; // 可选结果如 success、failure event_detail?: string | null; // 可选额外细节/变体 };技能文档估计约 90% 的事件都适配该 schema适用于点击、打开、关闭、创建、删除等交互。2.2 泛型如何“在调用点”完成校验frontend/src/metabase/analytics/event.ts 中trackSimpleEvent的实现export function trackSimpleEvent T extends SimpleEventSchema RecordExcludekeyof T, keyof SimpleEventSchema, never, (event: T) { trackSchemaEvent(simple_event, event); }两个约束合起来的效果T extends SimpleEventSchema保证必须包含event字段且各字段类型合法RecordExcludekeyof T, keyof SimpleEventSchema, never保证对象字面量中不允许出现SimpleEventSchema之外的任何字段——多传一个data_layer之类的自定义字段会直接在调用点报编译错误。也就是说缺少event、或携带 schema 之外的字段都是调用点编译错误。没有单独的事件类型要声明也没有satisfies子句要写——旧的ValidateEvent...辅助类型虽然仍残留在 frontend/src/metabase-types/analytics/event.ts 中供若干遗留...Event类型使用但已不属于现行工作流新事件不应模仿它。2.3 trackSchemaEventschema 名与载荷类型强关联trackSimpleEvent最终委托给trackSchemaEvent它同样是泛型的把 schema 名与载荷类型关联起来frontend/src/metabase/analytics/event.tsexport function trackSchemaEventS extends SchemaType( schema: S, event: SchemaEventMap[S], ): void {这意味着不能用simple_eventschema 发一条 dashboard 事件——schema 名与 payload 类型是绑定的。SchemaEventMap注册表定义在 frontend/src/metabase-types/analytics/schema.ts其中simple_event: SimpleEventSchema与dashboard、question、cleanup、search等约 20 个自定义/遗留 schema 并列。3. 运行时链路一条事件从发出到上报阅读trackSchemaEvent的函数体frontend/src/metabase/analytics/event.ts实际执行顺序是控制台日志若shouldLogAnalytics为真该常量定义于 frontend/src/metabase/env.ts先打印一条带样式的日志形如[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_appliedevent sent:true/false反映的是 Snowplow 是否实际上报Settings.snowplowEnabled()。总开关Settings.trackingEnabled()为假则直接返回不上报任何东西settings.ts 中trackingEnabled位于 L155 附近。Snowplow 上报Settings.snowplowEnabled()为真时调用Snowplow.trackSelfDescribingEventschema URI 由VERSIONS表拼接而成const VERSIONS: RecordSchemaType, SchemaVersion { account: 1-0-2, // ... simple_event: 1-0-0, // ... }; // 上报的 schema 形如 // iglu:com.metabase/simple_event/jsonschema/1-0-0VERSIONS表event.ts给每个 schema 绑定 Iglu 版本号simple_event当前为1-0-0。Metaplow内部若metaplow-tracking-enabled设置开启事件还会通过trackMetaplowEvent走一条内部分析通道。另外Snowplow 跟踪器本身由 frontend/src/metabase/analytics/snowplow.ts 的createSnowplowTracker创建tracker id 为sptrackSchemaEvent中[sp]参数即指向它appId: metabase并在插件中设置当前用户 id、附加iglu:com.metabase/instance/jsonschema/1-1-0的实例上下文版本、创建时间、token 特性等。所有导出统一从 frontend/src/metabase/analytics/index.ts 汇总因此业务代码只需import { trackSimpleEvent } from metabase/analytics。4. 自定义 Schema遗留原则上不再新增技能文档明确仅考虑在非常特殊的场景下新增事件 schema。现有示例包括DashboardEventSchema、CleanupEventSchema、QuestionEventSchema分别对应 frontend/src/metabase-types/analytics/ 目录下的dashboard.ts、cleanup.ts、question.ts等文件并通过SchemaEventMap注册、在VERSIONS中登记版本。普通产品事件请走simple_event通道。5. 分步实战跟踪“表选择器中应用了筛选”以下示例完整继承自技能文档的 Step-by-Step 章节。Step 1创建跟踪函数在功能自己的analytics.ts文件中例如企业版示例enterprise/frontend/src/metabase-enterprise/data-studio/analytics.tsimport { trackSimpleEvent } from metabase/analytics; export const trackDataStudioTablePickerFiltersApplied () { trackSimpleEvent({ event: data_studio_table_picker_filters_applied, }); }; export const trackDataStudioTablePickerFiltersCleared () { trackSimpleEvent({ event: data_studio_table_picker_filters_cleared, }); };Step 2在组件交互点调用import { trackDataStudioTablePickerFiltersApplied, trackDataStudioTablePickerFiltersCleared, } from metabase-enterprise/data-studio/analytics; function FilterPopover({ filters, onSubmit }) { const handleReset () { trackDataStudioTablePickerFiltersCleared(); // - Track here onSubmit(emptyFilters); }; return ( form onSubmit{(event) { event.preventDefault(); trackDataStudioTablePickerFiltersApplied(); // - Track here onSubmit(form); }} {/* form content */} /form ); }要点跟踪函数与业务副作用onSubmit在同一处理函数中先后执行事件命名与 UI 动作一一对应。6. SimpleEventSchema 各字段的实战用法以下四个示例全部位于功能自己的analytics.ts中没有任何中央注册。6.1 带 target_id关联实体 IDexport const trackDataStudioLibraryCreated (id: CollectionId) { trackSimpleEvent({ event: data_studio_library_created, target_id: Number(id), }); }; // Usage trackDataStudioLibraryCreated(newLibrary.id);6.2 带 triggered_from区分 UI 来源本地联合类型按字段命名仅当其他功能需要传递相同值时才导出// Local union, exported only if another feature needs to pass the same value export type NewButtonLocation app-bar | empty-collection; export const trackNewButtonClicked (location: NewButtonLocation) { trackSimpleEvent({ event: new_button_clicked, triggered_from: location, }); }; // Usage Button onClick{() { trackNewButtonClicked(app-bar); handleCreate(); }} New /Button6.3 带 event_detail记录变体真实代码技能文档给出的真实示例来自 frontend/src/metabase/metadata/pages/shared/analytics.ts。仓库当前实际代码该联合类型已扩展到 7 个变体export type MetadataEditEventDetail | type_casting | semantic_type_change | visibility_change | filtering_change | display_values | json_unfolding | formatting; export const trackMetadataChange (detail: MetadataEditEventDetail) { trackSimpleEvent({ event: metadata_edited, event_detail: detail, triggered_from: admin, }); };6.4 带 result 与 duration_ms记录结果与耗时真实代码技能文档指向 frontend/src/metabase/archive/analytics.ts 作为 result duration 计时的真实版本。该文件的实际实现是archiveAndTrack封装一个异步archive()操作用Date计时在.then/.catch中分别以result: success | failure上报且直接调用trackSchemaEvent(simple_event, {...})——与trackSimpleEvent等价展示了另一种写法type MoveToTrashTriggeredFrom | collection | detail_page | cleanup_modal | drag_and_drop; export const archiveAndTrack async ({ archive, model, modelId, triggeredFrom }) { const start new Date().getTime(); const logAnalytics (successful: boolean) { // ... return trackSchemaEvent(simple_event, { event: moved-to-trash, event_detail: eventDetail, target_id: modelId, triggered_from: triggeredFrom, duration_ms: new Date().getTime() - start, result: successful ? success : failure, }); }; return archive() .then((result) { logAnalytics(true); return result; }) .catch((error) { logAnalytics(false); throw error; }); };技能文档给出的通用模板版本trackMoveToTrash用trackSimpleEvent包装同样适用核心是 try/catch 两个分支都要带durationMs上报const startTime Date.now(); try { await moveToTrash(item); trackMoveToTrash({ targetId: item.id, triggeredFrom: collection, durationMs: Date.now() - startTime, result: success, itemType: question }); } catch (error) { trackMoveToTrash({ targetId: item.id, triggeredFrom: collection, durationMs: Date.now() - startTime, result: failure, itemType: question }); }7. 命名规范7.1 事件名snake_case、过去式// Good data_studio_library_created table_picker_filters_applied metabot_chat_opened // Bad DataStudioLibraryCreated // Wrong case tablePickerFiltersApplied // Wrong case filters-applied // Use underscore, not hyphen注意事件名内部可以用连字符真实代码中就有moved-to-trash、new_button_clicked但整体风格应保持下划线分词、小写、过去式。7.2 本地字段类型PascalCase、按所喂字段命名重构后通常不再需要...Event类型。需要字段值域联合时以其服务的字段命名// Good type MetricDimensionResult success | failure; // - result export type MetadataEditEventDetail type_casting; // - event_detail type NewButtonLocation app-bar | empty-collection; // - triggered_from7.3 跟踪函数camelCase track 前缀// Good trackDataStudioLibraryCreated trackTablePickerFiltersApplied trackMetabotChatOpened // Bad DataStudioLibraryCreated // Missing track prefix track_library_created // Wrong case logLibraryCreated // Use track prefix8. 常见模式模式 1跨功能共享字段类型两个功能发送同一事件、triggered_from不同或共享同一个event_detail值域时从拥有方功能的analytics.ts导出字段联合并导入——不要提升到metabase-types。真实代码 frontend/src/metabase/data-studio/data-model/analytics.ts 正是这样做import { trackSimpleEvent } from metabase/analytics; import type { MetadataEditEventDetail } from metabase/metadata/pages/shared/analytics; export function trackMetadataChange(detail: MetadataEditEventDetail) { trackSimpleEvent({ event: metadata_edited, event_detail: detail, triggered_from: data_studio, }); }这正是 extensible-events 设计的意图企业版与各功能层的类型保留在自己的模块内而不是被导入下沉到共享联合类型中那会造成模块边界违规——仓库的 lint 配置 frontend/lint/module-boundaries.mjs 即对模块边界有强制约束。模式 2条件跟踪按用户动作分支跟踪不同事件const handleSave async () { if (isNewItem) { await createItem(data); trackItemCreated(newItem.id); } else { await updateItem(id, data); trackItemUpdated(id); } };9. 常见陷阱Common Pitfalls陷阱 1给 simple event 加自定义字段// WRONG - SimpleEventSchema 不支持自定义字段这是编译错误 trackSimpleEvent({ event: filters_applied, data_layer: filters.dataLayer, // ❌ Not in SimpleEventSchema data_source: filters.dataSource, // ❌ Not in SimpleEventSchema with_owner: filters.hasOwner, // ❌ Not in SimpleEventSchema }); // RIGHT - 只用标准字段 trackSimpleEvent({ event: filters_applied }); // 或用 event_detail 表达单一变体 trackSimpleEvent({ event: filter_applied, event_detail: filterType, // ✓ data_layer、data_source 等 });这由 2.2 节的泛型约束在编译期强制——RecordExcludekeyof T, keyof SimpleEventSchema, never会让多余字段直接报错。陷阱 2往 metabase-types/analytics/event.ts 添加事件类型中央SimpleEvent联合类型已被移除——它迫使功能层类型被导入到共享代码中造成模块边界违规而trackSimpleEvent现在是泛型的集中声明类型只带来重复。// ❌ WRONG - 中央声明 为 satisfies 子句再导入 export type NewFeatureClickedEvent ValidateEvent{ event: new_feature_clicked; target_id: number; }; export const trackNewFeatureClicked (id: number) { trackSimpleEvent({ event: new_feature_clicked, target_id: id, } satisfies NewFeatureClickedEvent); }; // ✓ RIGHT - 对象字面量已被泛型直接检查 export const trackNewFeatureClicked (id: number) { trackSimpleEvent({ event: new_feature_clicked, target_id: id, }); };注意frontend/src/metabase-types/analytics/event.ts 中仍残留少量...Event类型如CustomVizPluginCreatedEvent、UserInvitedEvent等见该文件 L15-L99它们是重构前后 PR 的遗留物——不要模仿它们更不要继续往里加。陷阱 3混用事件名格式// WRONG event: dataStudioLibraryCreated // camelCase event: data-studio-library-created // kebab-case event: Data_Studio_Library_Created // Mixed case // RIGHT event: data_studio_library_created // snake_case陷阱 4跟踪 PII 或敏感数据// WRONG - 不要跟踪邮箱、姓名或敏感数据 trackSimpleEvent({ event: user_logged_in, event_detail: user.email, // ❌ PII }); // RIGHT - 只跟踪非敏感标识 trackSimpleEvent({ event: user_logged_in, target_id: user.id, // ✓ 仅 ID });陷阱 5只跟踪成功、漏掉失败// WRONG - 只跟踪成功 try { await saveData(); trackDataSaved(); } catch (error) { // ❌ No tracking for failure case } // RIGHT - 两个结果都跟踪 try { await saveData(); trackDataSaved({ result: success }); } catch (error) { trackDataSaved({ result: failure }); }10. 开发期验证确认事件真的发出了技能文档给出三种验证手段前两种在源码中可直接对应看浏览器控制台开发环境下开启SNOWPLOW_ENABLEDtrue事件会打印日志。对应实现即trackSchemaEvent中的shouldLogAnalytics分支event.tsshouldLogAnalytics常量定义在 frontend/src/metabase/env.ts设置 shouldLogAnalytics在metabase/env中设置为真可看到所有分析事件的控制台输出Snowplow 调试器使用 Snowplow 事件的浏览器扩展检查上报。示例控制台输出[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_applied其中event sent:后的布尔值由Settings.snowplowEnabled()决定区分“事件被构造”与“实际发往 Snowplow 服务器”两种状态。11. 文件组织与 Embedding SDK 边界跟踪函数及其本地字段类型新事件的落脚处frontend/src/metabase/{feature}/analytics.ts enterprise/frontend/src/metabase-enterprise/{feature}/analytics.ts核心跟踪工具frontend/src/metabase/analytics/统一从metabase/analytics导入。 仅共享SimpleEventSchema——不再往这里放任何新东西frontend/src/metabase-types/analytics/event.ts。Embedding SDK 特殊规则在嵌入 SDK 代码中要使用trackSdkSimpleEvent位于 frontend/src/embedding-sdk-bundle/analytics/snowplow.ts而不是主应用的trackSimpleEvent——因为主应用那个sptracker见 frontend/src/metabase/analytics/snowplow.ts 的Snowplow.newTracker(sp, ...)在客户页面里并未初始化trackSimpleEvent的 Snowplow 分支在那里是空操作。SDK 侧另有独立的 tracker.ts 与配套单元测试 snowplow.unit.spec.ts。12. 真实参考文件与工作流总结仓库中可直接对照的参考实现简单事件 本地字段联合frontend/src/metabase/metadata/pages/shared/analytics.ts复用其他功能的字段类型frontend/src/metabase/data-studio/data-model/analytics.tsresult duration 计时frontend/src/metabase/archive/analytics.ts企业版功能事件enterprise/frontend/src/metabase-enterprise/google_drive/analytics.tssheets_import_by_url_clicked、sheets_connection_clicked等事件配合本地from: db-page | add-data-modal参数联合完整工作流技能文档 Workflow Summary识别要跟踪的用户交互决定事件名snake_case、描述性、过去式在功能analytics.ts中创建跟踪函数内部调用trackSimpleEvent()某字段有固定值域时在同一文件添加本地字段联合在交互点导入并调用按第 10 节方法测试事件正确发出。最后是一些贯穿始终的经验法则具体化——filters_applied优于action_performed用过去式——library_created而不是create_library聚合相关事件——一个功能的跟踪函数集中放在其analytics.ts只跟踪有意义的动作——不是每个点击都值得跟踪为数据分析服务——想一想之后想按什么维度切片保持一致——遵循代码库现有命名模式记录上下文——用triggered_from表达动作发生的入口。以上所有约定在 Metabase 中不是靠文档自觉维持的而是靠trackSimpleEvent的泛型签名、SchemaEventMap的 schema-载荷关联以及模块边界 lint 在编译期共同兜底——这也正是这套“扩展式事件”设计值得借鉴之处。【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考