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/create-pr-feature-flag-registry-drift.yml b/.github/workflows/create-pr-feature-flag-registry-drift.yml new file mode 100644 index 00000000..6b649ae1 --- /dev/null +++ b/.github/workflows/create-pr-feature-flag-registry-drift.yml @@ -0,0 +1,158 @@ +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' + pr-label: + required: false + 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 + 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 }} + 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 + 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 + 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 + 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 $REPORT_ARTIFACT" + exit 1 + fi + { + echo "## Feature Flag Registry Drift Report" + echo "" + echo "Feature flag drift was detected between E2E tests and production." + 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 + id: commit + env: + REPOSITORY: ${{ inputs.repository }} + REGISTRY_ARTIFACT: ${{ inputs.registry-artifact-name }} + REGISTRY_PATH: ${{ inputs.registry-file-path }} + run: | + 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" + mkdir -p "$(dirname "$REGISTRY_PATH")" + 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 + 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" + echo "branch=$BRANCH" + echo "timestamp=$(date -u '+%Y-%m-%d %H:%M UTC')" + } >> "$GITHUB_OUTPUT" + fi + + - name: Create Pull Request + id: create-pr + if: steps.commit.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ secrets.github-token }} + BRANCH: ${{ steps.commit.outputs.branch }} + TIMESTAMP: ${{ steps.commit.outputs.timestamp }} + PR_LABEL: ${{ inputs.pr-label }} + run: | + # 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" \ + --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 - $TIMESTAMP" \ + --body-file pr-body.md \ + --base main \ + --head "$BRANCH" \ + "${LABEL_ARGS[@]}") + echo "pr-url=$PR_URL" >> "$GITHUB_OUTPUT"