Skip to content

Commit

Permalink
feat: implement error notification as pr comment (#124)
Browse files Browse the repository at this point in the history
* feat: implement error notification as pr comment

* Update action.yml

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>

* feat: implement gitlab client and surround with try catch

* docs: add error notification enablment in the doc

* feat: disable comment if dry-run

* feat: update the default comment on error

---------

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
  • Loading branch information
lampajr and earl-warren authored Apr 10, 2024
1 parent 6042bcc commit 2bb7f73
Show file tree
Hide file tree
Showing 28 changed files with 594 additions and 39 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ This tool comes with some inputs that allow users to override the default behavi
| Strategy Option | --strategy-option | N | Cherry pick merging strategy option, see [git-merge](https://git-scm.com/docs/git-merge#_merge_strategies) doc for all possible values | "theirs" |
| Cherry-pick Options | --cherry-pick-options | N | Additional cherry-pick options, see [git-cherry-pick](https://git-scm.com/docs/git-cherry-pick) doc for all possible values | "theirs" |
| Additional comments | --comments | N | Semicolon separated list of additional comments to be posted to the backported pull request | [] |
| Enable error notification | --enable-err-notification | N | If true, enable the error notification as comment on the original pull request | false |
| Dry Run | -d, --dry-run | N | If enabled the tool does not push nor create anything remotely, use this to skip PR creation | false |

> **NOTE**: `pull request` and (`target branch` or `target branch pattern`) are *mandatory*, they must be provided as CLI options or as part of the configuration file (if used).
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ inputs:
description: >
Semicolon separated list of additional comments to be posted to the backported pull request
required: false
enable-err-notification:
description: >
If true, enable the error notification as comment on the original pull request
required: false
default: "false"

runs:
using: node20
Expand Down
114 changes: 105 additions & 9 deletions dist/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ class ArgsParser {
strategy: this.getOrDefault(args.strategy),
strategyOption: this.getOrDefault(args.strategyOption),
cherryPickOptions: this.getOrDefault(args.cherryPickOptions),
comments: this.getOrDefault(args.comments)
comments: this.getOrDefault(args.comments),
enableErrorNotification: this.getOrDefault(args.enableErrorNotification, false),
};
}
}
Expand Down Expand Up @@ -108,7 +109,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getAsBooleanOrDefault = exports.getAsSemicolonSeparatedList = exports.getAsCommaSeparatedList = exports.getAsCleanedCommaSeparatedList = exports.getOrUndefined = exports.readConfigFile = exports.parseArgs = void 0;
exports.getAsBooleanOrUndefined = exports.getAsSemicolonSeparatedList = exports.getAsCommaSeparatedList = exports.getAsCleanedCommaSeparatedList = exports.getOrUndefined = exports.readConfigFile = exports.parseArgs = void 0;
const fs = __importStar(__nccwpck_require__(7147));
/**
* Parse the input configuation string as json object and
Expand Down Expand Up @@ -159,11 +160,11 @@ function getAsSemicolonSeparatedList(value) {
return trimmed !== "" ? trimmed.split(";").map(v => v.trim()) : undefined;
}
exports.getAsSemicolonSeparatedList = getAsSemicolonSeparatedList;
function getAsBooleanOrDefault(value) {
function getAsBooleanOrUndefined(value) {
const trimmed = value.trim();
return trimmed !== "" ? trimmed.toLowerCase() === "true" : undefined;
}
exports.getAsBooleanOrDefault = getAsBooleanOrDefault;
exports.getAsBooleanOrUndefined = getAsBooleanOrUndefined;


/***/ }),
Expand Down Expand Up @@ -204,12 +205,13 @@ class CLIArgsParser extends args_parser_1.default {
.option("--no-inherit-reviewers", "if provided and reviewers option is empty then inherit them from original pull request")
.option("--labels <labels>", "comma separated list of labels to be assigned to the backported pull request", args_utils_1.getAsCommaSeparatedList)
.option("--inherit-labels", "if true the backported pull request will inherit labels from the original one")
.option("--no-squash", "Backport all commits found in the pull request. The default behavior is to only backport the first commit that was merged in the base branch")
.option("--auto-no-squash", "If the pull request was merged or is open, backport all commits. If the pull request commits were squashed, backport the squashed commit.")
.option("--no-squash", "backport all commits found in the pull request. The default behavior is to only backport the first commit that was merged in the base branch")
.option("--auto-no-squash", "if the pull request was merged or is open, backport all commits. If the pull request commits were squashed, backport the squashed commit.")
.option("--strategy <strategy>", "cherry-pick merge strategy, default to 'recursive'", undefined)
.option("--strategy-option <strategy-option>", "cherry-pick merge strategy option, default to 'theirs'")
.option("--cherry-pick-options <options>", "additional cherry-pick options")
.option("--comments <comments>", "semicolon separated list of additional comments to be posted to the backported pull request", args_utils_1.getAsSemicolonSeparatedList)
.option("--enable-err-notification", "if true, enable the error notification as comment on the original pull request")
.option("-cf, --config-file <config-file>", "configuration file containing all valid options, the json must match Args interface");
}
readArgs() {
Expand Down Expand Up @@ -247,6 +249,7 @@ class CLIArgsParser extends args_parser_1.default {
strategyOption: opts.strategyOption,
cherryPickOptions: opts.cherryPickOptions,
comments: opts.comments,
enableErrorNotification: opts.enableErrNotification,
};
}
return args;
Expand Down Expand Up @@ -300,7 +303,9 @@ exports["default"] = ConfigsParser;
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.AuthTokenId = void 0;
exports.AuthTokenId = exports.MESSAGE_TARGET_BRANCH_PLACEHOLDER = exports.MESSAGE_ERROR_PLACEHOLDER = void 0;
exports.MESSAGE_ERROR_PLACEHOLDER = "{{error}}";
exports.MESSAGE_TARGET_BRANCH_PLACEHOLDER = "{{target-branch}}";
var AuthTokenId;
(function (AuthTokenId) {
// github specific token
Expand All @@ -327,6 +332,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", ({ value: true }));
const args_utils_1 = __nccwpck_require__(8048);
const configs_parser_1 = __importDefault(__nccwpck_require__(5799));
const configs_types_1 = __nccwpck_require__(4753);
const git_client_factory_1 = __importDefault(__nccwpck_require__(8550));
class PullRequestConfigsParser extends configs_parser_1.default {
constructor() {
Expand Down Expand Up @@ -374,12 +380,20 @@ class PullRequestConfigsParser extends configs_parser_1.default {
git: {
user: args.gitUser ?? this.gitClient.getDefaultGitUser(),
email: args.gitEmail ?? this.gitClient.getDefaultGitEmail(),
}
},
errorNotification: {
enabled: args.enableErrorNotification ?? false,
message: this.getDefaultErrorComment(),
},
};
}
getDefaultFolder() {
return "bp";
}
getDefaultErrorComment() {
// TODO: fetch from arg or set default with placeholder {{error}}
return `The backport to \`${configs_types_1.MESSAGE_TARGET_BRANCH_PLACEHOLDER}\` failed. Check the latest run for more details.`;
}
/**
* Parse the provided labels and return a list of target branches
* obtained by applying the provided pattern as regular expression extractor
Expand Down Expand Up @@ -934,6 +948,26 @@ class GitHubClient {
await Promise.all(promises);
return data.html_url;
}
async createPullRequestComment(prUrl, comment) {
let commentUrl = undefined;
try {
const { owner, project, id } = this.extractPullRequestData(prUrl);
const { data } = await this.octokit.issues.createComment({
owner: owner,
repo: project,
issue_number: id,
body: comment
});
if (!data) {
throw new Error("Pull request comment creation failed");
}
commentUrl = data.url;
}
catch (error) {
this.logger.error(`Error creating comment on pull request ${prUrl}: ${error}`);
}
return commentUrl;
}
// UTILS
/**
* Extract repository owner and project from the pull request url
Expand Down Expand Up @@ -1093,7 +1127,7 @@ class GitLabClient {
const projectId = this.getProjectId(namespace, repo);
const { data } = await this.client.get(`/projects/${projectId}/merge_requests/${mrNumber}`);
if (squash === undefined) {
squash = (0, git_util_1.inferSquash)(data.state == "opened", data.squash_commit_sha);
squash = (0, git_util_1.inferSquash)(data.state === "opened", data.squash_commit_sha);
}
const commits = [];
if (!squash) {
Expand Down Expand Up @@ -1175,6 +1209,25 @@ class GitLabClient {
await Promise.all(promises);
return mr.web_url;
}
// https://docs.gitlab.com/ee/api/notes.html#create-new-issue-note
async createPullRequestComment(mrUrl, comment) {
const commentUrl = undefined;
try {
const { namespace, project, id } = this.extractMergeRequestData(mrUrl);
const projectId = this.getProjectId(namespace, project);
const { data } = await this.client.post(`/projects/${projectId}/issues/${id}/notes`, {
body: comment,
});
if (!data) {
throw new Error("Merge request comment creation failed");
}
}
catch (error) {
this.logger.error(`Error creating comment on merge request ${mrUrl}: ${error}`);
}
return commentUrl;
}
// UTILS
/**
* Retrieve a gitlab user given its username
* @param username
Expand Down Expand Up @@ -1322,6 +1375,9 @@ class ConsoleLoggerService {
setContext(newContext) {
this.context = newContext;
}
getContext() {
return this.context;
}
clearContext() {
this.context = undefined;
}
Expand Down Expand Up @@ -1398,6 +1454,39 @@ class Logger {
exports["default"] = Logger;


/***/ }),

/***/ 9632:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.injectTargetBranch = exports.injectError = void 0;
const configs_types_1 = __nccwpck_require__(4753);
/**
* Inject the error message in the provided `message`.
* This is injected in place of the MESSAGE_ERROR_PLACEHOLDER placeholder
* @param message string that needs to be updated
* @param errMsg the error message that needs to be injected
*/
const injectError = (message, errMsg) => {
return message.replace(configs_types_1.MESSAGE_ERROR_PLACEHOLDER, errMsg);
};
exports.injectError = injectError;
/**
* Inject the target branch into the provided `message`.
* This is injected in place of the MESSAGE_TARGET_BRANCH_PLACEHOLDER placeholder
* @param message string that needs to be updated
* @param targetBranch the target branch to inject
* @returns
*/
const injectTargetBranch = (message, targetBranch) => {
return message.replace(configs_types_1.MESSAGE_TARGET_BRANCH_PLACEHOLDER, targetBranch);
};
exports.injectTargetBranch = injectTargetBranch;


/***/ }),

/***/ 8810:
Expand All @@ -1415,6 +1504,7 @@ const git_client_factory_1 = __importDefault(__nccwpck_require__(8550));
const git_types_1 = __nccwpck_require__(750);
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
const git_util_1 = __nccwpck_require__(9080);
const runner_util_1 = __nccwpck_require__(9632);
/**
* Main runner implementation, it implements the core logic flow
*/
Expand Down Expand Up @@ -1479,6 +1569,12 @@ class Runner {
}
catch (error) {
this.logger.error(`Something went wrong backporting to ${pr.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = (0, runner_util_1.injectError)(configs.errorNotification.message, error);
comment = (0, runner_util_1.injectTargetBranch)(comment, pr.base);
await gitApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
failures.push(error);
}
}
Expand Down
Loading

0 comments on commit 2bb7f73

Please sign in to comment.