From c611ab1fc2468b008608f76a27ebcb7948feaaa7 Mon Sep 17 00:00:00 2001 From: Stephy Sam Date: Mon, 19 Jan 2026 13:14:30 +0530 Subject: [PATCH 1/2] avg_tat added --- src/calculate-pr-avg-tat.ts | 58 +++++++++++++++++++++++++++++++++++++ src/get-activities.ts | 8 ++--- src/index.ts | 20 +++++++++---- 3 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 src/calculate-pr-avg-tat.ts 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..ab149e2 100644 --- a/src/get-activities.ts +++ b/src/get-activities.ts @@ -701,13 +701,13 @@ 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, }, }); } - // PR review events for (const review of pullRequest.reviews) { if (!review.author) { diff --git a/src/index.ts b/src/index.ts index 7cc04d2..d076154 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,11 @@ -/** - * 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", + name: "@Leaderboard Tasks/plugin-leaderboard-github-plugin", version: "0.1.0", async setup(ctx: PluginContext) { @@ -96,6 +94,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 +108,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 From 39e284baa317d9ccbe913528fc809c220b8024b5 Mon Sep 17 00:00:00 2001 From: Stephy Sam Date: Mon, 19 Jan 2026 13:23:23 +0530 Subject: [PATCH 2/2] fix --- src/get-activities.ts | 1 + src/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/get-activities.ts b/src/get-activities.ts index ab149e2..9a20052 100644 --- a/src/get-activities.ts +++ b/src/get-activities.ts @@ -708,6 +708,7 @@ function activitiesFromPullRequests( }, }); } + // PR review events for (const review of pullRequest.reviews) { if (!review.author) { diff --git a/src/index.ts b/src/index.ts index d076154..d22c1f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,7 @@ +/** + * Leaderboard github plugin + */ + import { getActivities } from "@/src/get-activities"; import type { Plugin, PluginContext } from "@ohcnetwork/leaderboard-api"; import { ActivityDefinition } from "./activity"; @@ -5,7 +9,7 @@ import { calculatePrAvgTat } from "./calculate-pr-avg-tat"; import { contributorAggregateDefinitionQueries } from "@ohcnetwork/leaderboard-api"; const plugin: Plugin = { - name: "@Leaderboard Tasks/plugin-leaderboard-github-plugin", + name: "@leaderboard/plugin-leaderboard-github-plugin", version: "0.1.0", async setup(ctx: PluginContext) {