Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom messages #46

Merged
merged 1 commit into from Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 39 additions & 28 deletions README.md
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
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
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
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