Skip to content

Commit 2219bf0

Browse files
msukkariclaude
andcommitted
feat(web): add source-segmented analytics with MCP/API tracking
Restructure analytics dashboard to segment metrics by source (web, MCP, API). Add audit events for file source, file tree, and repo listing actions. Pass source metadata through all audit event paths including MCP server, chat blocking API, and code navigation. Backfill historical audit events with sourcebot-web-client source. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1ce7e73 commit 2219bf0

File tree

10 files changed

+282
-86
lines changed

10 files changed

+282
-86
lines changed

packages/db/prisma/migrations/20260226000000_backfill_audit_source_metadata/migration.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ WHERE action IN ('user.performed_code_search', 'user.created_ask_chat')
1414
-- Navigation events (find references, goto definition) were web-only
1515
-- (created from the symbolHoverPopup client component)
1616
UPDATE "Audit"
17-
SET metadata = jsonb_set(COALESCE(metadata, '{}')::jsonb, '{source}', '"sourcebot-ui-codenav"')
17+
SET metadata = jsonb_set(COALESCE(metadata, '{}')::jsonb, '{source}', '"sourcebot-web-client"')
1818
WHERE action IN ('user.performed_find_references', 'user.performed_goto_definition')
1919
AND (metadata IS NULL OR metadata->>'source' IS NULL);

packages/db/tools/scripts/inject-audit-data-v2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ export const injectAuditDataV2: Script = {
180180
const activityChance = isWeekend ? user.weekendActivity : user.weekdayActivity;
181181
if (Math.random() >= activityChance) continue;
182182

183-
// --- Web UI activity (source='sourcebot-web-client' or 'sourcebot-ui-codenav') ---
183+
// --- Web UI activity (source='sourcebot-web-client') ---
184184
if (user.webWeight > 0) {
185185
const webMeta: Prisma.InputJsonValue = { source: 'sourcebot-web-client' };
186-
const codenavMeta: Prisma.InputJsonValue = { source: 'sourcebot-ui-codenav' };
186+
const codenavMeta: Prisma.InputJsonValue = { source: 'sourcebot-web-client' };
187187

188188
// Code searches (2-5 base)
189189
await createAudits(user.id, 'user.performed_code_search',

packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const CodePreviewPanel = async ({ path, repoName, revisionName }: CodePre
1818
path,
1919
repo: repoName,
2020
ref: revisionName,
21-
}),
21+
}, { sourceOverride: 'sourcebot-web-client' }),
2222
getRepoInfoByName(repoName),
2323
]);
2424

packages/web/src/app/api/(server)/chat/blocking/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export const POST = apiHandler(async (request: NextRequest) => {
4545
return serviceErrorResponse(requestBodySchemaValidationError(parsed.error));
4646
}
4747

48-
const response = await askCodebase(parsed.data);
48+
const source = request.headers.get('X-Sourcebot-Client-Source') ?? undefined;
49+
const response = await askCodebase({ ...parsed.data, source });
4950

5051
if (isServiceError(response)) {
5152
return serviceErrorResponse(response);

packages/web/src/ee/features/analytics/actions.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,26 +81,43 @@ export const getAnalytics = async (domain: string, apiKey: string | undefined =
8181
ELSE c.month
8282
END AS bucket,
8383
84-
-- Global active users (any action, any source)
85-
COUNT(DISTINCT c."actorId") AS active_users,
84+
-- Global active users (any action, any source; excludes web repo listings)
85+
COUNT(DISTINCT c."actorId") FILTER (
86+
WHERE NOT (c.action = 'user.listed_repos' AND c.metadata->>'source' LIKE 'sourcebot-%')
87+
) AS active_users,
8688
87-
-- Web App metrics (source LIKE 'sourcebot-%')
89+
-- Web App metrics
90+
COUNT(DISTINCT c."actorId") FILTER (
91+
WHERE c.action = 'user.performed_code_search'
92+
AND c.metadata->>'source' = 'sourcebot-web-client'
93+
) AS web_search_active_users,
8894
COUNT(*) FILTER (
8995
WHERE c.action = 'user.performed_code_search'
90-
AND c.metadata->>'source' LIKE 'sourcebot-%'
96+
AND c.metadata->>'source' = 'sourcebot-web-client'
9197
) AS web_code_searches,
9298
COUNT(*) FILTER (
9399
WHERE c.action IN ('user.performed_find_references', 'user.performed_goto_definition')
94-
AND c.metadata->>'source' LIKE 'sourcebot-%'
100+
AND c.metadata->>'source' = 'sourcebot-web-client'
95101
) AS web_navigations,
102+
COUNT(DISTINCT c."actorId") FILTER (
103+
WHERE c.action = 'user.created_ask_chat'
104+
AND c.metadata->>'source' = 'sourcebot-web-client'
105+
) AS web_ask_active_users,
96106
COUNT(*) FILTER (
97107
WHERE c.action = 'user.created_ask_chat'
98-
AND c.metadata->>'source' LIKE 'sourcebot-%'
108+
AND c.metadata->>'source' = 'sourcebot-web-client'
99109
) AS web_ask_chats,
100110
COUNT(DISTINCT c."actorId") FILTER (
101-
WHERE c.metadata->>'source' LIKE 'sourcebot-%'
111+
WHERE c.metadata->>'source' = 'sourcebot-web-client'
112+
AND c.action != 'user.listed_repos'
102113
) AS web_active_users,
103114
115+
-- MCP + API combined active users (any non-web source)
116+
COUNT(DISTINCT c."actorId") FILTER (
117+
WHERE c.metadata->>'source' IS NULL
118+
OR c.metadata->>'source' NOT LIKE 'sourcebot-%'
119+
) AS non_web_active_users,
120+
104121
-- MCP metrics (source = 'mcp')
105122
COUNT(*) FILTER (
106123
WHERE c.metadata->>'source' = 'mcp'
@@ -130,10 +147,13 @@ export const getAnalytics = async (domain: string, apiKey: string | undefined =
130147
b.period,
131148
b.bucket,
132149
COALESCE(a.active_users, 0)::int AS active_users,
150+
COALESCE(a.web_search_active_users, 0)::int AS web_search_active_users,
133151
COALESCE(a.web_code_searches, 0)::int AS web_code_searches,
134152
COALESCE(a.web_navigations, 0)::int AS web_navigations,
153+
COALESCE(a.web_ask_active_users, 0)::int AS web_ask_active_users,
135154
COALESCE(a.web_ask_chats, 0)::int AS web_ask_chats,
136155
COALESCE(a.web_active_users, 0)::int AS web_active_users,
156+
COALESCE(a.non_web_active_users, 0)::int AS non_web_active_users,
137157
COALESCE(a.mcp_requests, 0)::int AS mcp_requests,
138158
COALESCE(a.mcp_active_users, 0)::int AS mcp_active_users,
139159
COALESCE(a.api_requests, 0)::int AS api_requests,

0 commit comments

Comments
 (0)