Skip to content

Commit 40c832c

Browse files
msukkariclaude
andcommitted
feat(web): restructure analytics by source and add global active users
- Tag all audit events with source metadata (sourcebot-web-client, sourcebot-ask-agent, sourcebot-ui-codenav, mcp) via sourceOverride - Restructure analytics SQL to segment by Web App (sourcebot-*), MCP, and API (everything else) - Add global active users chart at top of analytics page - Add info hover tooltips explaining each chart - Prefix chart names with their section (Web/MCP/API) for clarity - Update inject-audit-data script to use correct source values Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4ffb9d0 commit 40c832c

File tree

11 files changed

+275
-91
lines changed

11 files changed

+275
-91
lines changed

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

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

183-
// --- Web UI activity (no source metadata) ---
183+
// --- Web UI activity (source='sourcebot-web-client' or 'sourcebot-ui-codenav') ---
184184
if (user.webWeight > 0) {
185+
const webMeta: Prisma.InputJsonValue = { source: 'sourcebot-web-client' };
186+
const codenavMeta: Prisma.InputJsonValue = { source: 'sourcebot-ui-codenav' };
187+
185188
// Code searches (2-5 base)
186189
await createAudits(user.id, 'user.performed_code_search',
187-
scaledCount(2, 5, user.webWeight, isWeekend), currentDate, isWeekend, 'search');
190+
scaledCount(2, 5, user.webWeight, isWeekend), currentDate, isWeekend, 'search', webMeta);
188191

189192
// Navigations: find references + goto definition (5-10 base)
190193
const navCount = scaledCount(5, 10, user.webWeight, isWeekend);
191194
for (let i = 0; i < navCount; i++) {
192195
const action = Math.random() < 0.6 ? 'user.performed_find_references' : 'user.performed_goto_definition';
193-
await createAudits(user.id, action, 1, currentDate, isWeekend, 'symbol');
196+
await createAudits(user.id, action, 1, currentDate, isWeekend, 'symbol', codenavMeta);
194197
}
195198

196199
// Ask chats (0-2 base) - web only
197200
await createAudits(user.id, 'user.created_ask_chat',
198-
scaledCount(0, 2, user.webWeight, isWeekend), currentDate, isWeekend, 'org');
201+
scaledCount(0, 2, user.webWeight, isWeekend), currentDate, isWeekend, 'org', webMeta);
199202

200203
// File source views (3-8 base)
201204
await createAudits(user.id, 'user.fetched_file_source',
202-
scaledCount(3, 8, user.webWeight, isWeekend), currentDate, isWeekend, 'file');
205+
scaledCount(3, 8, user.webWeight, isWeekend), currentDate, isWeekend, 'file', webMeta);
203206

204207
// File tree browsing (2-5 base)
205208
await createAudits(user.id, 'user.fetched_file_tree',
206-
scaledCount(2, 5, user.webWeight, isWeekend), currentDate, isWeekend, 'repo');
209+
scaledCount(2, 5, user.webWeight, isWeekend), currentDate, isWeekend, 'repo', webMeta);
207210

208211
// List repos (1-3 base)
209212
await createAudits(user.id, 'user.listed_repos',
210-
scaledCount(1, 3, user.webWeight, isWeekend), currentDate, isWeekend, 'org');
213+
scaledCount(1, 3, user.webWeight, isWeekend), currentDate, isWeekend, 'org', webMeta);
211214
}
212215

213216
// --- MCP activity (source='mcp') ---
@@ -279,17 +282,18 @@ export const injectAuditData: Script = {
279282
let webCount = 0, mcpCount = 0, apiCount = 0;
280283
for (const audit of allAudits) {
281284
const meta = audit.metadata as Record<string, unknown> | null;
282-
if (!meta || !meta.source) {
285+
const source = meta?.source as string | undefined;
286+
if (source && typeof source === 'string' && source.startsWith('sourcebot-')) {
283287
webCount++;
284-
} else if (meta.source === 'mcp') {
288+
} else if (source === 'mcp') {
285289
mcpCount++;
286290
} else {
287291
apiCount++;
288292
}
289293
}
290294
console.log('\nSource breakdown:');
291-
console.log(` Web UI (no source): ${webCount}`);
295+
console.log(` Web UI (source=sourcebot-*): ${webCount}`);
292296
console.log(` MCP (source=mcp): ${mcpCount}`);
293-
console.log(` API (source=other): ${apiCount}`);
297+
console.log(` API (source=other/null): ${apiCount}`);
294298
},
295299
};

packages/web/src/app/api/(server)/repos/listReposApi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils";
66
import { env } from "@sourcebot/shared";
77
import { headers } from "next/headers";
88

9-
export const listRepos = async ({ query, page, perPage, sort, direction }: ListReposQueryParams) => sew(() =>
9+
export const listRepos = async ({ query, page, perPage, sort, direction, sourceOverride }: ListReposQueryParams & { sourceOverride?: string }) => sew(() =>
1010
withOptionalAuthV2(async ({ org, prisma, user }) => {
1111
if (user) {
12-
const source = (await headers()).get('X-Sourcebot-Client-Source') ?? undefined;
12+
const source = sourceOverride ?? (await headers()).get('X-Sourcebot-Client-Source') ?? undefined;
1313
getAuditService().createAudit({
1414
action: 'user.listed_repos',
1515
actor: { id: user.id, type: 'user' },

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

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,45 @@ export const getAnalytics = async (domain: string, apiKey: string | undefined =
8080
WHEN 'week' THEN c.week
8181
ELSE c.month
8282
END AS bucket,
83-
COUNT(*) FILTER (WHERE c.action = 'user.performed_code_search') AS code_searches,
84-
COUNT(*) FILTER (WHERE c.action IN ('user.performed_find_references', 'user.performed_goto_definition')) AS navigations,
85-
COUNT(*) FILTER (WHERE c.action = 'user.created_ask_chat') AS ask_chats,
86-
COUNT(*) FILTER (WHERE c.metadata->>'source' = 'mcp') AS mcp_requests,
87-
COUNT(*) FILTER (WHERE c.metadata->>'source' IS NOT NULL AND c.metadata->>'source' != 'mcp') AS api_requests,
88-
COUNT(DISTINCT c."actorId") AS active_users
83+
84+
-- Global active users (any action, any source)
85+
COUNT(DISTINCT c."actorId") AS active_users,
86+
87+
-- Web App metrics (source LIKE 'sourcebot-%')
88+
COUNT(*) FILTER (
89+
WHERE c.action = 'user.performed_code_search'
90+
AND c.metadata->>'source' LIKE 'sourcebot-%'
91+
) AS web_code_searches,
92+
COUNT(*) FILTER (
93+
WHERE c.action IN ('user.performed_find_references', 'user.performed_goto_definition')
94+
AND c.metadata->>'source' LIKE 'sourcebot-%'
95+
) AS web_navigations,
96+
COUNT(*) FILTER (
97+
WHERE c.action = 'user.created_ask_chat'
98+
AND c.metadata->>'source' LIKE 'sourcebot-%'
99+
) AS web_ask_chats,
100+
COUNT(DISTINCT c."actorId") FILTER (
101+
WHERE c.metadata->>'source' LIKE 'sourcebot-%'
102+
) AS web_active_users,
103+
104+
-- MCP metrics (source = 'mcp')
105+
COUNT(*) FILTER (
106+
WHERE c.metadata->>'source' = 'mcp'
107+
) AS mcp_requests,
108+
COUNT(DISTINCT c."actorId") FILTER (
109+
WHERE c.metadata->>'source' = 'mcp'
110+
) AS mcp_active_users,
111+
112+
-- API metrics (source IS NULL or not sourcebot-*/mcp)
113+
COUNT(*) FILTER (
114+
WHERE c.metadata->>'source' IS NULL
115+
OR (c.metadata->>'source' NOT LIKE 'sourcebot-%' AND c.metadata->>'source' != 'mcp')
116+
) AS api_requests,
117+
COUNT(DISTINCT c."actorId") FILTER (
118+
WHERE c.metadata->>'source' IS NULL
119+
OR (c.metadata->>'source' NOT LIKE 'sourcebot-%' AND c.metadata->>'source' != 'mcp')
120+
) AS api_active_users
121+
89122
FROM core c
90123
JOIN LATERAL (
91124
SELECT unnest(array['day', 'week', 'month']) AS period
@@ -96,12 +129,15 @@ export const getAnalytics = async (domain: string, apiKey: string | undefined =
96129
SELECT
97130
b.period,
98131
b.bucket,
99-
COALESCE(a.code_searches, 0)::int AS code_searches,
100-
COALESCE(a.navigations, 0)::int AS navigations,
101-
COALESCE(a.ask_chats, 0)::int AS ask_chats,
132+
COALESCE(a.active_users, 0)::int AS active_users,
133+
COALESCE(a.web_code_searches, 0)::int AS web_code_searches,
134+
COALESCE(a.web_navigations, 0)::int AS web_navigations,
135+
COALESCE(a.web_ask_chats, 0)::int AS web_ask_chats,
136+
COALESCE(a.web_active_users, 0)::int AS web_active_users,
102137
COALESCE(a.mcp_requests, 0)::int AS mcp_requests,
138+
COALESCE(a.mcp_active_users, 0)::int AS mcp_active_users,
103139
COALESCE(a.api_requests, 0)::int AS api_requests,
104-
COALESCE(a.active_users, 0)::int AS active_users
140+
COALESCE(a.api_active_users, 0)::int AS api_active_users
105141
FROM buckets b
106142
LEFT JOIN aggregated a
107143
ON a.period = b.period AND a.bucket = b.bucket

0 commit comments

Comments
 (0)