analytics-events by metabase/metabase
npx skills add https://github.com/metabase/metabase --skill analytics-events此技能帮助你在 Metabase 前端代码库中添加产品分析(Snowplow)事件以追踪用户交互。
Metabase 中的分析事件使用 Snowplow 和类型化事件模式。所有事件在使用前都必须在 TypeScript 类型中定义。
关键文件:
frontend/src/metabase-types/analytics/event.ts - 事件类型定义frontend/src/metabase-types/analytics/schema.ts - 模式注册表frontend/src/metabase/lib/analytics.ts - 核心追踪函数analytics.ts 文件 - 追踪函数包装器添加新分析事件时:
frontend/src/metabase-types/analytics/event.ts 中定义事件类型DataStudioEvent、)广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
SimpleEventanalytics.ts 文件中创建追踪函数trackSimpleEvent()使用 SimpleEventSchema 进行直接追踪。它支持以下标准字段:
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% 的事件适合此模式。用于点击、打开、关闭、创建、删除等。
仅在非常特殊的情况下才考虑添加新的事件模式。
示例: DashboardEventSchema、CleanupEventSchema、QuestionEventSchema
将事件类型定义添加到 frontend/src/metabase-types/analytics/event.ts:
export type DataStudioTablePickerFiltersAppliedEvent = ValidateEvent<{
event: "data_studio_table_picker_filters_applied";
}>;
export type DataStudioTablePickerFiltersClearedEvent = ValidateEvent<{
event: "data_studio_table_picker_filters_cleared";
}>;
找到或创建适当的联合类型并添加你的事件:
export type DataStudioEvent =
| DataStudioLibraryCreatedEvent
| DataStudioTablePublishedEvent
| DataStudioGlossaryCreatedEvent
| DataStudioGlossaryEditedEvent
| DataStudioGlossaryDeletedEvent
| DataStudioTablePickerFiltersAppliedEvent // <- 添加到这里
| DataStudioTablePickerFiltersClearedEvent; // <- 添加到这里
在你功能的 analytics.ts 文件中(例如 enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts):
import { trackSimpleEvent } from "metabase/lib/analytics";
export const trackDataStudioTablePickerFiltersApplied = () => {
trackSimpleEvent({
event: "data_studio_table_picker_filters_applied",
});
};
export const trackDataStudioTablePickerFiltersCleared = () => {
trackSimpleEvent({
event: "data_studio_table_picker_filters_cleared",
});
};
在交互点导入并调用追踪函数:
import {
trackDataStudioTablePickerFiltersApplied,
trackDataStudioTablePickerFiltersCleared,
} from "metabase-enterprise/data-studio/analytics";
function FilterPopover({ filters, onSubmit }) {
const handleReset = () => {
trackDataStudioTablePickerFiltersCleared(); // <- 在这里追踪
onSubmit(emptyFilters);
};
return (
<form
onSubmit={(event) => {
event.preventDefault();
trackDataStudioTablePickerFiltersApplied(); // <- 在这里追踪
onSubmit(form);
}}
>
{/* 表单内容 */}
</form>
);
}
// 类型定义
export type DataStudioLibraryCreatedEvent = ValidateEvent<{
event: "data_studio_library_created";
target_id: number | null;
}>;
// 追踪函数
export const trackDataStudioLibraryCreated = (id: CollectionId) => {
trackSimpleEvent({
event: "data_studio_library_created",
target_id: Number(id),
});
};
// 使用
trackDataStudioLibraryCreated(newLibrary.id);
// 类型定义
export type NewButtonClickedEvent = ValidateEvent<{
event: "new_button_clicked";
triggered_from: "app-bar" | "empty-collection";
}>;
// 追踪函数
export const trackNewButtonClicked = (location: "app-bar" | "empty-collection") => {
trackSimpleEvent({
event: "new_button_clicked",
triggered_from: location,
});
};
// 使用
<Button onClick={() => {
trackNewButtonClicked("app-bar");
handleCreate();
}}>
新建
</Button>
// 类型定义
export type MetadataEditEvent = ValidateEvent<{
event: "metadata_edited";
event_detail: "type_casting" | "semantic_type_change" | "visibility_change";
triggered_from: "admin" | "data_studio";
}>;
// 追踪函数
export const trackMetadataChange = (
detail: "type_casting" | "semantic_type_change" | "visibility_change",
location: "admin" | "data_studio"
) => {
trackSimpleEvent({
event: "metadata_edited",
event_detail: detail,
triggered_from: location,
});
};
// 使用
trackMetadataChange("semantic_type_change", "data_studio");
// 类型定义
export type MoveToTrashEvent = ValidateEvent<{
event: "moved-to-trash";
target_id: number | null;
triggered_from: "collection" | "detail_page" | "cleanup_modal";
duration_ms: number | null;
result: "success" | "failure";
event_detail: "question" | "model" | "metric" | "dashboard";
}>;
// 追踪函数
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,
});
};
// 带计时的使用
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",
});
}
// 正确
"data_studio_library_created"
"table_picker_filters_applied"
"metabot_chat_opened"
// 错误
"DataStudioLibraryCreated" // 错误的大小写
"tablePickerFiltersApplied" // 错误的大小写
"filters-applied" // 使用下划线,而非连字符
// 正确
DataStudioLibraryCreatedEvent
TablePickerFiltersAppliedEvent
MetabotChatOpenedEvent
// 错误
dataStudioLibraryCreated // 错误的大小写
DataStudioLibraryCreated // 缺少 "Event" 后缀
// 正确
trackDataStudioLibraryCreated
trackTablePickerFiltersApplied
trackMetabotChatOpened
// 错误
DataStudioLibraryCreated // 缺少 "track" 前缀
track_library_created // 错误的大小写
logLibraryCreated // 使用 "track" 前缀
将相关事件分组:
export type DataStudioEvent =
| DataStudioLibraryCreatedEvent
| DataStudioTablePublishedEvent
| DataStudioGlossaryCreatedEvent;
export type MetabotEvent =
| MetabotChatOpenedEvent
| MetabotRequestSentEvent
| MetabotFixQueryClickedEvent;
// 然后添加到 SimpleEvent 联合类型
export type SimpleEvent =
| /* 其他事件 */
| DataStudioEvent
| MetabotEvent
| /* 更多事件 */;
根据用户操作追踪不同事件:
const handleSave = async () => {
if (isNewItem) {
await createItem(data);
trackItemCreated(newItem.id);
} else {
await updateItem(id, data);
trackItemUpdated(id);
}
};
// 错误 - SimpleEvent 不支持自定义字段
export const trackFiltersApplied = (filters: FilterState) => {
trackSimpleEvent({
event: "filters_applied",
data_layer: filters.dataLayer, // ❌ 不在 SimpleEventSchema 中
data_source: filters.dataSource, // ❌ 不在 SimpleEventSchema 中
with_owner: filters.hasOwner, // ❌ 不在 SimpleEventSchema 中
});
};
// 正确 - 仅使用标准 SimpleEventSchema 字段
export const trackFiltersApplied = () => {
trackSimpleEvent({
event: "filters_applied",
});
};
// 或者使用 event_detail 表示单个变体
export const trackFilterApplied = (filterType: string) => {
trackSimpleEvent({
event: "filter_applied",
event_detail: filterType, // ✓ "data_layer"、"data_source" 等
});
};
// 定义事件
export type NewFeatureClickedEvent = ValidateEvent<{
event: "new_feature_clicked";
}>;
// ❌ 错误 - 忘记添加到 SimpleEvent 联合类型
// TypeScript 将无法识别该事件
// ✓ 正确 - 添加到适当的联合类型
export type SimpleEvent =
| /* 其他事件 */
| NewFeatureClickedEvent;
// 错误
event: "dataStudioLibraryCreated" // camelCase
event: "data-studio-library-created" // kebab-case
event: "Data_Studio_Library_Created" // 混合大小写
// 正确
event: "data_studio_library_created" // snake_case
// 错误 - 不要追踪用户邮箱、姓名或敏感数据
trackSimpleEvent({
event: "user_logged_in",
event_detail: user.email, // ❌ PII
});
// 正确 - 仅追踪非敏感标识符
trackSimpleEvent({
event: "user_logged_in",
target_id: user.id, // ✓ 仅 ID
});
// 错误 - 仅追踪成功
try {
await saveData();
trackDataSaved();
} catch (error) {
// ❌ 失败情况没有追踪
}
// 正确 - 追踪两种结果
try {
await saveData();
trackDataSaved({ result: "success" });
} catch (error) {
trackDataSaved({ result: "failure" });
}
开发时,你可以验证事件是否触发:
SNOWPLOW_ENABLED=true 时,事件会被记录metabase/env 中设置以在控制台查看所有分析控制台输出示例:
[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_applied
特定功能的分析函数:
frontend/src/metabase/{feature}/analytics.ts
enterprise/frontend/src/metabase-enterprise/{feature}/analytics.ts
事件类型定义(集中存放):
frontend/src/metabase-types/analytics/event.ts
核心追踪工具:
frontend/src/metabase/lib/analytics.ts
参考以下文件:
enterprise/frontend/src/metabase-enterprise/data-studio/analytics.tsfrontend/src/metabase/dashboard/analytics.tsfrontend/src/metabase/query_builder/analytics.jsfrontend/src/metabase-types/analytics/event.tsevent.ts 中使用 ValidateEvent 定义事件类型analytics.ts 中创建追踪函数filters_applied 比 action_performed 更好library_created 而非 create_librarytriggered_from 追踪操作发生的位置每周安装次数
137
代码仓库
GitHub 星标
46.5K
首次出现
2026年1月22日
安全审计
安装于
claude-code129
opencode126
gemini-cli121
codex121
cursor118
github-copilot117
This skill helps you add product analytics (Snowplow) events to track user interactions in the Metabase frontend codebase.
Analytics events in Metabase use Snowplow with typed event schemas. All events must be defined in TypeScript types before use.
Key Files:
frontend/src/metabase-types/analytics/event.ts - Event type definitionsfrontend/src/metabase-types/analytics/schema.ts - Schema registryfrontend/src/metabase/lib/analytics.ts - Core tracking functionsanalytics.ts files - Tracking function wrappersWhen adding a new analytics event:
frontend/src/metabase-types/analytics/event.tsDataStudioEvent, SimpleEvent)analytics.ts filetrackSimpleEvent() for basic events (most common)Use SimpleEventSchema for straightforward tracking. It supports these standard fields:
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
};
When to use: 90% of events fit this schema. Use for clicks, opens, closes, creates, deletes, etc.
Consider adding new event schema only in very special cases.
Examples: DashboardEventSchema, CleanupEventSchema, QuestionEventSchema
Add event type definitions to frontend/src/metabase-types/analytics/event.ts:
export type DataStudioTablePickerFiltersAppliedEvent = ValidateEvent<{
event: "data_studio_table_picker_filters_applied";
}>;
export type DataStudioTablePickerFiltersClearedEvent = ValidateEvent<{
event: "data_studio_table_picker_filters_cleared";
}>;
Find or create the appropriate union type and add your events:
export type DataStudioEvent =
| DataStudioLibraryCreatedEvent
| DataStudioTablePublishedEvent
| DataStudioGlossaryCreatedEvent
| DataStudioGlossaryEditedEvent
| DataStudioGlossaryDeletedEvent
| DataStudioTablePickerFiltersAppliedEvent // <- Add here
| DataStudioTablePickerFiltersClearedEvent; // <- Add here
In your feature's analytics.ts file (e.g., enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts):
import { trackSimpleEvent } from "metabase/lib/analytics";
export const trackDataStudioTablePickerFiltersApplied = () => {
trackSimpleEvent({
event: "data_studio_table_picker_filters_applied",
});
};
export const trackDataStudioTablePickerFiltersCleared = () => {
trackSimpleEvent({
event: "data_studio_table_picker_filters_cleared",
});
};
Import and call the tracking function at the interaction point:
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>
);
}
// Type definition
export type DataStudioLibraryCreatedEvent = ValidateEvent<{
event: "data_studio_library_created";
target_id: number | null;
}>;
// Tracking function
export const trackDataStudioLibraryCreated = (id: CollectionId) => {
trackSimpleEvent({
event: "data_studio_library_created",
target_id: Number(id),
});
};
// Usage
trackDataStudioLibraryCreated(newLibrary.id);
// Type definition
export type NewButtonClickedEvent = ValidateEvent<{
event: "new_button_clicked";
triggered_from: "app-bar" | "empty-collection";
}>;
// Tracking function
export const trackNewButtonClicked = (location: "app-bar" | "empty-collection") => {
trackSimpleEvent({
event: "new_button_clicked",
triggered_from: location,
});
};
// Usage
<Button onClick={() => {
trackNewButtonClicked("app-bar");
handleCreate();
}}>
New
</Button>
// Type definition
export type MetadataEditEvent = ValidateEvent<{
event: "metadata_edited";
event_detail: "type_casting" | "semantic_type_change" | "visibility_change";
triggered_from: "admin" | "data_studio";
}>;
// Tracking function
export const trackMetadataChange = (
detail: "type_casting" | "semantic_type_change" | "visibility_change",
location: "admin" | "data_studio"
) => {
trackSimpleEvent({
event: "metadata_edited",
event_detail: detail,
triggered_from: location,
});
};
// Usage
trackMetadataChange("semantic_type_change", "data_studio");
// Type definition
export type MoveToTrashEvent = ValidateEvent<{
event: "moved-to-trash";
target_id: number | null;
triggered_from: "collection" | "detail_page" | "cleanup_modal";
duration_ms: number | null;
result: "success" | "failure";
event_detail: "question" | "model" | "metric" | "dashboard";
}>;
// Tracking function
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",
});
}
// 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
// Good
DataStudioLibraryCreatedEvent
TablePickerFiltersAppliedEvent
MetabotChatOpenedEvent
// Bad
dataStudioLibraryCreated // Wrong case
DataStudioLibraryCreated // Missing "Event" suffix
// Good
trackDataStudioLibraryCreated
trackTablePickerFiltersApplied
trackMetabotChatOpened
// Bad
DataStudioLibraryCreated // Missing "track" prefix
track_library_created // Wrong case
logLibraryCreated // Use "track" prefix
Group related events together:
export type DataStudioEvent =
| DataStudioLibraryCreatedEvent
| DataStudioTablePublishedEvent
| DataStudioGlossaryCreatedEvent;
export type MetabotEvent =
| MetabotChatOpenedEvent
| MetabotRequestSentEvent
| MetabotFixQueryClickedEvent;
// Then add to SimpleEvent union
export type SimpleEvent =
| /* other events */
| DataStudioEvent
| MetabotEvent
| /* more events */;
Track different events based on user action:
const handleSave = async () => {
if (isNewItem) {
await createItem(data);
trackItemCreated(newItem.id);
} else {
await updateItem(id, data);
trackItemUpdated(id);
}
};
// WRONG - SimpleEvent doesn't support custom fields
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.
});
};
// Define the event
export type NewFeatureClickedEvent = ValidateEvent<{
event: "new_feature_clicked";
}>;
// ❌ WRONG - Forgot to add to SimpleEvent union
// Event won't be recognized by TypeScript
// ✓ RIGHT - Add to appropriate union
export type SimpleEvent =
| /* other events */
| NewFeatureClickedEvent;
// 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
// 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" });
}
While developing, you can verify events are firing:
SNOWPLOW_ENABLED=true in dev, events are loggedmetabase/env to see all analytics in consoleExample console output:
[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_applied
Feature-specific analytics functions:
frontend/src/metabase/{feature}/analytics.ts
enterprise/frontend/src/metabase-enterprise/{feature}/analytics.ts
Event type definitions (all in one place):
frontend/src/metabase-types/analytics/event.ts
Core tracking utilities:
frontend/src/metabase/lib/analytics.ts
See these files for reference:
enterprise/frontend/src/metabase-enterprise/data-studio/analytics.tsfrontend/src/metabase/dashboard/analytics.tsfrontend/src/metabase/query_builder/analytics.jsfrontend/src/metabase-types/analytics/event.tsevent.ts using ValidateEventanalytics.tsfilters_applied is better than action_performedlibrary_created not create_librarytriggered_from to track where the action happenedWeekly Installs
137
Repository
GitHub Stars
46.5K
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code129
opencode126
gemini-cli121
codex121
cursor118
github-copilot117
Excel财务建模规范与xlsx文件处理指南:专业格式、零错误公式与数据分析
43,800 周安装