关于 analytics-events
面向 Metabase 前端开发者的技能指南,说明如何用 Snowplow 和 trackSimpleEvent 添加类型安全的产品分析事件,并将事件定义与功能模块放在一起。
主要功能
- 01使用 trackSimpleEvent 上报简单事件
- 02按标准字段校验事件载荷
- 03在功能模块内组织追踪函数和类型
- 04遵循事件命名及隐私规范
- 05验证事件是否成功触发
适用场景
- 01为界面交互添加产品分析
- 02按结果和耗时记录操作
- 03在开发环境检查 Snowplow 事件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ metabase/ metabase --skill analytics-events安装后在 Claude Code 中输入/analytics-events确认已加载。
详细介绍
前端分析事件 Skill
这个 skill 帮助你添加产品分析(Snowplow)事件,以跟踪 Metabase 前端代码库中的用户交互。
快速参考
Metabase 中的分析事件使用 Snowplow,并带有类型化的事件 schema。简单事件在使用它们的地方声明——trackSimpleEvent 是泛型的,并在调用点验证 payload。
关键文件:
frontend/src/metabase/analytics/event.ts- 核心跟踪函数,trackSimpleEvent/trackSchemaEvent(从metabase/analytics导入)frontend/src/metabase-types/analytics/event.ts- 仅包含共享的SimpleEventSchema。不要在这里添加事件类型(见下文)frontend/src/metabase-types/analytics/schema.ts- Schema 注册表(仅自定义/legacy schema)- 各功能自己的
analytics.ts文件 - 你的跟踪函数和任何本地类型所在的位置
快速检查清单
添加新的分析事件时:
- 选择一个事件名称(snake_case,过去式)
- 向该功能的
analytics.ts文件添加一个跟踪函数,调用trackSimpleEvent() - 将任何字段联合类型(例如
"success" | "failure")作为本地类型保留在同一个文件中 - 在交互点导入并调用该跟踪函数
- 不要向
metabase-types/analytics/event.ts或任何联合类型中添加事件类型
事件 Schema 类型
1. 简单事件(最常见)
使用 SimpleEventSchema 进行直接跟踪。它支持以下标准字段:
type SimpleEventSchema = {
event: string; // Required: Event name (snake_case)
target_id?: number | null; // Optional: ID of affected entity
triggered_from?: string | null; // Optional: UI location/context
duration_ms?: number | null; // Optional: Duration in milliseconds
result?: string | null; // Optional: Outcome (e.g., "success", "failure")
event_detail?: string | null; // Optional: Additional detail/variant
};
何时使用: 90% 的事件都符合这个 schema。用于点击、打开、关闭、创建、删除等。
trackSimpleEvent 是泛型的,并会对你传入的对象字面量强制执行这个 schema:
// frontend/src/metabase/analytics/event.ts
export function trackSimpleEvent<
T extends SimpleEventSchema &
Record<Exclude<keyof T, keyof SimpleEventSchema>, never>,
>(event: T) {
trackSchemaEvent("simple_event", event);
}
这意味着缺少 event 或任何不在 SimpleEventSchema 中的字段都会在调用点产生编译错误。没有单独的事件类型需要声明,也没有 satisfies 子句需要添加——旧的 ValidateEvent<...> 辅助类型不再导出,也不属于工作流的一部分。
trackSchemaEvent 也是泛型的:它会把 schema 名称与 payload 类型关联起来,因此你无法在 simple_event schema 下发送 dashboard 事件。
2. 自定义 Schema(legacy,不再添加事件)
只有在非常特殊的情况下才考虑添加新的事件 schema。
示例: DashboardEventSchema、CleanupEventSchema、QuestionEventSchema
分步指南:添加一个简单事件
示例:跟踪用户在表格选择器中应用筛选器
第 1 步:创建跟踪函数
在你功能的 analytics.ts 文件中(例如 enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts):
import { 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",
});
};
第 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>
);
}
使用 SimpleEventSchema 字段
下面的所有示例都位于功能自己的 analytics.ts 中——没有任何内容被集中注册。
示例:带 target_id 的事件
export const trackDataStudioLibraryCreated = (id: CollectionId) => {
trackSimpleEvent({
event: "data_studio_library_created",
target_id: Number(id),
});
};
// Usage
trackDataStudioLibraryCreated(newLibrary.id);
示例:带 triggered_from 的事件
// 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
</Button>
示例:带 event_detail 的事件
真实示例——frontend/src/metabase/metadata/pages/shared/analytics.ts:
export type MetadataEditEventDetail =
| "type_casting"
| "semantic_type_change"
| "visibility_change";
export const trackMetadataChange = (detail: MetadataEditEventDetail) => {
trackSimpleEvent({
event: "metadata_edited",
event_detail: detail,
triggered_from: "admin",
});
};
// Usage
trackMetadataChange("semantic_type_change");
示例:带 result 和 duration 的事件
真实的版本请参见 frontend/src/metabase/archive/analytics.ts。
export const trackMoveToTrash = (params: {
targetId: number | null;
triggeredFrom: "collection" | "detail_page" | "cleanup_modal";
durationMs: number | null;
result: "success" | "failure";
itemType: "question" | "model" | "metric" | "dashboard";
}) => {
trackSimpleEvent({
event: "moved-to-trash",
target_id: params.targetId,
triggered_from: params.triggeredFrom,
duration_ms: params.durationMs,
result: params.result,
event_detail: params.itemType,
});
};
// Usage with timing
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",
});
}
命名约定
事件名称(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
本地字段类型(PascalCase,以字段命名)
通常不再有 ...Event 类型需要命名。当你确实需要为某个字段定义联合类型时,请以它所服务的字段命名:
// Good
type MetricDimensionResult = "success" | "failure"; // -> result
export type MetadataEditEventDetail = "type_casting"; // -> event_detail
type NewButtonLocation = "app-bar" | "empty-collection"; // -> triggered_from
跟踪函数名称(camelCase,带 "track" 前缀)
// Good
trackDataStudioLibraryCreated
trackTablePickerFiltersApplied
trackMetabotChatOpened
// Bad
DataStudioLibraryCreated // Missing "track" prefix
track_library_created // Wrong case
logLibraryCreated // Use "track" prefix
常见模式
模式 1:跨功能共享字段类型
当两个功能发送相同的事件但具有不同的 triggered_from 时,从拥有该字段的功能的 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",
});
}
这就是可扩展事件设计的要点:企业版和功能层级的类型保留在它们自己的模块中,而不是被导入到一个共享联合类型中。
模式 2:条件跟踪
根据用户操作跟踪不同的事件:
const handleSave = async () => {
if (isNewItem) {
await createItem(data);
trackItemCreated(newItem.id);
} else {
await updateItem(id, data);
trackItemUpdated(id);
}
};
常见陷阱
不要:为简单事件添加自定义字段
// WRONG - SimpleEventSchema doesn't support custom fields (this is a compile error)
export const trackFiltersApplied = (filters: FilterState) => {
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 - Use only standard SimpleEventSchema fields
export const trackFiltersApplied = () => {
trackSimpleEvent({
event: "filters_applied",
});
};
// Or use event_detail for a single variant
export const trackFilterApplied = (filterType: string) => {
trackSimpleEvent({
event: "filter_applied",
event_detail: filterType, // ✓ "data_layer", "data_source", etc.
});
};
不要:向 metabase-types/analytics/event.ts 添加事件类型
中心的 SimpleEvent 联合类型已被移除——它迫使功能层级类型被向下导入到共享代码中,导致模块边界违规。trackSimpleEvent 现在是泛型的,因此该类型除了重复之外别无用处。
// ❌ WRONG - central declaration + re-import for a `satisfies` clause
// frontend/src/metabase-types/analytics/event.ts
export type NewFeatureClickedEvent = ValidateEvent<{
event: "new_feature_clicked";
target_id: number;
}>;
// frontend/src/metabase/my-feature/analytics.ts
import type { NewFeatureClickedEvent } from "metabase-types/analytics";
export const trackNewFeatureClicked = (id: number) => {
trackSimpleEvent({
event: "new_feature_clicked",
target_id: id,
} satisfies NewFeatureClickedEvent);
};
// ✓ RIGHT - the object literal is already checked by the generic
// frontend/src/metabase/my-feature/analytics.ts
export const trackNewFeatureClicked = (id: number) => {
trackSimpleEvent({
event: "new_feature_clicked",
target_id: id,
});
};
仍有少数 ...Event 类型留在 metabase-types/analytics/event.ts 中。它们是重构前后合并的 PR 的遗留物——不要复制它们,也不要向其中添加内容。
不要:混淆事件名称格式
// 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
不要:跟踪 PII 或敏感数据
// WRONG - Don't track user emails, names, or sensitive data
trackSimpleEvent({
event: "user_logged_in",
event_detail: user.email, // ❌ PII
});
// RIGHT - Track non-sensitive identifiers only
trackSimpleEvent({
event: "user_logged_in",
target_id: user.id, // ✓ Just the ID
});
不要:忘记同时跟踪成功和失败
// WRONG - Only tracking success
try {
await saveData();
trackDataSaved();
} catch (error) {
// ❌ No tracking for failure case
}
// RIGHT - Track both outcomes
try {
await saveData();
trackDataSaved({ result: "success" });
} catch (error) {
trackDataSaved({ result: "failure" });
}
测试分析事件
在开发过程中,你可以验证事件是否正在触发:
- 检查浏览器控制台 - 在开发环境中当
SNOWPLOW_ENABLED=true时,事件会被记录 - 使用 shouldLogAnalytics - 在
metabase/env中设置以在控制台中查看所有分析 - 检查 Snowplow 调试器 - 用于 Snowplow 事件的浏览器扩展
控制台输出示例:
[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_applied
文件组织
将跟踪函数放在哪里:
Tracking functions AND their local field types (this is where new events live):
frontend/src/metabase/{feature}/analytics.ts
enterprise/frontend/src/metabase-enterprise/{feature}/analytics.ts
Core tracking utilities:
frontend/src/metabase/analytics/ (import from `metabase/analytics`)
Shared SimpleEventSchema only — nothing new goes here:
frontend/src/metabase-types/analytics/event.ts
在 embedding SDK 代码中,改用 trackSdkSimpleEvent
(frontend/src/embedding-sdk-bundle/analytics/snowplow.ts)——主应用的 "sp" 跟踪器
未在客户的页面中初始化,因此 trackSimpleEvent 的 Snowplow 部分在那里是空操作。
真实示例
参考以下文件:
- 简单事件 + 本地字段联合类型:
frontend/src/metabase/metadata/pages/shared/analytics.ts - 复用另一个功能的字段类型:
frontend/src/metabase/data-studio/data-model/analytics.ts - 结果 + 持续时间计时:
frontend/src/metabase/archive/analytics.ts - 企业功能事件:
enterprise/frontend/src/metabase-enterprise/google_drive/analytics.ts
工作流总结
- 确定要跟踪的用户交互
- 决定事件名称(snake_case,具有描述性)
- 创建跟踪函数,放在功能的
analytics.ts中,调用trackSimpleEvent() - 添加本地字段联合类型,如果某个字段有一组固定值,则在同一文件中添加
- 导入并调用,在交互点处
- 测试事件是否正确触发
提示
- 要具体 -
filters_applied比action_performed更好 - 使用过去时 -
library_created而不是create_library - 将相关事件分组 - 将某个功能的跟踪函数保留在其
analytics.ts中 - 跟踪有意义的操作 - 不是每次点击都需要跟踪
- 考虑数据 - 你以后想要分析什么?
- 保持一致性 - 遵循代码库中现有的命名模式
- 记录上下文 - 使用
triggered_from来跟踪操作发生的位置