diff --git a/src/calculate-pr-avg-tat.ts b/src/calculate-pr-avg-tat.ts new file mode 100644 index 0000000..67fe135 --- /dev/null +++ b/src/calculate-pr-avg-tat.ts @@ -0,0 +1,58 @@ +import { + contributorAggregateQueries, + activityQueries, + Database, + Logger, +} from "@ohcnetwork/leaderboard-api"; +import { ActivityDefinition } from "./activity"; +import { formatDuration, intervalToDuration } from "date-fns"; + +export async function calculatePrAvgTat(db: Database, logger: Logger) { + const allActivities = await activityQueries.getAll(db); + const activities = allActivities.filter(activity => activity.activity_definition === ActivityDefinition.PR_MERGED); + + const contributorPRs = new Map(); + for (const activity of activities) { + if (!activity.contributor) continue; + + const prs = contributorPRs.get(activity.contributor) || []; + prs.push(activity); + contributorPRs.set(activity.contributor, prs); + } + + for (const [username, prs] of contributorPRs) { + let totalTat = 0; + let prCount = 0; + + for (const pr of prs) { + const meta = pr.meta as any; + if (meta?.created_at && meta?.merged_at) { + totalTat += new Date(meta.merged_at).getTime() - new Date(meta.created_at).getTime(); + prCount++; + } + } + + if (prCount === 0) continue; + + const avgTat = totalTat / prCount; + const avgTatHours = avgTat / (1000 * 60 * 60); + + const duration = intervalToDuration({ start: 0, end: avgTat }); + const formattedDuration = formatDuration(duration, { format: ['months', 'weeks', 'days', 'hours'] }); + + await contributorAggregateQueries.upsert(db, { + aggregate: "avg_tat", + contributor: username, + value: { + type: "string", + value: formattedDuration, + }, + meta: { + calculated_at: new Date().toISOString(), + pr_count: prCount, + }, + }); + } + + logger.info(`Calculated PR avg TAT for ${contributorPRs.size} contributors`); +} \ No newline at end of file diff --git a/src/get-activities.ts b/src/get-activities.ts index e66426f..9a20052 100644 --- a/src/get-activities.ts +++ b/src/get-activities.ts @@ -701,9 +701,10 @@ function activitiesFromPullRequests( link: pullRequest.url, points: null, meta: { - pr_avg_tat: - new Date(pullRequest.merged_at).getTime() - - new Date(pullRequest.created_at).getTime(), + created_at: pullRequest.created_at, + merged_at: pullRequest.merged_at, + pr_number: pullRequest.number, + repo: repo, }, }); } diff --git a/src/index.ts b/src/index.ts index 7cc04d2..d22c1f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,12 @@ -/** +/** * Leaderboard github plugin */ import { getActivities } from "@/src/get-activities"; import type { Plugin, PluginContext } from "@ohcnetwork/leaderboard-api"; import { ActivityDefinition } from "./activity"; +import { calculatePrAvgTat } from "./calculate-pr-avg-tat"; +import { contributorAggregateDefinitionQueries } from "@ohcnetwork/leaderboard-api"; const plugin: Plugin = { name: "@leaderboard/plugin-leaderboard-github-plugin", @@ -96,6 +98,13 @@ const plugin: Plugin = { ); } + await contributorAggregateDefinitionQueries.upsert(ctx.db, { + slug: "avg_tat", + name: "PR Merge Turn Around Time", + description: "Average time taken from opening to merging a PR", + hidden: false, + }); + ctx.logger.info("Setup complete"); }, @@ -103,8 +112,11 @@ const plugin: Plugin = { ctx.logger.info("Starting leaderboard-github-plugin data scraping..."); await getActivities(ctx); + + await calculatePrAvgTat(ctx.db, ctx.logger); + ctx.logger.info("Scraping complete"); }, }; -export default plugin; +export default plugin; \ No newline at end of file