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

Only use a single comment when the action fails #53

Merged
merged 3 commits into from
Apr 2, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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