mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Update projects-metrics to use more Clickhouse
This commit is contained in:
parent
e42ec65c88
commit
a6762b00fa
@ -1,4 +1,4 @@
|
||||
import { globalPrismaClient } from "@/prisma-client";
|
||||
import { getClickhouseAdminClientForMetrics } from "@/lib/clickhouse";
|
||||
import { listManagedProjectIds } from "@/lib/projects";
|
||||
import { DEFAULT_BRANCH_ID } from "@/lib/tenancies";
|
||||
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
|
||||
@ -10,6 +10,11 @@ import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"
|
||||
const SIGNUPS_WINDOW_DAYS = 30;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
function formatClickhouseDateTimeParam(date: Date): string {
|
||||
// ClickHouse DateTime params are passed as "YYYY-MM-DDTHH:MM:SS" (no timezone); treat them as UTC.
|
||||
return date.toISOString().slice(0, 19);
|
||||
}
|
||||
|
||||
type ProjectMetrics = {
|
||||
total_users: number,
|
||||
daily_signups: { date: string, activity: number }[],
|
||||
@ -111,41 +116,56 @@ export const GET = createSmartRouteHandler({
|
||||
return buildResponse();
|
||||
}
|
||||
|
||||
let totalRows: Array<{ projectId: string, totalUsers: bigint | number }>;
|
||||
let signupRows: Array<{ projectId: string, day: Date | string, signups: bigint | number }>;
|
||||
let totalRows: Array<{ projectId: string, totalUsers: string | number }>;
|
||||
let signupRows: Array<{ projectId: string, day: string, signups: string | number }>;
|
||||
try {
|
||||
[totalRows, signupRows] = await Promise.all([
|
||||
globalPrismaClient.$queryRawUnsafe<Array<{ projectId: string, totalUsers: bigint | number }>>(
|
||||
`
|
||||
SELECT "mirroredProjectId" AS "projectId", COUNT(*)::bigint AS "totalUsers"
|
||||
FROM "ProjectUser"
|
||||
WHERE "mirroredProjectId" = ANY($1::text[])
|
||||
AND "mirroredBranchId" = $2
|
||||
AND "isAnonymous" = false
|
||||
GROUP BY "mirroredProjectId"
|
||||
`,
|
||||
projectIds,
|
||||
DEFAULT_BRANCH_ID,
|
||||
),
|
||||
globalPrismaClient.$queryRawUnsafe<Array<{ projectId: string, day: Date | string, signups: bigint | number }>>(
|
||||
`
|
||||
const clickhouseClient = getClickhouseAdminClientForMetrics();
|
||||
const [totalResult, signupResult] = await Promise.all([
|
||||
clickhouseClient.query({
|
||||
query: `
|
||||
SELECT
|
||||
"mirroredProjectId" AS "projectId",
|
||||
date_trunc('day', COALESCE("signedUpAt", "createdAt") AT TIME ZONE 'UTC')::date AS "day",
|
||||
COUNT(*)::bigint AS "signups"
|
||||
FROM "ProjectUser"
|
||||
WHERE "mirroredProjectId" = ANY($1::text[])
|
||||
AND "mirroredBranchId" = $2
|
||||
AND "isAnonymous" = false
|
||||
AND COALESCE("signedUpAt", "createdAt") >= $3
|
||||
AND COALESCE("signedUpAt", "createdAt") < $4
|
||||
GROUP BY "mirroredProjectId", "day"
|
||||
project_id AS projectId,
|
||||
count() AS totalUsers
|
||||
FROM analytics_internal.users FINAL
|
||||
WHERE project_id IN {projectIds:Array(String)}
|
||||
AND branch_id = {branchId:String}
|
||||
AND sync_is_deleted = 0
|
||||
AND is_anonymous = 0
|
||||
GROUP BY project_id
|
||||
`,
|
||||
projectIds,
|
||||
DEFAULT_BRANCH_ID,
|
||||
since,
|
||||
untilExclusive,
|
||||
),
|
||||
query_params: {
|
||||
projectIds,
|
||||
branchId: DEFAULT_BRANCH_ID,
|
||||
},
|
||||
format: "JSONEachRow",
|
||||
}),
|
||||
clickhouseClient.query({
|
||||
query: `
|
||||
SELECT
|
||||
project_id AS projectId,
|
||||
toString(toDate(signed_up_at, 'UTC')) AS day,
|
||||
count() AS signups
|
||||
FROM analytics_internal.users FINAL
|
||||
WHERE project_id IN {projectIds:Array(String)}
|
||||
AND branch_id = {branchId:String}
|
||||
AND sync_is_deleted = 0
|
||||
AND is_anonymous = 0
|
||||
AND signed_up_at >= {since:DateTime}
|
||||
AND signed_up_at < {untilExclusive:DateTime}
|
||||
GROUP BY project_id, day
|
||||
`,
|
||||
query_params: {
|
||||
projectIds,
|
||||
branchId: DEFAULT_BRANCH_ID,
|
||||
since: formatClickhouseDateTimeParam(since),
|
||||
untilExclusive: formatClickhouseDateTimeParam(untilExclusive),
|
||||
},
|
||||
format: "JSONEachRow",
|
||||
}),
|
||||
]);
|
||||
[totalRows, signupRows] = await Promise.all([
|
||||
totalResult.json(),
|
||||
signupResult.json(),
|
||||
]);
|
||||
} catch (cause) {
|
||||
throw new StackAssertionError("Failed to load project metrics.", {
|
||||
@ -162,7 +182,7 @@ export const GET = createSmartRouteHandler({
|
||||
totalRows.map((r) => ({ projectId: r.projectId, totalUsers: Number(r.totalUsers) })),
|
||||
signupRows.map((r) => ({
|
||||
projectId: r.projectId,
|
||||
day: r.day instanceof Date ? r.day.toISOString() : String(r.day),
|
||||
day: r.day,
|
||||
signups: Number(r.signups),
|
||||
})),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user