From 6008ef38d7350168a91dab200a9d48ae21b42676 Mon Sep 17 00:00:00 2001 From: Michael Heap Date: Fri, 30 Dec 2022 20:56:08 +0000 Subject: [PATCH] Add ability to customise comment / error message --- README.md | 67 ++++++++++++++++++++++++++++++--------------------- action.yml | 5 +++- index.js | 22 +++++++++++++---- index.test.js | 30 +++++++++++++++++++++-- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 634b638..f1c826d 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,19 @@ This action allows you to fail the build if/unless a certain combination of labe ## Usage -This action has three inputs: +This action has three required inputs; `labels`, `mode` and `count` -### `labels` +| Name | Description | Required | Default | +| ------------- | ----------------------------------------------------------------------------------------------------------- | -------- | ------------------- | +| `labels` | Comma separated list of labels to match | true | +| `mode` | The mode of comparison to use. One of: exactly, minimum, maximum | true | +| `count` | The required number of labels to match | true | +| `token` | The GitHub token to use when calling the API | false | ${{ github.token }} | +| `message` | The message to log and to add to the PR (if add_comment is true). See the README for available placeholders | false | +| `add_comment` | Add a comment to the PR if required labels are missing | false | false | +| `exit_type` | The exit type of the action. One of: failure, success | false | -This is a list of comma separated labels to match on. - -``` -labels: 'label-one, another:label, bananas' -``` - -### `mode` - -One of: `exactly`, `minimum`, `maximum` - -### `count` - -The number of labels to apply `mode` to +This action calls the GitHub API to fetch labels for a PR rather than reading `event.json`. This allows the action to run as intended when an earlier step adds a label. It will use `github.token` by default, and you can set the `token` input to provide alternative authentication. ## Examples @@ -42,30 +38,34 @@ jobs: labels: "semver:patch, semver:minor, semver:major" ``` -By default this actions reads `event.json`, which will not detect when a label is added in an earlier step. -To force an API call, set the `GITHUB_TOKEN` environment variable like so: +### Prevent merging if a label exists ```yaml - uses: mheap/github-action-required-labels@v3 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: mode: exactly - count: 1 - labels: "semver:patch, semver:minor, semver:major" + count: 0 + labels: "do not merge" ``` -### Prevent merging if a label exists +### Post a comment when the check fails + +You can choose to add a comment to the PR when the action fails. The default format is: + +> Label error. Requires {{ errorString }} {{ count }} of: {{ provided }}. Found: {{ applied }} ```yaml - uses: mheap/github-action-required-labels@v3 with: mode: exactly - count: 0 - labels: "do not merge" + count: 1 + labels: "semver:patch, semver:minor, semver:major" + add_comment: true ``` -### Post a comment when the check fails +### Customising the failure message / comment + +You can also customise the message used by providing the `message` input: ```yaml - uses: mheap/github-action-required-labels@v3 @@ -74,8 +74,19 @@ To force an API call, set the `GITHUB_TOKEN` environment variable like so: count: 1 labels: "semver:patch, semver:minor, semver:major" add_comment: true + message: "This PR is being prevented from merging because you have added one of our blocking labels: {{ provided }}. You'll need to remove it before this PR can be merged." ``` +The following tokens are available for use in custom messages: + +| Token | Value | +| ----------- | ---------------------------------------- | +| mode | One of: `exactly`, `minimum`, `maximum` | +| count | The value of the `count` input | +| errorString | One of: `exactly`, `at least`, `at most` | +| provided | The value of the `lavels` input | +| applied | The labels that are applied to the PR | + ### Require multiple labels ```yaml @@ -86,7 +97,9 @@ To force an API call, set the `GITHUB_TOKEN` environment variable like so: labels: "community-reviewed, team-reviewed, codeowner-reviewed" ``` -### Exit with a neutral result rather than failure +### Controlling failure + +You can set `exit_type` to success then inspect `outputs.status` to see if the action passed or failed. This is useful when you want to perform additional actions if a label is not present, but not fail the entire build. ```yaml - uses: mheap/github-action-required-labels@v3 @@ -97,8 +110,6 @@ To force an API call, set the `GITHUB_TOKEN` environment variable like so: exit_type: success # Can be: success or failure (default: failure) ``` -You can set `exit_type` to success then inspect `outputs.status` to see if the action passed or failed. This is useful when you want to perform additional actions if a label is not present, but not fail the entire build. - If the action passed, `outputs.status` will be `success`. If it failed, `outputs.status` will be `failure`. Here is a complete workflow example for this use case: diff --git a/action.yml b/action.yml index 6f5659c..39f9a4d 100644 --- a/action.yml +++ b/action.yml @@ -20,10 +20,13 @@ inputs: count: description: "The required number of labels to match" required: true + message: + description: "The message to log and to add to the PR (if add_comment is true). See the README for available placeholders" + required: false add_comment: description: "Add a comment to the PR if required labels are missing" default: "false" required: false exit_type: - description: "The exit type of the action. One of: failure, success, neutral" + description: "The exit type of the action. One of: failure, success" required: false diff --git a/index.js b/index.js index 63f0724..573f1f5 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ async function action() { // Process inputs for use later const mode = core.getInput("mode", { required: true }); const count = parseInt(core.getInput("count", { required: true }), 10); - const allowedLabels = core + const providedLabels = core .getInput("labels", { required: true }) .split(",") .map((l) => l.trim()) @@ -57,7 +57,7 @@ async function action() { const appliedLabels = labels.map((label) => label.name); // How many labels overlap? - let intersection = allowedLabels.filter((x) => appliedLabels.includes(x)); + let intersection = providedLabels.filter((x) => appliedLabels.includes(x)); // Is there an error? let errorMode; @@ -71,9 +71,15 @@ async function action() { // If so, add a comment (if enabled) and fail the run if (errorMode !== undefined) { - const errorMessage = `Label error. Requires ${errorMode} ${count} of: ${allowedLabels.join( - ", " - )}. Found: ${appliedLabels.join(", ")}`; + const comment = core.getInput("message"); + const errorMessage = tmpl(comment, { + mode, + count, + errorString: errorMode, + provided: providedLabels.join(", "), + applied: appliedLabels.join(", "), + }); + await exitWithError(exitType, octokit, shouldAddComment, errorMessage); return; } @@ -84,6 +90,12 @@ async function action() { } } +function tmpl(t, o) { + return t.replace(/\{\{\s*(.*?)\s*\}\}/g, function (item, param) { + return o[param]; + }); +} + async function exitWithError(exitType, octokit, shouldAddComment, message) { if (shouldAddComment) { await octokit.rest.issues.createComment({ diff --git a/index.test.js b/index.test.js index 0b365f1..1190848 100644 --- a/index.test.js +++ b/index.test.js @@ -20,6 +20,8 @@ describe("Required Labels", () => { GITHUB_EVENT_NAME: "", GITHUB_EVENT_PATH: "", INPUT_TOKEN: "this_is_invalid", + INPUT_MESSAGE: + "Label error. Requires {{errorString}} {{count}} of: {{ provided }}. Found: {{ applied }}", }); core.setOutput = jest.fn(); @@ -355,20 +357,44 @@ describe("Required Labels", () => { ); }); - it("adds a comment when add_comment is true", async () => { + it("adds a custom comment when comment is provided", async () => { restoreTest = mockPr({ GITHUB_TOKEN: "abc123", INPUT_LABELS: "enhancement,bug", INPUT_MODE: "exactly", INPUT_COUNT: "1", INPUT_ADD_COMMENT: "true", + INPUT_MESSAGE: "This is a static comment", }); mockLabels(["enhancement", "bug"]); nock("https://api.github.com") .post("/repos/mheap/missing-repo/issues/28/comments", { - body: "Label error. Requires exactly 1 of: enhancement, bug. Found: enhancement, bug", + body: "This is a static comment", + }) + .reply(201); + + await action(); + }); + + it("interpolates correctly", async () => { + restoreTest = mockPr({ + GITHUB_TOKEN: "abc123", + INPUT_LABELS: "enhancement,bug", + INPUT_MODE: "exactly", + INPUT_COUNT: "1", + INPUT_ADD_COMMENT: "true", + // Spacing is important within braces. Proves that templating is space-tolerant + INPUT_MESSAGE: + "Mode: {{mode }}, Count: {{ count}}, Error String: {{errorString }}, Provided: {{ provided }}, Applied: {{applied}}", + }); + + mockLabels(["enhancement", "bug"]); + + nock("https://api.github.com") + .post("/repos/mheap/missing-repo/issues/28/comments", { + body: "Mode: exactly, Count: 1, Error String: exactly, Provided: enhancement, bug, Applied: enhancement, bug", }) .reply(201);