Skip to content

Commit b3fe315

Browse files
[v5] chore(web): add dau/wau/mau tracking (#1228)
1 parent e47959a commit b3fe315

5 files changed

Lines changed: 68 additions & 3 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "User" ADD COLUMN "lastActiveAt" TIMESTAMP(3);

packages/db/prisma/schema.prisma

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,9 @@ model User {
411411
createdAt DateTime @default(now())
412412
updatedAt DateTime @updatedAt
413413
414+
/// Last time the user performed an authenticated action.
415+
lastActiveAt DateTime?
416+
414417
}
415418

416419
enum AccountPermissionSyncJobStatus {

packages/web/src/ee/features/lighthouse/servicePing.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,19 @@ export const syncWithLighthouse = async (orgId: number) => {
1818
where: { orgId },
1919
});
2020

21-
const [userCount, repoCount] = await Promise.all([
21+
const now = Date.now();
22+
const DAY_MS = 24 * 60 * 60 * 1000;
23+
const dauCutoff = new Date(now - 1 * DAY_MS);
24+
const wauCutoff = new Date(now - 7 * DAY_MS);
25+
const mauCutoff = new Date(now - 30 * DAY_MS);
26+
27+
const [
28+
userCount,
29+
repoCount,
30+
dauCount,
31+
wauCount,
32+
mauCount,
33+
] = await Promise.all([
2234
__unsafePrisma.userToOrg.count({
2335
where: {
2436
orgId,
@@ -29,6 +41,24 @@ export const syncWithLighthouse = async (orgId: number) => {
2941
orgId,
3042
},
3143
}),
44+
__unsafePrisma.user.count({
45+
where: {
46+
orgs: { some: { orgId } },
47+
lastActiveAt: { gte: dauCutoff },
48+
},
49+
}),
50+
__unsafePrisma.user.count({
51+
where: {
52+
orgs: { some: { orgId } },
53+
lastActiveAt: { gte: wauCutoff },
54+
},
55+
}),
56+
__unsafePrisma.user.count({
57+
where: {
58+
orgs: { some: { orgId } },
59+
lastActiveAt: { gte: mauCutoff },
60+
},
61+
}),
3262
]);
3363

3464
const activationCode = license?.activationCode
@@ -40,6 +70,9 @@ export const syncWithLighthouse = async (orgId: number) => {
4070
version: SOURCEBOT_VERSION,
4171
userCount,
4272
repoCount,
73+
dauCount,
74+
wauCount,
75+
mauCount,
4376
deploymentType: inferDeploymentType(),
4477
isTelemetryEnabled: env.SOURCEBOT_TELEMETRY_DISABLED === 'false',
4578
...(activationCode && { activationCode }),

packages/web/src/ee/features/lighthouse/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { z } from "zod";
33
export const servicePingRequestSchema = z.object({
44
installId: z.string(),
55
version: z.string(),
6-
userCount: z.number(),
7-
repoCount: z.number(),
6+
userCount: z.number().int().nonnegative(),
7+
repoCount: z.number().int().nonnegative(),
8+
dauCount: z.number().int().nonnegative(),
9+
wauCount: z.number().int().nonnegative(),
10+
mauCount: z.number().int().nonnegative(),
811
deploymentType: z.string(),
912
isTelemetryEnabled: z.boolean(),
1013
activationCode: z.string().optional(),

packages/web/src/middleware/withAuth.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { ErrorCode } from "../lib/errorCodes";
1010
import { getOrgMetadata, isServiceError } from "../lib/utils";
1111
import { hasEntitlement, isAnonymousAccessAvailable } from "@/lib/entitlements";
1212

13+
const LAST_ACTIVE_AT_THRESHOLD_MS = 5 * 60 * 1000;
14+
1315
type RequiredAuthContext = {
1416
user: UserWithAccounts;
1517
role: OrgRole;
@@ -107,12 +109,34 @@ export const getAuthContext = async (): Promise<OptionalAuthContext | ServiceErr
107109

108110
const prisma = __unsafePrisma.$extends(await userScopedPrismaClientExtension(user)) as PrismaClient;
109111

112+
if (user) {
113+
updateUserLastActiveAt(user);
114+
}
115+
110116
if (user && role) {
111117
return { user, org, role, prisma };
112118
}
113119
return { user, org, prisma };
114120
};
115121

122+
const updateUserLastActiveAt = (user: UserWithAccounts) => {
123+
const now = Date.now();
124+
if (
125+
user.lastActiveAt &&
126+
(now - user.lastActiveAt.getTime()) < LAST_ACTIVE_AT_THRESHOLD_MS
127+
) {
128+
return;
129+
}
130+
131+
// Fired without a await to avoid blocking.
132+
void __unsafePrisma.user
133+
.update({
134+
where: { id: user.id },
135+
data: { lastActiveAt: new Date(now) },
136+
})
137+
.catch(() => { /* updaing the lastActiveAt is best effort. */ });
138+
};
139+
116140
type AuthSource = 'session' | 'oauth' | 'api_key';
117141

118142
export const getAuthenticatedUser = async (): Promise<{ user: UserWithAccounts, source: AuthSource } | undefined> => {

0 commit comments

Comments
 (0)