import { createServerFn } from "@tanstack/react-start"; import { z } from "zod"; import { viewerMiddleware } from "./middleware"; import { repoDb } from "~/server/scoped-db"; import { accountsForEmails } from "~/server/commit-authors"; import { limitBrowse } from "~/server/limits"; import { requireRepoAccess } from "~/server/repos"; import { repoInsights } from "~/server/insights/repo-insights-do"; import { repoRef } from "~/lib/validation"; const DAY_MS = 24 * 60 * 60 * 1000; const PERIODS = { week: 7, month: 30 } as const; type PulseCounts = { issuesOpened: number; issuesClosed: number; pullsOpened: number; pullsMerged: number; activeAuthors: number; }; async function pulse(repoId: string, since: number): Promise { const row = await repoDb(repoId).first( `SELECT (SELECT COUNT(*) FROM issues WHERE repo_id = ?1 AND is_pull = 0 AND created_at >= ?2) AS issuesOpened, (SELECT COUNT(*) FROM issues WHERE repo_id = ?1 AND is_pull = 0 AND closed_at >= ?2) AS issuesClosed, (SELECT COUNT(*) FROM issues WHERE repo_id = ?1 AND is_pull = 1 AND created_at >= ?2) AS pullsOpened, (SELECT COUNT(*) FROM pull_requests WHERE repo_id = ?1 AND merged_at >= ?2) AS pullsMerged, (SELECT COUNT(DISTINCT author_id) FROM issues WHERE repo_id = ?1 AND created_at >= ?2) AS activeAuthors`, repoId, since, ); return row ?? { issuesOpened: 0, issuesClosed: 0, pullsOpened: 0, pullsMerged: 0, activeAuthors: 0 }; } type Contributor = { name: string; account: string | null; commits: number; first: number; last: number; weekly: number[]; }; /** One person committing from several verified addresses is one contributor. */ function mergeByAccount( rows: Array<{ email: string; name: string; commits: number; first: number; last: number; weekly: number[] }>, accounts: Map, ): Contributor[] { const merged = new Map(); for (const row of rows) { const account = accounts.get(row.email) ?? null; const key = account ? `account:${account}` : `email:${row.email}`; const existing = merged.get(key); if (!existing) { merged.set(key, { name: row.name, account, commits: row.commits, first: row.first, last: row.last, weekly: row.weekly, }); continue; } existing.commits += row.commits; existing.first = Math.min(existing.first, row.first); existing.last = Math.max(existing.last, row.last); existing.weekly = existing.weekly.length === 0 ? row.weekly : existing.weekly.map((count, week) => count + (row.weekly[week] ?? 0)); } return [...merged.values()].sort((left, right) => right.commits - left.commits); } export const getRepoInsights = createServerFn({ method: "GET" }) .middleware([viewerMiddleware]) .validator(repoRef.extend({ period: z.enum(["week", "month"]).default("week") })) .handler(async ({ data, context }) => { await limitBrowse(context.viewer); const { repo } = await requireRepoAccess(data.owner, data.repo, context.viewer, "read"); const since = Date.now() - PERIODS[data.period] * DAY_MS; const [summary, counts] = await Promise.all([repoInsights(repo.id).summary(repo.id), pulse(repo.id, since)]); const accounts = await accountsForEmails(summary.contributors.map((row) => row.email)); return { period: data.period, pulse: counts, status: summary.status, totalCommits: summary.totalCommits, truncated: summary.truncated, weekly: summary.weekly, weekStart: summary.weekStart, contributors: mergeByAccount(summary.contributors, accounts), }; });