cloud-premium-webhook #6732
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: Webhook Receiver for Private Repos | |
| on: | |
| repository_dispatch: | |
| types: [private-repo-webhook, cloud-premium-webhook] | |
| workflow_dispatch: | |
| inputs: | |
| event_type: | |
| description: "Event type (pr_opened, pr_sync, push)" | |
| required: true | |
| type: choice | |
| options: | |
| - pr_opened | |
| - pr_sync | |
| - push | |
| pr_number: | |
| description: "PR number (for PR events)" | |
| required: false | |
| type: string | |
| pr_ref: | |
| description: "Git ref/branch to test" | |
| required: true | |
| type: string | |
| workflows: | |
| description: "Workflows to trigger (comma-separated: flutter,rust,mobile,ios,rust_coverage,docker,commit_lint,backend,frontend,e2e,cloud_docker,rustlint,integration)" | |
| required: true | |
| type: string | |
| default: "flutter,rust" | |
| source_repo: | |
| description: "Source repository (AppFlowy-Premium or AppFlowy-Cloud-Premium)" | |
| required: false | |
| type: string | |
| default: "AppFlowy-Premium" | |
| concurrency: | |
| group: webhook-pr-${{ github.event.client_payload.pr_number || github.event.inputs.pr_number || format('push-{0}', github.event.client_payload.pr_ref || github.event.inputs.pr_ref || github.run_id) }} | |
| cancel-in-progress: true | |
| jobs: | |
| dispatch-workflows: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| flutter_run_url: ${{ steps.get_run_urls.outputs.flutter_run_url }} | |
| rust_run_url: ${{ steps.get_run_urls.outputs.rust_run_url }} | |
| mobile_run_url: ${{ steps.get_run_urls.outputs.mobile_run_url }} | |
| ios_run_url: ${{ steps.get_run_urls.outputs.ios_run_url }} | |
| rust_coverage_run_url: ${{ steps.get_run_urls.outputs.rust_coverage_run_url }} | |
| docker_run_url: ${{ steps.get_run_urls.outputs.docker_run_url }} | |
| commit_lint_run_url: ${{ steps.get_run_urls.outputs.commit_lint_run_url }} | |
| backend_run_url: ${{ steps.get_run_urls.outputs.backend_run_url }} | |
| frontend_run_url: ${{ steps.get_run_urls.outputs.frontend_run_url }} | |
| e2e_run_url: ${{ steps.get_run_urls.outputs.e2e_run_url }} | |
| cloud_docker_run_url: ${{ steps.get_run_urls.outputs.cloud_docker_run_url }} | |
| rustlint_run_url: ${{ steps.get_run_urls.outputs.rustlint_run_url }} | |
| integration_run_url: ${{ steps.get_run_urls.outputs.integration_run_url }} | |
| commercial_run_url: ${{ steps.get_run_urls.outputs.commercial_run_url }} | |
| steps: | |
| - name: Parse event data | |
| id: parse | |
| run: | | |
| if [ "${{ github.event.action }}" = "cloud-premium-webhook" ]; then | |
| echo "source_repo=AppFlowy-Cloud-Premium" >> $GITHUB_OUTPUT | |
| elif [ "${{ github.event.action }}" = "private-repo-webhook" ]; then | |
| echo "source_repo=AppFlowy-Premium" >> $GITHUB_OUTPUT | |
| else | |
| echo "source_repo=${{ github.event.inputs.source_repo || 'AppFlowy-Premium' }}" >> $GITHUB_OUTPUT | |
| fi | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| echo "event_type=${{ github.event.client_payload.event_type }}" >> $GITHUB_OUTPUT | |
| echo "pr_number=${{ github.event.client_payload.pr_number }}" >> $GITHUB_OUTPUT | |
| echo "pr_ref=${{ github.event.client_payload.pr_ref }}" >> $GITHUB_OUTPUT | |
| echo "workflows=${{ github.event.client_payload.workflows }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "event_type=${{ github.event.inputs.event_type }}" >> $GITHUB_OUTPUT | |
| echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT | |
| echo "pr_ref=${{ github.event.inputs.pr_ref }}" >> $GITHUB_OUTPUT | |
| echo "workflows=${{ github.event.inputs.workflows }}" >> $GITHUB_OUTPUT | |
| fi | |
| echo "execution_id=exec-$(date +%s)-${{ github.run_id }}" >> $GITHUB_OUTPUT | |
| - name: Display event information | |
| run: | | |
| echo "Source Repository: ${{ steps.parse.outputs.source_repo }}" | |
| echo "Event Type: ${{ steps.parse.outputs.event_type }}" | |
| echo "PR Number: ${{ steps.parse.outputs.pr_number }}" | |
| echo "Git Ref: ${{ steps.parse.outputs.pr_ref }}" | |
| echo "Workflows: ${{ steps.parse.outputs.workflows }}" | |
| echo "Execution ID: ${{ steps.parse.outputs.execution_id }}" | |
| - name: Trigger Flutter CI | |
| if: contains(steps.parse.outputs.workflows, 'flutter') | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-flutter-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "flutter" | |
| } | |
| - name: Trigger Rust CI | |
| if: contains(steps.parse.outputs.workflows, 'rust') && steps.parse.outputs.source_repo == 'AppFlowy-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-rust-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "rust" | |
| } | |
| - name: Trigger Mobile CI | |
| if: contains(steps.parse.outputs.workflows, 'mobile') | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-mobile-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "mobile" | |
| } | |
| - name: Trigger iOS CI | |
| if: contains(steps.parse.outputs.workflows, 'ios') | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-ios-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "ios" | |
| } | |
| - name: Trigger Rust Coverage | |
| if: contains(steps.parse.outputs.workflows, 'rust_coverage') | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-rust-coverage | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "rust_coverage" | |
| } | |
| - name: Trigger Docker CI | |
| if: contains(steps.parse.outputs.workflows, 'docker') | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-docker-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "docker" | |
| } | |
| - name: Trigger Commit Lint | |
| if: contains(steps.parse.outputs.workflows, 'commit_lint') | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: private-repo-commit-lint | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "commit_lint" | |
| } | |
| - name: Trigger Backend CI | |
| if: contains(steps.parse.outputs.workflows, 'backend') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: cloud-premium-backend-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "backend" | |
| } | |
| - name: Trigger Frontend CI | |
| if: contains(steps.parse.outputs.workflows, 'frontend') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: cloud-premium-frontend-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "frontend" | |
| } | |
| - name: Trigger E2E CI | |
| if: contains(steps.parse.outputs.workflows, 'e2e') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: cloud-premium-e2e-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "e2e" | |
| } | |
| - name: Trigger Cloud Docker CI | |
| if: contains(steps.parse.outputs.workflows, 'cloud_docker') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: cloud-premium-docker-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "cloud_docker" | |
| } | |
| - name: Trigger Rustlint CI | |
| if: contains(steps.parse.outputs.workflows, 'rustlint') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: cloud-premium-rustlint-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "rustlint" | |
| } | |
| - name: Trigger Integration CI | |
| if: contains(steps.parse.outputs.workflows, 'integration') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: cloud-premium-integration-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "integration" | |
| } | |
| - name: Trigger CommercialIntegration CI | |
| if: contains(steps.parse.outputs.workflows, 'commercial') && steps.parse.outputs.source_repo == 'AppFlowy-Cloud-Premium' | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| event-type: commercial-cloud-integration-ci | |
| client-payload: | | |
| { | |
| "pr_number": "${{ steps.parse.outputs.pr_number }}", | |
| "pr_ref": "${{ steps.parse.outputs.pr_ref }}", | |
| "event_type": "${{ steps.parse.outputs.event_type }}", | |
| "execution_id": "${{ steps.parse.outputs.execution_id }}", | |
| "workflow_type": "commercial" | |
| } | |
| - name: Wait for workflows to start and get run URLs | |
| id: get_run_urls | |
| env: | |
| ADMIN_GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| WORKFLOWS: ${{ steps.parse.outputs.workflows }} | |
| run: | | |
| echo "⏳ Waiting for workflows to start and fetching run URLs..." | |
| declare -A workflow_files=( | |
| ["flutter"]="flutter_ci.yaml" | |
| ["rust"]="rust_ci.yaml" | |
| ["mobile"]="mobile_ci.yml" | |
| ["ios"]="ios_ci.yaml" | |
| ["rust_coverage"]="rust_coverage.yml" | |
| ["docker"]="docker_ci.yml" | |
| ["commit_lint"]="commit_lint.yml" | |
| ["backend"]="cloud_backend_ci.yaml" | |
| ["frontend"]="cloud_frontend_ci.yaml" | |
| ["e2e"]="cloud_e2e_ci.yaml" | |
| ["cloud_docker"]="cloud_docker_ci.yaml" | |
| ["rustlint"]="cloud_rustlint_ci.yaml" | |
| ["integration"]="cloud_integration_ci.yaml" | |
| ["commercial"]="cloud_commercial_integration_ci.yaml" | |
| ) | |
| IFS=',' read -ra WORKFLOW_ARRAY <<< "$WORKFLOWS" | |
| sleep 10 | |
| flutter_run_url="" | |
| rust_run_url="" | |
| mobile_run_url="" | |
| ios_run_url="" | |
| rust_coverage_run_url="" | |
| docker_run_url="" | |
| commit_lint_run_url="" | |
| backend_run_url="" | |
| frontend_run_url="" | |
| e2e_run_url="" | |
| cloud_docker_run_url="" | |
| rustlint_run_url="" | |
| integration_run_url="" | |
| commercial_run_url="" | |
| for workflow in "${WORKFLOW_ARRAY[@]}"; do | |
| workflow_file="${workflow_files[$workflow]}" | |
| if [ -n "$workflow_file" ]; then | |
| echo "Fetching latest run for workflow: $workflow_file" | |
| response=$(curl -s -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/AppFlowy-IO/AppFlowy-CI/actions/workflows/$workflow_file/runs?per_page=5") | |
| for i in {0..4}; do | |
| run_event=$(echo "$response" | jq -r --arg i "$i" '.workflow_runs[$i | tonumber].event // "null"') | |
| run_created=$(echo "$response" | jq -r --arg i "$i" '.workflow_runs[$i | tonumber].created_at // "null"') | |
| run_url=$(echo "$response" | jq -r --arg i "$i" '.workflow_runs[$i | tonumber].html_url // "null"') | |
| if [ "$run_event" = "null" ]; then | |
| break | |
| fi | |
| if [ "$run_event" = "repository_dispatch" ]; then | |
| run_created_timestamp=$(date -d "$run_created" +%s 2>/dev/null || echo "0") | |
| current_timestamp=$(date +%s) | |
| time_diff=$((current_timestamp - run_created_timestamp)) | |
| if [ $time_diff -le 300 ]; then | |
| case $workflow in | |
| "flutter") | |
| flutter_run_url="$run_url" | |
| ;; | |
| "rust") | |
| rust_run_url="$run_url" | |
| ;; | |
| "mobile") | |
| mobile_run_url="$run_url" | |
| ;; | |
| "ios") | |
| ios_run_url="$run_url" | |
| ;; | |
| "rust_coverage") | |
| rust_coverage_run_url="$run_url" | |
| ;; | |
| "docker") | |
| docker_run_url="$run_url" | |
| ;; | |
| "commit_lint") | |
| commit_lint_run_url="$run_url" | |
| ;; | |
| "backend") | |
| backend_run_url="$run_url" | |
| ;; | |
| "frontend") | |
| frontend_run_url="$run_url" | |
| ;; | |
| "e2e") | |
| e2e_run_url="$run_url" | |
| ;; | |
| "cloud_docker") | |
| cloud_docker_run_url="$run_url" | |
| ;; | |
| "rustlint") | |
| rustlint_run_url="$run_url" | |
| ;; | |
| "integration") | |
| integration_run_url="$run_url" | |
| ;; | |
| "commercial") | |
| commercial_run_url="$run_url" | |
| ;; | |
| esac | |
| echo "Found run URL for $workflow: $run_url" | |
| break | |
| fi | |
| fi | |
| done | |
| fi | |
| done | |
| echo "flutter_run_url=$flutter_run_url" >> $GITHUB_OUTPUT | |
| echo "rust_run_url=$rust_run_url" >> $GITHUB_OUTPUT | |
| echo "mobile_run_url=$mobile_run_url" >> $GITHUB_OUTPUT | |
| echo "ios_run_url=$ios_run_url" >> $GITHUB_OUTPUT | |
| echo "rust_coverage_run_url=$rust_coverage_run_url" >> $GITHUB_OUTPUT | |
| echo "docker_run_url=$docker_run_url" >> $GITHUB_OUTPUT | |
| echo "commit_lint_run_url=$commit_lint_run_url" >> $GITHUB_OUTPUT | |
| echo "backend_run_url=$backend_run_url" >> $GITHUB_OUTPUT | |
| echo "frontend_run_url=$frontend_run_url" >> $GITHUB_OUTPUT | |
| echo "e2e_run_url=$e2e_run_url" >> $GITHUB_OUTPUT | |
| echo "cloud_docker_run_url=$cloud_docker_run_url" >> $GITHUB_OUTPUT | |
| echo "rustlint_run_url=$rustlint_run_url" >> $GITHUB_OUTPUT | |
| echo "integration_run_url=$integration_run_url" >> $GITHUB_OUTPUT | |
| echo "commercial_run_url=$commercial_run_url" >> $GITHUB_OUTPUT | |
| create-pr-comment: | |
| runs-on: ubuntu-latest | |
| needs: [dispatch-workflows] | |
| if: github.event.client_payload.pr_number || github.event.inputs.pr_number | |
| steps: | |
| - name: Parse event data | |
| id: parse | |
| run: | | |
| if [ "${{ github.event.action }}" = "cloud-premium-webhook" ]; then | |
| echo "source_repo=AppFlowy-Cloud-Premium" >> $GITHUB_OUTPUT | |
| elif [ "${{ github.event.action }}" = "private-repo-webhook" ]; then | |
| echo "source_repo=AppFlowy-Premium" >> $GITHUB_OUTPUT | |
| else | |
| echo "source_repo=${{ github.event.inputs.source_repo || 'AppFlowy-Premium' }}" >> $GITHUB_OUTPUT | |
| fi | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| echo "pr_number=${{ github.event.client_payload.pr_number }}" >> $GITHUB_OUTPUT | |
| echo "workflows=${{ github.event.client_payload.workflows }}" >> $GITHUB_OUTPUT | |
| echo "pr_ref=${{ github.event.client_payload.pr_ref }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT | |
| echo "workflows=${{ github.event.inputs.workflows }}" >> $GITHUB_OUTPUT | |
| echo "pr_ref=${{ github.event.inputs.pr_ref }}" >> $GITHUB_OUTPUT | |
| fi | |
| echo "timestamp=$(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT | |
| - name: Generate workflow links | |
| id: links | |
| run: | | |
| workflows="${{ steps.parse.outputs.workflows }}" | |
| pr_number="${{ steps.parse.outputs.pr_number }}" | |
| flutter_run_url="${{ needs.dispatch-workflows.outputs.flutter_run_url }}" | |
| rust_run_url="${{ needs.dispatch-workflows.outputs.rust_run_url }}" | |
| mobile_run_url="${{ needs.dispatch-workflows.outputs.mobile_run_url }}" | |
| ios_run_url="${{ needs.dispatch-workflows.outputs.ios_run_url }}" | |
| rust_coverage_run_url="${{ needs.dispatch-workflows.outputs.rust_coverage_run_url }}" | |
| docker_run_url="${{ needs.dispatch-workflows.outputs.docker_run_url }}" | |
| commit_lint_run_url="${{ needs.dispatch-workflows.outputs.commit_lint_run_url }}" | |
| backend_run_url="${{ needs.dispatch-workflows.outputs.backend_run_url }}" | |
| frontend_run_url="${{ needs.dispatch-workflows.outputs.frontend_run_url }}" | |
| e2e_run_url="${{ needs.dispatch-workflows.outputs.e2e_run_url }}" | |
| cloud_docker_run_url="${{ needs.dispatch-workflows.outputs.cloud_docker_run_url }}" | |
| rustlint_run_url="${{ needs.dispatch-workflows.outputs.rustlint_run_url }}" | |
| integration_run_url="${{ needs.dispatch-workflows.outputs.integration_run_url }}" | |
| commercial_run_url="${{ needs.dispatch-workflows.outputs.commercial_run_url }}" | |
| flutter_link="" | |
| rust_link="" | |
| mobile_link="" | |
| ios_link="" | |
| rust_coverage_link="" | |
| docker_link="" | |
| commit_lint_link="" | |
| backend_link="" | |
| frontend_link="" | |
| e2e_link="" | |
| cloud_docker_link="" | |
| rustlint_link="" | |
| integration_link="" | |
| commercial_link="" | |
| if [[ ",$workflows," == *",flutter,"* ]]; then | |
| if [ -n "$flutter_run_url" ]; then | |
| flutter_link="- **Flutter CI/CD**: [$flutter_run_url]($flutter_run_url) (PR $pr_number)" | |
| else | |
| flutter_link="- **Flutter CI/CD**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/flutter_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",rust,"* ]]; then | |
| if [ -n "$rust_run_url" ]; then | |
| rust_link="- **Rust Testing**: [$rust_run_url]($rust_run_url) (PR $pr_number)" | |
| else | |
| rust_link="- **Rust Testing**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/rust_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",mobile,"* ]]; then | |
| if [ -n "$mobile_run_url" ]; then | |
| mobile_link="- **Mobile CI (iOS/Android)**: [$mobile_run_url]($mobile_run_url) (PR $pr_number)" | |
| else | |
| mobile_link="- **Mobile CI (iOS/Android)**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/mobile_ci.yml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",ios,"* ]]; then | |
| if [ -n "$ios_run_url" ]; then | |
| ios_link="- **iOS CI**: [$ios_run_url]($ios_run_url) (PR $pr_number)" | |
| else | |
| ios_link="- **iOS CI**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/ios_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",rust_coverage,"* ]]; then | |
| if [ -n "$rust_coverage_run_url" ]; then | |
| rust_coverage_link="- **Rust Coverage**: [$rust_coverage_run_url]($rust_coverage_run_url) (PR $pr_number)" | |
| else | |
| rust_coverage_link="- **Rust Coverage**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/rust_coverage.yml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",docker,"* ]]; then | |
| if [ -n "$docker_run_url" ]; then | |
| docker_link="- **Docker CI**: [$docker_run_url]($docker_run_url) (PR $pr_number)" | |
| else | |
| docker_link="- **Docker CI**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/docker_ci.yml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",commit_lint,"* ]]; then | |
| if [ -n "$commit_lint_run_url" ]; then | |
| commit_lint_link="- **Commit Lint**: [$commit_lint_run_url]($commit_lint_run_url) (PR $pr_number)" | |
| else | |
| commit_lint_link="- **Commit Lint**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/commit_lint.yml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",backend,"* ]]; then | |
| if [ -n "$backend_run_url" ]; then | |
| backend_link="- **Backend CI**: [$backend_run_url]($backend_run_url) (PR $pr_number)" | |
| else | |
| backend_link="- **Backend CI**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_backend_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",frontend,"* ]]; then | |
| if [ -n "$frontend_run_url" ]; then | |
| frontend_link="- **Frontend CI**: [$frontend_run_url]($frontend_run_url) (PR $pr_number)" | |
| else | |
| frontend_link="- **Frontend CI**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_frontend_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",e2e,"* ]]; then | |
| if [ -n "$e2e_run_url" ]; then | |
| e2e_link="- **E2E Testing**: [$e2e_run_url]($e2e_run_url) (PR $pr_number)" | |
| else | |
| e2e_link="- **E2E Testing**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_e2e_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",cloud_docker,"* ]]; then | |
| if [ -n "$cloud_docker_run_url" ]; then | |
| cloud_docker_link="- **Cloud Docker CI**: [$cloud_docker_run_url]($cloud_docker_run_url) (PR $pr_number)" | |
| else | |
| cloud_docker_link="- **Cloud Docker CI**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_docker_ci.yaml) (PR $pr_number)" | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",rustlint,"* ]]; then | |
| if [ -n "$rustlint_run_url" ]; then | |
| rustlint_link="- **Rust Lint (fmt & clippy)**: [$rustlint_run_url]($rustlint_run_url) (PR #${pr_number})" | |
| else | |
| rustlint_link="- **Rust Lint (fmt & clippy)**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_rustlint_ci.yaml) (PR #${pr_number}) - Starting up..." | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",integration,"* ]]; then | |
| if [ -n "$integration_run_url" ]; then | |
| integration_link="- **Integration Tests**: [$integration_run_url]($integration_run_url) (PR #${pr_number})" | |
| else | |
| integration_link="- **Integration Tests**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_integration_ci.yaml) (PR #${pr_number}) - Starting up..." | |
| fi | |
| fi | |
| if [[ ",$workflows," == *",commercial,"* ]]; then | |
| if [ -n "$commercial_run_url" ]; then | |
| commercial_link="- **Commercial Integration Tests**: [$commercial_run_url]($commercial_run_url) (PR #${pr_number})" | |
| else | |
| commercial_link="- **Commercial Integration Tests**: [View Workflow](https://github.com/AppFlowy-IO/AppFlowy-CI/actions/workflows/cloud_commercial_integration_ci.yaml) (PR #${pr_number}) - Starting up..." | |
| fi | |
| fi | |
| echo "flutter_link=$flutter_link" >> $GITHUB_OUTPUT | |
| echo "rust_link=$rust_link" >> $GITHUB_OUTPUT | |
| echo "mobile_link=$mobile_link" >> $GITHUB_OUTPUT | |
| echo "ios_link=$ios_link" >> $GITHUB_OUTPUT | |
| echo "rust_coverage_link=$rust_coverage_link" >> $GITHUB_OUTPUT | |
| echo "docker_link=$docker_link" >> $GITHUB_OUTPUT | |
| echo "commit_lint_link=$commit_lint_link" >> $GITHUB_OUTPUT | |
| echo "backend_link=$backend_link" >> $GITHUB_OUTPUT | |
| echo "frontend_link=$frontend_link" >> $GITHUB_OUTPUT | |
| echo "e2e_link=$e2e_link" >> $GITHUB_OUTPUT | |
| echo "cloud_docker_link=$cloud_docker_link" >> $GITHUB_OUTPUT | |
| echo "rustlint_link=$rustlint_link" >> $GITHUB_OUTPUT | |
| echo "integration_link=$integration_link" >> $GITHUB_OUTPUT | |
| echo "commercial_link=$commercial_link" >> $GITHUB_OUTPUT | |
| - name: Post CI status comment to private repo PR | |
| env: | |
| ADMIN_GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.parse.outputs.pr_number }} | |
| run: | | |
| cat > comment_body.md << 'EOF' | |
| **Started**: ${{ steps.parse.outputs.timestamp }} | |
| **Branch**: `${{ steps.parse.outputs.pr_ref }}` | |
| **Run ID**: `${{ github.run_id }}` | |
| Your PR has triggered our CI/CD pipeline in the public repository. You can monitor the test results and build status here: | |
| **Main CI Workflows**: https://github.com/AppFlowy-IO/AppFlowy-CI/actions | |
| ${{ steps.links.outputs.flutter_link }} | |
| ${{ steps.links.outputs.rust_link }} | |
| ${{ steps.links.outputs.mobile_link }} | |
| ${{ steps.links.outputs.ios_link }} | |
| ${{ steps.links.outputs.rust_coverage_link }} | |
| ${{ steps.links.outputs.docker_link }} | |
| ${{ steps.links.outputs.commit_lint_link }} | |
| ${{ steps.links.outputs.backend_link }} | |
| ${{ steps.links.outputs.frontend_link }} | |
| ${{ steps.links.outputs.e2e_link }} | |
| ${{ steps.links.outputs.cloud_docker_link }} | |
| ${{ steps.links.outputs.rustlint_link }} | |
| ${{ steps.links.outputs.integration_link }} | |
| ${{ steps.links.outputs.commercial_link }} | |
| EOF | |
| sed -i '/^$/d' comment_body.md | |
| curl -X POST \ | |
| -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| -H "Content-Type: application/json" \ | |
| https://api.github.com/repos/AppFlowy-IO/${{ steps.parse.outputs.source_repo }}/issues/$PR_NUMBER/comments \ | |
| -d "{\"body\": $(jq -Rs . < comment_body.md)}" | |
| echo "✅ Posted CI status comment to ${{ steps.parse.outputs.source_repo }} PR $PR_NUMBER" | |
| auto-approve-pr: | |
| runs-on: ubuntu-latest | |
| needs: [dispatch-workflows, create-pr-comment] | |
| if: github.event.client_payload.pr_number || github.event.inputs.pr_number | |
| steps: | |
| - name: Parse event data | |
| id: parse | |
| run: | | |
| if [ "${{ github.event.action }}" = "cloud-premium-webhook" ]; then | |
| echo "source_repo=AppFlowy-Cloud-Premium" >> $GITHUB_OUTPUT | |
| elif [ "${{ github.event.action }}" = "private-repo-webhook" ]; then | |
| echo "source_repo=AppFlowy-Premium" >> $GITHUB_OUTPUT | |
| else | |
| echo "source_repo=${{ github.event.inputs.source_repo || 'AppFlowy-Premium' }}" >> $GITHUB_OUTPUT | |
| fi | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| echo "pr_number=${{ github.event.client_payload.pr_number }}" >> $GITHUB_OUTPUT | |
| echo "workflows=${{ github.event.client_payload.workflows }}" >> $GITHUB_OUTPUT | |
| echo "pr_ref=${{ github.event.client_payload.pr_ref }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT | |
| echo "workflows=${{ github.event.inputs.workflows }}" >> $GITHUB_OUTPUT | |
| echo "pr_ref=${{ github.event.inputs.pr_ref }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Wait for CI workflows to complete | |
| env: | |
| ADMIN_GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| WORKFLOWS: ${{ steps.parse.outputs.workflows }} | |
| PR_NUMBER: ${{ steps.parse.outputs.pr_number }} | |
| FLUTTER_RUN_URL: ${{ needs.dispatch-workflows.outputs.flutter_run_url }} | |
| RUST_RUN_URL: ${{ needs.dispatch-workflows.outputs.rust_run_url }} | |
| MOBILE_RUN_URL: ${{ needs.dispatch-workflows.outputs.mobile_run_url }} | |
| IOS_RUN_URL: ${{ needs.dispatch-workflows.outputs.ios_run_url }} | |
| RUST_COVERAGE_RUN_URL: ${{ needs.dispatch-workflows.outputs.rust_coverage_run_url }} | |
| DOCKER_RUN_URL: ${{ needs.dispatch-workflows.outputs.docker_run_url }} | |
| COMMIT_LINT_RUN_URL: ${{ needs.dispatch-workflows.outputs.commit_lint_run_url }} | |
| BACKEND_RUN_URL: ${{ needs.dispatch-workflows.outputs.backend_run_url }} | |
| FRONTEND_RUN_URL: ${{ needs.dispatch-workflows.outputs.frontend_run_url }} | |
| E2E_RUN_URL: ${{ needs.dispatch-workflows.outputs.e2e_run_url }} | |
| CLOUD_DOCKER_RUN_URL: ${{ needs.dispatch-workflows.outputs.cloud_docker_run_url }} | |
| RUSTLINT_RUN_URL: ${{ needs.dispatch-workflows.outputs.rustlint_run_url }} | |
| INTEGRATION_RUN_URL: ${{ needs.dispatch-workflows.outputs.integration_run_url }} | |
| COMMERCIAL_RUN_URL: ${{ needs.dispatch-workflows.outputs.commercial_run_url }} | |
| run: | | |
| echo "⏳ Waiting for CI workflows to complete for PR $PR_NUMBER..." | |
| # Map workflow names to their run URLs | |
| declare -A workflow_run_urls=( | |
| ["flutter"]="$FLUTTER_RUN_URL" | |
| ["rust"]="$RUST_RUN_URL" | |
| ["mobile"]="$MOBILE_RUN_URL" | |
| ["ios"]="$IOS_RUN_URL" | |
| ["rust_coverage"]="$RUST_COVERAGE_RUN_URL" | |
| ["docker"]="$DOCKER_RUN_URL" | |
| ["commit_lint"]="$COMMIT_LINT_RUN_URL" | |
| ["backend"]="$BACKEND_RUN_URL" | |
| ["frontend"]="$FRONTEND_RUN_URL" | |
| ["e2e"]="$E2E_RUN_URL" | |
| ["cloud_docker"]="$CLOUD_DOCKER_RUN_URL" | |
| ["rustlint"]="$RUSTLINT_RUN_URL" | |
| ["integration"]="$INTEGRATION_RUN_URL" | |
| ["commercial"]="$COMMERCIAL_RUN_URL" | |
| ) | |
| IFS=',' read -ra WORKFLOW_ARRAY <<< "$WORKFLOWS" | |
| echo "Monitoring workflows for PR $PR_NUMBER: $WORKFLOWS" | |
| # Wait 30 seconds for workflows to be dispatched and show up in API | |
| echo "⏳ Waiting 30 seconds for workflows to initialize..." | |
| sleep 30 | |
| max_wait=7200 | |
| wait_interval=30 | |
| elapsed=0 | |
| while [ $elapsed -lt $max_wait ]; do | |
| all_complete=true | |
| all_success=true | |
| workflow_status_summary="" | |
| for workflow in "${WORKFLOW_ARRAY[@]}"; do | |
| run_url="${workflow_run_urls[$workflow]}" | |
| if [ -z "$run_url" ]; then | |
| echo " No run URL found for $workflow, skipping" | |
| continue | |
| fi | |
| # Extract run ID from URL (format: https://github.com/AppFlowy-IO/AppFlowy-CI/actions/runs/12345) | |
| run_id=$(echo "$run_url" | grep -oE '[0-9]+$') | |
| if [ -z "$run_id" ]; then | |
| echo " Could not extract run ID from URL: $run_url" | |
| all_complete=false | |
| continue | |
| fi | |
| echo "Checking status of workflow: $workflow (run ID: $run_id)" | |
| # Query the specific run by ID | |
| response=$(curl -s -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/AppFlowy-IO/AppFlowy-CI/actions/runs/$run_id") | |
| status=$(echo "$response" | jq -r '.status // "unknown"') | |
| conclusion=$(echo "$response" | jq -r '.conclusion // "null"') | |
| echo " Status: $status, Conclusion: $conclusion" | |
| workflow_status_summary="$workflow_status_summary\n- $workflow: $status ($conclusion)" | |
| if [ "$status" != "completed" ]; then | |
| all_complete=false | |
| fi | |
| if [ "$conclusion" != "success" ] && [ "$conclusion" != "unknown" ] && [ "$conclusion" != "null" ]; then | |
| all_success=false | |
| fi | |
| done | |
| if [ "$all_complete" = true ]; then | |
| if [ "$all_success" = true ]; then | |
| echo "✅ All workflows completed successfully for PR $PR_NUMBER" | |
| echo "all_workflows_passed=true" >> $GITHUB_ENV | |
| break | |
| else | |
| echo "❌ Some workflows failed for PR $PR_NUMBER" | |
| echo "all_workflows_passed=false" >> $GITHUB_ENV | |
| break | |
| fi | |
| fi | |
| echo "⏳ Workflows still running for PR $PR_NUMBER..." | |
| echo -e "Current status:$workflow_status_summary" | |
| sleep $wait_interval | |
| elapsed=$((elapsed + wait_interval)) | |
| done | |
| if [ $elapsed -ge $max_wait ]; then | |
| echo "⏰ Timeout reached waiting for workflows for PR $PR_NUMBER" | |
| echo "all_workflows_passed=false" >> $GITHUB_ENV | |
| fi | |
| - name: Auto-approve PR if all tests pass | |
| if: env.all_workflows_passed == 'true' | |
| env: | |
| ADMIN_GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.parse.outputs.pr_number }} | |
| run: | | |
| echo "🎉 All CI tests passed! Auto-approving PR $PR_NUMBER..." | |
| curl -X POST \ | |
| -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| -H "Content-Type: application/json" \ | |
| https://api.github.com/repos/AppFlowy-IO/${{ steps.parse.outputs.source_repo }}/pulls/$PR_NUMBER/reviews \ | |
| -d '{ | |
| "event": "APPROVE", | |
| "body": "✅ **All CI tests passed!** PR '"$PR_NUMBER"' has been automatically approved and is ready for merge." | |
| }' | |
| echo "✅ PR $PR_NUMBER auto-approved successfully!" | |
| - name: Report failed tests | |
| if: env.all_workflows_passed == 'false' | |
| env: | |
| ADMIN_GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.parse.outputs.pr_number }} | |
| FLUTTER_RUN_URL: ${{ needs.dispatch-workflows.outputs.flutter_run_url }} | |
| RUST_RUN_URL: ${{ needs.dispatch-workflows.outputs.rust_run_url }} | |
| IOS_RUN_URL: ${{ needs.dispatch-workflows.outputs.ios_run_url }} | |
| run: | | |
| echo "❌ Some CI tests failed or timed out for PR $PR_NUMBER" | |
| # Only support these three main CI workflows for detailed error reporting | |
| declare -A workflow_run_urls=( | |
| ["flutter"]="$FLUTTER_RUN_URL" | |
| ["rust"]="$RUST_RUN_URL" | |
| ["ios"]="$IOS_RUN_URL" | |
| ) | |
| declare -A workflow_display_names=( | |
| ["flutter"]="Flutter CI/CD" | |
| ["rust"]="Rust Testing" | |
| ["ios"]="iOS CI" | |
| ) | |
| # Process only the supported workflows | |
| WORKFLOW_ARRAY=("flutter" "rust" "ios") | |
| # Start building the failure report | |
| FAILURE_REPORT="❌ **Some CI tests failed or timed out** for PR $PR_NUMBER. Please check the workflow results and fix any issues before requesting another review.\n\n" | |
| FAILURE_REPORT+="### Failed Workflows\n\n" | |
| HAS_FAILURES=false | |
| for workflow in "${WORKFLOW_ARRAY[@]}"; do | |
| run_url="${workflow_run_urls[$workflow]}" | |
| workflow_name="${workflow_display_names[$workflow]}" | |
| if [ -z "$run_url" ]; then | |
| echo "No run URL found for $workflow, skipping" | |
| continue | |
| fi | |
| # Extract run ID from URL | |
| run_id=$(echo "$run_url" | grep -oE '[0-9]+$') | |
| if [ -z "$run_id" ]; then | |
| echo "Could not extract run ID from URL: $run_url" | |
| continue | |
| fi | |
| echo "Checking workflow: $workflow (run ID: $run_id)" | |
| # Get workflow run details | |
| run_response=$(curl -s -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/AppFlowy-IO/AppFlowy-CI/actions/runs/$run_id") | |
| run_conclusion=$(echo "$run_response" | jq -r '.conclusion // "null"') | |
| # Skip if workflow succeeded | |
| if [ "$run_conclusion" = "success" ]; then | |
| echo "Workflow $workflow succeeded, skipping" | |
| continue | |
| fi | |
| HAS_FAILURES=true | |
| # Add workflow to report | |
| FAILURE_REPORT+="**$workflow_name**: $run_url (PR $PR_NUMBER)\n" | |
| # Get all jobs for this workflow run | |
| jobs_response=$(curl -s -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/AppFlowy-IO/AppFlowy-CI/actions/runs/$run_id/jobs?per_page=100") | |
| # Count failed jobs | |
| failed_jobs=$(echo "$jobs_response" | jq '[.jobs[] | select(.conclusion == "failure")] | length') | |
| echo "Found $failed_jobs failed jobs for workflow $workflow" | |
| if [ "$failed_jobs" -eq 0 ]; then | |
| FAILURE_REPORT+="- Status: $run_conclusion (no specific job failures detected)\n\n" | |
| continue | |
| fi | |
| # Parse each failed job using process substitution to avoid subshell | |
| while IFS= read -r line; do | |
| if [[ $line == JOB_NAME:* ]]; then | |
| JOB_NAME="${line#JOB_NAME:}" | |
| elif [[ $line == JOB_ID:* ]]; then | |
| JOB_ID="${line#JOB_ID:}" | |
| elif [[ $line == JOB_URL:* ]]; then | |
| JOB_URL="${line#JOB_URL:}" | |
| elif [[ $line == JOB_RUNNER:* ]]; then | |
| JOB_RUNNER="${line#JOB_RUNNER:}" | |
| elif [[ $line == "---" ]]; then | |
| echo "Processing failed job: $JOB_NAME (ID: $JOB_ID)" | |
| # Add job to report | |
| FAILURE_REPORT+="- **Job**: $JOB_NAME ($JOB_RUNNER) - [View Job]($JOB_URL)\n" | |
| # Get job details including steps | |
| job_details=$(curl -s -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/AppFlowy-IO/AppFlowy-CI/actions/jobs/$JOB_ID") | |
| # Find failed steps | |
| failed_steps=$(echo "$job_details" | jq -r '.steps[] | select(.conclusion == "failure") | .name' || true) | |
| if [ -n "$failed_steps" ]; then | |
| while IFS= read -r step_name; do | |
| if [ -n "$step_name" ]; then | |
| FAILURE_REPORT+=" - ❌ Failed step: $step_name\n" | |
| fi | |
| done <<< "$failed_steps" | |
| else | |
| FAILURE_REPORT+=" - ❌ Job failed (see logs for details)\n" | |
| fi | |
| # Try to fetch logs and extract test failures | |
| echo "Attempting to fetch logs for job $JOB_ID..." | |
| job_logs=$(curl -s -L -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/AppFlowy-IO/AppFlowy-CI/actions/jobs/$JOB_ID/logs" 2>/dev/null || true) | |
| if [ -n "$job_logs" ]; then | |
| # Detect workflow type and extract failures accordingly | |
| test_failures="" | |
| # For Rust tests: look for "test ... FAILED" patterns | |
| if [[ "$workflow" == "rust" ]]; then | |
| echo "Extracting Rust test failures..." | |
| test_failures=$(echo "$job_logs" | grep -E "^test .* \.\.\. FAILED$" | head -n 20 || true) | |
| if [ -n "$test_failures" ]; then | |
| echo "Found Rust test failures" | |
| FAILURE_REPORT+=" **Failed Tests:**\n" | |
| while IFS= read -r failure_line; do | |
| if [ -n "$failure_line" ]; then | |
| # Extract test name from "test test_name ... FAILED" | |
| test_name=$(echo "$failure_line" | sed -E 's/^test (.*) \.\.\. FAILED$/\1/') | |
| FAILURE_REPORT+=" - \`$test_name\`\n" | |
| fi | |
| done <<< "$test_failures" | |
| else | |
| # Try alternative pattern for test failures | |
| test_failures=$(echo "$job_logs" | grep -E "---- .* ----" | head -n 20 || true) | |
| if [ -n "$test_failures" ]; then | |
| echo "Found Rust test failures (alternative pattern)" | |
| FAILURE_REPORT+=" **Failed Tests:**\n" | |
| while IFS= read -r failure_line; do | |
| if [ -n "$failure_line" ]; then | |
| # Extract test name from "---- test_name ----" | |
| test_name=$(echo "$failure_line" | sed -E 's/^---- (.*) ----$/\1/') | |
| FAILURE_REPORT+=" - \`$test_name\`\n" | |
| fi | |
| done <<< "$test_failures" | |
| fi | |
| fi | |
| fi | |
| # For Flutter tests: look for various failure patterns | |
| if [[ "$workflow" == "flutter" ]]; then | |
| echo "Extracting Flutter test failures..." | |
| # Try multiple patterns for Flutter test failures | |
| test_failures=$(echo "$job_logs" | grep -E "(^✖|^FAILED:.*test|^Error:|test.*FAILED)" | head -n 20 || true) | |
| if [ -n "$test_failures" ]; then | |
| echo "Found Flutter test failures" | |
| FAILURE_REPORT+=" **Failed Tests/Errors:**\n" | |
| while IFS= read -r failure_line; do | |
| if [ -n "$failure_line" ]; then | |
| # Clean and truncate the failure line | |
| cleaned_line=$(echo "$failure_line" | sed 's/^[[:space:]]*//g' | head -c 200) | |
| FAILURE_REPORT+=" - \`$cleaned_line\`\n" | |
| fi | |
| done <<< "$test_failures" | |
| fi | |
| fi | |
| # Generic error extraction if no specific test failures found | |
| if [ -z "$test_failures" ]; then | |
| echo "No specific test failures found, looking for generic errors..." | |
| generic_errors=$(echo "$job_logs" | grep -iE "(error:|failed|fatal:|panic)" | grep -v "0 failed" | head -n 10 || true) | |
| if [ -n "$generic_errors" ]; then | |
| echo "Found generic errors" | |
| FAILURE_REPORT+=" **Errors Found:**\n" | |
| while IFS= read -r error_line; do | |
| if [ -n "$error_line" ]; then | |
| cleaned_line=$(echo "$error_line" | sed 's/^[[:space:]]*//g' | head -c 200) | |
| FAILURE_REPORT+=" - \`$cleaned_line\`\n" | |
| fi | |
| done <<< "$generic_errors" | |
| fi | |
| fi | |
| fi | |
| FAILURE_REPORT+="\n" | |
| fi | |
| done < <(echo "$jobs_response" | jq -r '.jobs[] | select(.conclusion == "failure") | "JOB_NAME:\(.name)\nJOB_ID:\(.id)\nJOB_URL:\(.html_url)\nJOB_RUNNER:\(.runner_name // \"unknown\")\n---"') | |
| done | |
| if [ "$HAS_FAILURES" = false ]; then | |
| FAILURE_REPORT+="No specific workflow failures detected. This may be due to a timeout or cancellation.\n\n" | |
| fi | |
| # Save the report to a file | |
| echo -e "$FAILURE_REPORT" > failure_report.md | |
| # Post the failure report as a comment | |
| curl -X POST \ | |
| -H "Authorization: token $ADMIN_GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| -H "Content-Type: application/json" \ | |
| https://api.github.com/repos/AppFlowy-IO/${{ steps.parse.outputs.source_repo }}/issues/$PR_NUMBER/comments \ | |
| -d "{\"body\": $(jq -Rs . < failure_report.md)}" | |
| echo "ℹ️ Failure notification posted to ${{ steps.parse.outputs.source_repo }} PR $PR_NUMBER" |