From 3bbce12d8a8806ea476ad4e2be869b3ac5b479b0 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:25:01 +0000 Subject: [PATCH] clean --all (#265) * clean --all --- src/bot/events/handlers/cleanHandler.ts | 10 +++++--- src/bot/parse/ParsedCommand.ts | 2 +- .../help/parts/commands_preset.pug | 23 +++++++++++++++---- src/command-configs/help/parts/navigation.pug | 2 +- src/command-configs/renderHelpPage.ts | 18 ++++++++++++--- src/commander/commander.ts | 5 ++-- src/github.ts | 22 +++++++++++++++--- src/schema/schema.cmd.json | 12 ++++++++++ 8 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/bot/events/handlers/cleanHandler.ts b/src/bot/events/handlers/cleanHandler.ts index 24e84a5..48a0e15 100644 --- a/src/bot/events/handlers/cleanHandler.ts +++ b/src/bot/events/handlers/cleanHandler.ts @@ -9,13 +9,17 @@ export async function cleanHandler(this: EventHandler): Promise { throw new EventHandlerError(); } - const { comment } = payload; + const { comment, sender } = payload; const reactionParams = { owner: pr.owner, repo: pr.repo, comment_id: comment.id }; const reactionId = await reactToComment(ctx, octokit, { ...reactionParams, content: "eyes" }); - await cleanComments(ctx, octokit, { ...commentParams }); + await cleanComments(ctx, octokit, comment, parsedCommand.all, { ...commentParams, requester: sender.login }); await Promise.all([ - reactionId ? await removeReactionToComment(ctx, octokit, { ...reactionParams, reaction_id: reactionId }) : null, + reactionId + ? await removeReactionToComment(ctx, octokit, { ...reactionParams, reaction_id: reactionId }).catch((e) => { + ctx.logger.error(e, "Failed to remove reaction"); + }) + : null, await reactToComment(ctx, octokit, { ...reactionParams, content: "+1" }), ]); } diff --git a/src/bot/parse/ParsedCommand.ts b/src/bot/parse/ParsedCommand.ts index 7461e8d..d6c6428 100644 --- a/src/bot/parse/ParsedCommand.ts +++ b/src/bot/parse/ParsedCommand.ts @@ -15,7 +15,7 @@ export class CancelCommand extends ParsedCommand { } export class CleanCommand extends ParsedCommand { - constructor() { + constructor(public all: boolean = false) { super("clean"); } } diff --git a/src/command-configs/help/parts/commands_preset.pug b/src/command-configs/help/parts/commands_preset.pug index e136128..e4d11c2 100644 --- a/src/command-configs/help/parts/commands_preset.pug +++ b/src/command-configs/help/parts/commands_preset.pug @@ -1,9 +1,12 @@ -div(class="preset " + preset.repos.join(' ')) +div(class="preset " + (typeof(preset.repos) !== 'undefined' ? preset.repos : []).join(' ')) h6.mb-md(id="link-"+ commandName + "-" + presetId) #{commandStart} #{ commandName } #{presetId === 'default' ? '' : presetId} p.mb-md.muted - | Works in these repos:  - each repo in preset.repos - span.ms-label #{repo} + if typeof(preset.repos) !== 'undefined' + | Works in these repos:  + each repo in (typeof(preset.repos) !== 'undefined' ? preset.repos : []) + span.ms-label #{repo} + else + | Works in all repos -let defaultArgs = [] if preset.args && Object.values(preset.args).length > 0 div.preset-args.mb-lg.ml-md @@ -15,17 +18,29 @@ div(class="preset " + preset.repos.join(' ')) | --#{arg.label} div.col-8.lowercase each one, i in arg.type_one_of + if one.explanation + p(style={"margin-top": 0}) #{one.explanation}  span.mb-sm if i === 0 | #{one} (default)  else | | #{one}  + if arg.type_boolean + div.col-4.lowercase + | --#{arg.label} + div.col-8 + if arg.explanation + p(style={"margin-top": 0}) #{arg.explanation}  + span.mb-sm + | Default: #{arg.default}  else if arg.type_rule div.col-4.lowercase | --#{arg.label} span(style="color: red;")  *  | (required): div.col-8.lowercase + if arg.explanation + p(style={"margin-top": 0}) #{arg.explanation}  | #{arg.type_rule}  if arg.example | (Example: "#{arg.example}") diff --git a/src/command-configs/help/parts/navigation.pug b/src/command-configs/help/parts/navigation.pug index 1248931..e9b1d2e 100644 --- a/src/command-configs/help/parts/navigation.pug +++ b/src/command-configs/help/parts/navigation.pug @@ -12,7 +12,7 @@ div.sticky-nav if cfg.command.presets ul.nav-preset each preset, presetId in cfg.command.presets - li(class="preset-link " + preset.repos.join(' ') + (presetId === 'default' ? ' hidden' : '')) + li(class="preset-link " + (typeof(preset.repos) !== 'undefined' ? preset.repos : []).join(' ') + (presetId === 'default' ? ' hidden' : '')) a(href="\#link-" + commandName + "-" + presetId) #{ presetId } | Env Variables ul diff --git a/src/command-configs/renderHelpPage.ts b/src/command-configs/renderHelpPage.ts index db8d043..8e3de5b 100644 --- a/src/command-configs/renderHelpPage.ts +++ b/src/command-configs/renderHelpPage.ts @@ -40,7 +40,19 @@ function prepareConfigs(cmdConfigs: CommandConfigs): CommandConfigs { // these commands are added here, as they are defined inside of bot newCmdConfigs.help = mockStaticConfig("Generates the help page & provides a link."); - newCmdConfigs.clean = mockStaticConfig("Clears bot comments in the PR."); + newCmdConfigs.clean = mockStaticConfig("Clears bot comments in the PR.", { + default: { + description: "Clears all bot comments in the PR. ", + args: { + all: { + label: "all", + type_boolean: true, + default: false, + explanation: "Clears all bot comments in the PR, including human comments requesting a command-bot.", + }, + }, + }, + }); // clean up excluded for (const cmdName in cmdConfigs) { @@ -55,7 +67,7 @@ function prepareConfigs(cmdConfigs: CommandConfigs): CommandConfigs { } // append local (or "hardcoded") commands into documentation -function mockStaticConfig(description: string) { - const config: CmdJson = { command: { description, configuration: {} } }; +function mockStaticConfig(description: string, presets?: CmdJson["command"]["presets"]): CmdJson { + const config: CmdJson = { command: { description, configuration: {}, presets } }; return config; } diff --git a/src/commander/commander.ts b/src/commander/commander.ts index b9e87a0..b7fdfcf 100644 --- a/src/commander/commander.ts +++ b/src/commander/commander.ts @@ -85,8 +85,9 @@ export function getCommanderFromConfiguration( .command("clear") .alias("clean") .exitOverride() - .action(() => { - parsedCommand = new CleanCommand(); + .option("--all", "clear all comments, including bot and requesters' comments") + .action((options: { all: boolean }) => { + parsedCommand = new CleanCommand(options.all); }); root diff --git a/src/github.ts b/src/github.ts index 7afe7d6..0caa51f 100644 --- a/src/github.ts +++ b/src/github.ts @@ -2,9 +2,11 @@ import { Logger } from "@eng-automation/js"; import { OctokitResponse } from "@octokit/plugin-paginate-rest/dist-types/types"; import { RequestError } from "@octokit/request-error"; import { EndpointInterface, Endpoints, RequestInterface } from "@octokit/types"; +import { IssueComment } from "@octokit/webhooks-types"; import { Mutex } from "async-mutex"; import { Probot } from "probot"; +import { config } from "src/config"; import { PullRequestTask } from "src/task"; import { CommandOutput, Context } from "src/types"; import { displayError, Err, millisecondsDelay, Ok } from "src/utils"; @@ -251,6 +253,8 @@ export const removeReactionToComment = async ( export const cleanComments = async ( ctx: Context, octokit: Octokit, + originalComment: IssueComment, + isAll: boolean, ...args: Parameters ): Promise => { if (ctx.disablePRComment) { @@ -281,10 +285,22 @@ export const cleanComments = async ( params.page = params.page + 1; ctx.logger.debug(null, `cleanComments: increase page ${params.page}`); } - // save ids - botCommentIds.push(...response.data.filter((comment) => comment.user?.type === "Bot").map((r) => r.id)); + const filteredComments = response.data + .filter((comment) => { + const isBot = comment.user?.type === "Bot"; + // to avoid deleting the original comment + const isOriginalComment = comment.id === originalComment.id; + // testing each comment with real commander and pulling repos (even cached) is quite expensive + // so we just check if comment has the command pattern, assuming that if it includes pattern, it's a request to bot + const commandPattern = new RegExp(`^${config.botPullRequestCommentMention} .*$`, "i"); + const hasCommand = comment.body?.split("\n").find((line) => commandPattern.test(line)); + return (isBot || (isAll && hasCommand)) && !isOriginalComment; + }) + .map((comment) => comment.id); + + botCommentIds.push(...filteredComments); } else { - ctx.logger.error(response, "cleanComments: listComments ended up unsuccessfully"); + ctx.logger.error(response, "cleanComments: listComments ended up with error"); stopped = true; } } diff --git a/src/schema/schema.cmd.json b/src/schema/schema.cmd.json index 54a6d0f..eb7ca16 100644 --- a/src/schema/schema.cmd.json +++ b/src/schema/schema.cmd.json @@ -86,6 +86,9 @@ "label": { "type": "string" }, + "explanation": { + "type": "string" + }, "type_one_of": { "type": "array", "items": { @@ -104,8 +107,14 @@ "type_rule": { "type": "string" }, + "type_boolean": { + "type": "boolean" + }, "example": { "type": "string" + }, + "default": { + "type": "string" } }, "anyOf": [ @@ -120,6 +129,9 @@ }, { "required": ["label", "type_rule", "example"] + }, + { + "required": ["label", "type_boolean", "explanation", "default"] } ] }