fix(analytics): include username on page_generated events
Memind CI / Test, build, and release guards (pull_request) Successful in 5m34s
Memind CI / Test, build, and release guards (pull_request) Successful in 5m34s
Umami creator labels read event_data.username; server events only sent owner_label, so dashboards fell back to MindSpace UUID path segments. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
---
|
||||
description: MindSpace 生成页埋点必须上报创建者 username,且不得用 owner_id identify 匿名访客
|
||||
globs: mindspace-analytics.mjs,mindspace-rybbit.mjs,mindspace-analytics.test.mjs,mindspace-rybbit.test.mjs,server/portal-session-routes.mjs,server/portal-integration-services-bootstrap.mjs
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# MindSpace Analytics — 创建用户昵称与 Public 身份
|
||||
|
||||
Umami「创建用户」依赖 `event_data.username` / session `username`。路径里的 MindSpace UUID **不是**昵称。
|
||||
|
||||
## 必须
|
||||
|
||||
- 服务端 `page_generated`(`sendMindSpaceAnalyticsEvent` / `sendMindSpaceRybbitEvent`)的事件属性 **必须**包含 `username`(创建者展示名,来自 `resolveAnalyticsOwnerLabel`)
|
||||
- 可同时保留 `owner_label` 作兼容,值与 `username` 相同
|
||||
- Public HTML 内嵌脚本的 `metadata` **必须**继续带 `username`(给 scroll/click 等客户端事件)
|
||||
|
||||
## 禁止(避免破坏访客统计)
|
||||
|
||||
- Public **匿名**访问路径 **禁止** `umami.identify(owner_id, …)` / 把创建者 ID 当作访客 `id`
|
||||
- 仅已登录 viewer 可 `identifyViewer()`;不得为了补创建用户昵称而恢复「全员 identify 创建者」
|
||||
|
||||
## 改完必跑
|
||||
|
||||
```bash
|
||||
node --test mindspace-analytics.test.mjs mindspace-rybbit.test.mjs
|
||||
```
|
||||
@@ -123,6 +123,9 @@ export function sendMindSpaceAnalyticsEvent({
|
||||
const owner = resolveAnalyticsIdentity(ownerId, config);
|
||||
if (!owner) return Promise.resolve(false);
|
||||
const endpoint = `${String(config.analyticsUrl || 'http://127.0.0.1:3100').replace(/\/$/, '')}/api/send`;
|
||||
// Umami Memind dashboards resolve「创建用户」from event_data.username (not owner_label).
|
||||
// Keep owner_label for backward compatibility; never omit username on generation events.
|
||||
const username = resolveAnalyticsOwnerLabel({ displayName: ownerLabel });
|
||||
const payload = {
|
||||
website: config.websiteId,
|
||||
id: owner,
|
||||
@@ -137,7 +140,8 @@ export function sendMindSpaceAnalyticsEvent({
|
||||
channel,
|
||||
owner_segment: String(ownerSegment || 'unknown'),
|
||||
plan_type: String(planType || 'unknown'),
|
||||
owner_label: resolveAnalyticsOwnerLabel({ displayName: ownerLabel }),
|
||||
username,
|
||||
owner_label: username,
|
||||
generated_at: String(generatedAt || ''),
|
||||
identity_mode: config.identityMode === 'raw' ? 'raw' : 'pseudonymous',
|
||||
},
|
||||
|
||||
@@ -280,6 +280,7 @@ test('generation events are attributed to raw identity and include analysis dime
|
||||
assert.equal(requestBody.payload.id, 'user-123');
|
||||
assert.deepEqual(requestBody.payload.data, expectPayload({
|
||||
owner_id: 'user-123',
|
||||
username: '张三',
|
||||
owner_label: '张三',
|
||||
owner_segment: 'plan:pro',
|
||||
plan_type: 'pro',
|
||||
|
||||
@@ -60,6 +60,7 @@ export function sendMindSpaceRybbitEvent({
|
||||
const owner = pseudonymizeAnalyticsId(ownerId, config.idSecret);
|
||||
if (!owner) return Promise.resolve(false);
|
||||
const endpoint = `${String(config.rybbitUrl || 'https://rybbit.tkmind.cn').replace(/\/$/, '')}/api/track`;
|
||||
const username = resolveAnalyticsOwnerLabel({ displayName: ownerLabel });
|
||||
const payload = {
|
||||
site_id: String(config.siteId),
|
||||
type: 'custom_event',
|
||||
@@ -76,7 +77,8 @@ export function sendMindSpaceRybbitEvent({
|
||||
agent_run_id: String(agentRunId || ''),
|
||||
channel,
|
||||
owner_segment: String(ownerSegment || 'unknown'),
|
||||
owner_label: resolveAnalyticsOwnerLabel({ displayName: ownerLabel }),
|
||||
username,
|
||||
owner_label: username,
|
||||
}),
|
||||
};
|
||||
return fetch(endpoint, {
|
||||
|
||||
Reference in New Issue
Block a user