Skip to content

Commit

Permalink
Only use a single comment when the action fails (#53)
Browse files Browse the repository at this point in the history
* Update README to require write permission for comments
* Update existing comment if found
* Delete comment when action passes
* Update README to v4
  • Loading branch information
mheap authored Apr 2, 2023
1 parent 9ac1278 commit e330921
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 25 deletions.
42 changes: 22 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ This action allows you to fail the build if/unless a certain combination of labe

This action has three required inputs; `labels`, `mode` and `count`

| 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 |
| 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. If a comment already exists, it will be updated. When the action passes, the comment will be deleted | false | false |
| `exit_type` | The exit type of the action. One of: failure, success | false |

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.

Expand All @@ -31,10 +31,10 @@ jobs:
label:
runs-on: ubuntu-latest
permissions:
issues: read
pull-requests: read
issues: write
pull-requests: write
steps:
- uses: mheap/github-action-required-labels@v3
- uses: mheap/github-action-required-labels@v4
with:
mode: exactly
count: 1
Expand All @@ -44,7 +44,7 @@ jobs:
### Prevent merging if a label exists
```yaml
- uses: mheap/github-action-required-labels@v3
- uses: mheap/github-action-required-labels@v4
with:
mode: exactly
count: 0
Expand All @@ -58,20 +58,22 @@ You can choose to add a comment to the PR when the action fails. The default for
> Label error. Requires {{ errorString }} {{ count }} of: {{ provided }}. Found: {{ applied }}
```yaml
- uses: mheap/github-action-required-labels@v3
- uses: mheap/github-action-required-labels@v4
with:
mode: exactly
count: 1
labels: "semver:patch, semver:minor, semver:major"
add_comment: true
```
If a comment already exists, it will be updated. When the action passes, the comment will be deleted.
### 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
- uses: mheap/github-action-required-labels@v4
with:
mode: exactly
count: 1
Expand All @@ -93,7 +95,7 @@ The following tokens are available for use in custom messages:
### Require multiple labels

```yaml
- uses: mheap/github-action-required-labels@v3
- uses: mheap/github-action-required-labels@v4
with:
mode: minimum
count: 2
Expand All @@ -105,7 +107,7 @@ The following tokens are available for use in custom messages:
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
- uses: mheap/github-action-required-labels@v4
with:
mode: minimum
count: 2
Expand All @@ -126,13 +128,13 @@ jobs:
label:
runs-on: ubuntu-latest
permissions:
issues: read
pull-requests: read
issues: write
pull-requests: write
outputs:
status: ${{ steps.check-labels.outputs.status }}
steps:
- id: check-labels
uses: mheap/github-action-required-labels@v3
uses: mheap/github-action-required-labels@v4
with:
mode: exactly
count: 1
Expand Down
39 changes: 37 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const core = require("@actions/core");
const github = require("@actions/github");

const matchToken = `<!-- reqlabelmessage -->`;
async function action() {
try {
const token = core.getInput("token", { required: true });
Expand Down Expand Up @@ -84,6 +85,24 @@ async function action() {
return;
}

// Remove the comment if it exists
if (shouldAddComment) {
const { data: existing } = await octokit.rest.issues.listComments({
...github.context.repo,
issue_number: github.context.issue.number,
});

const generatedComment = existing.find((c) =>
c.body.includes(matchToken)
);
if (generatedComment) {
await octokit.rest.issues.deleteComment({
...github.context.repo,
comment_id: generatedComment.id,
});
}
}

core.setOutput("status", "success");
} catch (e) {
core.setFailed(e.message);
Expand All @@ -98,11 +117,27 @@ function tmpl(t, o) {

async function exitWithError(exitType, octokit, shouldAddComment, message) {
if (shouldAddComment) {
await octokit.rest.issues.createComment({
// Is there an existing comment?
const { data: existing } = await octokit.rest.issues.listComments({
...github.context.repo,
issue_number: github.context.issue.number,
body: message,
});

const generatedComment = existing.find((c) => c.body.includes(matchToken));

const params = {
...github.context.repo,
issue_number: github.context.issue.number,
body: `${matchToken}${message}`,
};

// If so, update it
let method = "createComment";
if (generatedComment) {
method = "updateComment";
params.comment_id = generatedComment.id;
}
await octokit.rest.issues[method](params);
}

core.setOutput("status", "failure");
Expand Down
90 changes: 87 additions & 3 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const mockedEnv = require("mocked-env");
const nock = require("nock");
nock.disableNetConnect();

const matchToken = `<!-- reqlabelmessage -->`;

describe("Required Labels", () => {
let restore;
let restoreTest;
Expand Down Expand Up @@ -90,14 +92,35 @@ describe("Required Labels", () => {

mockLabels(["bug"]);

mockListComments([]);

nock("https://api.github.com")
.post("/repos/mheap/missing-repo/issues/28/comments", {
body: "Label error. Requires exactly 1 of: enhancement. Found: bug",
body: `${matchToken}Label error. Requires exactly 1 of: enhancement. Found: bug`,
})
.reply(201);

await action();
});

it("deletes a comment when passing", async () => {
restoreTest = mockPr({
INPUT_LABELS: "bug",
INPUT_MODE: "exactly",
INPUT_COUNT: "1",
INPUT_ADD_COMMENT: "true",
GITHUB_TOKEN: "mock-token-here-abc",
});

mockLabels(["bug"]);
mockListComments([{ id: "12345", body: `${matchToken}This` }]);

nock("https://api.github.com")
.delete("/repos/mheap/missing-repo/issues/comments/12345")
.reply(200);

await action();
});
});

describe("success", () => {
Expand Down Expand Up @@ -369,9 +392,58 @@ describe("Required Labels", () => {

mockLabels(["enhancement", "bug"]);

mockListComments([]);

nock("https://api.github.com")
.post("/repos/mheap/missing-repo/issues/28/comments", {
body: `${matchToken}This is a static comment`,
})
.reply(201);

await action();
});

it("updates an existing comment when one is found", 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"]);

mockListComments([{ id: "12345", body: `${matchToken}This` }]);

nock("https://api.github.com")
.patch("/repos/mheap/missing-repo/issues/comments/12345", {
issue_number: 28,
body: `${matchToken}This is a static comment`,
})
.reply(200);

await action();
});

it("creates when comments exist but don't match", 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"]);

mockListComments([{ id: "12345", body: `No Match` }]);

nock("https://api.github.com")
.post("/repos/mheap/missing-repo/issues/28/comments", {
body: "This is a static comment",
body: `${matchToken}This is a static comment`,
})
.reply(201);

Expand All @@ -392,9 +464,10 @@ describe("Required Labels", () => {

mockLabels(["enhancement", "bug"]);

mockListComments([]);
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",
body: `${matchToken}Mode: exactly, Count: 1, Error String: exactly, Provided: enhancement, bug, Applied: enhancement, bug`,
})
.reply(201);

Expand Down Expand Up @@ -428,6 +501,17 @@ function mockLabels(labels) {
);
}

function mockListComments(comments) {
nock("https://api.github.com")
.get("/repos/mheap/missing-repo/issues/28/comments")
.reply(
200,
comments.map((c) => {
return { body: c.body, id: c.id };
})
);
}

function mockEvent(eventName, mockPayload, additionalParams = {}) {
github.context.payload = mockPayload;

Expand Down

0 comments on commit e330921

Please sign in to comment.