Skip to content

Commit a4be9fa

Browse files
authored
Merge branch 'main' into msukkarieh/tcl
2 parents b8f5b6a + 4bb93c9 commit a4be9fa

File tree

36 files changed

+1812
-97
lines changed

36 files changed

+1812
-97
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
- Added code nav and syntax highlighting for TCL. [#362](https://github.com/sourcebot-dev/sourcebot/pull/362)
12+
- Added analytics dashboard. [#358](https://github.com/sourcebot-dev/sourcebot/pull/358)
1213

1314
### Fixed
1415
- Fixed issue where invites appeared to be created successfully, but were not actually being created in the database. [#359](https://github.com/sourcebot-dev/sourcebot/pull/359)
1516

17+
### Changed
18+
- Audit logging is now enabled by default. [#358](https://github.com/sourcebot-dev/sourcebot/pull/358)
19+
1620
## [4.4.0] - 2025-06-18
1721

1822
### Added

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
]
3737
},
3838
"docs/features/code-navigation",
39+
"docs/features/analytics",
3940
"docs/features/mcp-server",
4041
{
4142
"group": "Agents",

docs/docs/configuration/audit-logs.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ action, and when the action took place.
1212

1313
This feature gives security and compliance teams the necessary information to ensure proper governance and administration of your Sourcebot deployment.
1414

15-
## Enabling Audit Logs
16-
Audit logs must be explicitly enabled by setting the `SOURCEBOT_EE_AUDIT_LOGGING_ENABLED` [environment variable](/docs/configuration/environment-variables) to `true`
15+
## Enabling/Disabling Audit Logs
16+
Audit logs are enabled by default and can be controlled with the `SOURCEBOT_EE_AUDIT_LOGGING_ENABLED` [environment variable](/docs/configuration/environment-variables).
1717

1818
## Fetching Audit Logs
1919
Audit logs are stored in the [postgres database](/docs/overview#architecture) connected to Sourcebot. To fetch all of the audit logs, you can use the following API:
@@ -40,7 +40,7 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
4040
{
4141
"id": "cmc146c8r0001xgo2xyu0p463",
4242
"timestamp": "2025-06-17T22:47:58.587Z",
43-
"action": "query.code_search",
43+
"action": "user.performed_code_search",
4444
"actorId": "cmc12tnje0000xgn58jj8655h",
4545
"actorType": "user",
4646
"targetId": "1",
@@ -54,7 +54,7 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
5454
{
5555
"id": "cmc12vqgb0008xgn5nv5hl9y5",
5656
"timestamp": "2025-06-17T22:11:44.171Z",
57-
"action": "query.code_search",
57+
"action": "user.performed_code_search",
5858
"actorId": "cmc12tnje0000xgn58jj8655h",
5959
"actorType": "user",
6060
"targetId": "1",
@@ -68,7 +68,7 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
6868
{
6969
"id": "cmc12txwn0006xgn51ow1odid",
7070
"timestamp": "2025-06-17T22:10:20.519Z",
71-
"action": "query.code_search",
71+
"action": "user.performed_code_search",
7272
"actorId": "cmc12tnje0000xgn58jj8655h",
7373
"actorType": "user",
7474
"targetId": "1",
@@ -116,6 +116,9 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
116116
| `api_key.deleted` | `user` | `api_key` |
117117
| `user.creation_failed` | `user` | `user` |
118118
| `user.owner_created` | `user` | `org` |
119+
| `user.performed_code_search` | `user` | `org` |
120+
| `user.performed_find_references` | `user` | `org` |
121+
| `user.performed_goto_definition` | `user` | `org` |
119122
| `user.jit_provisioning_failed` | `user` | `org` |
120123
| `user.jit_provisioned` | `user` | `org` |
121124
| `user.join_request_creation_failed` | `user` | `org` |
@@ -131,9 +134,6 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
131134
| `user.signed_out` | `user` | `user` |
132135
| `org.ownership_transfer_failed` | `user` | `org` |
133136
| `org.ownership_transferred` | `user` | `org` |
134-
| `query.file_source` | `user \| api_key` | `file` |
135-
| `query.code_search` | `user \| api_key` | `org` |
136-
| `query.list_repositories` | `user \| api_key` | `org` |
137137

138138

139139
## Response schema

docs/docs/configuration/environment-variables.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ The following environment variables allow you to configure your Sourcebot deploy
3939
### Enterprise Environment Variables
4040
| Variable | Default | Description |
4141
| :------- | :------ | :---------- |
42-
| `SOURCEBOT_EE_AUDIT_LOGGING_ENABLED` | `false` | <p>Enables/disables audit logging</p> |
42+
| `SOURCEBOT_EE_AUDIT_LOGGING_ENABLED` | `true` | <p>Enables/disables audit logging</p> |
4343
| `AUTH_EE_ENABLE_JIT_PROVISIONING` | `false` | <p>Enables/disables just-in-time user provisioning for SSO providers.</p> |
4444
| `AUTH_EE_GITHUB_BASE_URL` | `https://github.com` | <p>The base URL for GitHub Enterprise SSO authentication.</p> |
4545
| `AUTH_EE_GITHUB_CLIENT_ID` | `-` | <p>The client ID for GitHub Enterprise SSO authentication.</p> |

docs/docs/features/analytics.mdx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
title: Analytics
3+
sidebarTitle: Analytics
4+
---
5+
6+
import LicenseKeyRequired from '/snippets/license-key-required.mdx'
7+
import { Callout } from 'nextra/components'
8+
9+
<LicenseKeyRequired />
10+
11+
12+
## Overview
13+
14+
Analytics provides comprehensive insights into your organization's usage of Sourcebot, helping you understand adoption patterns and
15+
quantify the value of time saved.
16+
17+
This dashboard is backed by [audit log](/docs/configuration/audit-logs) events. Please ensure you have audit logging enabled in order to see these insights.
18+
19+
<video
20+
autoPlay
21+
muted
22+
loop
23+
playsInline
24+
className="w-full aspect-video"
25+
src="/images/analytics_demo.mp4"
26+
></video>
27+
28+
## Data Metrics
29+
30+
### Active Users
31+
Tracks the number of unique users who performed any Sourcebot operation within each time period. This metric helps you understand team adoption
32+
and engagement with Sourcebot.
33+
34+
![DAU Chart](/images/dau_chart.png)
35+
36+
### Code Searches
37+
Counts the number of code search operations performed by your team.
38+
39+
![Code Search Chart](/images/code_search_chart.png)
40+
41+
### Code Navigation
42+
Tracks "Go to Definition" and "Find All References" navigation actions. Navigation actions help developers quickly move
43+
between code locations and understand code relationships.
44+
45+
![Code Nav Chart](/images/code_nav_chart.png)
46+
47+
## Cost Savings Calculator
48+
49+
The analytics dashboard includes a built-in cost savings calculator that helps you quantify the ROI of using Sourcebot.
50+
51+
![Cost Savings Chart](/images/cost_savings_chart.png)

docs/images/analytics_demo.mp4

4.18 MB
Binary file not shown.

docs/images/code_nav_chart.png

597 KB
Loading

docs/images/code_search_chart.png

612 KB
Loading

docs/images/cost_savings_chart.png

739 KB
Loading

docs/images/dau_chart.png

561 KB
Loading
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- CreateIndex
2+
CREATE INDEX "idx_audit_core_actions_full" ON "Audit"("orgId", "timestamp", "action", "actorId");
3+
4+
-- CreateIndex
5+
CREATE INDEX "idx_audit_actor_time_full" ON "Audit"("actorId", "timestamp");

packages/db/prisma/schema.prisma

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ model Audit {
245245
orgId Int
246246
247247
@@index([actorId, actorType, targetId, targetType, orgId])
248+
249+
// Fast path for analytics queries – orgId is first because we assume most deployments are single tenant
250+
@@index([orgId, timestamp, action, actorId], map: "idx_audit_core_actions_full")
251+
252+
// Fast path for analytics queries for a specific user
253+
@@index([actorId, timestamp], map: "idx_audit_actor_time_full")
248254
}
249255

250256
// @see : https://authjs.dev/concepts/database-models#user

packages/db/tools/scriptRunner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PrismaClient } from "@sourcebot/db";
22
import { ArgumentParser } from "argparse";
33
import { migrateDuplicateConnections } from "./scripts/migrate-duplicate-connections";
4+
import { injectAuditData } from "./scripts/inject-audit-data";
45
import { confirmAction } from "./utils";
56
import { createLogger } from "@sourcebot/logger";
67

@@ -10,6 +11,7 @@ export interface Script {
1011

1112
export const scripts: Record<string, Script> = {
1213
"migrate-duplicate-connections": migrateDuplicateConnections,
14+
"inject-audit-data": injectAuditData,
1315
}
1416

1517
const parser = new ArgumentParser();
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Script } from "../scriptRunner";
2+
import { PrismaClient } from "../../dist";
3+
import { confirmAction } from "../utils";
4+
import { createLogger } from "@sourcebot/logger";
5+
6+
const logger = createLogger('inject-audit-data');
7+
8+
// Generate realistic audit data for analytics testing
9+
// Simulates 50 engineers with varying activity patterns
10+
export const injectAuditData: Script = {
11+
run: async (prisma: PrismaClient) => {
12+
const orgId = 1;
13+
14+
// Check if org exists
15+
const org = await prisma.org.findUnique({
16+
where: { id: orgId }
17+
});
18+
19+
if (!org) {
20+
logger.error(`Organization with id ${orgId} not found. Please create it first.`);
21+
return;
22+
}
23+
24+
logger.info(`Injecting audit data for organization: ${org.name} (${org.domain})`);
25+
26+
// Generate 50 fake user IDs
27+
const userIds = Array.from({ length: 50 }, (_, i) => `user_${String(i + 1).padStart(3, '0')}`);
28+
29+
// Actions we're tracking
30+
const actions = [
31+
'user.performed_code_search',
32+
'user.performed_find_references',
33+
'user.performed_goto_definition'
34+
];
35+
36+
// Generate data for the last 90 days
37+
const endDate = new Date();
38+
const startDate = new Date();
39+
startDate.setDate(startDate.getDate() - 90);
40+
41+
logger.info(`Generating data from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`);
42+
43+
confirmAction();
44+
45+
// Generate data for each day
46+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
47+
const currentDate = new Date(d);
48+
const dayOfWeek = currentDate.getDay(); // 0 = Sunday, 6 = Saturday
49+
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
50+
51+
// For each user, generate activity for this day
52+
for (const userId of userIds) {
53+
// Determine if user is active today (higher chance on weekdays)
54+
const isActiveToday = isWeekend
55+
? Math.random() < 0.15 // 15% chance on weekends
56+
: Math.random() < 0.85; // 85% chance on weekdays
57+
58+
if (!isActiveToday) continue;
59+
60+
// Generate code searches (2-5 per day)
61+
const codeSearches = isWeekend
62+
? Math.floor(Math.random() * 2) + 1 // 1-2 on weekends
63+
: Math.floor(Math.random() * 4) + 2; // 2-5 on weekdays
64+
65+
// Generate navigation actions (5-10 per day)
66+
const navigationActions = isWeekend
67+
? Math.floor(Math.random() * 3) + 1 // 1-3 on weekends
68+
: Math.floor(Math.random() * 6) + 5; // 5-10 on weekdays
69+
70+
// Create code search records
71+
for (let i = 0; i < codeSearches; i++) {
72+
const timestamp = new Date(currentDate);
73+
// Spread throughout the day (9 AM to 6 PM on weekdays, more random on weekends)
74+
if (isWeekend) {
75+
timestamp.setHours(9 + Math.floor(Math.random() * 12));
76+
timestamp.setMinutes(Math.floor(Math.random() * 60));
77+
} else {
78+
timestamp.setHours(9 + Math.floor(Math.random() * 9));
79+
timestamp.setMinutes(Math.floor(Math.random() * 60));
80+
}
81+
timestamp.setSeconds(Math.floor(Math.random() * 60));
82+
83+
await prisma.audit.create({
84+
data: {
85+
timestamp,
86+
action: 'user.performed_code_search',
87+
actorId: userId,
88+
actorType: 'user',
89+
targetId: `search_${Math.floor(Math.random() * 1000)}`,
90+
targetType: 'search',
91+
sourcebotVersion: '1.0.0',
92+
orgId
93+
}
94+
});
95+
}
96+
97+
// Create navigation action records
98+
for (let i = 0; i < navigationActions; i++) {
99+
const timestamp = new Date(currentDate);
100+
if (isWeekend) {
101+
timestamp.setHours(9 + Math.floor(Math.random() * 12));
102+
timestamp.setMinutes(Math.floor(Math.random() * 60));
103+
} else {
104+
timestamp.setHours(9 + Math.floor(Math.random() * 9));
105+
timestamp.setMinutes(Math.floor(Math.random() * 60));
106+
}
107+
timestamp.setSeconds(Math.floor(Math.random() * 60));
108+
109+
// Randomly choose between find references and goto definition
110+
const action = Math.random() < 0.6 ? 'user.performed_find_references' : 'user.performed_goto_definition';
111+
112+
await prisma.audit.create({
113+
data: {
114+
timestamp,
115+
action,
116+
actorId: userId,
117+
actorType: 'user',
118+
targetId: `symbol_${Math.floor(Math.random() * 1000)}`,
119+
targetType: 'symbol',
120+
sourcebotVersion: '1.0.0',
121+
orgId
122+
}
123+
});
124+
}
125+
}
126+
}
127+
128+
logger.info(`\nAudit data injection complete!`);
129+
logger.info(`Users: ${userIds.length}`);
130+
logger.info(`Date range: ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`);
131+
132+
// Show some statistics
133+
const stats = await prisma.audit.groupBy({
134+
by: ['action'],
135+
where: { orgId },
136+
_count: { action: true }
137+
});
138+
139+
logger.info('\nAction breakdown:');
140+
stats.forEach(stat => {
141+
logger.info(` ${stat.action}: ${stat._count.action}`);
142+
});
143+
},
144+
};

packages/shared/src/entitlements.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,16 @@ const entitlements = [
3737
"multi-tenancy",
3838
"sso",
3939
"code-nav",
40-
"audit"
40+
"audit",
41+
"analytics"
4142
] as const;
4243
export type Entitlement = (typeof entitlements)[number];
4344

4445
const entitlementsByPlan: Record<Plan, Entitlement[]> = {
4546
oss: [],
4647
"cloud:team": ["billing", "multi-tenancy", "sso", "code-nav"],
47-
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit"],
48-
"self-hosted:enterprise-unlimited": ["search-contexts", "public-access", "sso", "code-nav", "audit"],
48+
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit", "analytics"],
49+
"self-hosted:enterprise-unlimited": ["search-contexts", "public-access", "sso", "code-nav", "audit", "analytics"],
4950
// Special entitlement for https://demo.sourcebot.dev
5051
"cloud:demo": ["public-access", "code-nav", "search-contexts"],
5152
} as const;

packages/web/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"codemirror-lang-sparql": "^2.0.0",
113113
"codemirror-lang-spreadsheet": "^1.3.0",
114114
"codemirror-lang-zig": "^0.1.0",
115+
"date-fns": "^4.1.0",
115116
"embla-carousel-auto-scroll": "^8.3.0",
116117
"embla-carousel-react": "^8.3.0",
117118
"escape-string-regexp": "^5.0.0",
@@ -120,7 +121,7 @@
120121
"graphql": "^16.9.0",
121122
"http-status-codes": "^2.3.0",
122123
"input-otp": "^1.4.2",
123-
"lucide-react": "^0.435.0",
124+
"lucide-react": "^0.517.0",
124125
"micromatch": "^4.0.8",
125126
"next": "14.2.26",
126127
"next-auth": "^5.0.0-beta.25",
@@ -139,6 +140,7 @@
139140
"react-hotkeys-hook": "^4.5.1",
140141
"react-icons": "^5.3.0",
141142
"react-resizable-panels": "^2.1.1",
143+
"recharts": "^2.15.3",
142144
"scroll-into-view-if-needed": "^3.1.0",
143145
"server-only": "^0.0.1",
144146
"sharp": "^0.33.5",

0 commit comments

Comments
 (0)