Add prebuilt integration to send a slack notification when github pr merged - #56
Add prebuilt integration to send a slack notification when github pr merged#56TharaniDJ wants to merge 35 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughNew Ballerina integration: listens for GitHub pull-request closed events, filters merged PRs by configurable criteria, computes PR cycle time, routes and posts formatted notifications to Slack; includes Choreo component descriptor, schema, docs, and CI registry update. Changes
Sequence Diagram(s)sequenceDiagram
participant GitHub as GitHub (Event Source)
participant Service as Ballerina Integration
participant Logic as Filter & Routing Logic
participant Slack as Slack API
GitHub->>Service: onClosed(PullRequestEvent)
Service->>Service: Check event.pull_request.merged
alt PR Not Merged
Service-->>GitHub: Exit
else PR Merged
Service->>Logic: shouldProcessPullRequest(pr)
alt Filters Not Matched
Service-->>GitHub: Exit
else Filters Matched
Service->>Logic: calculateCycleTime(pr)
Logic-->>Service: cycle_time
Service->>Logic: buildSlackMessage(pr, repo)
Logic-->>Service: message_text
Service->>Logic: getTargetChannel(repo, branch)
Logic-->>Service: target_channel
Service->>Slack: POST /chat.postMessage {channel, text}
Slack-->>Service: {ok, ts}
Service-->>GitHub: Log success & return
end
end
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new prebuilt Ballerina integration that listens for GitHub Pull Request “closed” events and posts a Slack notification when the PR is merged, with optional filtering and channel routing.
Changes:
- Implement GitHub PR-merged webhook service, Slack client wiring, and configurable filters/routing.
- Add helper functions for filtering, cycle time calculation, routing resolution, and Slack message formatting.
- Add integration documentation + Choreo assets (instructions, diagram, config schema) and register the project in the workflow list.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| ballerina-integrator/slack-notification-github-pr-merged/main.bal | GitHub PR webhook service that triggers Slack notifications on merged PRs. |
| ballerina-integrator/slack-notification-github-pr-merged/functions.bal | Filtering, routing, cycle-time calculation, and Slack message construction helpers. |
| ballerina-integrator/slack-notification-github-pr-merged/connections.bal | Initializes Slack client and GitHub webhook listener. |
| ballerina-integrator/slack-notification-github-pr-merged/config.bal | Defines configurable inputs (Slack auth, webhook settings, filters, routing). |
| ballerina-integrator/slack-notification-github-pr-merged/README.md | User-facing setup and configuration documentation. |
| ballerina-integrator/slack-notification-github-pr-merged/Ballerina.toml | Package metadata and dependencies. |
| ballerina-integrator/slack-notification-github-pr-merged/.gitignore | Ignores build artifacts and local Config.toml. |
| ballerina-integrator/slack-notification-github-pr-merged/.choreo/instructions.md | Choreo setup steps for Slack + GitHub + configuration. |
| ballerina-integrator/slack-notification-github-pr-merged/.choreo/diagram.md | Mermaid flow/overview diagram for the integration. |
| ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.json | Config form schema for rendering configuration in tooling. |
| ballerina-integrator/slack-notification-github-pr-merged/.choreo/choreo-config-schema.json | Choreo-specific copy of the configuration schema. |
| .github/workflows/projects.json | Registers the new integration path in the workflow project list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| string? prBody = pr.body; | ||
| if prBody is string && prBody.trim() != "" { | ||
| string description = prBody; | ||
| if description.length() > 200 { | ||
| description = description.substring(0, 200) + "..."; | ||
| } | ||
| message += "*Description:*\n" + description + "\n\n"; | ||
| } |
There was a problem hiding this comment.
PR title/body/login are untrusted inputs but are inserted into Slack markup without escaping. This can lead to unwanted mentions (e.g., @channel, <!here>) and malformed links/formatting, effectively allowing notification content injection/spam. Consider escaping Slack special characters (&, <, >) and neutralizing mention patterns (or using Slack API options/fields to disable linkifying/mentions) before embedding these values into the message.
| time:Utc|error createdTime = time:utcFromString(createdAt); | ||
| time:Utc|error mergedTime = time:utcFromString(mergedAt); | ||
|
|
||
| if createdTime is time:Utc && mergedTime is time:Utc { | ||
| decimal diffSeconds = time:utcDiffSeconds(mergedTime, createdTime); | ||
| decimal hours = diffSeconds / 3600.0; | ||
| return hours; | ||
| } |
There was a problem hiding this comment.
If timestamp parsing fails (utcFromString returns error), the error case is silently ignored and cycle time is omitted with no observability. Consider explicitly handling/logging the parse errors (at least at debug/warn level) so misformatted timestamps or API shape changes are diagnosable in production.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.json (1)
164-227: Consider adding meaningful descriptions for configuration fields.All description fields under
wso2.devantare empty strings. Adding descriptions would improve the user experience when filling out configuration forms, especially for fields likeslackToken,slackChannelId,channelRouting, and filter options.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.json` around lines 164 - 227, The schema contains empty description strings for configuration properties (e.g., slackToken, slackChannelId, webhookPort, githubCallback, githubRepos, filterBaseBranches, filterLabels, filterAuthor, includePrDescription, includeReviewers, includeDiffStats, includeCycleTime, channelRouting); update each property's "description" to a concise, user-friendly sentence explaining its purpose and expected value (for example: slackToken — "Slack Bot User OAuth token (xoxb-...) used to post messages", slackChannelId — "Target Slack channel ID to post PR merge notifications", channelRouting — "Optional mapping of repositories to Slack channels", filterBaseBranches/filterLabels/filterAuthor — "Filters to limit which PRs trigger notifications"), ensure boolean flags clarify true/false behavior, and keep wording consistent across all listed fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.json`:
- Around line 1-241: The two schema files are duplicates: remove the redundant
.choreo/config-schema.json and keep .choreo/choreo-config-schema.json (or vice
versa), update any references in the repo or CI that import "config-schema.json"
to point to the retained file, and ensure the retained schema file remains at
the exact content from the removed duplicate; confirm no other tooling expects
both filenames before deleting the duplicate.
In
`@ballerina-integrator/slack-notification-github-pr-merged/.choreo/instructions.md`:
- Around line 137-139: The fenced code block showing the array example should
include a language tag to satisfy markdownlint MD040; update the fence that
currently wraps ["TharaniDJ/devant/main:C0AKY8K8DT3",
"TharaniDJ/devant/develop:C0AKY8UU74Z"] to use `json` (i.e., change the opening
``` to ```json) so the block is properly typed and linting passes.
In `@ballerina-integrator/slack-notification-github-pr-merged/config.bal`:
- Around line 5-10: Add a configurable string variable named githubWebhookSecret
to config.bal (e.g., configurable string githubWebhookSecret = "";), then update
the listener initialization in connections.bal to pass that secret into the
trigger.github:Listener via its webhookSecret parameter (ensure the variable
name used matches the configurable and is passed when constructing/initializing
the listener in the function or statement that creates the GitHub listener).
In `@ballerina-integrator/slack-notification-github-pr-merged/functions.bal`:
- Around line 103-117: The PR title and body are injected raw into the Slack
top-level text via the message string (references: pr.title, pr.body in the
message concatenation and the chat.postMessage call where mrkdwn is used), which
lets `<`, `>`, `&` trigger Slack markdown/mentions; fix by escaping those
characters in pr.title and pr.body (replace & with &, < with <, > with
>) before concatenation into message (or alternatively disable mrkdwn by
setting mrkdwn: false in the chat.postMessage payload), and ensure the updated
sanitized variables are used wherever message is composed (look for the message
+= lines that reference pr.title and prBody).
In `@ballerina-integrator/slack-notification-github-pr-merged/main.bal`:
- Around line 16-23: Add a guard in the webhook handler to check the configured
allowlist `githubRepos` against `event.repository.full_name` before any further
processing: if the repository is not present in `githubRepos`, log/return early
so you never reach `shouldProcessPullRequest` or `buildSlackMessage`. Locate the
webhook handler where `shouldProcessPullRequest(event.pull_request)` is called
and insert the repository membership check there (using
`event.repository.full_name` and the `githubRepos` collection) to bail out when
the repo is not allowed.
In `@ballerina-integrator/slack-notification-github-pr-merged/README.md`:
- Around line 121-144: The fenced code block showing the "Pull Request Merged
Successfully!" message is missing a language specifier; update the opening
triple-backtick fence to include a language like text or plaintext (e.g., change
``` to ```text) so the static analysis warning is resolved and the block is
explicitly marked as plain text; locate the markdown fenced block that begins
with ``` and contains the PR summary and add the language identifier there.
---
Nitpick comments:
In
`@ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.json`:
- Around line 164-227: The schema contains empty description strings for
configuration properties (e.g., slackToken, slackChannelId, webhookPort,
githubCallback, githubRepos, filterBaseBranches, filterLabels, filterAuthor,
includePrDescription, includeReviewers, includeDiffStats, includeCycleTime,
channelRouting); update each property's "description" to a concise,
user-friendly sentence explaining its purpose and expected value (for example:
slackToken — "Slack Bot User OAuth token (xoxb-...) used to post messages",
slackChannelId — "Target Slack channel ID to post PR merge notifications",
channelRouting — "Optional mapping of repositories to Slack channels",
filterBaseBranches/filterLabels/filterAuthor — "Filters to limit which PRs
trigger notifications"), ensure boolean flags clarify true/false behavior, and
keep wording consistent across all listed fields.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 36e56f3a-9dbd-4177-89b7-02772fa3bff4
📒 Files selected for processing (12)
.github/workflows/projects.jsonballerina-integrator/slack-notification-github-pr-merged/.choreo/choreo-config-schema.jsonballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.jsonballerina-integrator/slack-notification-github-pr-merged/.choreo/diagram.mdballerina-integrator/slack-notification-github-pr-merged/.choreo/instructions.mdballerina-integrator/slack-notification-github-pr-merged/.gitignoreballerina-integrator/slack-notification-github-pr-merged/Ballerina.tomlballerina-integrator/slack-notification-github-pr-merged/README.mdballerina-integrator/slack-notification-github-pr-merged/config.balballerina-integrator/slack-notification-github-pr-merged/connections.balballerina-integrator/slack-notification-github-pr-merged/functions.balballerina-integrator/slack-notification-github-pr-merged/main.bal
| // GitHub webhook configuration | ||
| configurable int webhookPort = 8090; | ||
| configurable string githubCallback = ""; | ||
|
|
||
| // GitHub repository (org/repo format) | ||
| configurable string[] githubRepos = ?; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f "config\.bal|connections\.bal" --max-depth 3Repository: wso2/integration-samples
Length of output: 3726
🏁 Script executed:
cat -n ballerina-integrator/slack-notification-github-pr-merged/config.balRepository: wso2/integration-samples
Length of output: 1141
🏁 Script executed:
cat -n ballerina-integrator/slack-notification-github-pr-merged/connections.balRepository: wso2/integration-samples
Length of output: 520
🏁 Script executed:
# Search for trigger.github usage in the repo to see if secret is used anywhere
rg "trigger\.github|github:Listener" -A 5 -B 2Repository: wso2/integration-samples
Length of output: 3099
🌐 Web query:
Ballerina trigger.github webhook secret listener configuration
💡 Result:
To configure a GitHub webhook secret for the Ballerina ballerinax/trigger.github listener, set it in the listener config and pass that config when creating the listener:
import ballerinax/trigger.github;
configurable github:ListenerConfig cfg = {
webhookSecret: "<same value you set as the GitHub webhook secret>"
};
listener github:Listener webhookListener = new (cfg, 8090);- The listener config is optional; if you didn’t set a secret in GitHub, you can omit the config:
listener github:Listener webhookListener = new (listenOn = 8090);(default port is 8090). [1] - On the GitHub side, signature headers (e.g.,
X-Hub-Signature-256) are only sent when a webhook secret is configured. [2]
Sources: [1] Ballerina Central ballerinax/trigger.github docs, [2] GitHub Docs “Securing your webhooks / Validating webhook deliveries”.
🏁 Script executed:
cat -n ballerina-integrator/slack-notification-github-pr-merged/main.balRepository: wso2/integration-samples
Length of output: 3037
Expose a GitHub webhook secret in config to validate webhook deliveries.
The endpoint currently accepts webhook payloads without verifying their authenticity. GitHub sends signature headers only when a webhook secret is configured, and the trigger.github:Listener supports validation through a webhookSecret parameter. Without it, an attacker can forge PR events and trigger unauthorized Slack notifications.
Add the configurable to config.bal and thread it through connections.bal:
Required changes
config.bal (add to lines 5-10):
// GitHub webhook configuration
configurable int webhookPort = 8090;
configurable string githubCallback = "";
+configurable string githubWebhookSecret = ?;connections.bal (update listener initialization):
-listener github:Listener githubListener = new ({
- "port": webhookPort,
- "callbackUrl": githubCallback
-});
+listener github:Listener githubListener = new ({
+ webhookSecret: githubWebhookSecret,
+ port: webhookPort,
+ callbackUrl: githubCallback
+});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // GitHub webhook configuration | |
| configurable int webhookPort = 8090; | |
| configurable string githubCallback = ""; | |
| // GitHub repository (org/repo format) | |
| configurable string[] githubRepos = ?; | |
| // GitHub webhook configuration | |
| configurable int webhookPort = 8090; | |
| configurable string githubCallback = ""; | |
| configurable string githubWebhookSecret = ?; | |
| // GitHub repository (org/repo format) | |
| configurable string[] githubRepos = ?; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ballerina-integrator/slack-notification-github-pr-merged/config.bal` around
lines 5 - 10, Add a configurable string variable named githubWebhookSecret to
config.bal (e.g., configurable string githubWebhookSecret = "";), then update
the listener initialization in connections.bal to pass that secret into the
trigger.github:Listener via its webhookSecret parameter (ensure the variable
name used matches the configurable and is passed when constructing/initializing
the listener in the function or statement that creates the GitHub listener).
| // Apply filters | ||
| if !shouldProcessPullRequest(event.pull_request) { | ||
| log:printInfo("PR did not match filters, skipping notification"); | ||
| return; | ||
| } | ||
|
|
||
| // Build and send Slack message | ||
| string slackMessage = buildSlackMessage(event.pull_request, event.repository); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the file mentioned in the review
find . -name "main.bal" -path "*slack-notification-github-pr-merged*" -type fRepository: wso2/integration-samples
Length of output: 135
🏁 Script executed:
# Get the full content of the file to understand the context
cat -n ballerina-integrator/slack-notification-github-pr-merged/main.balRepository: wso2/integration-samples
Length of output: 3037
🏁 Script executed:
# Search for githubRepos definition and usage in the repository
rg "githubRepos" --type bal -B 2 -A 2Repository: wso2/integration-samples
Length of output: 95
🏁 Script executed:
# Search for githubRepos without type filtering
rg "githubRepos" -B 2 -A 2Repository: wso2/integration-samples
Length of output: 1730
🏁 Script executed:
# Search for other files in the same directory to find config/setup
find ballerina-integrator/slack-notification-github-pr-merged/ -type fRepository: wso2/integration-samples
Length of output: 884
🏁 Script executed:
# Search for config files and initialization
rg "configurable" ballerina-integrator/slack-notification-github-pr-merged/ -B 2 -A 2Repository: wso2/integration-samples
Length of output: 3376
🏁 Script executed:
# Read the functions.bal file to see if githubRepos is used in filtering
cat -n ballerina-integrator/slack-notification-github-pr-merged/functions.balRepository: wso2/integration-samples
Length of output: 7142
🏁 Script executed:
# Read the config.bal file to see the full configuration
cat -n ballerina-integrator/slack-notification-github-pr-merged/config.balRepository: wso2/integration-samples
Length of output: 1141
Add repository allowlist check before processing.
The githubRepos configuration is never checked against incoming repository names in the webhook handler. Currently, any repository pointed at this webhook will produce Slack notifications regardless of the configured allowlist. Add a guard to validate event.repository.full_name against githubRepos before proceeding with the merge check and filters.
Suggested guard
// Apply filters
+ boolean allowedRepo = false;
+ foreach string configuredRepo in githubRepos {
+ if configuredRepo == event.repository.full_name {
+ allowedRepo = true;
+ break;
+ }
+ }
+ if !allowedRepo {
+ log:printInfo("Repository not in allowlist, skipping notification");
+ return;
+ }
if !shouldProcessPullRequest(event.pull_request) {
log:printInfo("PR did not match filters, skipping notification");
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Apply filters | |
| if !shouldProcessPullRequest(event.pull_request) { | |
| log:printInfo("PR did not match filters, skipping notification"); | |
| return; | |
| } | |
| // Build and send Slack message | |
| string slackMessage = buildSlackMessage(event.pull_request, event.repository); | |
| // Apply filters | |
| boolean allowedRepo = false; | |
| foreach string configuredRepo in githubRepos { | |
| if configuredRepo == event.repository.full_name { | |
| allowedRepo = true; | |
| break; | |
| } | |
| } | |
| if !allowedRepo { | |
| log:printInfo("Repository not in allowlist, skipping notification"); | |
| return; | |
| } | |
| if !shouldProcessPullRequest(event.pull_request) { | |
| log:printInfo("PR did not match filters, skipping notification"); | |
| return; | |
| } | |
| // Build and send Slack message | |
| string slackMessage = buildSlackMessage(event.pull_request, event.repository); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ballerina-integrator/slack-notification-github-pr-merged/main.bal` around
lines 16 - 23, Add a guard in the webhook handler to check the configured
allowlist `githubRepos` against `event.repository.full_name` before any further
processing: if the repository is not present in `githubRepos`, log/return early
so you never reach `shouldProcessPullRequest` or `buildSlackMessage`. Locate the
webhook handler where `shouldProcessPullRequest(event.pull_request)` is called
and insert the repository membership check there (using
`event.repository.full_name` and the `githubRepos` collection) to bail out when
the repo is not allowed.
|
Prebuilt Integration Checklist
|
No change in core functionality
There was a problem hiding this comment.
♻️ Duplicate comments (1)
ballerina-integrator/slack-notification-github-pr-merged/README.md (1)
114-137:⚠️ Potential issue | 🟡 MinorAdd a language specifier to the fenced example block.
Line 114 opens a fenced block without a language identifier, which keeps MD040 warning active.
Proposed fix
-``` +```text 🎉 *Pull Request Merged Successfully!*🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ballerina-integrator/slack-notification-github-pr-merged/README.md` around lines 114 - 137, The fenced code block containing the "Pull Request Merged Successfully!" message is missing a language specifier; update the opening triple-backtick for that example block to include a language (e.g., use ```text) so the markdown linter MD040 is satisfied and the block is explicitly marked as plain text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@ballerina-integrator/slack-notification-github-pr-merged/README.md`:
- Around line 114-137: The fenced code block containing the "Pull Request Merged
Successfully!" message is missing a language specifier; update the opening
triple-backtick for that example block to include a language (e.g., use ```text)
so the markdown linter MD040 is satisfied and the block is explicitly marked as
plain text.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6bceeeb6-b757-4b90-89b6-76b091875440
📒 Files selected for processing (3)
ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.jsonballerina-integrator/slack-notification-github-pr-merged/README.mdballerina-integrator/slack-notification-github-pr-merged/config.bal
✅ Files skipped from review due to trivial changes (2)
- ballerina-integrator/slack-notification-github-pr-merged/config.bal
- ballerina-integrator/slack-notification-github-pr-merged/.choreo/config-schema.json
|
@TharaniDJ I think the projects.json file has a conflict. Can we fix that |
Purpose
This PR adds a new prebuilt Ballerina integration that automatically sends Slack notifications when a GitHub Pull Request is merged. Resolves https://github.com/wso2-enterprise/integration-engineering/issues/65
Goals
Approach
Implemented a Ballerina event-driven integration using:
ballerinax/trigger.githubto listen for GitHub PR webhook eventsballerinax/slackto post formatted notificationsDocumentation
README.mdincluded in the integration directory.choreo/instructions.mdincluded with Slack and GitHub webhook setup guides.choreo/diagram.mdincluded with Mermaid flow diagram.choreo/config-schema.jsonfor configuration form renderingTests
Tested the following scenarios:
Test Environment
Learning
Summary by CodeRabbit
New Features
Documentation
Chores