From 3623fd8ce98fb7fb42d905012f24271e8105ebc6 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Thu, 21 Nov 2024 18:14:55 +0900 Subject: [PATCH] feat: add handling for comments during issue assignment Implement functionality to skip comments during user assignment periods. --- src/configuration/data-purge-config.ts | 7 ++- src/helpers/user-assigned-timespans.ts | 70 ++++++++++++++++++++++++++ src/parser/data-purge-module.ts | 14 ++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/helpers/user-assigned-timespans.ts diff --git a/src/configuration/data-purge-config.ts b/src/configuration/data-purge-config.ts index 8bb7f866..c2669e38 100644 --- a/src/configuration/data-purge-config.ts +++ b/src/configuration/data-purge-config.ts @@ -1,5 +1,10 @@ import { Type, Static } from "@sinclair/typebox"; -export const dataPurgeConfigurationType = Type.Object({}); +export const dataPurgeConfigurationType = Type.Object({ + skipCommentsWhileAssigned: Type.Boolean({ + default: true, + description: "Skip comments posted by a user while it is assigned to the issue", + }), +}); export type DataPurgeConfiguration = Static; diff --git a/src/helpers/user-assigned-timespans.ts b/src/helpers/user-assigned-timespans.ts new file mode 100644 index 00000000..7045c6a0 --- /dev/null +++ b/src/helpers/user-assigned-timespans.ts @@ -0,0 +1,70 @@ +import { GitHubIssue, GitHubIssueComment, GitHubPullRequest, GitHubPullRequestReviewComment } from "../github-types"; +import { IssueParams } from "../start"; +import { ContextPlugin } from "../types/plugin-input"; + +export interface AssignmentPeriod { + assignedAt: string; + unassignedAt: string | null; +} + +export interface UserAssignments { + [username: string]: AssignmentPeriod[]; +} + +/* + * Returns the list of assignment periods per user for a given issue. + */ +export async function getAssignmentPeriods(octokit: ContextPlugin["octokit"], issueParams: IssueParams) { + const events = await octokit.paginate(octokit.rest.issues.listEvents, { + ...issueParams, + per_page: 100, + }); + + const userAssignments: UserAssignments = {}; + + events + .filter((event) => ["assigned", "unassigned"].includes(event.event)) + .forEach((event) => { + const username = "assignee" in event ? event.assignee?.login : null; + if (!username) return; + + if (!userAssignments[username]) { + userAssignments[username] = []; + } + + const lastPeriod = userAssignments[username][userAssignments[username].length - 1]; + + if (event.event === "assigned") { + userAssignments[username].push({ + assignedAt: event.created_at, + unassignedAt: null, + }); + } else if (event.event === "unassigned" && lastPeriod && lastPeriod.unassignedAt === null) { + lastPeriod.unassignedAt = event.created_at; + } + }); + + Object.values(userAssignments).forEach((periods) => { + const lastPeriod = periods[periods.length - 1]; + if (lastPeriod && lastPeriod.unassignedAt === null) { + lastPeriod.unassignedAt = new Date().toISOString(); + } + }); + + return userAssignments; +} + +export function isCommentDuringAssignment( + comment: GitHubIssueComment | GitHubPullRequestReviewComment | GitHubIssue | GitHubPullRequest, + assignments: AssignmentPeriod[] +) { + const commentDate = new Date(comment.created_at); + if (!assignments?.length) { + return false; + } + return assignments.some((period) => { + const assignedAt = new Date(period.assignedAt); + const unassignedAt = period.unassignedAt ? new Date(period.unassignedAt) : new Date(); + return commentDate >= assignedAt && commentDate <= unassignedAt; + }); +} diff --git a/src/parser/data-purge-module.ts b/src/parser/data-purge-module.ts index 81bac909..c057dc6d 100644 --- a/src/parser/data-purge-module.ts +++ b/src/parser/data-purge-module.ts @@ -3,6 +3,8 @@ import { GitHubPullRequestReviewComment } from "../github-types"; import { IssueActivity } from "../issue-activity"; import { BaseModule } from "../types/module"; import { Result } from "../types/results"; +import { getAssignmentPeriods, isCommentDuringAssignment } from "../helpers/user-assigned-timespans"; +import { parseGitHubUrl } from "../start"; /** * Removes the data in the comments that we do not want to be processed. @@ -19,12 +21,24 @@ export class DataPurgeModule extends BaseModule { } async transform(data: Readonly, result: Result) { + const assignmentPeriods = await getAssignmentPeriods( + this.context.octokit, + parseGitHubUrl(this.context.payload.issue.html_url) + ); for (const comment of data.allComments) { // Skips comments if they are minimized if ("isMinimized" in comment && comment.isMinimized) { this.context.logger.debug("Skipping hidden comment", { comment }); continue; } + if (this._configuration?.skipCommentsWhileAssigned) { + if (comment.user?.login && isCommentDuringAssignment(comment, assignmentPeriods[comment.user?.login])) { + this.context.logger.debug("Skipping comment during assignment", { + comment, + }); + continue; + } + } if (comment.body && comment.user?.login && result[comment.user.login]) { const newContent = comment.body // Remove quoted text