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

release 32 #94

Closed
wants to merge 37 commits into from
Closed
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
99 changes: 64 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,50 @@ This GitHub Action sends notifications to a Slack channel whenever a pull reques
## Features

1. **Initial Commit List Notification**: When a pull request is created (opened), the action sends a message to a designated Slack channel with the PR details:
- **Message**:
```
New release pull request created: "PR title"
Branch: source_branch -> target_branch
```
- Additionally, a thread is created with a list of commits related to the PR:
```
Commits in this pull request:
- commit1 by @SlackUser1
- commit2 by @commit.author
```
- If `github-to-slack-map` is provided, the action will use the Slack user instead of GitHub usernames to tag users in Slack messages.

- **Message**:
```
New release pull request created: "PR title"
Branch: source_branch -> target_branch
```
- Additionally, a thread is created with a list of commits related to the PR:
```
Commits in this pull request:
- commit1 by @SlackUser1
- commit2 by @commit.author
```
- If `github-to-slack-map` is provided, the action will use the Slack user instead of GitHub usernames to tag users in Slack messages.

2. **Slack Message Timestamp in PR Description**: The action updates the PR description to include the Slack message timestamp (`Slack message_ts`). This allows the timestamp to be retrieved later for sending follow-up messages in the same Slack thread when new commits are pushed to the branch.

3. **New Commits Notification**: When a pull request is updated (synchronized), the action retrieves the Slack message timestamp from the PR body (description) and sends a message with the last commit added to the PR using the GitHub username of the commit author:
- **Message**:
```
New commit added: last commit by @githubUser
```

- **Message**:
```
New commit added: last commit by @githubUser
```

4. **PR Merged Notification**: When a pull request is merged (closed), the action fetches the Slack message timestamp and sends a notification to the same Slack thread, informing the team that the PR has been merged using the GitHub username of the user who merged the PR:
- **Message**:
```
Pull request "PR title" was merged by @githubUser
```
- **Message**:
```
Pull request "PR title" was merged by @githubUser
```

## Inputs

| Input | Required | Description | Default Value |
|------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
| slack-bot-token | required | The Slack bot token. | N/A |
| slack-channel | required | The Slack channel ID where the notifications will be sent. | N/A |
| github-token | required | The GitHub token (typically `${{ secrets.GITHUB_TOKEN }}`). | N/A |
| github-to-slack-map | optional | A JSON string mapping GitHub usernames to Slack user IDs for tagging in messages. If not provided, GitHub usernames will be used in the Slack messages. Example: `{"githubUsername1": "slackUserID1", "githubUsername2": "slackUserID2"}`. | N/A |
| initial-message-template | optional | Template for the initial message when a PR is created. | `New release pull request created: \<${prUrl}\|\${prTitle}>\n*From*: ${branchName} → *To*: ${targetBranch}` |
| commit-list-message-template | optional | Template for the message with the list of commits when a PR is created. | `Commits in this pull request:\n${commitListMessage}\n\n\<${changelogUrl}\|Full Changelog: ${branchName} to ${targetBranch}>` |
| update-message-template | optional | Template for the message when a PR is updated with new commits. | `New commit added: \<${commitUrl}\|\${commitMessage}> by @${githubUser}` |
| close-message-template | optional | Template for the message when a PR is merged. | `Pull request \<${prUrl}\|\${prTitle}> was merged by @${mergedBy}` |


| Input | Required | Description | Default Value |
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| slack-bot-token | required | The Slack bot token. | N/A |
| slack-channel | required | The Slack channel ID where the notifications will be sent. | N/A |
| github-token | required | The GitHub token (typically `${{ secrets.GITHUB_TOKEN }}`). | N/A |
| github-to-slack-map | optional | A JSON string mapping GitHub usernames to Slack user IDs for tagging in messages. If not provided, GitHub usernames will be used in the Slack messages. Example: `{"githubUsername1": "slackUserID1", "githubUsername2": "slackUserID2"}`. | N/A |
| initial-message-template | optional | Template for the initial message when a PR is created. | `New release pull request created: \<${prUrl}\|\${prTitle}>\n*From*: ${branchName} → *To*: ${targetBranch}` |
| commit-list-message-template | optional | Template for the message with the list of commits when a PR is created. | `Commits in this pull request:\n${commitListMessage}\n\n\<${changelogUrl}\|Full Changelog: ${branchName} to ${targetBranch}>` |
| update-message-template | optional | Template for the message when a PR is updated with new commits. | `New commit added: \<${commitUrl}\|\${commitMessage}> by @${githubUser}` |
| close-message-template | optional | Template for the message when a PR is merged. | `Pull request \<${prUrl}\|\${prTitle}> was merged by @${mergedBy}` |

To use this action, create a workflow file in your repository (e.g., `.github/workflows/release-notifications.yml`) with the following content:

```yml
name: Release Notification on PR Opened or Updated

Expand Down Expand Up @@ -91,8 +92,8 @@ jobs:

1. In your app settings, go to "OAuth & Permissions".
2. Scroll down to "Bot Token Scopes" and add the following scopes:
- `chat:write`
- `chat:write.public`
- `chat:write`
- `chat:write.public`
3. Click "Save Changes".

#### Install App to Workspace:
Expand All @@ -111,7 +112,35 @@ jobs:

Ensure that the GitHub token (`GITHUB_TOKEN`) has read and write permissions. You can configure this in your repository settings under `Settings` > `Actions` > `General` > `Workflow permissions`.


## License

This project is licensed under the MIT License - see the LICENSE file for details.
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
111 changes: 83 additions & 28 deletions dist/handlePROpened.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,42 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.handlePROpened = void 0;
const github = __importStar(require("@actions/github"));
const core = __importStar(require("@actions/core"));
async function fetchAllCommits(owner, repo, pullNumber, githubToken) {
const allCommits = [];
let url = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/commits?per_page=100`;
let page = 1;
while (url) {
core.info(`Fetching page ${page}: ${url}`);
const response = await fetch(url, {
headers: {
Authorization: `token ${githubToken}`,
},
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`GitHub API request failed: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
}
const commitsData = await response.json();
core.info(`Fetched ${commitsData.length} commits on page ${page}`);
if (!Array.isArray(commitsData) || commitsData.length === 0) {
break;
}
allCommits.push(...commitsData);
const linkHeader = response.headers.get('link');
core.info(`Link Header: ${linkHeader}`);
if (linkHeader) {
const nextLinkMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
url = nextLinkMatch ? nextLinkMatch[1] : null;
}
else {
url = null;
}
page++;
}
core.info(`Fetched a total of ${allCommits.length} commits`);
return allCommits;
}
async function handlePROpened(slackToken, slackChannel, githubToken, initialMessageTemplate, commitListMessageTemplate, githubToSlackMap) {
const pr = github.context.payload.pull_request;
if (!pr) {
Expand Down Expand Up @@ -65,15 +101,11 @@ async function handlePROpened(slackToken, slackChannel, githubToken, initialMess
pull_number: prNumber,
body: newPrBody,
});
const commitsUrl = pr.commits_url;
const commitsResponse = await fetch(commitsUrl, {
headers: {
Authorization: `token ${githubToken}`,
},
});
const commitsData = await commitsResponse.json();
const repoUrl = `https://github.com/${github.context.repo.owner}/${github.context.repo.repo}`;
const commitMessages = commitsData
const { owner, repo } = github.context.repo;
core.info(`Commits URL: https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/commits`);
const commitsData = await fetchAllCommits(owner, repo, prNumber, githubToken);
const repoUrl = `https://github.com/${owner}/${repo}`;
let commitMessages = commitsData
.map((commit) => {
const commitMessage = commit.commit.message.split('\n')[0]; // Extract only the first line
const commitSha = commit.sha;
Expand All @@ -86,24 +118,47 @@ async function handlePROpened(slackToken, slackChannel, githubToken, initialMess
return `- <${commitUrl}|${commitMessage}> by ${userDisplay}`;
})
.join('\n');
const changelogUrl = `${repoUrl}/compare/${targetBranch}...${branchName}`;
const commitListMessage = commitListMessageTemplate
.replace('${commitListMessage}', commitMessages)
.replace('${changelogUrl}', changelogUrl)
.replace('${branchName}', branchName)
.replace('${targetBranch}', targetBranch)
.replace(/\\n/g, '\n'); // Replace escaped newline characters with actual newline characters
await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
Authorization: `Bearer ${slackToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: slackChannel,
text: commitListMessage,
thread_ts: messageTs,
}),
});
if (commitMessages.length > 4000) {
// Slack message limit is 4000 characters
const commitMessagesArr = commitMessages.match(/[\s\S]{1,4000}/g) || [];
for (let i = 0; i < commitMessagesArr.length; i++) {
const text = i === commitMessagesArr.length - 1
? `${commitMessagesArr[i]}\n\n<${repoUrl}/compare/${targetBranch}...${branchName}|Full Changelog: ${branchName} to ${targetBranch}>`
: commitMessagesArr[i];
await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
Authorization: `Bearer ${slackToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: slackChannel,
text: text,
thread_ts: messageTs,
}),
});
}
}
else {
const changelogUrl = `${repoUrl}/compare/${targetBranch}...${branchName}`;
const commitListMessage = commitListMessageTemplate
.replace('${commitListMessage}', commitMessages)
.replace('${changelogUrl}', changelogUrl)
.replace('${branchName}', branchName)
.replace('${targetBranch}', targetBranch)
.replace(/\\n/g, '\n'); // Replace escaped newline characters with actual newline characters
await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
Authorization: `Bearer ${slackToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: slackChannel,
text: commitListMessage,
thread_ts: messageTs,
}),
});
}
}
exports.handlePROpened = handlePROpened;
Loading