diff --git a/src/events/pr-handlers/actions/syncLabelsAfterCommentBodyEdited.ts b/src/events/pr-handlers/actions/commentBodyEdited.ts similarity index 65% rename from src/events/pr-handlers/actions/syncLabelsAfterCommentBodyEdited.ts rename to src/events/pr-handlers/actions/commentBodyEdited.ts index e5c96a183..c4f6de4bb 100644 --- a/src/events/pr-handlers/actions/syncLabelsAfterCommentBodyEdited.ts +++ b/src/events/pr-handlers/actions/commentBodyEdited.ts @@ -1,39 +1,22 @@ import type { EventsWithRepository, RepoContext } from 'context/repoContext'; import type { ProbotEvent } from 'events/probot-types'; -import type { - PullRequestFromRestEndpoint, - PullRequestWithDecentData, -} from '../utils/PullRequestData'; +import type { AppContext } from '../../../context/AppContext'; +import type { PullRequestFromRestEndpoint } from '../utils/PullRequestData'; import type { ReviewflowPrContext } from '../utils/createPullRequestContext'; import { autoMergeIfPossible } from './autoMergeIfPossible'; +import { editOpenedPR } from './editOpenedPR'; +import { updateBranch } from './updateBranch'; import { updatePrCommentBodyIfNeeded } from './updatePrCommentBody'; -import type { Options } from './utils/body/prOptions'; +import { updateStatusCheckFromLabels } from './updateStatusCheckFromLabels'; +import { calcDefaultOptions } from './utils/body/prOptions'; import { updateCommentOptions } from './utils/body/updateBody'; import hasLabelInPR from './utils/hasLabelInPR'; import syncLabel from './utils/syncLabel'; -export const calcDefaultOptions = ( - repoContext: RepoContext, - pullRequest: PullRequestWithDecentData, -): Options => { - const automergeLabel = repoContext.labels['merge/automerge']; - const skipCiLabel = repoContext.labels['merge/skip-ci']; - - const prHasSkipCiLabel = hasLabelInPR(pullRequest.labels, skipCiLabel); - const prHasAutoMergeLabel = hasLabelInPR(pullRequest.labels, automergeLabel); - - return { - ...repoContext.config.prDefaultOptions, - autoMergeWithSkipCi: prHasSkipCiLabel, - autoMerge: prHasAutoMergeLabel, - }; -}; - -export const syncLabelsAfterCommentBodyEdited = async < - Name extends EventsWithRepository, ->( +export const commentBodyEdited = async ( pullRequest: PullRequestFromRestEndpoint, context: ProbotEvent, + appContext: AppContext, repoContext: RepoContext, reviewflowPrContext: ReviewflowPrContext, ): Promise => { @@ -43,7 +26,7 @@ export const syncLabelsAfterCommentBodyEdited = async < const prHasSkipCiLabel = hasLabelInPR(pullRequest.labels, skipCiLabel); const prHasAutoMergeLabel = hasLabelInPR(pullRequest.labels, automergeLabel); - const { commentBody, options } = updateCommentOptions( + const { commentBody, options, actions } = updateCommentOptions( context.payload.repository.html_url, repoContext.config.labels.list, reviewflowPrContext.commentBody, @@ -62,6 +45,28 @@ export const syncLabelsAfterCommentBodyEdited = async < skipCiLabel, prHasSkipCiLabel, ), + + actions.includes('updateBranch') && + updateBranch(pullRequest, context, context.payload.sender.login), + actions.includes('updateChecks') && + Promise.all([ + editOpenedPR( + pullRequest, + context, + appContext, + repoContext, + reviewflowPrContext, + true, + ), + updateStatusCheckFromLabels( + pullRequest, + context, + appContext, + repoContext, + reviewflowPrContext, + pullRequest.labels, + ), + ]), automergeLabel && syncLabel( pullRequest, diff --git a/src/events/pr-handlers/actions/editOpenedPR.ts b/src/events/pr-handlers/actions/editOpenedPR.ts index 7155e9089..b472959d5 100644 --- a/src/events/pr-handlers/actions/editOpenedPR.ts +++ b/src/events/pr-handlers/actions/editOpenedPR.ts @@ -8,9 +8,9 @@ import type { PullRequestWithDecentData } from '../utils/PullRequestData'; import type { ReviewflowPrContext } from '../utils/createPullRequestContext'; import { checkIfUserIsBot } from '../utils/isBotUser'; import { readCommitsAndUpdateInfos } from './readCommitsAndUpdateInfos'; -import { calcDefaultOptions } from './syncLabelsAfterCommentBodyEdited'; import { updatePrIfNeeded } from './updatePr'; import { updatePrCommentBodyIfNeeded } from './updatePrCommentBody'; +import { calcDefaultOptions } from './utils/body/prOptions'; import { updateCommentBodyInfos, defaultCommentBody, diff --git a/src/events/pr-handlers/actions/updateBranch.ts b/src/events/pr-handlers/actions/updateBranch.ts index 866761b42..26b8f248e 100644 --- a/src/events/pr-handlers/actions/updateBranch.ts +++ b/src/events/pr-handlers/actions/updateBranch.ts @@ -33,7 +33,7 @@ export const updateBranch = async ( 'update branch result', ); - if (result.status === 204) { + if (result.status === 204 || result.error?.status === 204) { context.octokit.issues.createComment( context.repo({ issue_number: pullRequest.number, @@ -43,7 +43,7 @@ export const updateBranch = async ( }), ); return true; - } else if (result.status === 409) { + } else if (result.status === 409 || result.error?.status === 409) { context.octokit.issues.createComment( context.repo({ issue_number: pullRequest.number, @@ -57,11 +57,12 @@ export const updateBranch = async ( context.octokit.issues.createComment( context.repo({ issue_number: pullRequest.number, - body: `${ - login ? `@${login} ` : '' - }Could not update branch (unknown error${ - result.status ? `, status = ${result.status}` : '' - }).`, + body: `${login ? `@${login} ` : ''}Could not update branch ${ + result?.error?.response?.data?.message ?? + `(unknown error${ + result?.error.status ? `, status = ${result.error.status}` : '' + })` + }.`, }), ); return false; diff --git a/src/events/pr-handlers/actions/updateStatusCheckFromLabels.ts b/src/events/pr-handlers/actions/updateStatusCheckFromLabels.ts index c3eb04c6c..1410ae6d8 100644 --- a/src/events/pr-handlers/actions/updateStatusCheckFromLabels.ts +++ b/src/events/pr-handlers/actions/updateStatusCheckFromLabels.ts @@ -1,5 +1,5 @@ import type { AppContext } from 'context/AppContext'; -import type { RepoContext } from 'context/repoContext'; +import type { EventsWithRepository, RepoContext } from 'context/repoContext'; import type { ProbotEvent } from 'events/probot-types'; import type { StatusInfo } from '../../../accountConfigs/types'; import { ExcludesFalsy } from '../../../utils/Excludes'; @@ -8,10 +8,9 @@ import type { PullRequestWithDecentData, } from '../utils/PullRequestData'; import type { ReviewflowPrContext } from '../utils/createPullRequestContext'; -import type { EventsWithPullRequest } from '../utils/createPullRequestHandler'; import createStatus, { isSameStatus } from './utils/createStatus'; -const addStatusCheck = async function ( +const addStatusCheck = async function ( pullRequest: PullRequestWithDecentData, context: ProbotEvent, appContext: AppContext, @@ -98,7 +97,7 @@ const addStatusCheck = async function ( }; export const updateStatusCheckFromLabels = < - EventName extends EventsWithPullRequest, + EventName extends EventsWithRepository, >( pullRequest: PullRequestWithDecentData, context: ProbotEvent, diff --git a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simple.ts b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simple.ts index 4b509fde9..edbd7ad24 100644 --- a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simple.ts +++ b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simple.ts @@ -4,6 +4,9 @@ export default ` - [ ] Add \`[skip ci]\` on merge commit - [ ] Auto merge when this PR is ready and has no failed statuses. (Also has a queue per repo to prevent multiple useless "Update branch" triggers) - [x] Automatic branch delete after this PR is merged +### Actions: +- [ ] :bug: Force updating reviewflow checks for this PR. Use this to try to fix reviewflow checks that are still missing/pending, which might happen if webhook failed or something bad happened when reviewflow tried to send the status check to github. +- [ ] [:arrows_counterclockwise: update branch](https://github.com/christophehurpeau/reviewflow/labels/%3Aarrows_counterclockwise%3A%20update%20branch): Merge base branch in this PR's branch. Only works if merging is possible without conflicts. `.trim(); // [ONK-0000](https://a;dlkas;dlkas;dk) diff --git a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simpleWithInfos.ts b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simpleWithInfos.ts index 5c2303c21..07ee51695 100644 --- a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simpleWithInfos.ts +++ b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-simpleWithInfos.ts @@ -7,6 +7,9 @@ Some informations here, like links. - [ ] Add \`[skip ci]\` on merge commit - [ ] Auto merge when this PR is ready and has no failed statuses. (Also has a queue per repo to prevent multiple useless "Update branch" triggers) - [x] Automatic branch delete after this PR is merged +### Actions: +- [ ] :bug: Force updating reviewflow checks for this PR. Use this to try to fix reviewflow checks that are still missing/pending, which might happen if webhook failed or something bad happened when reviewflow tried to send the status check to github. +- [ ] [:arrows_counterclockwise: update branch](https://github.com/christophehurpeau/reviewflow/labels/%3Aarrows_counterclockwise%3A%20update%20branch): Merge base branch in this PR's branch. Only works if merging is possible without conflicts. `.trim(); // [ONK-0000](https://a;dlkas;dlkas;dk) diff --git a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-table.ts b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-table.ts index 4b509fde9..edbd7ad24 100644 --- a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-table.ts +++ b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v1-initialAfterEdit-table.ts @@ -4,6 +4,9 @@ export default ` - [ ] Add \`[skip ci]\` on merge commit - [ ] Auto merge when this PR is ready and has no failed statuses. (Also has a queue per repo to prevent multiple useless "Update branch" triggers) - [x] Automatic branch delete after this PR is merged +### Actions: +- [ ] :bug: Force updating reviewflow checks for this PR. Use this to try to fix reviewflow checks that are still missing/pending, which might happen if webhook failed or something bad happened when reviewflow tried to send the status check to github. +- [ ] [:arrows_counterclockwise: update branch](https://github.com/christophehurpeau/reviewflow/labels/%3Aarrows_counterclockwise%3A%20update%20branch): Merge base branch in this PR's branch. Only works if merging is possible without conflicts. `.trim(); // [ONK-0000](https://a;dlkas;dlkas;dk) diff --git a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v2-initialAfterEdit-simple.ts b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v2-initialAfterEdit-simple.ts index 0c69f3281..e86835c41 100644 --- a/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v2-initialAfterEdit-simple.ts +++ b/src/events/pr-handlers/actions/utils/body/mocks/commentBody-v2-initialAfterEdit-simple.ts @@ -3,6 +3,9 @@ export default ` - [ ] [:vertical_traffic_light: automerge](https://github.com/christophehurpeau/reviewflow/labels/%3Avertical_traffic_light%3A%20automerge): Automatically merge when this PR is ready and has no failed statuses. When the repository requires _branches to be up to date before merging_, it merges default branch, with a queue per repo to prevent multiple merges when several PRs are ready. A fail job prevents the merge. - [ ] [:vertical_traffic_light: skip-ci](https://github.com/christophehurpeau/reviewflow/labels/%3Avertical_traffic_light%3A%20skip-ci): Add \`[skip ci]\` on merge commit when merge is done with autoMerge. - [x] :recycle: Automatically delete the branch after this PR is merged. +### Actions: +- [ ] :bug: Force updating reviewflow checks for this PR. Use this to try to fix reviewflow checks that are still missing/pending, which might happen if webhook failed or something bad happened when reviewflow tried to send the status check to github. +- [ ] [:arrows_counterclockwise: update branch](https://github.com/christophehurpeau/reviewflow/labels/%3Aarrows_counterclockwise%3A%20update%20branch): Merge base branch in this PR's branch. Only works if merging is possible without conflicts. `.trim(); // [ONK-0000](https://a;dlkas;dlkas;dk) diff --git a/src/events/pr-handlers/actions/utils/body/parseBody.ts b/src/events/pr-handlers/actions/utils/body/parseBody.ts index db9563712..a723c970a 100644 --- a/src/events/pr-handlers/actions/utils/body/parseBody.ts +++ b/src/events/pr-handlers/actions/utils/body/parseBody.ts @@ -1,3 +1,5 @@ +import type { ActionKeys } from './prActions'; +import { actionRegexps } from './prActions'; import { optionsRegexps } from './prOptions'; import type { Options } from './prOptions'; @@ -19,6 +21,19 @@ export const parseOptions = ( return options as Options; }; +export const parseActions = (content: string): ActionKeys[] => { + const actions: ActionKeys[] = []; + + actionRegexps.forEach(({ key, regexp }) => { + const match = regexp.exec(content); + if (match && (match[1] === 'x' || match[1] === 'X')) { + actions.push(key); + } + }); + + return actions; +}; + export const parseCommitNotes = (content: string): string => { const commitNotes = content.replace( /^.*####? Commits Notes:(.*)####? Options:.*$/s, diff --git a/src/events/pr-handlers/actions/utils/body/prActions.ts b/src/events/pr-handlers/actions/utils/body/prActions.ts new file mode 100644 index 000000000..622d0e0ac --- /dev/null +++ b/src/events/pr-handlers/actions/utils/body/prActions.ts @@ -0,0 +1,32 @@ +export type ActionKeys = 'updateChecks' | 'updateBranch'; + +export const actions: ActionKeys[] = ['updateChecks', 'updateBranch']; +export const actionRegexps: { + key: ActionKeys; + regexp: RegExp; +}[] = actions.map((action) => ({ + key: action, + regexp: new RegExp(`\\[([ xX]?)]\\s*`), +})); + +interface ActionDisplay { + key: ActionKeys; + labelKey?: string; + icon?: string; + description: string; +} + +export const actionDescriptions: ActionDisplay[] = [ + { + key: 'updateChecks', + icon: ':bug:', + description: + 'Force updating reviewflow checks for this PR. Use this to try to fix reviewflow checks that are still missing/pending, which might happen if webhook failed or something bad happened when reviewflow tried to send the status check to github.', + }, + { + key: 'updateBranch', + labelKey: 'merge/update-branch', + description: + "Merge base branch in this PR's branch. Only works if merging is possible without conflicts.", + }, +]; diff --git a/src/events/pr-handlers/actions/utils/body/prOptions.ts b/src/events/pr-handlers/actions/utils/body/prOptions.ts index 881f210d6..d82fc8f35 100644 --- a/src/events/pr-handlers/actions/utils/body/prOptions.ts +++ b/src/events/pr-handlers/actions/utils/body/prOptions.ts @@ -1,3 +1,7 @@ +import type { RepoContext } from 'context/repoContext'; +import type { PullRequestWithDecentData } from 'events/pr-handlers/utils/PullRequestData'; +import hasLabelInPR from '../hasLabelInPR'; + export type OptionsKeys = | 'autoMerge' | 'autoMergeWithSkipCi' @@ -44,3 +48,20 @@ export const optionsDescriptions: OptionDisplay[] = [ description: 'Automatically delete the branch after this PR is merged.', }, ]; + +export const calcDefaultOptions = ( + repoContext: RepoContext, + pullRequest: PullRequestWithDecentData, +): Options => { + const automergeLabel = repoContext.labels['merge/automerge']; + const skipCiLabel = repoContext.labels['merge/skip-ci']; + + const prHasSkipCiLabel = hasLabelInPR(pullRequest.labels, skipCiLabel); + const prHasAutoMergeLabel = hasLabelInPR(pullRequest.labels, automergeLabel); + + return { + ...repoContext.config.prDefaultOptions, + autoMergeWithSkipCi: prHasSkipCiLabel, + autoMerge: prHasAutoMergeLabel, + }; +}; diff --git a/src/events/pr-handlers/actions/utils/body/updateBody.ts b/src/events/pr-handlers/actions/utils/body/updateBody.ts index 15cc1d624..9e11bd594 100644 --- a/src/events/pr-handlers/actions/utils/body/updateBody.ts +++ b/src/events/pr-handlers/actions/utils/body/updateBody.ts @@ -1,6 +1,8 @@ import type { LabelList, StatusInfo } from 'accountConfigs/types'; import type { Options } from './parseBody'; -import { parseOptions } from './parseBody'; +import { parseActions, parseOptions } from './parseBody'; +import type { ActionKeys } from './prActions'; +import { actionDescriptions } from './prActions'; import { optionsDescriptions } from './prOptions'; export const defaultCommentBody = 'This will be auto filled by reviewflow.'; @@ -29,6 +31,28 @@ const toMarkdownOptions = ( .join('\n'); }; +const toMarkdownActions = ( + repoLink: string, + labelsConfig: LabelList, +): string => { + return actionDescriptions + .map(({ key, labelKey, description, icon: iconValue }) => { + // should always update without ticking the box + const checkboxWithId = `[ ] `; + + const labelDescription = labelKey && labelsConfig[labelKey]; + const labelLink = labelDescription + ? `[${labelDescription.name}](${repoLink}/labels/${encodeURIComponent( + labelDescription.name, + )}): ` + : ''; + const icon = labelLink || !iconValue ? '' : `${iconValue} `; + + return `- ${checkboxWithId}${icon}${labelLink}${description}`; + }) + .join('\n'); +}; + const toMarkdownInfos = (infos: StatusInfo[]): string => { return infos .map((info) => { @@ -41,6 +65,7 @@ const toMarkdownInfos = (infos: StatusInfo[]): string => { interface UpdatedBodyWithOptions { commentBody: string; options?: Options; + actions: ActionKeys[]; } const getInfosReplacement = (infos?: StatusInfo[]): string => { @@ -75,7 +100,7 @@ const internalUpdateBodyOptionsAndInfos = ( repoLink, labelsConfig, options, - )}`; + )}\n### Actions:\n${toMarkdownActions(repoLink, labelsConfig)}`; }; export const createCommentBody = ( @@ -105,6 +130,7 @@ export const updateCommentOptions = ( return { options: updatedOptions, + actions: parseActions(commentBody), commentBody: internalUpdateBodyOptionsAndInfos( repoLink, labelsConfig, diff --git a/src/events/pr-handlers/commentEditedOrDeleted.ts b/src/events/pr-handlers/commentEditedOrDeleted.ts index a3dbb2fdd..37d6fc985 100644 --- a/src/events/pr-handlers/commentEditedOrDeleted.ts +++ b/src/events/pr-handlers/commentEditedOrDeleted.ts @@ -1,6 +1,6 @@ import type { Probot } from 'probot'; import type { AppContext } from '../../context/AppContext'; -import { syncLabelsAfterCommentBodyEdited } from './actions/syncLabelsAfterCommentBodyEdited'; +import { commentBodyEdited } from './actions/commentBodyEdited'; import { createPullRequestHandler } from './utils/createPullRequestHandler'; import { createMrkdwnSectionBlock } from './utils/createSlackMessageWithSecondaryBlock'; import { fetchPr } from './utils/fetchPr'; @@ -50,9 +50,10 @@ export default function prCommentEditedOrDeleted( ) { const updatedPr = await fetchPr(context, pullRequest.number); if (!updatedPr.closed_at) { - await syncLabelsAfterCommentBodyEdited( + await commentBodyEdited( updatedPr, context, + appContext, repoContext, reviewflowPrContext, );