From 482c66f7edfe6e13629dc8fa15f66a88dc14ce1e Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Wed, 18 Mar 2026 15:42:24 +0700 Subject: [PATCH 1/7] feat: add feature flag drift report action and registry sync workflow - Add feature-flag-drift-report action (Slack notification with optional pr-url) - Add check-feature-flag-registry-drift reusable workflow (creates PR, outputs pr-url) - Use download-artifact@v7 with explicit path for artifacts - Use github.run_id and repository in branch name to avoid collisions - Add registry file validation before cp - Include repository in PR title Made-with: Cursor --- .../feature-flag-drift-report/action.yml | 60 +++++++++ .../check-feature-flag-registry-drift.yml | 118 ++++++++++++++++++ CHANGELOG.md | 5 + package.json | 2 +- 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 .github/actions/feature-flag-drift-report/action.yml create mode 100644 .github/workflows/check-feature-flag-registry-drift.yml diff --git a/.github/actions/feature-flag-drift-report/action.yml b/.github/actions/feature-flag-drift-report/action.yml new file mode 100644 index 00000000..61be3794 --- /dev/null +++ b/.github/actions/feature-flag-drift-report/action.yml @@ -0,0 +1,60 @@ +name: Feature Flag Drift Report +description: 'Sends a Slack notification when feature flag drift is detected in E2E tests vs production.' + +inputs: + title: + description: 'Project identifier shown in the message (e.g. MetaMask Extension, MetaMask Mobile)' + required: true + slack-webhook: + description: 'Slack incoming webhook URL' + required: true + workflow-run-url: + description: 'Full URL to the workflow run' + required: true + pr-url: + description: 'URL of the sync PR (optional; included in message when provided)' + required: false + default: '' + +runs: + using: composite + steps: + - name: Send Slack notification + if: inputs.pr-url == '' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a + with: + webhook: ${{ inputs.slack-webhook }} + webhook-type: incoming-webhook + payload: | + { + "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\n<${{ inputs.workflow-run-url }}|View workflow run>", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\n<${{ inputs.workflow-run-url }}|View workflow run>" + } + } + ] + } + + - name: Send Slack notification with PR link + if: inputs.pr-url != '' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a + with: + webhook: ${{ inputs.slack-webhook }} + webhook-type: incoming-webhook + payload: | + { + "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\nA sync PR has been created: <${{ inputs.pr-url }}|View PR>\n\n<${{ inputs.workflow-run-url }}|View workflow run>", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\nA sync PR has been created: <${{ inputs.pr-url }}|View PR>\n\n<${{ inputs.workflow-run-url }}|View workflow run>" + } + } + ] + } diff --git a/.github/workflows/check-feature-flag-registry-drift.yml b/.github/workflows/check-feature-flag-registry-drift.yml new file mode 100644 index 00000000..83824312 --- /dev/null +++ b/.github/workflows/check-feature-flag-registry-drift.yml @@ -0,0 +1,118 @@ +name: Create PR for Feature Flag Registry Drift + +on: + workflow_call: + inputs: + repository: + required: true + type: string + description: 'The repository name (e.g. metamask-extension)' + registry-file-path: + required: true + type: string + description: 'Path in the repo where the registry file lives (e.g. app/scripts/feature-flags/registry.json)' + registry-artifact-name: + required: true + type: string + description: 'Name of the artifact containing the updated registry file' + report-artifact-name: + required: true + type: string + description: 'Name of the artifact containing report.json' + secrets: + github-token: + required: true + description: 'Token with contents write and pull-requests write permissions' + outputs: + pr-url: + description: 'URL of the created pull request (empty if no PR was created)' + value: ${{ jobs.create-drift-pr.outputs.pr-url }} + +jobs: + create-drift-pr: + outputs: + pr-url: ${{ steps.create-pr.outputs.pr-url }} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + token: ${{ secrets.github-token }} + + - name: Download registry artifact + uses: actions/download-artifact@v7 + with: + name: ${{ inputs.registry-artifact-name }} + path: ${{ inputs.registry-artifact-name }} + + - name: Download report artifact + uses: actions/download-artifact@v7 + with: + name: ${{ inputs.report-artifact-name }} + path: ${{ inputs.report-artifact-name }} + + - name: Build PR body from report + id: pr-body + run: | + REPORT_FILE="${{ inputs.report-artifact-name }}/report.json" + if [[ ! -f "$REPORT_FILE" ]]; then + REPORT_FILE=$(find "${{ inputs.report-artifact-name }}" -name "report.json" -type f | head -1) + fi + if [[ -z "$REPORT_FILE" || ! -f "$REPORT_FILE" ]]; then + echo "::error::report.json not found in artifact ${{ inputs.report-artifact-name }}" + exit 1 + fi + { + echo "## Feature Flag Registry Drift Report" + echo "" + echo "The following drift was detected between E2E tests and production:" + echo "" + echo '```json' + cat "$REPORT_FILE" + echo '```' + } > pr-body.md + + - name: Create branch and commit registry change + run: | + BRANCH="qa/sync-ff-flag-${{ inputs.repository }}-${{ github.run_id }}" + REGISTRY_ARTIFACT="${{ inputs.registry-artifact-name }}" + REGISTRY_PATH="${{ inputs.registry-file-path }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + mkdir -p "$(dirname "$REGISTRY_PATH")" + REGISTRY_SRC=$(find "./$REGISTRY_ARTIFACT" -type f | head -1) + if [[ -z "$REGISTRY_SRC" ]]; then + echo "::error::No registry file found in artifact $REGISTRY_ARTIFACT" + exit 1 + fi + cp "$REGISTRY_SRC" "$REGISTRY_PATH" + git add "$REGISTRY_PATH" + git status + if git diff --staged --quiet; then + echo "::warning::No changes to registry file. Nothing to commit." + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + git commit -m "chore: sync feature flag registry from E2E" + git push origin "$BRANCH" + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + fi + id: commit + + - name: Create Pull Request + id: create-pr + if: steps.commit.outputs.has_changes == 'true' + run: | + PR_URL=$(gh pr create \ + --title "[QA] Sync Feature Flag Registry (${{ inputs.repository }})" \ + --body-file pr-body.md \ + --base main \ + --head "${{ steps.commit.outputs.branch }}" \ + --label "team-qa") + echo "pr-url=$PR_URL" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.github-token }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 496e4dcc..548cd5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `feature-flag-drift-report` action to notify Slack when feature flag drift is detected (supports optional `pr-url` input to include sync PR link) +- Add `check-feature-flag-registry-drift` reusable workflow to create PRs for feature flag registry updates (outputs `pr-url` for use in Slack notifications) + ## [1.8.0] ### Added diff --git a/package.json b/package.json index c9d5372e..68755b65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/github-tools", - "version": "1.8.0", + "version": "1.7.1", "private": true, "description": "Tools for interacting with the GitHub API to do metrics gathering", "repository": { From d385655a7dc7d1e4f507624c9117b691feaf852a Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Wed, 18 Mar 2026 15:49:43 +0700 Subject: [PATCH 2/7] test: revert changelog --- CHANGELOG.md | 5 ----- package.json | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548cd5ac..496e4dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added - -- Add `feature-flag-drift-report` action to notify Slack when feature flag drift is detected (supports optional `pr-url` input to include sync PR link) -- Add `check-feature-flag-registry-drift` reusable workflow to create PRs for feature flag registry updates (outputs `pr-url` for use in Slack notifications) - ## [1.8.0] ### Added diff --git a/package.json b/package.json index 68755b65..05abd401 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/github-tools", - "version": "1.7.1", + "version": "1.8.1", "private": true, "description": "Tools for interacting with the GitHub API to do metrics gathering", "repository": { From 6562cb38a961f6ecd2374df856b6149d9c39d23a Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Wed, 18 Mar 2026 15:50:12 +0700 Subject: [PATCH 3/7] test: revert version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05abd401..c9d5372e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/github-tools", - "version": "1.8.1", + "version": "1.8.0", "private": true, "description": "Tools for interacting with the GitHub API to do metrics gathering", "repository": { From f785c087cd80541e3c89d736c30d2abe0747bd83 Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Wed, 18 Mar 2026 16:17:50 +0700 Subject: [PATCH 4/7] test: update --- .../feature-flag-drift-report/action.yml | 56 ++++++++----------- .../check-feature-flag-registry-drift.yml | 53 +++++++++++++----- 2 files changed, 61 insertions(+), 48 deletions(-) diff --git a/.github/actions/feature-flag-drift-report/action.yml b/.github/actions/feature-flag-drift-report/action.yml index 61be3794..5dee915d 100644 --- a/.github/actions/feature-flag-drift-report/action.yml +++ b/.github/actions/feature-flag-drift-report/action.yml @@ -19,42 +19,30 @@ inputs: runs: using: composite steps: - - name: Send Slack notification - if: inputs.pr-url == '' - uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a - with: - webhook: ${{ inputs.slack-webhook }} - webhook-type: incoming-webhook - payload: | - { - "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\n<${{ inputs.workflow-run-url }}|View workflow run>", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\n<${{ inputs.workflow-run-url }}|View workflow run>" - } - } - ] - } + - name: Build Slack payload + id: payload + shell: bash + env: + TITLE: ${{ inputs.title }} + WORKFLOW_URL: ${{ inputs.workflow-run-url }} + PR_URL: ${{ inputs.pr-url }} + run: | + BASE="*[${TITLE}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command \`yarn feature-flags:sync:update\` locally to update the registry.\n\n" + if [[ -n "$PR_URL" ]]; then + MSG="${BASE}A sync PR has been created: <${PR_URL}|View PR>\n\n<${WORKFLOW_URL}|View workflow run>" + else + MSG="${BASE}<${WORKFLOW_URL}|View workflow run>" + fi + PAYLOAD=$(jq -n --arg m "$MSG" '{text: $m, blocks: [{type: "section", text: {type: "mrkdwn", text: $m}}]}') + { + echo "payload<> "$GITHUB_OUTPUT" - - name: Send Slack notification with PR link - if: inputs.pr-url != '' + - name: Send Slack notification uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a with: webhook: ${{ inputs.slack-webhook }} webhook-type: incoming-webhook - payload: | - { - "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\nA sync PR has been created: <${{ inputs.pr-url }}|View PR>\n\n<${{ inputs.workflow-run-url }}|View workflow run>", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*[${{ inputs.title }}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command `yarn feature-flags:sync:update` locally to update the registry.\n\nA sync PR has been created: <${{ inputs.pr-url }}|View PR>\n\n<${{ inputs.workflow-run-url }}|View workflow run>" - } - } - ] - } + payload: ${{ steps.payload.outputs.payload }} diff --git a/.github/workflows/check-feature-flag-registry-drift.yml b/.github/workflows/check-feature-flag-registry-drift.yml index 83824312..981eb791 100644 --- a/.github/workflows/check-feature-flag-registry-drift.yml +++ b/.github/workflows/check-feature-flag-registry-drift.yml @@ -19,6 +19,11 @@ on: required: true type: string description: 'Name of the artifact containing report.json' + pr-label: + required: false + type: string + default: '' + description: 'Label to apply to the created PR (must exist in the target repo)' secrets: github-token: required: true @@ -27,11 +32,15 @@ on: pr-url: description: 'URL of the created pull request (empty if no PR was created)' value: ${{ jobs.create-drift-pr.outputs.pr-url }} + has-changes: + description: 'Whether the registry had drift that needed syncing' + value: ${{ jobs.create-drift-pr.outputs.has-changes }} jobs: create-drift-pr: outputs: pr-url: ${{ steps.create-pr.outputs.pr-url }} + has-changes: ${{ steps.commit.outputs.has_changes }} runs-on: ubuntu-latest permissions: contents: write @@ -56,13 +65,15 @@ jobs: - name: Build PR body from report id: pr-body + env: + REPORT_ARTIFACT: ${{ inputs.report-artifact-name }} run: | - REPORT_FILE="${{ inputs.report-artifact-name }}/report.json" + REPORT_FILE="$REPORT_ARTIFACT/report.json" if [[ ! -f "$REPORT_FILE" ]]; then - REPORT_FILE=$(find "${{ inputs.report-artifact-name }}" -name "report.json" -type f | head -1) + REPORT_FILE=$(find "$REPORT_ARTIFACT" -name "report.json" -type f | head -1) fi if [[ -z "$REPORT_FILE" || ! -f "$REPORT_FILE" ]]; then - echo "::error::report.json not found in artifact ${{ inputs.report-artifact-name }}" + echo "::error::report.json not found in artifact $REPORT_ARTIFACT" exit 1 fi { @@ -76,15 +87,18 @@ jobs: } > pr-body.md - name: Create branch and commit registry change + id: commit + env: + REPOSITORY: ${{ inputs.repository }} + REGISTRY_ARTIFACT: ${{ inputs.registry-artifact-name }} + REGISTRY_PATH: ${{ inputs.registry-file-path }} run: | - BRANCH="qa/sync-ff-flag-${{ inputs.repository }}-${{ github.run_id }}" - REGISTRY_ARTIFACT="${{ inputs.registry-artifact-name }}" - REGISTRY_PATH="${{ inputs.registry-file-path }}" + BRANCH="qa/sync-ff-registry-$REPOSITORY" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git checkout -b "$BRANCH" mkdir -p "$(dirname "$REGISTRY_PATH")" - REGISTRY_SRC=$(find "./$REGISTRY_ARTIFACT" -type f | head -1) + REGISTRY_SRC=$(find "./$REGISTRY_ARTIFACT" -name "$(basename "$REGISTRY_PATH")" -type f | head -1) if [[ -z "$REGISTRY_SRC" ]]; then echo "::error::No registry file found in artifact $REGISTRY_ARTIFACT" exit 1 @@ -97,22 +111,33 @@ jobs: echo "has_changes=false" >> "$GITHUB_OUTPUT" else git commit -m "chore: sync feature flag registry from E2E" - git push origin "$BRANCH" + git push --force-with-lease origin "$BRANCH" echo "has_changes=true" >> "$GITHUB_OUTPUT" echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" fi - id: commit - name: Create Pull Request id: create-pr if: steps.commit.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ secrets.github-token }} + REPOSITORY: ${{ inputs.repository }} + BRANCH: ${{ steps.commit.outputs.branch }} + PR_LABEL: ${{ inputs.pr-label }} run: | + EXISTING=$(gh pr list --head "$BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null || true) + if [[ -n "$EXISTING" ]]; then + echo "pr-url=$EXISTING" >> "$GITHUB_OUTPUT" + exit 0 + fi + LABEL_ARGS=() + if [[ -n "$PR_LABEL" ]]; then + LABEL_ARGS=(--label "$PR_LABEL") + fi PR_URL=$(gh pr create \ - --title "[QA] Sync Feature Flag Registry (${{ inputs.repository }})" \ + --title "[QA] Sync Feature Flag Registry ($REPOSITORY)" \ --body-file pr-body.md \ --base main \ - --head "${{ steps.commit.outputs.branch }}" \ - --label "team-qa") + --head "$BRANCH" \ + "${LABEL_ARGS[@]}") echo "pr-url=$PR_URL" >> "$GITHUB_OUTPUT" - env: - GH_TOKEN: ${{ secrets.github-token }} From 5c64952dd2151c52c7a7121765f0c26c8bc97c16 Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Wed, 18 Mar 2026 21:42:06 +0700 Subject: [PATCH 5/7] test: update --- .../feature-flag-drift-report/action.yml | 48 ------------- .../feature-flag-drift-slack-noti/action.yml | 71 +++++++++++++++++++ ...create-pr-feature-flag-registry-drift.yml} | 39 ++++++---- 3 files changed, 97 insertions(+), 61 deletions(-) delete mode 100644 .github/actions/feature-flag-drift-report/action.yml create mode 100644 .github/actions/feature-flag-drift-slack-noti/action.yml rename .github/workflows/{check-feature-flag-registry-drift.yml => create-pr-feature-flag-registry-drift.yml} (76%) diff --git a/.github/actions/feature-flag-drift-report/action.yml b/.github/actions/feature-flag-drift-report/action.yml deleted file mode 100644 index 5dee915d..00000000 --- a/.github/actions/feature-flag-drift-report/action.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Feature Flag Drift Report -description: 'Sends a Slack notification when feature flag drift is detected in E2E tests vs production.' - -inputs: - title: - description: 'Project identifier shown in the message (e.g. MetaMask Extension, MetaMask Mobile)' - required: true - slack-webhook: - description: 'Slack incoming webhook URL' - required: true - workflow-run-url: - description: 'Full URL to the workflow run' - required: true - pr-url: - description: 'URL of the sync PR (optional; included in message when provided)' - required: false - default: '' - -runs: - using: composite - steps: - - name: Build Slack payload - id: payload - shell: bash - env: - TITLE: ${{ inputs.title }} - WORKFLOW_URL: ${{ inputs.workflow-run-url }} - PR_URL: ${{ inputs.pr-url }} - run: | - BASE="*[${TITLE}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command \`yarn feature-flags:sync:update\` locally to update the registry.\n\n" - if [[ -n "$PR_URL" ]]; then - MSG="${BASE}A sync PR has been created: <${PR_URL}|View PR>\n\n<${WORKFLOW_URL}|View workflow run>" - else - MSG="${BASE}<${WORKFLOW_URL}|View workflow run>" - fi - PAYLOAD=$(jq -n --arg m "$MSG" '{text: $m, blocks: [{type: "section", text: {type: "mrkdwn", text: $m}}]}') - { - echo "payload<> "$GITHUB_OUTPUT" - - - name: Send Slack notification - uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a - with: - webhook: ${{ inputs.slack-webhook }} - webhook-type: incoming-webhook - payload: ${{ steps.payload.outputs.payload }} diff --git a/.github/actions/feature-flag-drift-slack-noti/action.yml b/.github/actions/feature-flag-drift-slack-noti/action.yml new file mode 100644 index 00000000..7d06030b --- /dev/null +++ b/.github/actions/feature-flag-drift-slack-noti/action.yml @@ -0,0 +1,71 @@ +name: Feature Flag Drift Report +description: 'Sends a Slack notification when feature flag drift is detected in E2E tests vs production.' + +inputs: + title: + description: 'Project identifier shown in the message (e.g. MetaMask Extension, MetaMask Mobile)' + required: true + slack-webhook: + description: 'Slack incoming webhook URL' + required: true + workflow-run-url: + description: 'Full URL to the workflow run' + required: true + pr-url: + description: 'URL of the sync PR (optional; included in message when provided)' + required: false + default: '' + +runs: + using: composite + steps: + - name: Build Slack payload + id: payload + shell: bash + env: + TITLE: ${{ inputs.title }} + WORKFLOW_URL: ${{ inputs.workflow-run-url }} + PR_URL: ${{ inputs.pr-url }} + run: | + # Use multiple blocks instead of newlines - more reliable in Slack + if [[ -n "$PR_URL" ]]; then + PAYLOAD=$(jq -n \ + --arg title "$TITLE" \ + --arg pr_url "$PR_URL" \ + --arg workflow_url "$WORKFLOW_URL" \ + '{ + text: ("*[" + $title + "] Feature Flags Drift Detected in E2E tests vs Prod* :warning:"), + blocks: [ + { type: "section", text: { type: "mrkdwn", text: ("*[" + $title + "] Feature Flags Drift Detected in E2E tests vs Prod* :warning:") } }, + { type: "section", text: { type: "mrkdwn", text: "Check the workflow run for details: download the drift report JSON artifact and review the report." } }, + { type: "section", text: { type: "mrkdwn", text: "You can run command `yarn feature-flags:sync:update` locally to update the registry." } }, + { type: "section", text: { type: "mrkdwn", text: ("A sync PR has been created: <" + $pr_url + "|View PR>") } }, + { type: "section", text: { type: "mrkdwn", text: ("<" + $workflow_url + "|View workflow run>") } } + ] + }') + else + PAYLOAD=$(jq -n \ + --arg title "$TITLE" \ + --arg workflow_url "$WORKFLOW_URL" \ + '{ + text: ("*[" + $title + "] Feature Flags Drift Detected in E2E tests vs Prod* :warning:"), + blocks: [ + { type: "section", text: { type: "mrkdwn", text: ("*[" + $title + "] Feature Flags Drift Detected in E2E tests vs Prod* :warning:") } }, + { type: "section", text: { type: "mrkdwn", text: "Check the workflow run for details: download the drift report JSON artifact and review the report." } }, + { type: "section", text: { type: "mrkdwn", text: "You can run command `yarn feature-flags:sync:update` locally to update the registry." } }, + { type: "section", text: { type: "mrkdwn", text: ("<" + $workflow_url + "|View workflow run>") } } + ] + }') + fi + { + echo "payload<> "$GITHUB_OUTPUT" + + - name: Send Slack notification + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a + with: + webhook: ${{ inputs.slack-webhook }} + webhook-type: incoming-webhook + payload: ${{ steps.payload.outputs.payload }} diff --git a/.github/workflows/check-feature-flag-registry-drift.yml b/.github/workflows/create-pr-feature-flag-registry-drift.yml similarity index 76% rename from .github/workflows/check-feature-flag-registry-drift.yml rename to .github/workflows/create-pr-feature-flag-registry-drift.yml index 981eb791..ab344562 100644 --- a/.github/workflows/check-feature-flag-registry-drift.yml +++ b/.github/workflows/create-pr-feature-flag-registry-drift.yml @@ -24,6 +24,11 @@ on: type: string default: '' description: 'Label to apply to the created PR (must exist in the target repo)' + workflow-run-url: + required: false + type: string + default: '' + description: 'URL of the workflow run (for artifact download link in PR body)' secrets: github-token: required: true @@ -67,6 +72,7 @@ jobs: id: pr-body env: REPORT_ARTIFACT: ${{ inputs.report-artifact-name }} + WORKFLOW_RUN_URL: ${{ inputs.workflow-run-url }} run: | REPORT_FILE="$REPORT_ARTIFACT/report.json" if [[ ! -f "$REPORT_FILE" ]]; then @@ -79,11 +85,13 @@ jobs: { echo "## Feature Flag Registry Drift Report" echo "" - echo "The following drift was detected between E2E tests and production:" + echo "Feature flag drift was detected between E2E tests and production." echo "" - echo '```json' - cat "$REPORT_FILE" - echo '```' + if [[ -n "$WORKFLOW_RUN_URL" ]]; then + echo "Download the [\`$REPORT_ARTIFACT\` artifact]($WORKFLOW_RUN_URL) for the full report." + else + echo "Download the \`$REPORT_ARTIFACT\` artifact from the workflow run for the full report." + fi } > pr-body.md - name: Create branch and commit registry change @@ -93,7 +101,7 @@ jobs: REGISTRY_ARTIFACT: ${{ inputs.registry-artifact-name }} REGISTRY_PATH: ${{ inputs.registry-file-path }} run: | - BRANCH="qa/sync-ff-registry-$REPOSITORY" + BRANCH="qa/sync-ff-registry-$REPOSITORY-$(date -u +%Y-%m-%d-%H%M)" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git checkout -b "$BRANCH" @@ -111,9 +119,10 @@ jobs: echo "has_changes=false" >> "$GITHUB_OUTPUT" else git commit -m "chore: sync feature flag registry from E2E" - git push --force-with-lease origin "$BRANCH" + git push --force origin "$BRANCH" echo "has_changes=true" >> "$GITHUB_OUTPUT" echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + echo "timestamp=$(date -u '+%Y-%m-%d %H:%M UTC')" >> "$GITHUB_OUTPUT" fi - name: Create Pull Request @@ -121,21 +130,25 @@ jobs: if: steps.commit.outputs.has_changes == 'true' env: GH_TOKEN: ${{ secrets.github-token }} - REPOSITORY: ${{ inputs.repository }} BRANCH: ${{ steps.commit.outputs.branch }} + TIMESTAMP: ${{ steps.commit.outputs.timestamp }} PR_LABEL: ${{ inputs.pr-label }} run: | - EXISTING=$(gh pr list --head "$BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null || true) - if [[ -n "$EXISTING" ]]; then - echo "pr-url=$EXISTING" >> "$GITHUB_OUTPUT" - exit 0 - fi + # Close stale open PRs superseded by this run + gh pr list --search "Sync Feature Flag Registry in:title" \ + --state open --json number --jq '.[].number' 2>/dev/null | \ + while read -r pr_number; do + [[ -z "$pr_number" ]] && continue + gh pr close "$pr_number" \ + --comment "Superseded by a newer sync PR." \ + --delete-branch 2>/dev/null || true + done LABEL_ARGS=() if [[ -n "$PR_LABEL" ]]; then LABEL_ARGS=(--label "$PR_LABEL") fi PR_URL=$(gh pr create \ - --title "[QA] Sync Feature Flag Registry ($REPOSITORY)" \ + --title "[QA] Sync Feature Flag Registry - $TIMESTAMP" \ --body-file pr-body.md \ --base main \ --head "$BRANCH" \ From 3d3ce09981aa067a75bdd453f570b466c8a120ff Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Wed, 18 Mar 2026 22:14:44 +0700 Subject: [PATCH 6/7] test: fix lint --- .../workflows/create-pr-feature-flag-registry-drift.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-pr-feature-flag-registry-drift.yml b/.github/workflows/create-pr-feature-flag-registry-drift.yml index ab344562..7d578c00 100644 --- a/.github/workflows/create-pr-feature-flag-registry-drift.yml +++ b/.github/workflows/create-pr-feature-flag-registry-drift.yml @@ -120,9 +120,11 @@ jobs: else git commit -m "chore: sync feature flag registry from E2E" git push --force origin "$BRANCH" - echo "has_changes=true" >> "$GITHUB_OUTPUT" - echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" - echo "timestamp=$(date -u '+%Y-%m-%d %H:%M UTC')" >> "$GITHUB_OUTPUT" + { + echo "has_changes=true" + echo "branch=$BRANCH" + echo "timestamp=$(date -u '+%Y-%m-%d %H:%M UTC')" + } >> "$GITHUB_OUTPUT" fi - name: Create Pull Request From 01e3ae0286fd353170f6145794a80eff091abf74 Mon Sep 17 00:00:00 2001 From: LeVinhGithub Date: Tue, 24 Mar 2026 22:35:19 +0700 Subject: [PATCH 7/7] test: fix comment --- .../workflows/create-pr-feature-flag-registry-drift.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/create-pr-feature-flag-registry-drift.yml b/.github/workflows/create-pr-feature-flag-registry-drift.yml index 7d578c00..6b649ae1 100644 --- a/.github/workflows/create-pr-feature-flag-registry-drift.yml +++ b/.github/workflows/create-pr-feature-flag-registry-drift.yml @@ -119,7 +119,7 @@ jobs: echo "has_changes=false" >> "$GITHUB_OUTPUT" else git commit -m "chore: sync feature flag registry from E2E" - git push --force origin "$BRANCH" + git push origin "$BRANCH" { echo "has_changes=true" echo "branch=$BRANCH" @@ -136,9 +136,9 @@ jobs: TIMESTAMP: ${{ steps.commit.outputs.timestamp }} PR_LABEL: ${{ inputs.pr-label }} run: | - # Close stale open PRs superseded by this run - gh pr list --search "Sync Feature Flag Registry in:title" \ - --state open --json number --jq '.[].number' 2>/dev/null | \ + # Close stale open PRs on qa/sync-ff-registry-* branches (not title search, to avoid closing unrelated PRs) + gh pr list --state open --json number,headRefName 2>/dev/null | jq -r --arg head "$BRANCH" \ + '.[] | select(.headRefName | startswith("qa/sync-ff-registry-")) | select(.headRefName != $head) | .number' | \ while read -r pr_number; do [[ -z "$pr_number" ]] && continue gh pr close "$pr_number" \