-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Related: Homebrew/homebrew-core#190657 (comment) Co-authored-by: Sean Molenaar <[email protected]> Co-authored-by: Mike McQuaid <[email protected]>
- Loading branch information
1 parent
f3fe7e5
commit 42a7fa0
Showing
4 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Create Or Update Issue | ||
|
||
An action to create or update an issue in a repository. | ||
It supports posting a comment under an existing issue with the same title or | ||
closing it based on the outcome of a previous step. | ||
|
||
## Usage | ||
|
||
```yaml | ||
- uses: Homebrew/actions/create-issue@master | ||
with: | ||
token: ${{ github.token }} # defaults to this | ||
repository: ${{ github.repository }} # defaults to this | ||
title: Issue title | ||
body: Issue body | ||
labels: label1,label2 # optional | ||
assignees: user1,user2 # optional | ||
# If true: post `body` as a comment under the issue with the same title, if | ||
# such an issue is found; otherwise, create a new issue. | ||
update-existing: ${{ steps.<step-id>.conclusion == 'failure' }} | ||
# If true: close an existing issue with the same title as completed, if such | ||
# an issue is found; otherwise, do nothing. | ||
close-existing: ${{ steps.<step-id>.conclusion == 'success' }} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
name: Create or update issue | ||
description: Create or update an issue in a repository | ||
author: ZhongRuoyu | ||
branding: | ||
icon: alert-circle | ||
color: blue | ||
inputs: | ||
token: | ||
description: GitHub token | ||
required: false | ||
default: ${{ github.token }} | ||
repository: | ||
description: Repository to create or update the issue in | ||
required: false | ||
default: ${{ github.repository }} | ||
title: | ||
description: The title of the issue | ||
required: true | ||
body: | ||
description: The body of the issue | ||
required: true | ||
labels: | ||
description: Comma-separated list of labels to add to the issue | ||
required: false | ||
assignees: | ||
description: Comma-separated list of users to assign the issue to | ||
required: false | ||
update-existing: | ||
description: > | ||
Whether to post `body` as a comment under the issue with the same title, | ||
if such an issue is found; otherwise, create a new issue | ||
required: false | ||
default: "false" | ||
close-existing: | ||
description: > | ||
Whether to close an existing issue with the same title as completed, if | ||
such an issue is found; otherwise, do nothing. | ||
NOTE: if set to `true`, no new issue will be created! | ||
required: false | ||
default: "false" | ||
outputs: | ||
outcome: | ||
description: > | ||
One of `created`, `commented`, `closed`, or `none`; indicates the action | ||
taken | ||
issue_number: | ||
description: > | ||
The number of the created, updated, or closed issue; undefined if | ||
`outcome` is `none` | ||
node_id: | ||
description: > | ||
The node ID of the created or updated issue, used in GitHub GraphQL API | ||
queries; undefined if `outcome` is `none` | ||
runs: | ||
using: node20 | ||
main: main.mjs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import core from "@actions/core"; | ||
import github from "@actions/github"; | ||
|
||
async function main() { | ||
try { | ||
const token = core.getInput("token", { required: true }); | ||
const [owner, repo] = | ||
core.getInput("repository", { required: true }).split("/"); | ||
|
||
const title = core.getInput("title", { required: true }); | ||
const body = core.getInput("body", { required: true }); | ||
|
||
const labelsInput = core.getInput("labels"); | ||
const labels = labelsInput ? labelsInput.split(",") : []; | ||
const assigneesInput = core.getInput("assignees"); | ||
const assignees = assigneesInput ? assigneesInput.split(",") : []; | ||
|
||
const updateExisting = core.getInput("update-existing") === "true"; | ||
const closeExisting = core.getInput("close-existing") === "true"; | ||
|
||
const client = github.getOctokit(token); | ||
|
||
let existingIssue = undefined; | ||
if (updateExisting || closeExisting) { | ||
for await (const response of client.paginate.iterator( | ||
client.rest.issues.listForRepo, | ||
{ | ||
owner, | ||
repo, | ||
state: "open", | ||
sort: "created", | ||
direction: "desc", | ||
per_page: 100, | ||
} | ||
)) { | ||
existingIssue = response.data.find((issue) => issue.title === title); | ||
if (existingIssue) { | ||
break; | ||
} | ||
} | ||
} | ||
if (existingIssue) { | ||
if (updateExisting) { | ||
const response = await client.rest.issues.createComment({ | ||
owner, | ||
repo, | ||
issue_number: existingIssue.number, | ||
body, | ||
}); | ||
const commentUrl = response.data.html_url; | ||
|
||
core.info(`Posted comment under existing issue: ${commentUrl}`); | ||
|
||
core.setOutput("outcome", "commented"); | ||
core.setOutput("number", existingIssue.number); | ||
core.setOutput("node_id", existingIssue.node_id); | ||
return; | ||
} | ||
if (closeExisting) { | ||
const response = await client.rest.issues.update({ | ||
owner, | ||
repo, | ||
issue_number: existingIssue.number, | ||
state: "closed", | ||
state_reason: "completed", | ||
}); | ||
const issueUrl = response.data.html_url; | ||
|
||
core.info(`Closed existing issue as completed: ${issueUrl}`); | ||
|
||
core.setOutput("outcome", "closed"); | ||
core.setOutput("number", existingIssue.number); | ||
core.setOutput("node_id", existingIssue.node_id); | ||
return; | ||
} | ||
} | ||
|
||
if (closeExisting) { | ||
core.info("No existing issue found."); | ||
core.setOutput("outcome", "none"); | ||
return; | ||
} | ||
|
||
const response = await client.rest.issues.create({ | ||
owner, | ||
repo, | ||
title, | ||
body, | ||
labels, | ||
assignees, | ||
}); | ||
const issueNumber = response.data.number; | ||
const issueNodeId = response.data.node_id; | ||
const issueUrl = response.data.html_url; | ||
|
||
core.info(`Issue created: ${issueUrl}`); | ||
|
||
core.setOutput("outcome", "created"); | ||
core.setOutput("number", issueNumber); | ||
core.setOutput("node_id", issueNodeId); | ||
} catch (error) { | ||
core.setFailed(error); | ||
} | ||
} | ||
|
||
await main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import util from "node:util"; | ||
|
||
describe("create-issue", async () => { | ||
const token = "fake-token"; | ||
const title = "Issue title"; | ||
const body = "Issue body.\nLorem ipsum dolor sit amet."; | ||
const labels = "label1,label2"; | ||
const assignees = "assignee1,assignee2"; | ||
|
||
const issueNumber = 12345; | ||
|
||
beforeEach(async () => { | ||
mockInput("token", token); | ||
mockInput("repository", GITHUB_REPOSITORY); | ||
mockInput("title", title); | ||
mockInput("body", body); | ||
mockInput("labels", labels); | ||
mockInput("assignees", assignees); | ||
}); | ||
|
||
it("creates an issue", async () => { | ||
const mockPool = githubMockPool(); | ||
|
||
mockPool.intercept({ | ||
method: "POST", | ||
path: `/repos/${GITHUB_REPOSITORY}/issues`, | ||
headers: { | ||
Authorization: `token ${token}`, | ||
}, | ||
body: (htmlBody) => util.isDeepStrictEqual(JSON.parse(htmlBody), { | ||
title, | ||
body, | ||
labels: labels.split(","), | ||
assignees: assignees.split(","), | ||
}), | ||
}).defaultReplyHeaders({ | ||
"Content-Type": "application/json", | ||
}).reply(200, { | ||
number: issueNumber, | ||
}); | ||
|
||
await loadMain(); | ||
}); | ||
|
||
it("for advanced use case with `close-existing: true`", async () => { | ||
mockInput("update-existing", "true"); | ||
|
||
const mockPool = githubMockPool(); | ||
|
||
mockPool.intercept({ | ||
method: "GET", | ||
path: `/repos/${GITHUB_REPOSITORY}/issues?` + | ||
`direction=desc&per_page=100&sort=created&state=open`, | ||
headers: { | ||
Authorization: `token ${token}`, | ||
}, | ||
}).defaultReplyHeaders({ | ||
"Content-Type": "application/json", | ||
}).reply(200, [ | ||
{ | ||
title: "Not the same issue", | ||
number: 54321, | ||
}, | ||
{ | ||
title, | ||
number: issueNumber, | ||
}, | ||
]); | ||
|
||
mockPool.intercept({ | ||
method: "POST", | ||
path: `/repos/${GITHUB_REPOSITORY}/issues/${issueNumber}/comments`, | ||
headers: { | ||
Authorization: `token ${token}`, | ||
}, | ||
body: (htmlBody) => util.isDeepStrictEqual(JSON.parse(htmlBody), { | ||
body, | ||
}), | ||
}).defaultReplyHeaders({ | ||
"Content-Type": "application/json", | ||
}).reply(200, { | ||
html_url: "https://github.com/owner/repo/issues/12345#issuecomment-67890", | ||
}); | ||
|
||
await loadMain(); | ||
}); | ||
|
||
it("for advanced use case with `close-existing: true`", async () => { | ||
mockInput("close-existing", "true"); | ||
|
||
const mockPool = githubMockPool(); | ||
|
||
mockPool.intercept({ | ||
method: "GET", | ||
path: `/repos/${GITHUB_REPOSITORY}/issues?` + | ||
`direction=desc&per_page=100&sort=created&state=open`, | ||
headers: { | ||
Authorization: `token ${token}`, | ||
}, | ||
}).defaultReplyHeaders({ | ||
"Content-Type": "application/json", | ||
}).reply(200, [ | ||
{ | ||
title: "Not the same issue", | ||
number: 54321, | ||
}, | ||
{ | ||
title, | ||
number: issueNumber, | ||
}, | ||
]); | ||
|
||
mockPool.intercept({ | ||
method: "PATCH", | ||
path: `/repos/${GITHUB_REPOSITORY}/issues/${issueNumber}`, | ||
headers: { | ||
Authorization: `token ${token}`, | ||
}, | ||
body: (htmlBody) => util.isDeepStrictEqual(JSON.parse(htmlBody), { | ||
state: "closed", | ||
state_reason: "completed", | ||
}), | ||
}).defaultReplyHeaders({ | ||
"Content-Type": "application/json", | ||
}).reply(200, { | ||
html_url: "https://github.com/owner/repo/issues/12345#issuecomment-67890", | ||
}); | ||
|
||
await loadMain(); | ||
}); | ||
}); |