Skip to content

cloud-premium-webhook #6732

cloud-premium-webhook

cloud-premium-webhook #6732

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"