Skip to content

Commit 90af26a

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/babel/preset-env-7.29.7
2 parents b8a2cc3 + 2dfc2e0 commit 90af26a

27 files changed

Lines changed: 3129 additions & 1205 deletions

File tree

.github/workflows/branch-deploy.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ jobs:
299299

300300
- name: Configure AWS credentials for ECR interaction
301301
id: aws-config
302-
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
302+
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
303303
with:
304304
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ env.AWS_ECR_PUSH_ROLE }}
305305
role-session-name: GithubActionsRoleSession
@@ -442,7 +442,7 @@ jobs:
442442

443443
- name: Configure AWS credentials for ECR interaction
444444
id: aws-config
445-
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
445+
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
446446
with:
447447
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ env.AWS_ECR_PUSH_ROLE }}
448448
role-session-name: GithubActionsRoleSession
@@ -462,7 +462,7 @@ jobs:
462462
docker push ${{ steps.aws-config.outputs.aws-account-id }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ env.tagged_image }}-${{ env.timestamp }}
463463
464464
- name: Configure AWS credentials for EKS interaction
465-
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
465+
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
466466
with:
467467
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ env.AWS_EKS_DEPLOY_ROLE }}
468468
role-session-name: GithubActionsRoleSessio
@@ -773,7 +773,7 @@ jobs:
773773
id-token: write
774774
steps:
775775
- name: Configure AWS credentials for EKS interaction
776-
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
776+
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
777777
with:
778778
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ env.AWS_EKS_DEPLOY_ROLE }}
779779
role-session-name: GithubActionsRoleSession

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
- name: Build the forge
7070
run: npm run build:nuxt:skip-images
7171
working-directory: 'website'
72-
- uses: untitaker/hyperlink@1ae2cef25222e3307a82bc7388f0667137f4240b # 0.2.1
72+
- uses: untitaker/hyperlink@9375bc4063712ad490d5eb3d54df0b6aade15e54 # 0.3.2
7373
with:
7474
args: website/nuxt/dist/ --check-anchors --sources website/src
7575
publish:

.github/workflows/tests.yml

Lines changed: 150 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -279,93 +279,156 @@ jobs:
279279
runs-on: ubuntu-latest
280280

281281
steps:
282-
- name: Map users
283-
id: map-actor-to-slack
284-
uses: icalia-actions/map-github-actor@e568d1dd6023e406a1db36db4e1e0b92d9dd7824 # v0.0.2
285-
with:
286-
actor-map: ${{ vars.SLACK_GITHUB_USERS_MAP }}
287-
default-mapping: C067BD0377F
288-
289-
- name: Generate payload variables
290-
run: |
291-
if [[ "${{ github.ref_name }}" == 'main' || "${{ github.ref_name }}" == 'maintenance' ]] ; then
292-
echo "HEADER_MESSAGE=Tests failed against ${{ github.ref_name }} branch" >> $GITHUB_ENV
293-
echo "SUMMARY_ICON=no_entry" >> $GITHUB_ENV
294-
echo "SUMMARY_MESSAGE= Deployment to FFC environments will not happen until this issue is resolved." >> $GITHUB_ENV
295-
echo "LAST_COMMIT_SHA=${{ github.sha}}" >> $GITHUB_ENV
296-
echo "PR_LINK=*Branch:* ${{ github.ref_name }}" >> $GITHUB_ENV
297-
else
298-
echo "HEADER_MESSAGE=Tests failed against ${{ github.event.number }} pull request" >> $GITHUB_ENV
299-
echo "SUMMARY_ICON=warning" >> $GITHUB_ENV
300-
echo "SUMMARY_MESSAGE= Please resolve the problem before merging your changes into the main branch." >> $GITHUB_ENV
301-
echo "LAST_COMMIT_SHA=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV
302-
echo "PR_LINK=*Pull request:* <https://github.com/FlowFuse/flowfuse/pull/${{ github.event.pull_request.number }}|${{ github.event.pull_request.number }}>" >> $GITHUB_ENV
303-
fi
282+
# On pull_request failure: resolve the PR author's Slack ID by scanning Slack
283+
# profiles and matching the custom profile field (GitHub). Bot authors are skipped
284+
# silently; an unresolved author logs a warning and no notification is sent.
285+
# On push to main: target the gh-pipelines channel directly.
286+
# Outputs `recipients` as JSON: [{ slack_id, github?, role }].
287+
- name: Resolve Slack recipients
288+
id: resolve
289+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
290+
env:
291+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }}
292+
SLACK_GITHUB_FIELD_ID: "Xf0A2BPU8U77"
293+
CHANNEL_ID: "C067BD0377F"
294+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
295+
ACTOR: ${{ github.actor }}
296+
EVENT_NAME: ${{ github.event_name }}
297+
with:
298+
script: |
299+
const token = process.env.SLACK_BOT_TOKEN;
300+
const fieldId = process.env.SLACK_GITHUB_FIELD_ID;
304301
305-
- name: Send notification
306-
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
307-
with:
308-
method: chat.postMessage
309-
token: ${{ secrets.SLACK_GHBOT_TOKEN }}
310-
payload: |
311-
{
312-
"channel": "C067BD0377F",
313-
"blocks": [
314-
{
315-
"type": "header",
316-
"text": {
317-
"type": "plain_text",
318-
"text": ":x: ${{ env.HEADER_MESSAGE }}",
319-
"emoji": true
302+
// Resolve a GitHub login -> Slack user ID by scanning workspace profiles and
303+
// matching the custom profile field (GitHub). Returns undefined if not found.
304+
async function resolveSlackId(login) {
305+
const target = login.toLowerCase();
306+
let cursor;
307+
do {
308+
const params = new URLSearchParams({ limit: '200' });
309+
if (cursor) params.set('cursor', cursor);
310+
const res = await fetch(`https://slack.com/api/users.list?${params}`, {
311+
headers: { Authorization: `Bearer ${token}` }
312+
});
313+
const data = await res.json();
314+
if (!data.ok) { core.setFailed(`Slack users.list error: ${data.error}`); return; }
315+
for (const member of data.members) {
316+
if (member.deleted || member.is_bot) continue;
317+
const profileRes = await fetch(`https://slack.com/api/users.profile.get?user=${member.id}`, {
318+
headers: { Authorization: `Bearer ${token}` }
319+
});
320+
const profileData = await profileRes.json();
321+
if (!profileData.ok) continue;
322+
const ghField = profileData.profile?.fields?.[fieldId]?.value;
323+
if (!ghField) continue;
324+
if (ghField.toLowerCase() === target) return member.id;
320325
}
321-
},
322-
{
323-
"type": "divider"
324-
},
325-
{
326-
"type": "rich_text",
327-
"elements": [
328-
{
329-
"type": "rich_text_section",
330-
"elements": [
331-
{
332-
"type": "emoji",
333-
"name": "${{ env.SUMMARY_ICON }}"
334-
},
335-
{
336-
"type": "text",
337-
"text": " ${{ env.SUMMARY_MESSAGE }}",
338-
"style": {
339-
"bold": true
340-
}
341-
}
342-
]
343-
}
344-
]
345-
},
346-
{
347-
"type": "divider"
348-
},
349-
{
350-
"type": "section",
351-
"fields": [
352-
{
353-
"type": "mrkdwn",
354-
"text": "*Author:* <@${{ steps.map-actor-to-slack.outputs.actor-mapping }}>"
355-
},
356-
{
357-
"type": "mrkdwn",
358-
"text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View failed workflow>"
359-
},
360-
{
361-
"type": "mrkdwn",
362-
"text": "*Last commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ env.LAST_COMMIT_SHA }}|${{ env.LAST_COMMIT_SHA }}>"
363-
},
364-
{
365-
"type": "mrkdwn",
366-
"text": "${{ env.PR_LINK }}"
367-
}
368-
]
326+
cursor = data.response_metadata?.next_cursor;
327+
} while (cursor);
328+
return undefined;
329+
}
330+
331+
// Push to main - notify the channel, mentioning the actor who pushed.
332+
if (process.env.EVENT_NAME !== 'pull_request') {
333+
const actor = process.env.ACTOR;
334+
const mention = actor ? await resolveSlackId(actor) : undefined;
335+
if (!mention) core.warning(`No Slack user found with GitHub username: ${actor}`);
336+
core.setOutput('recipients', JSON.stringify([{ slack_id: process.env.CHANNEL_ID, role: 'Channel', mention: mention || null }]));
337+
return;
338+
}
339+
340+
// Pull request - notify the author. Bot authors have no Slack profile: skip silently.
341+
const author = process.env.PR_AUTHOR;
342+
if (!author || author.endsWith('[bot]')) {
343+
core.setOutput('recipients', '[]');
344+
return;
345+
}
346+
347+
const slackId = await resolveSlackId(author);
348+
if (!slackId) {
349+
core.warning(`No Slack user found with GitHub username: ${author}`);
350+
core.setOutput('recipients', '[]');
351+
return;
352+
}
353+
core.setOutput('recipients', JSON.stringify([{ slack_id: slackId, github: author.toLowerCase(), role: 'Author' }]));
354+
355+
# Sends the failure notification to each recipient (DM to the PR author, or the channel
356+
# on main). chat.postMessage accepts a user ID or a channel ID as `channel`.
357+
- name: Send Slack notification
358+
if: steps.resolve.outputs.recipients != '' && steps.resolve.outputs.recipients != '[]'
359+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
360+
env:
361+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }}
362+
RECIPIENTS: ${{ steps.resolve.outputs.recipients }}
363+
EVENT_NAME: ${{ github.event_name }}
364+
REF_NAME: ${{ github.ref_name }}
365+
PR_NUMBER: ${{ github.event.pull_request.number }}
366+
COMMIT_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
367+
ACTOR: ${{ github.actor }}
368+
SERVER_URL: ${{ github.server_url }}
369+
REPOSITORY: ${{ github.repository }}
370+
RUN_ID: ${{ github.run_id }}
371+
with:
372+
script: |
373+
const token = process.env.SLACK_BOT_TOKEN;
374+
const recipients = JSON.parse(process.env.RECIPIENTS);
375+
const isBranch = process.env.EVENT_NAME !== 'pull_request';
376+
const refName = process.env.REF_NAME;
377+
const prNumber = process.env.PR_NUMBER;
378+
const commitSha = process.env.COMMIT_SHA;
379+
const actor = process.env.ACTOR;
380+
const serverUrl = process.env.SERVER_URL;
381+
const repository = process.env.REPOSITORY;
382+
const runId = process.env.RUN_ID;
383+
384+
const headerText = isBranch
385+
? `Tests failed against ${refName} branch`
386+
: `Tests failed against ${prNumber} pull request`;
387+
const summaryIcon = isBranch ? 'no_entry' : 'warning';
388+
const summaryText = isBranch
389+
? ' Deployment to FFC environments will not happen until this issue is resolved.'
390+
: ' Please resolve the problem before merging your changes into the main branch.';
391+
const refLink = isBranch
392+
? `*Branch:* ${refName}`
393+
: `*Pull request:* <${serverUrl}/${repository}/pull/${prNumber}|${prNumber}>`;
394+
395+
let failures = 0;
396+
for (const r of recipients) {
397+
// Channel posts mention the actor's Slack ID (a real ping) if resolved;
398+
// otherwise (and for DMs) show the plain GitHub login.
399+
const authorText = r.mention ? `<@${r.mention}>` : actor;
400+
const blocks = [
401+
{ type: 'header', text: { type: 'plain_text', text: `:x: ${headerText}`, emoji: true } },
402+
{ type: 'divider' },
403+
{ type: 'rich_text', elements: [ { type: 'rich_text_section', elements: [
404+
{ type: 'emoji', name: summaryIcon },
405+
{ type: 'text', text: summaryText, style: { bold: true } }
406+
] } ] },
407+
{ type: 'divider' },
408+
{ type: 'section', fields: [
409+
{ type: 'mrkdwn', text: `*Author:* ${authorText}` },
410+
{ type: 'mrkdwn', text: `<${serverUrl}/${repository}/actions/runs/${runId}|View failed workflow>` },
411+
{ type: 'mrkdwn', text: `*Last commit:* <${serverUrl}/${repository}/commit/${commitSha}|${commitSha}>` },
412+
{ type: 'mrkdwn', text: refLink }
413+
] }
414+
];
415+
const res = await fetch('https://slack.com/api/chat.postMessage', {
416+
method: 'POST',
417+
headers: {
418+
Authorization: `Bearer ${token}`,
419+
'Content-Type': 'application/json; charset=utf-8'
420+
},
421+
body: JSON.stringify({ channel: r.slack_id, blocks })
422+
});
423+
const data = await res.json();
424+
if (!data.ok) {
425+
core.warning(`Slack chat.postMessage failed for ${r.slack_id}: ${data.error}`);
426+
failures++;
427+
} else {
428+
core.info(`Notified ${r.slack_id} (${r.role})`);
369429
}
370-
]
371-
}
430+
}
431+
432+
if (failures > 0 && failures === recipients.length) {
433+
core.setFailed(`All ${failures} Slack notifications failed`);
434+
}

forge/comms/commsClient.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class CommsClient extends EventEmitter {
2525
username: 'forge_platform',
2626
password: await this.app.settings.get('commsToken'),
2727
reconnectPeriod: 5000,
28+
protocolVersion: 5,
2829
will: {
2930
topic: 'ff/v1/platform/leader',
3031
payload: JSON.stringify({ id: this.platformId, vote: -1 })
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { DataTypes } = require('sequelize')
2+
3+
module.exports = {
4+
/**
5+
* upgrade database
6+
* @param {QueryInterface} context Sequelize.QueryInterface
7+
*/
8+
up: async (context, Sequelize) => {
9+
await context.addColumn('GitTokens', 'username', {
10+
type: DataTypes.STRING,
11+
allowNull: true
12+
})
13+
await context.addColumn('GitTokens', 'caCertificate', {
14+
type: DataTypes.TEXT,
15+
allowNull: true
16+
})
17+
},
18+
down: async (context, Sequelize) => { }
19+
}

forge/db/models/Team.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,8 @@ module.exports = {
749749
const instanceList = await app.db.models.Project.findAll({
750750
attributes: [
751751
'id',
752+
'name',
753+
'safeName',
752754
'state',
753755
'ProjectStackId',
754756
'TeamId'

forge/ee/db/models/GitToken.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ module.exports = {
1515
type: DataTypes.STRING,
1616
allowNull: false,
1717
default: 'github'
18+
},
19+
username: {
20+
type: DataTypes.STRING,
21+
allowNull: true
22+
},
23+
caCertificate: {
24+
type: DataTypes.TEXT,
25+
allowNull: true
1826
}
1927
},
2028
associations: function (M) {

forge/ee/db/models/PipelineStageGitRepo.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ module.exports = {
103103
await app.gitops.pushToRepository({
104104
token: gitToken.token,
105105
tokenType: gitToken.type,
106+
username: gitToken.username,
107+
caCertificate: gitToken.caCertificate,
106108
url: this.url,
107109
branch: this.branch,
108110
credentialSecret: this.credentialSecret,
@@ -156,6 +158,8 @@ module.exports = {
156158
const snapshotContent = await app.gitops.pullFromRepository({
157159
token: gitToken.token,
158160
tokenType: gitToken.type,
161+
username: gitToken.username,
162+
caCertificate: gitToken.caCertificate,
159163
url: this.url,
160164
branch: this.pullBranch || this.branch,
161165
credentialSecret: this.credentialSecret,

forge/ee/db/views/PipelineStage.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ module.exports = function (app) {
2828
pullBranch: { type: 'string' },
2929
pushPath: { type: 'string' },
3030
pullPath: { type: 'string' },
31-
lastPushAt: { type: 'string' },
32-
lastPullAt: { type: 'string' },
33-
status: { type: 'string' },
34-
statusMessage: { type: 'string' },
31+
lastPushAt: { type: 'string', nullable: true },
32+
lastPullAt: { type: 'string', nullable: true },
33+
status: { type: 'string', nullable: true },
34+
statusMessage: { type: 'string', nullable: true },
3535
credentialSecret: { type: 'boolean' }
3636
},
3737
required: ['gitTokenId', 'url', 'branch', 'pullBranch', 'pushPath', 'pullPath', 'lastPushAt', 'lastPullAt', 'status', 'statusMessage', 'credentialSecret'],

0 commit comments

Comments
 (0)