docs: clarify EMQX broker dependency and dependent features #10884
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - maintenance | |
| pull_request: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| check-changes: | |
| name: Verify changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| run_backend_tests: ${{ steps.changed-files.outputs.backend_changed }} | |
| run_postgres_tests: ${{ steps.changed-files.outputs.postgres_changed }} | |
| run_ui_tests: ${{ steps.changed-files.outputs.ui_changed }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Find changed files | |
| id: changed-files | |
| run: | | |
| ./.github/scripts/detect_changed_files.sh | |
| check-ts-drift: | |
| name: Check TypeScript drift | |
| runs-on: ubuntu-latest | |
| needs: check-changes | |
| if: | | |
| needs.check-changes.outputs.run_backend_tests == 'true' || | |
| needs.check-changes.outputs.run_postgres_tests == 'true' || | |
| needs.check-changes.outputs.run_ui_tests == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Use Node.js 24 | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 24 | |
| - name: Install Dependencies | |
| run: npm ci | |
| - name: Build the forge | |
| run: npm run build | |
| - name: Generate TypeScript types | |
| run: npm run generate:types | |
| env: | |
| DEV_ENTERPRISE_TIER_LICENSE: ${{ secrets.DEV_ENTERPRISE_TIER_LICENSE }} | |
| - name: Check drift | |
| run: | | |
| if ! git diff --exit-code frontend/src/types/generated.ts ; then | |
| echo "" | |
| echo "::error::Type drift detected! Run 'npm run generate:types' locally and commit the result." | |
| exit 1 | |
| fi | |
| backend-tests: | |
| if: ${{ needs.check-changes.outputs.run_backend_tests == 'true' }} | |
| name: Backend tests | |
| needs: | |
| - check-changes | |
| - check-ts-drift | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Use Node.js 24 | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 24 | |
| - name: Install Dependencies | |
| run: npm ci | |
| - name: Build the forge | |
| run: npm run build | |
| - name: Run forge unit tests | |
| run: npm run cover:unit:forge | |
| - name: Run forge system tests | |
| run: npm run cover:system | |
| - name: Upload coverage reports to Codecov | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| flags: backend | |
| postgres-tests: | |
| if: ${{ needs.check-changes.outputs.run_postgres_tests == 'true' }} | |
| name: Postgres tests | |
| needs: | |
| - check-changes | |
| - check-ts-drift | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| services: | |
| postgres: | |
| image: postgres:14 | |
| env: | |
| POSTGRES_PASSWORD: secret | |
| POSTGRES_USER: postgres | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| -m 3GB | |
| ports: | |
| - 5432:5432 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup NodeJS 24 | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 24 | |
| - name: Install Dependencies | |
| run: npm ci | |
| - name: Build the forge | |
| run: npm run build | |
| - name: Test the forge (with PostgreSQL) | |
| env: | |
| FF_TEST_DB_POSTGRES: true | |
| FF_TEST_DB_POSTGRES_HOST: localhost | |
| FF_TEST_DB_POSTGRES_PORT: 5432 | |
| FF_TEST_DB_POSTGRES_USER: postgres | |
| FF_TEST_DB_POSTGRES_PASSWORD: secret | |
| FF_TEST_DB_POSTGRES_DATABASE: flowforge | |
| NODE_OPTIONS: "--max-old-space-size=6144" | |
| run: npm run test | |
| ui-unit-tests: | |
| if: ${{ needs.check-changes.outputs.run_ui_tests == 'true' }} | |
| name: UI unit tests | |
| needs: | |
| - check-changes | |
| - check-ts-drift | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Install Dependencies | |
| run: npm ci | |
| - name: Run Linting Tests | |
| run: npm run lint | |
| - name: Run UI Unit Tests | |
| run: npm run test:unit:frontend | |
| ui-os-tests: | |
| if: ${{ needs.check-changes.outputs.run_ui_tests == 'true' }} | |
| name: UI OS tests | |
| needs: | |
| - check-changes | |
| - check-ts-drift | |
| runs-on: ubuntu-latest | |
| services: | |
| email-service: | |
| image: axllent/mailpit | |
| ports: | |
| - 8025:8025 | |
| - 1025:1025 | |
| options: >- | |
| --env MP_MAX_MESSAGES=5000 | |
| --env MP_SMTP_AUTH_ACCEPT_ANY=1 | |
| --env MP_SMTP_AUTH_ALLOW_INSECURE=1 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Install Dependencies | |
| run: npm ci | |
| - name: Run UI E2E (Cypress) Tests - OS | |
| uses: cypress-io/github-action@948d67d3074f1bbb6379c8bdbb04e95d2f8e593f # v7.4.0 | |
| with: | |
| install: false | |
| config-file: config/cypress-os.config.js | |
| build: npm run build | |
| start: npm run cy:web-server:os | |
| env: | |
| NO_SMTP_SERVER: 'true' | |
| SMTP_HOST: 'localhost' | |
| SMTP_PORT: '1025' | |
| SMTP_WEB_PORT: '8025' | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: failure() | |
| with: | |
| name: os-cypress-output-${{ github.run_id }}-${{ github.run_number }} | |
| path: | | |
| test/e2e/frontend/cypress/screenshots | |
| test/e2e/frontend/cypress/videos | |
| ui-ee-tests: | |
| if: ${{ needs.check-changes.outputs.run_ui_tests == 'true' }} | |
| name: UI EE tests | |
| needs: | |
| - check-changes | |
| - check-ts-drift | |
| runs-on: ubuntu-latest | |
| services: | |
| email-service: | |
| image: axllent/mailpit | |
| ports: | |
| - 8025:8025 | |
| - 1025:1025 | |
| options: >- | |
| --env MP_MAX_MESSAGES=5000 | |
| --env MP_SMTP_AUTH_ACCEPT_ANY=1 | |
| --env MP_SMTP_AUTH_ALLOW_INSECURE=1 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Install Dependencies | |
| run: npm ci | |
| - name: Run UI E2E (Cypress) Tests - EE | |
| uses: cypress-io/github-action@948d67d3074f1bbb6379c8bdbb04e95d2f8e593f # v7.4.0 | |
| with: | |
| install: false | |
| config-file: config/cypress-ee.config.js | |
| build: npm run build | |
| start: npm run cy:web-server:ee | |
| env: | |
| NO_SMTP_SERVER: 'true' | |
| SMTP_HOST: 'localhost' | |
| SMTP_PORT: '1025' | |
| SMTP_WEB_PORT: '8025' | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: failure() | |
| with: | |
| name: ee-cypress-output-${{ github.run_id }}-${{ github.run_number }} | |
| path: | | |
| test/e2e/frontend/cypress/screenshots | |
| test/e2e/frontend/cypress/videos | |
| ### We use this job to gather results from all test jobs and determine the final status of the tests execution. | |
| ### Jobs executed with the matrix strategy have dynamic names and cannot be referenced in the status checks of the pull request. | |
| tests-summary: | |
| name: Tests summary | |
| if: ${{ always() }} | |
| needs: | |
| - check-changes | |
| - check-ts-drift | |
| - backend-tests | |
| - postgres-tests | |
| - ui-unit-tests | |
| - ui-os-tests | |
| - ui-ee-tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Evaluate test job results | |
| run: | | |
| results=( | |
| '${{ needs.check-ts-drift.result }}' | |
| '${{ needs.backend-tests.result }}' | |
| '${{ needs.postgres-tests.result }}' | |
| '${{ needs.ui-unit-tests.result }}' | |
| '${{ needs.ui-os-tests.result }}' | |
| '${{ needs.ui-ee-tests.result }}' | |
| ) | |
| echo 'Test job results:' | |
| printf '%s\n' "${results[@]}" | |
| for result in "${results[@]}"; do | |
| if [ "$result" = 'failure' ] || [ "$result" = 'cancelled' ]; then | |
| echo 'One or more test jobs failed or were cancelled' | |
| exit 1 | |
| fi | |
| done | |
| echo 'All test jobs succeeded or were skipped' | |
| notify-slack: | |
| name: Notify on failure | |
| needs: tests-summary | |
| if: failure() | |
| runs-on: ubuntu-latest | |
| steps: | |
| # On pull_request failure: resolve the PR author's Slack ID by scanning Slack | |
| # profiles and matching the custom profile field (GitHub). Bot authors are skipped | |
| # silently; an unresolved author logs a warning and no notification is sent. | |
| # On push to main: target the gh-pipelines channel directly. | |
| # Outputs `recipients` as JSON: [{ slack_id, github?, role }]. | |
| - name: Resolve Slack recipients | |
| id: resolve | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }} | |
| SLACK_GITHUB_FIELD_ID: "Xf0A2BPU8U77" | |
| CHANNEL_ID: "C067BD0377F" | |
| PR_AUTHOR: ${{ github.event.pull_request.user.login }} | |
| ACTOR: ${{ github.actor }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| with: | |
| script: | | |
| const token = process.env.SLACK_BOT_TOKEN; | |
| const fieldId = process.env.SLACK_GITHUB_FIELD_ID; | |
| // Resolve a GitHub login -> Slack user ID by scanning workspace profiles and | |
| // matching the custom profile field (GitHub). Returns undefined if not found. | |
| async function resolveSlackId(login) { | |
| const target = login.toLowerCase(); | |
| let cursor; | |
| do { | |
| const params = new URLSearchParams({ limit: '200' }); | |
| if (cursor) params.set('cursor', cursor); | |
| const res = await fetch(`https://slack.com/api/users.list?${params}`, { | |
| headers: { Authorization: `Bearer ${token}` } | |
| }); | |
| const data = await res.json(); | |
| if (!data.ok) { core.setFailed(`Slack users.list error: ${data.error}`); return; } | |
| for (const member of data.members) { | |
| if (member.deleted || member.is_bot) continue; | |
| const profileRes = await fetch(`https://slack.com/api/users.profile.get?user=${member.id}`, { | |
| headers: { Authorization: `Bearer ${token}` } | |
| }); | |
| const profileData = await profileRes.json(); | |
| if (!profileData.ok) continue; | |
| const ghField = profileData.profile?.fields?.[fieldId]?.value; | |
| if (!ghField) continue; | |
| if (ghField.toLowerCase() === target) return member.id; | |
| } | |
| cursor = data.response_metadata?.next_cursor; | |
| } while (cursor); | |
| return undefined; | |
| } | |
| // Push to main - notify the channel, mentioning the actor who pushed. | |
| if (process.env.EVENT_NAME !== 'pull_request') { | |
| const actor = process.env.ACTOR; | |
| const mention = actor ? await resolveSlackId(actor) : undefined; | |
| if (!mention) core.warning(`No Slack user found with GitHub username: ${actor}`); | |
| core.setOutput('recipients', JSON.stringify([{ slack_id: process.env.CHANNEL_ID, role: 'Channel', mention: mention || null }])); | |
| return; | |
| } | |
| // Pull request - notify the author. Bot authors have no Slack profile: skip silently. | |
| const author = process.env.PR_AUTHOR; | |
| if (!author || author.endsWith('[bot]')) { | |
| core.setOutput('recipients', '[]'); | |
| return; | |
| } | |
| const slackId = await resolveSlackId(author); | |
| if (!slackId) { | |
| core.warning(`No Slack user found with GitHub username: ${author}`); | |
| core.setOutput('recipients', '[]'); | |
| return; | |
| } | |
| core.setOutput('recipients', JSON.stringify([{ slack_id: slackId, github: author.toLowerCase(), role: 'Author' }])); | |
| # Sends the failure notification to each recipient (DM to the PR author, or the channel | |
| # on main). chat.postMessage accepts a user ID or a channel ID as `channel`. | |
| - name: Send Slack notification | |
| if: steps.resolve.outputs.recipients != '' && steps.resolve.outputs.recipients != '[]' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }} | |
| RECIPIENTS: ${{ steps.resolve.outputs.recipients }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF_NAME: ${{ github.ref_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| COMMIT_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| ACTOR: ${{ github.actor }} | |
| SERVER_URL: ${{ github.server_url }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| with: | |
| script: | | |
| const token = process.env.SLACK_BOT_TOKEN; | |
| const recipients = JSON.parse(process.env.RECIPIENTS); | |
| const isBranch = process.env.EVENT_NAME !== 'pull_request'; | |
| const refName = process.env.REF_NAME; | |
| const prNumber = process.env.PR_NUMBER; | |
| const commitSha = process.env.COMMIT_SHA; | |
| const actor = process.env.ACTOR; | |
| const serverUrl = process.env.SERVER_URL; | |
| const repository = process.env.REPOSITORY; | |
| const runId = process.env.RUN_ID; | |
| const headerText = isBranch | |
| ? `Tests failed against ${refName} branch` | |
| : `Tests failed against ${prNumber} pull request`; | |
| const summaryIcon = isBranch ? 'no_entry' : 'warning'; | |
| const summaryText = isBranch | |
| ? ' Deployment to FFC environments will not happen until this issue is resolved.' | |
| : ' Please resolve the problem before merging your changes into the main branch.'; | |
| const refLink = isBranch | |
| ? `*Branch:* ${refName}` | |
| : `*Pull request:* <${serverUrl}/${repository}/pull/${prNumber}|${prNumber}>`; | |
| let failures = 0; | |
| for (const r of recipients) { | |
| // Channel posts mention the actor's Slack ID (a real ping) if resolved; | |
| // otherwise (and for DMs) show the plain GitHub login. | |
| const authorText = r.mention ? `<@${r.mention}>` : actor; | |
| const blocks = [ | |
| { type: 'header', text: { type: 'plain_text', text: `:x: ${headerText}`, emoji: true } }, | |
| { type: 'divider' }, | |
| { type: 'rich_text', elements: [ { type: 'rich_text_section', elements: [ | |
| { type: 'emoji', name: summaryIcon }, | |
| { type: 'text', text: summaryText, style: { bold: true } } | |
| ] } ] }, | |
| { type: 'divider' }, | |
| { type: 'section', fields: [ | |
| { type: 'mrkdwn', text: `*Author:* ${authorText}` }, | |
| { type: 'mrkdwn', text: `<${serverUrl}/${repository}/actions/runs/${runId}|View failed workflow>` }, | |
| { type: 'mrkdwn', text: `*Last commit:* <${serverUrl}/${repository}/commit/${commitSha}|${commitSha}>` }, | |
| { type: 'mrkdwn', text: refLink } | |
| ] } | |
| ]; | |
| const res = await fetch('https://slack.com/api/chat.postMessage', { | |
| method: 'POST', | |
| headers: { | |
| Authorization: `Bearer ${token}`, | |
| 'Content-Type': 'application/json; charset=utf-8' | |
| }, | |
| body: JSON.stringify({ channel: r.slack_id, blocks }) | |
| }); | |
| const data = await res.json(); | |
| if (!data.ok) { | |
| core.warning(`Slack chat.postMessage failed for ${r.slack_id}: ${data.error}`); | |
| failures++; | |
| } else { | |
| core.info(`Notified ${r.slack_id} (${r.role})`); | |
| } | |
| } | |
| if (failures > 0 && failures === recipients.length) { | |
| core.setFailed(`All ${failures} Slack notifications failed`); | |
| } |