Skip to content

Commit

Permalink
Add ability to customise comment / error message
Browse files Browse the repository at this point in the history
  • Loading branch information
mheap committed Dec 30, 2022
1 parent 43f0d74 commit 6008ef3
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 36 deletions.
67 changes: 39 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 17 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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({
Expand Down
30 changes: 28 additions & 2 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 6008ef3

Please # to comment.