sourcebot/packages/web/src/ee/features/analytics/actions.ts
Michael Sukkarieh aac1d4529e
Add anonymous access option to core (#385)
* migrate anonymous access logic out of ee

* add anonymous access toggle

* handle anon toggle properly based on perms

* add forceEnableAnonymousAccess setting

* add docs for access settings

* change forceEnableAnonymousAccess to be an env var

* add FORCE_ENABLE_ANONYMOUS_ACCESS to list in docs

* add back the enablePublicAccess setting as deprecated

* add changelog entry

* fix build errors

* add news entry for anonymous access

* feedback
2025-07-19 14:04:41 -07:00

103 lines
No EOL
3.5 KiB
TypeScript

'use server';
import { sew, withAuth, withOrgMembership } from "@/actions";
import { OrgRole } from "@sourcebot/db";
import { prisma } from "@/prisma";
import { ServiceError } from "@/lib/serviceError";
import { AnalyticsResponse } from "./types";
import { hasEntitlement } from "@sourcebot/shared";
import { ErrorCode } from "@/lib/errorCodes";
import { StatusCodes } from "http-status-codes";
export const getAnalytics = async (domain: string, apiKey: string | undefined = undefined): Promise<AnalyticsResponse | ServiceError> => sew(() =>
withAuth((userId, _apiKeyHash) =>
withOrgMembership(userId, domain, async ({ org }) => {
if (!hasEntitlement("analytics")) {
return {
statusCode: StatusCodes.FORBIDDEN,
errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
message: "Analytics is not available in your current plan",
} satisfies ServiceError;
}
const rows = await prisma.$queryRaw<AnalyticsResponse>`
WITH core AS (
SELECT
date_trunc('day', "timestamp") AS day,
date_trunc('week', "timestamp") AS week,
date_trunc('month', "timestamp") AS month,
action,
"actorId"
FROM "Audit"
WHERE "orgId" = ${org.id}
AND action IN (
'user.performed_code_search',
'user.performed_find_references',
'user.performed_goto_definition'
)
),
periods AS (
SELECT unnest(array['day', 'week', 'month']) AS period
),
buckets AS (
SELECT
generate_series(
date_trunc('day', (SELECT MIN("timestamp") FROM "Audit" WHERE "orgId" = ${org.id})),
date_trunc('day', CURRENT_DATE),
interval '1 day'
) AS bucket,
'day' AS period
UNION ALL
SELECT
generate_series(
date_trunc('week', (SELECT MIN("timestamp") FROM "Audit" WHERE "orgId" = ${org.id})),
date_trunc('week', CURRENT_DATE),
interval '1 week'
),
'week'
UNION ALL
SELECT
generate_series(
date_trunc('month', (SELECT MIN("timestamp") FROM "Audit" WHERE "orgId" = ${org.id})),
date_trunc('month', CURRENT_DATE),
interval '1 month'
),
'month'
),
aggregated AS (
SELECT
b.period,
CASE b.period
WHEN 'day' THEN c.day
WHEN 'week' THEN c.week
ELSE c.month
END AS bucket,
COUNT(*) FILTER (WHERE c.action = 'user.performed_code_search') AS code_searches,
COUNT(*) FILTER (WHERE c.action IN ('user.performed_find_references', 'user.performed_goto_definition')) AS navigations,
COUNT(DISTINCT c."actorId") AS active_users
FROM core c
JOIN LATERAL (
SELECT unnest(array['day', 'week', 'month']) AS period
) b ON true
GROUP BY b.period, bucket
)
SELECT
b.period,
b.bucket,
COALESCE(a.code_searches, 0)::int AS code_searches,
COALESCE(a.navigations, 0)::int AS navigations,
COALESCE(a.active_users, 0)::int AS active_users
FROM buckets b
LEFT JOIN aggregated a
ON a.period = b.period AND a.bucket = b.bucket
ORDER BY b.period, b.bucket;
`;
return rows;
}, /* minRequiredRole = */ OrgRole.MEMBER), /* allowAnonymousAccess = */ true, apiKey ? { apiKey, domain } : undefined)
);