Skip to content

Commit

Permalink
Allow message overrides for statuses (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
mshick committed Nov 8, 2022
1 parent 75079b4 commit f116a1a
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 86 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ jobs:
- name: Build action
run: |
npm run build
npm run release
- uses: ./
with:
message: |
**Hello ${{ github.run_number }}**
🌏
!
!
repo-token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Supports emoji 😂😂😂!
- Supports a proxy for fork-based PRs. [See below](#proxy-for-fork-based-prs).
- Supports creating a message from a file path.
- Optional message / status overrides.

## Usage

Expand Down Expand Up @@ -66,13 +67,19 @@ jobs:
| -------------------- | -------- | ---------------------------------------------------------------------------------------------------- | -------- | ------- |
| message | with | The message you'd like displayed, supports Markdown and all valid Unicode characters. | maybe | |
| message-path | with | Path to a message you'd like displayed. Will be read and displayed just like a normal message. | maybe | |
| message-success | with | A message override, printed in case of success. | maybe | |
| message-failure | with | A message override, printed in case of failure. | maybe | |
| message-cancelled | with | A message override, printed in case of cancelled. | maybe | |
| status | with | Required if you want to use message status overrides. | maybe | |
| repo-token | with | Valid GitHub token, either the temporary token GitHub provides or a personal access token. | maybe | |
| message-id | with | Message id to use when searching existing comments. If found, updates the existing (sticky comment). | no | |
| allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false |
| proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | |
| GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | maybe | |

## Proxy for Fork-based PRs
## Advanced Uses

### Proxy for Fork-based PRs

GitHub limits `GITHUB_TOKEN` and other API access token permissions when creating a PR from a fork. This precludes adding comments when your PRs are coming from forks, which is the norm for open source projects. To work around this situation I've created a simple companion app you can deploy to Cloud Run or another host to proxy the create comment requests with a personal access token you provide.

Expand All @@ -99,3 +106,32 @@ jobs:
proxy-url: https://add-pr-comment-proxy-94idvmwyie-uc.a.run.app
repo-token: ${{ secrets.GITHUB_TOKEN }}
```

### Status Message Overrides

You can override your messages based on your job status. This can be helpful
if you don't anticipate having the data required to create a helpful message in
case of failure, but you still want a message to be sent to the PR comment.

**Example**

```yaml
on:
pull_request:

jobs:
pr:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@v2
with:
if: always()
message: |
**Howdie!**
message-failure: |
Uh oh!
repo-token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
```
86 changes: 84 additions & 2 deletions __tests__/add-pr-comment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ type Inputs = {
'repo-token': string
'message-id': string
'allow-repeats': string
'message-success'?: string
'message-failure'?: string
'message-cancelled'?: string
status?: 'success' | 'failure' | 'cancelled'
}

const inputs: Inputs = {
Expand All @@ -36,18 +40,27 @@ let postIssueCommentsResponse = {
id: 42,
}

type MessagePayload = {
comment_id?: number
body: string
}

let messagePayload: MessagePayload | undefined

vi.mock('@actions/core')

export const handlers = [
rest.post(
`https://api.github.com/repos/${repoFullName}/issues/:issueNumber/comments`,
(req, res, ctx) => {
async (req, res, ctx) => {
messagePayload = await req.json<MessagePayload>()
return res(ctx.status(200), ctx.json(postIssueCommentsResponse))
},
),
rest.patch(
`https://api.github.com/repos/${repoFullName}/issues/comments/:commentId`,
(req, res, ctx) => {
async (req, res, ctx) => {
messagePayload = await req.json<MessagePayload>()
return res(ctx.status(200), ctx.json(postIssueCommentsResponse))
},
),
Expand Down Expand Up @@ -225,4 +238,73 @@ describe('add-pr-comment action', () => {
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})

it('overrides the default message with a success message on success', async () => {
inputs.message = simpleMessage
inputs['message-path'] = undefined
inputs['repo-token'] = repoToken
inputs['allow-repeats'] = 'false'
inputs['message-success'] = '666'
inputs.status = 'success'

const commentId = 123

getIssueCommentsResponse = [
{
id: commentId,
},
]
postIssueCommentsResponse = {
id: commentId,
}

await run()
expect(messagePayload?.body).toContain('666')
})

it('overrides the default message with a failure message on failure', async () => {
inputs.message = simpleMessage
inputs['message-path'] = undefined
inputs['repo-token'] = repoToken
inputs['allow-repeats'] = 'false'
inputs['message-failure'] = '666'
inputs.status = 'failure'

const commentId = 123

getIssueCommentsResponse = [
{
id: commentId,
},
]
postIssueCommentsResponse = {
id: commentId,
}

await run()
expect(messagePayload?.body).toContain('666')
})

it('overrides the default message with a cancelled message on cancelled', async () => {
inputs.message = simpleMessage
inputs['message-path'] = undefined
inputs['repo-token'] = repoToken
inputs['allow-repeats'] = 'false'
inputs['message-cancelled'] = '666'
inputs.status = 'cancelled'

const commentId = 123

getIssueCommentsResponse = [
{
id: commentId,
},
]
postIssueCommentsResponse = {
id: commentId,
}

await run()
expect(messagePayload?.body).toContain('666')
})
})
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ inputs:
proxy-url:
description: "Proxy URL for comment creation"
required: false
status:
description: "Provide a job status for status headers."
required: false
message-success:
description: "Override the message when a run is successful."
required: false
message-failure:
description: "Override the message when a run fails."
required: false
message-cancelled:
description: "Override the message when a run is cancelled."
required: false
outputs:
comment-created:
description: "Whether a comment was created."
Expand Down
72 changes: 48 additions & 24 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,49 +38,73 @@ const github = __importStar(__nccwpck_require__(5438));
const http_client_1 = __nccwpck_require__(6255);
const promises_1 = __importDefault(__nccwpck_require__(3977));
const getIssueNumberFromCommitPullsList = (commitPullsList) => (commitPullsList.length ? commitPullsList[0].number : null);
const createCommentProxy = async (params) => {
async function createCommentProxy(params) {
const { repoToken, owner, repo, issueNumber, body, commentId, proxyUrl } = params;
const http = new http_client_1.HttpClient('http-client-add-pr-comment');
const response = await http.postJson(`${proxyUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`, { comment_id: commentId, body }, {
['temporary-github-token']: repoToken,
});
return response.result;
};
const getExistingCommentId = (comments, messageId) => {
}
function getExistingCommentId(comments, messageId) {
const found = comments.find(({ body }) => {
var _a;
return ((_a = body === null || body === void 0 ? void 0 : body.search(messageId)) !== null && _a !== void 0 ? _a : -1) > -1;
});
return found === null || found === void 0 ? void 0 : found.id;
};
const getInputs = () => {
}
async function getInputs() {
const messageId = core.getInput('message-id');
const messageInput = core.getInput('message');
const messagePath = core.getInput('message-path');
const repoToken = core.getInput('repo-token') || process.env['GITHUB_TOKEN'];
const status = core.getInput('status');
if (!repoToken) {
throw new Error('no github token provided, set one with the repo-token input or GITHUB_TOKEN env variable');
}
if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path');
}
let message;
if (messagePath) {
message = await promises_1.default.readFile(messagePath, { encoding: 'utf8' });
}
else {
message = messageInput;
}
const messageSuccess = core.getInput(`message-success`);
const messageFailure = core.getInput(`message-failure`);
const messageCancelled = core.getInput(`message-cancelled`);
if ((messageSuccess || messageFailure || messageCancelled) && !status) {
throw new Error('to use a status message you must provide a status input');
}
if (status) {
if (status === 'success' && messageSuccess) {
message = messageSuccess;
}
if (status === 'failure' && messageFailure) {
message = messageFailure;
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled;
}
}
if (!message) {
throw new Error('no message, check your message inputs');
}
return {
allowRepeats: Boolean(core.getInput('allow-repeats') === 'true'),
message: core.getInput('message'),
message,
messageId: messageId === '' ? 'add-pr-comment' : messageId,
messagePath: core.getInput('message-path'),
proxyUrl: core.getInput('proxy-url').replace(/\/$/, ''),
repoToken: core.getInput('repo-token') || process.env['GITHUB_TOKEN'],
repoToken,
status,
};
};
}
const run = async () => {
try {
const { allowRepeats, message, messageId, messagePath, repoToken, proxyUrl } = getInputs();
const { allowRepeats, message, messageId, repoToken, proxyUrl } = await getInputs();
const messageIdComment = `<!-- ${messageId} -->`;
if (!repoToken) {
throw new Error('no github token provided, set one with the repo-token input or GITHUB_TOKEN env variable');
}
if (message && messagePath) {
throw new Error('must specify only one, message or message-path');
}
let messageText = message;
if (messagePath) {
messageText = await promises_1.default.readFile(messagePath, { encoding: 'utf8' });
}
if (!messageText) {
throw new Error('could not get message text, check your message-path');
}
const { payload: { pull_request: pullRequest, issue, repository }, sha: commitSha, } = github.context;
if (!repository) {
core.info('unable to determine repository from request type');
Expand Down Expand Up @@ -130,7 +154,7 @@ const run = async () => {
}
}
let comment;
const body = `${messageIdComment}\n\n${messageText}`;
const body = `${messageIdComment}\n\n${message}`;
if (proxyUrl) {
comment = await createCommentProxy({
commentId: existingCommentId,
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit f116a1a

Please sign in to comment.