From c4f4d1fed028c6549290f81c89ed9eb8b28a80a6 Mon Sep 17 00:00:00 2001 From: RajuGangitla Date: Tue, 17 Sep 2024 05:34:52 +0000 Subject: [PATCH] refactor done --- lib/github/hooks/issue.ts | 158 ++++++++++++++++++-------------------- lib/github/utils.ts | 104 ++++++++++++++++++++++++- types/githubUser.ts | 28 +++++++ 3 files changed, 205 insertions(+), 85 deletions(-) diff --git a/lib/github/hooks/issue.ts b/lib/github/hooks/issue.ts index 5bdddab..1861cbb 100644 --- a/lib/github/hooks/issue.ts +++ b/lib/github/hooks/issue.ts @@ -15,14 +15,13 @@ import { REJECT_IDENTIFIER, UNASSIGN_IDENTIFIER, } from "@/lib/constants"; -import { assignUserPoints } from "@/lib/points/service"; import { getRepositoryByGithubId } from "@/lib/repository/service"; -import { createUser, getUser, getUserByGithubId } from "@/lib/user/service"; +import { getUser } from "@/lib/user/service"; import { triggerDotDevClient } from "@/trigger"; import { Webhooks } from "@octokit/webhooks"; import { isMemberOfRepository } from "../services/user"; -import { extractIssueNumbers, extractIssueNumbersFromPrBody, getOctokitInstance } from "../utils"; +import { checkOssGgLabel, extractIssueNumbers, extractIssueNumbersFromPrBody, extractPointsFromLabels, filterValidLabels, getOctokitInstance, postComment, processAndComment, processUserPoints } from "../utils"; export const onIssueOpened = async (webhooks: Webhooks) => { webhooks.on(EVENT_TRIGGERS.ISSUE_OPENED, async (context) => { @@ -334,7 +333,6 @@ export const onAwardPoints = async (webhooks: Webhooks) => { const isPR = !!context.payload.issue.pull_request; const issueNumber = isPR ? context.payload.issue.number : undefined; const owner = context.payload.repository.owner.login; - let comment: string = ""; if (match) { @@ -364,33 +362,25 @@ export const onAwardPoints = async (webhooks: Webhooks) => { if (!ossGgRepo) { comment = "If you are the repo owner, please register at oss.gg to be able to award points"; } else { - const prAuthorGithubId = context.payload.issue.user.id; - const prAuthorUsername = context.payload.issue.user.login; - const { data: prAuthorProfile } = await octokit.users.getByUsername({ - username: prAuthorUsername, - }); - let user = await getUserByGithubId(prAuthorGithubId); - if (!user) { - user = await createUser({ - githubId: prAuthorGithubId, - login: prAuthorUsername, - email: prAuthorProfile.email, - name: prAuthorProfile.name, - avatarUrl: context.payload.issue.user.avatar_url, - }); - comment = "Please register at oss.gg to be able to claim your rewards."; - } - await assignUserPoints( - user?.id, + const prAuthorUsername = context.payload.issue.user.login + + //process user points + let user = await processUserPoints({ + installationId: context.payload.installation?.id!, + prAuthorGithubId: context.payload.issue.user.id, + prAuthorUsername: prAuthorUsername, + avatarUrl: context.payload.issue.user.avatar_url, points, - "Awarded points", - context.payload.comment.html_url, - ossGgRepo?.id - ); + url: context.payload.comment.html_url, + repoId: ossGgRepo?.id, + comment + }) + comment = `Awarding ${user.login}: ${points} points! Check out your new contribution on [oss.gg/${user.login}](https://oss.gg/${user.login})` + " " + comment; + await triggerDotDevClient.sendEvent({ name: DISCORD_POINTS_MESSAGE_TRIGGER_ID, payload: { @@ -401,12 +391,14 @@ export const onAwardPoints = async (webhooks: Webhooks) => { } } - await octokit.issues.createComment({ + //post comment + postComment({ + installationId: context.payload.installation?.id!, body: comment, - issue_number: issueNumber, + issueNumber: issueNumber, repo, owner, - }); + }) } } catch (err) { console.error(err); @@ -440,13 +432,6 @@ export const onPullRequestMerged = async (webhooks: Webhooks) => { const octokit = getOctokitInstance(context.payload.installation?.id!); - // Parse the pull request body to find linked issues (e.g., " #123") - const issueNumbers = extractIssueNumbersFromPrBody(pullRequest.body!); - if (!issueNumbers.length) { - console.log("No issues are mentioned in the pull request."); - return; - } - // Check repository enrollment first const ossGgRepo = await getRepositoryByGithubId(context.payload.repository.id); if (!ossGgRepo) { @@ -454,61 +439,66 @@ export const onPullRequestMerged = async (webhooks: Webhooks) => { return; } - // Loop through all the extracted issue numbers - for (const issueNumber of issueNumbers) { - try { - // Fetch the issue details using the GitHub API - const { data: issue } = await octokit.issues.get({ - owner, + // Check if the pull request itself has the 🕹️ oss.gg label and process it + const validPrLabels = filterValidLabels(pullRequest.labels); + const isPrOssGgLabel = checkOssGgLabel(validPrLabels); + + if (isPrOssGgLabel) { + const points = extractPointsFromLabels(validPrLabels); + if (points) { + await processAndComment({ + context, + pullRequest, repo, - issue_number: issueNumber, + owner, + points, + issueNumber: pullRequest.number, + ossGgRepoId: ossGgRepo.id, }); + return; // Early exit since points were awarded based on PR labels + } + } - // Get the labels of the linked issue - const issueLabels = issue.labels.map((label: { name: string }) => label.name); + // If no oss.gg label on the PR, proceed with issue checks + const issueNumbers = extractIssueNumbersFromPrBody(pullRequest.body!); + if (!issueNumbers.length) { + console.log("No issues are mentioned in the pull request."); + return; + } - // Check if the "🕹️ oss.gg" label is present, skip if not - const ossLabel = issueLabels.find((label) => label.includes(OSS_GG_LABEL)); - if (!ossLabel) { - console.log(`Issue #${issueNumber} does not have the 🕹️ oss.gg label. Skipping.`); - continue; // Skip this issue and move to the next one - } + // Process each issue + for (const issueNumber of issueNumbers) { + const { data: issue } = await octokit.issues.get({ + owner, + repo, + issue_number: issueNumber, + }); - // Extract points from labels like "🕹️ 50 points", "🕹️ 100 points", etc. - const pointsLabel = issueLabels.find( - (label) => (label.includes("🕹️") || label.includes(":joystick:")) && label.includes("points") - ); - if (pointsLabel) { - // Extract number from the points label - const points = parseInt(pointsLabel.match(/\d+/)?.[0] || "0", 10); - console.log(`Points for issue #${issueNumber}:`, points); - - // Fetch PR author details only if the oss.gg label is present - const prAuthorGithubId = pullRequest.user.id; - const prAuthorUsername = pullRequest.user.login; - const { data: prAuthorProfile } = await octokit.users.getByUsername({ - username: prAuthorUsername, - }); + const validLabels = filterValidLabels(issue.labels); + const isOssGgLabel = checkOssGgLabel(validLabels); - // Fetch or create user profile only if the oss.gg label is present - let user = await getUserByGithubId(prAuthorGithubId); - if (!user) { - user = await createUser({ - githubId: prAuthorGithubId, - login: prAuthorUsername, - email: prAuthorProfile.email, - name: prAuthorProfile.name, - avatarUrl: pullRequest.user.avatar_url, - }); - } + if (!isOssGgLabel) { + console.log(`Issue #${issueNumber} does not have the 🕹️ oss.gg label. Skipping.`); + return; + } - // Award points to the user - await assignUserPoints(user?.id, points, "Awarded points", pullRequest.html_url, ossGgRepo?.id); - } else { - console.log(`No points label found for issue #${issueNumber}.`); - } - } catch (error) { - console.error(`Failed to fetch details for issue #${issueNumber}:`, error); + const points = extractPointsFromLabels(validLabels); + + if (points) { + console.log(`Points for issue #${issueNumber}:`, points); + + await processAndComment({ + context, + pullRequest, + repo, + owner, + points, + issueNumber: pullRequest.number, + // issueNumber, + ossGgRepoId: ossGgRepo.id, + }); + } else { + console.log(`No points label found for issue #${issueNumber}.`); } } }); diff --git a/lib/github/utils.ts b/lib/github/utils.ts index 21b196e..48bdbe0 100644 --- a/lib/github/utils.ts +++ b/lib/github/utils.ts @@ -1,8 +1,12 @@ import { createAppAuth } from "@octokit/auth-app"; import { Octokit } from "@octokit/rest"; import crypto from "node:crypto"; +import { GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, OSS_GG_LABEL } from "../constants"; +import { createUser, getUserByGithubId } from "../user/service"; +import { assignUserPoints } from "../points/service"; +import { TUser } from "@/types/user"; +import { TPostComment, TUserPoints } from "@/types/githubUser"; -import { GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY } from "../constants"; export const getOctokitInstance = (installationId: number) => { if (!installationId) { @@ -74,3 +78,101 @@ export const extractPointsFromLabels = (labels: { name: string }[]): number | nu return null; }; + +// Helper to process user points and post a comment +export const processAndComment = async ({ + context, + pullRequest, + repo, + owner, + points, + issueNumber, + ossGgRepoId, +}: { + context: any; + pullRequest: any; + repo: string; + owner: string; + points: number; + issueNumber: number; + ossGgRepoId: string; +}) => { + const user = await processUserPoints({ + installationId: context.payload.installation?.id!, + prAuthorGithubId: pullRequest.user.id, + prAuthorUsername: pullRequest.user.login, + avatarUrl: pullRequest.user.avatar_url, + points, + url: pullRequest.html_url, + repoId: ossGgRepoId, + comment: "", + }); + + const comment = `Awarding ${user.login}: ${points} points! Check out your new contribution on [oss.gg/${user.login}](https://oss.gg/${user.login})`; + + // Post comment on the issue or pull request + postComment({ + installationId: context.payload.installation?.id!, + body: comment, + issueNumber: issueNumber, + repo, + owner, + }); +}; + +export const processUserPoints = async ({ + installationId, + prAuthorGithubId, + prAuthorUsername, + avatarUrl, + points, + url, + repoId, + comment +}: TUserPoints): Promise => { + const octokit = getOctokitInstance(installationId!); + + const { data: prAuthorProfile } = await octokit.users.getByUsername({ + username: prAuthorUsername, + }); + + // Fetch or create user profile only if the oss.gg label is present + let user = await getUserByGithubId(prAuthorGithubId); + if (!user) { + user = await createUser({ + githubId: prAuthorGithubId, + login: prAuthorUsername, + email: prAuthorProfile.email, + name: prAuthorProfile.name, + avatarUrl: avatarUrl, + }); + comment = "Please register at oss.gg to be able to claim your rewards."; + } + + // Award points to the user + await assignUserPoints(user?.id, points, "Awarded points", url, repoId); + + return user +} + +export const postComment = async ({ + installationId, + body, + issueNumber, + repo, + owner, +}: TPostComment) => { + const octokit = getOctokitInstance(installationId!); + await octokit.issues.createComment({ + body, + issue_number: issueNumber, + repo, + owner, + }); +}; + +export const filterValidLabels = (labels: any[]) => labels.filter((label: any) => typeof label === 'object' && label.name); + +export const checkOssGgLabel = (labels: any[]) => labels.some((label: { name: string }) => label.name === OSS_GG_LABEL); + + diff --git a/types/githubUser.ts b/types/githubUser.ts index a0567f9..19242a3 100644 --- a/types/githubUser.ts +++ b/types/githubUser.ts @@ -11,3 +11,31 @@ export const ZGithubUserData = z .strict(); export type TGithubUserData = z.infer; + + +// Zod schema for ZUserPointsSchema +export const ZUserPointsSchema = z.object({ + installationId: z.number(), + prAuthorGithubId: z.number(), + prAuthorUsername: z.string(), + avatarUrl: z.string(), + points: z.number(), + url: z.string(), + repoId: z.string(), + comment: z.string(), +}); + +export type TUserPoints = z.infer; + + +// Zod schema for PostCommentProps +export const PostComment = z.object({ + installationId: z.number(), + body: z.string(), + issueNumber: z.number(), + repo: z.string(), + owner: z.string(), +}); + +export type TPostComment = z.infer; +