Skip to content

Commit 139253a

Browse files
ppawlowskippawlowski
andauthored
ci: Replace manual scripts with slack_notification action in Create pre-staging environment (#7761)
Co-authored-by: ppawlowski <piotr@flowfuse.com>
1 parent c8eeece commit 139253a

1 file changed

Lines changed: 25 additions & 170 deletions

File tree

.github/workflows/branch-deploy.yaml

Lines changed: 25 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -584,178 +584,33 @@ jobs:
584584
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }}
585585

586586
steps:
587-
# Builds the recipient list (PR author + requested reviewers, or github.actor on workflow_dispatch)
588-
# and resolves each GitHub username to a Slack user ID by scanning workspace profiles and picking the value of custom profile field (GitHub).
589-
# Outputs `recipients` as JSON: [{ slack_id, github, role }, ...]. Unresolved users are skipped with a warning.
590-
- name: Resolve Slack users for recipients
591-
id: resolve
592-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
593-
env:
594-
SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }}
595-
SLACK_GITHUB_FIELD_ID: "Xf0A2BPU8U77"
596-
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
597-
REQUESTED_REVIEWERS: ${{ toJSON(github.event.pull_request.requested_reviewers) }}
598-
GITHUB_ACTOR: ${{ github.actor }}
599-
EVENT_NAME: ${{ github.event_name }}
587+
- name: Notify Slack
588+
uses: FlowFuse/github-actions-workflows/actions/slack_notification@slack_notification/v1
600589
with:
601-
script: |
602-
const token = process.env.SLACK_BOT_TOKEN;
603-
const fieldId = process.env.SLACK_GITHUB_FIELD_ID;
604-
605-
// Build recipient list with roles, deduped (first-added role wins: Author > Reviewer > Pusher).
606-
// Bot accounts (e.g. dependabot[bot]) are skipped — they have no Slack profile.
607-
const recipients = [];
608-
const seen = new Set();
609-
const add = (login, role) => {
610-
if (!login) return;
611-
if (login.endsWith('[bot]')) return;
612-
const key = login.toLowerCase();
613-
if (seen.has(key)) return;
614-
seen.add(key);
615-
recipients.push({ github: key, role });
616-
};
617-
618-
if (process.env.EVENT_NAME === 'workflow_dispatch') {
619-
add(process.env.GITHUB_ACTOR, 'Trigger');
620-
} else {
621-
add(process.env.PR_AUTHOR, 'Author');
622-
const reviewers = JSON.parse(process.env.REQUESTED_REVIEWERS || '[]');
623-
for (const r of reviewers) add(r.login, 'Reviewer');
624-
add(process.env.GITHUB_ACTOR, 'Pusher');
625-
}
626-
627-
if (recipients.length === 0) {
628-
core.warning('No recipients to notify');
629-
core.setOutput('recipients', '[]');
630-
return;
590+
bot-token: ${{ secrets.SLACK_GHBOT_TOKEN }}
591+
mode: contributors
592+
values: |
593+
{
594+
"PR_TITLE": ${{ toJSON(github.event.pull_request.title || format('#{0}', env.PR_NUMBER)) }},
595+
"LOGS_URL": ${{ toJSON(format('https://{0}/explore?orgId=1&left=%7B%22datasource%22:%22P8E80F9AEF21F6940%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22editorMode%22:%22code%22,%22expr%22:%22%7Bnamespace%3D%5C%22pr-{1}%5C%22%7D%20%7C%3D%20%60%60%20%7C%20json%22,%22queryType%22:%22range%22%7D%5D,%22range%22:%7B%22from%22:%22now-30m%22,%22to%22:%22now%22%7D%7D', secrets.STAGING_GRAFANA_URL, env.PR_NUMBER)) }}
631596
}
632-
633-
// Build Slack username -> ID map by scanning workspace profiles
634-
const targets = new Set(recipients.map(r => r.github));
635-
const resolved = new Map();
636-
let cursor;
637-
outer: do {
638-
const params = new URLSearchParams({ limit: '200' });
639-
if (cursor) params.set('cursor', cursor);
640-
const res = await fetch(`https://slack.com/api/users.list?${params}`, {
641-
headers: { Authorization: `Bearer ${token}` }
642-
});
643-
const data = await res.json();
644-
if (!data.ok) return core.setFailed(`Slack users.list error: ${data.error}`);
645-
for (const member of data.members) {
646-
if (member.deleted || member.is_bot) continue;
647-
const profileRes = await fetch(`https://slack.com/api/users.profile.get?user=${member.id}`, {
648-
headers: { Authorization: `Bearer ${token}` }
649-
});
650-
const profileData = await profileRes.json();
651-
if (!profileData.ok) continue;
652-
const ghField = profileData.profile?.fields?.[fieldId]?.value;
653-
if (!ghField) continue;
654-
const ghLower = ghField.toLowerCase();
655-
if (targets.has(ghLower)) {
656-
resolved.set(ghLower, member.id);
657-
if (resolved.size === targets.size) break outer;
658-
}
659-
}
660-
cursor = data.response_metadata?.next_cursor;
661-
} while (cursor);
662-
663-
const output = [];
664-
for (const r of recipients) {
665-
const slack_id = resolved.get(r.github);
666-
if (slack_id) {
667-
output.push({ slack_id, github: r.github, role: r.role });
668-
} else {
669-
core.warning(`No Slack user found with GitHub username: ${r.github} (role: ${r.role})`);
670-
}
671-
}
672-
core.info(`Resolved ${output.length}/${recipients.length} recipients`);
673-
core.setOutput('recipients', JSON.stringify(output));
674-
675-
# Sends one DM per resolved recipient via Slack chat.postMessage.
676-
# Same message content for everyone, with a *Role:* field indicating Author / Reviewer / Trigger.
677-
# Step fails only if all sends fail; partial failures are logged as warnings.
678-
- name: Send Slack notifications
679-
if: steps.resolve.outputs.recipients != '' && steps.resolve.outputs.recipients != '[]'
680-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
681-
env:
682-
SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }}
683-
RECIPIENTS: ${{ steps.resolve.outputs.recipients }}
684-
PR_NUMBER: ${{ env.PR_NUMBER }}
685-
PR_TITLE: ${{ github.event.pull_request.title }}
686-
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
687-
DEPLOY_RESULT: ${{ needs.deploy.result }}
688-
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
689-
SERVER_URL: ${{ github.server_url }}
690-
REPOSITORY: ${{ github.repository }}
691-
RUN_ID: ${{ github.run_id }}
692-
GRAFANA_URL: ${{ secrets.STAGING_GRAFANA_URL }}
693-
with:
694-
script: |
695-
const token = process.env.SLACK_BOT_TOKEN;
696-
const recipients = JSON.parse(process.env.RECIPIENTS);
697-
const prNumber = process.env.PR_NUMBER;
698-
const prTitle = process.env.PR_TITLE || `#${prNumber}`;
699-
const prAuthor = process.env.PR_AUTHOR || 'n/a';
700-
const deployResult = process.env.DEPLOY_RESULT;
701-
const commitSha = process.env.COMMIT_SHA;
702-
const serverUrl = process.env.SERVER_URL;
703-
const repository = process.env.REPOSITORY;
704-
const runId = process.env.RUN_ID;
705-
const grafanaUrl = process.env.GRAFANA_URL;
706-
707-
const statusText = deployResult === 'success' ? ':white_check_mark: Success' : ':x: Failure ';
708-
const logsUrl = `https://${grafanaUrl}/explore?orgId=1&left=%7B%22datasource%22:%22P8E80F9AEF21F6940%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22editorMode%22:%22code%22,%22expr%22:%22%7Bnamespace%3D%5C%22pr-${prNumber}%5C%22%7D%20%7C%3D%20%60%60%20%7C%20json%22,%22queryType%22:%22range%22%7D%5D,%22range%22:%7B%22from%22:%22now-30m%22,%22to%22:%22now%22%7D%7D`;
709-
710-
let failures = 0;
711-
for (const r of recipients) {
712-
const blocks = [
713-
{
714-
type: 'header',
715-
text: { type: 'plain_text', text: `Pull Request ${prNumber} pre-staging deployment`, emoji: true }
716-
},
717-
{
718-
type: 'section',
719-
fields: [
720-
{ type: 'mrkdwn', text: `*Status:*\n${statusText}` },
721-
{ type: 'mrkdwn', text: `*Role:*\n${r.role}` },
722-
{ type: 'mrkdwn', text: `*Pull Request:*\n<https://github.com/FlowFuse/flowfuse/pull/${prNumber}|${prTitle}>` },
723-
{ type: 'mrkdwn', text: `*Workflow run:*\n<${serverUrl}/${repository}/actions/runs/${runId}|View>` }
724-
]
725-
},
726-
{
727-
type: 'section',
728-
fields: [
729-
{ type: 'mrkdwn', text: `*Author:*\n${prAuthor}` },
730-
{ type: 'mrkdwn', text: `*Commit SHA:*\n<${serverUrl}/${repository}/commit/${commitSha}|${commitSha}>` },
731-
{ type: 'mrkdwn', text: `*Deployed to:*\n<https://${prNumber}.flowfuse.dev|https://${prNumber}.flowfuse.dev>` },
732-
{ type: 'mrkdwn', text: `*Logs:*\n<${logsUrl}|View>` }
733-
]
734-
}
735-
];
736-
737-
const res = await fetch('https://slack.com/api/chat.postMessage', {
738-
method: 'POST',
739-
headers: {
740-
Authorization: `Bearer ${token}`,
741-
'Content-Type': 'application/json; charset=utf-8'
742-
},
743-
body: JSON.stringify({ channel: r.slack_id, blocks })
744-
});
745-
const data = await res.json();
746-
if (!data.ok) {
747-
core.warning(`Slack chat.postMessage failed for ${r.github} (${r.slack_id}): ${data.error}`);
748-
failures++;
749-
} else {
750-
core.info(`Notified ${r.github} (${r.role}) at ${r.slack_id}`);
751-
}
752-
}
753-
754-
if (failures > 0 && failures === recipients.length) {
755-
core.setFailed(`All ${failures} Slack notifications failed`);
756-
}
757-
758-
597+
blocks: |
598+
[
599+
{ "type": "header", "text": { "type": "plain_text", "text": "Pull Request ${{ env.PR_NUMBER }} pre-staging deployment", "emoji": true } },
600+
{ "type": "section", "fields": [
601+
{ "type": "mrkdwn", "text": "*Status:*\n${{ needs.deploy.result == 'success' && ':white_check_mark: Success' || ':x: Failure' }}" },
602+
{ "type": "mrkdwn", "text": "*Role:*\n{{ROLE}}" },
603+
{ "type": "mrkdwn", "text": "*Pull Request:*\n<https://github.com/FlowFuse/flowfuse/pull/${{ env.PR_NUMBER }}|{{PR_TITLE}}>" },
604+
{ "type": "mrkdwn", "text": "*Workflow run:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View>" }
605+
] },
606+
{ "type": "section", "fields": [
607+
{ "type": "mrkdwn", "text": "*Author:*\n${{ github.event.pull_request.user.login || 'n/a' }}" },
608+
{ "type": "mrkdwn", "text": "*Commit SHA:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha }}|${{ github.event.pull_request.head.sha }}>" },
609+
{ "type": "mrkdwn", "text": "*Deployed to:*\n<https://${{ env.PR_NUMBER }}.flowfuse.dev|https://${{ env.PR_NUMBER }}.flowfuse.dev>" },
610+
{ "type": "mrkdwn", "text": "*Logs:*\n<{{LOGS_URL}}|View>" }
611+
] }
612+
]
613+
759614
destroy:
760615
name: Remove application
761616
needs: [ validate-user ]

0 commit comments

Comments
 (0)