Merge: dependabot/uv/sqlalchemy-asyncio--gte-2.0.49 #4105
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: "Impl: Merge" | |
| run-name: "Merge: ${{ github.event.inputs.pr_number || github.event.pull_request.head.ref }}" | |
| # Auto-merge implementation PRs when ai-approved label is added | |
| # Creates per-library metadata file and promotes GCS images | |
| on: | |
| pull_request: | |
| types: [labeled] | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to auto-merge' | |
| required: true | |
| type: number | |
| jobs: | |
| merge: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| actions: write # Required for gh workflow run sync-postgres.yml | |
| steps: | |
| - name: Check conditions | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| PR_NUM="${{ github.event.inputs.pr_number }}" | |
| PR_DATA=$(gh pr view "$PR_NUM" --repo ${{ github.repository }} --json headRefName,labels) | |
| BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName') | |
| HAS_APPROVED=$(echo "$PR_DATA" | jq -r '[.labels[].name] | any(. == "ai-approved")') | |
| else | |
| ACTION="${{ github.event.action }}" | |
| LABEL="${{ github.event.label.name }}" | |
| BRANCH="${{ github.event.pull_request.head.ref }}" | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| if [[ "$ACTION" != "labeled" || "$LABEL" != "ai-approved" ]]; then | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| HAS_APPROVED="true" | |
| fi | |
| # Only process implementation/* branches | |
| if [[ ! "$BRANCH" =~ ^implementation/ ]]; then | |
| echo "::notice::Skipping: Branch '$BRANCH' is not implementation/*" | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| if [[ "$HAS_APPROVED" != "true" ]]; then | |
| echo "::notice::Skipping: PR does not have ai-approved label" | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| echo "should_run=true" >> $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| if: steps.check.outputs.should_run == 'true' | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract info from branch | |
| if: steps.check.outputs.should_run == 'true' | |
| id: extract | |
| run: | | |
| BRANCH="${{ steps.check.outputs.branch }}" | |
| # Format: implementation/{specification-id}/{library} | |
| SPEC_ID=$(echo "$BRANCH" | cut -d'/' -f2) | |
| LIBRARY=$(echo "$BRANCH" | cut -d'/' -f3) | |
| echo "specification_id=$SPEC_ID" >> $GITHUB_OUTPUT | |
| echo "library=$LIBRARY" >> $GITHUB_OUTPUT | |
| - name: Validate PR completeness before merge | |
| if: steps.check.outputs.should_run == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SPEC_ID: ${{ steps.extract.outputs.specification_id }} | |
| LIBRARY: ${{ steps.extract.outputs.library }} | |
| BRANCH: ${{ steps.check.outputs.branch }} | |
| PR_NUM: ${{ steps.check.outputs.pr_number }} | |
| run: | | |
| # Fetch the PR branch to check its contents | |
| git fetch origin "$BRANCH" | |
| IMPL_FILE="plots/${SPEC_ID}/implementations/${LIBRARY}.py" | |
| META_FILE="plots/${SPEC_ID}/metadata/${LIBRARY}.yaml" | |
| # Check if implementation file exists on the PR branch | |
| if ! git show "origin/${BRANCH}:${IMPL_FILE}" &>/dev/null; then | |
| echo "::error::Implementation file missing on branch: ${IMPL_FILE}" | |
| echo "::error::This indicates an incomplete generation - metadata exists but implementation is missing" | |
| echo "::error::Closing PR to prevent partial merge" | |
| gh pr close "$PR_NUM" --comment "**Merge blocked:** Implementation file \`${IMPL_FILE}\` is missing from this PR branch. Only metadata was found. This indicates an incomplete code generation. Please regenerate using \`generate:${LIBRARY}\` label." | |
| exit 1 | |
| fi | |
| # Check if metadata file exists | |
| if ! git show "origin/${BRANCH}:${META_FILE}" &>/dev/null; then | |
| echo "::warning::Metadata file missing on branch: ${META_FILE}" | |
| echo "::warning::This is unusual but not blocking - metadata will be created on next review" | |
| fi | |
| echo "::notice::PR completeness validated: both implementation and metadata present" | |
| - name: Get parent issue from PR body | |
| if: steps.check.outputs.should_run == 'true' | |
| id: issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ steps.check.outputs.pr_number }}" | |
| PR_BODY=$(gh pr view "$PR_NUM" --json body -q '.body' 2>/dev/null || echo "") | |
| ISSUE=$(echo "$PR_BODY" | grep -oP '\*\*Parent Issue:\*\* #\K\d+' | head -1 || echo "") | |
| echo "number=$ISSUE" >> $GITHUB_OUTPUT | |
| - name: Extract quality score from PR labels | |
| if: steps.check.outputs.should_run == 'true' | |
| id: quality | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ steps.check.outputs.pr_number }}" | |
| LABELS=$(gh pr view "$PR_NUM" --json labels -q '.labels[].name' 2>/dev/null || echo "") | |
| SCORE=$(echo "$LABELS" | grep -oP 'quality:\K\d+' | head -1 || echo "") | |
| echo "score=$SCORE" >> $GITHUB_OUTPUT | |
| - name: React with rocket emoji | |
| if: steps.check.outputs.should_run == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh api repos/${{ github.repository }}/issues/${{ steps.check.outputs.pr_number }}/reactions \ | |
| -f content=rocket | |
| - name: Merge PR to main (with retry) | |
| if: steps.check.outputs.should_run == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ steps.check.outputs.pr_number }}" | |
| MAX_ATTEMPTS=5 | |
| for attempt in $(seq 1 $MAX_ATTEMPTS); do | |
| echo "::notice::Merge attempt $attempt/$MAX_ATTEMPTS" | |
| # Update branch before merge attempt | |
| gh pr update-branch "$PR_NUM" --repo ${{ github.repository }} 2>/dev/null || true | |
| sleep 2 | |
| if gh pr merge "$PR_NUM" \ | |
| --repo ${{ github.repository }} \ | |
| --squash \ | |
| --delete-branch; then | |
| echo "::notice::Merge successful on attempt $attempt" | |
| exit 0 | |
| fi | |
| if [ $attempt -lt $MAX_ATTEMPTS ]; then | |
| DELAY=$((attempt * 10)) | |
| echo "::warning::Merge failed, retrying in ${DELAY}s..." | |
| sleep $DELAY | |
| fi | |
| done | |
| echo "::error::Merge failed after $MAX_ATTEMPTS attempts" | |
| exit 1 | |
| # Note: quality_score is now set by impl-review.yml before merge | |
| # The PR already contains the updated metadata when merged | |
| - name: Promote GCS images to production | |
| if: steps.check.outputs.should_run == 'true' | |
| env: | |
| GCS_CREDENTIALS: ${{ secrets.GCS_CREDENTIALS }} | |
| SPEC_ID: ${{ steps.extract.outputs.specification_id }} | |
| LIBRARY: ${{ steps.extract.outputs.library }} | |
| run: | | |
| if [ -z "$GCS_CREDENTIALS" ]; then | |
| echo "::warning::GCS_CREDENTIALS not configured - skipping promotion" | |
| exit 0 | |
| fi | |
| echo "$GCS_CREDENTIALS" > /tmp/gcs-key.json | |
| gcloud auth activate-service-account --key-file=/tmp/gcs-key.json | |
| STAGING="gs://pyplots-images/staging/${SPEC_ID}/${LIBRARY}" | |
| PRODUCTION="gs://pyplots-images/plots/${SPEC_ID}/${LIBRARY}" | |
| # Copy from staging to production | |
| gsutil -m -h "Cache-Control:public, max-age=604800" cp -r "${STAGING}/*" "${PRODUCTION}/" 2>/dev/null || echo "No staging files to promote" | |
| # Make production files public | |
| gsutil -m acl ch -r -u AllUsers:R "${PRODUCTION}/" 2>/dev/null || true | |
| # Clean up staging | |
| gsutil -m rm -r "${STAGING}/" 2>/dev/null || true | |
| rm -f /tmp/gcs-key.json | |
| echo "::notice::Promoted images to production" | |
| - name: Update issue labels | |
| if: steps.check.outputs.should_run == 'true' && steps.issue.outputs.number != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ISSUE: ${{ steps.issue.outputs.number }} | |
| LIBRARY: ${{ steps.extract.outputs.library }} | |
| run: | | |
| # Create labels if they don't exist | |
| gh label create "impl:${LIBRARY}:done" --color "0e8a16" --description "${LIBRARY} implementation merged" 2>/dev/null || true | |
| # Remove trigger and pending, add done | |
| gh issue edit "$ISSUE" --remove-label "generate:${LIBRARY}" 2>/dev/null || true | |
| gh issue edit "$ISSUE" --remove-label "impl:${LIBRARY}:pending" 2>/dev/null || true | |
| gh issue edit "$ISSUE" --add-label "impl:${LIBRARY}:done" | |
| - name: Post success to issue | |
| if: steps.check.outputs.should_run == 'true' && steps.issue.outputs.number != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ISSUE: ${{ steps.issue.outputs.number }} | |
| LIBRARY: ${{ steps.extract.outputs.library }} | |
| SPEC_ID: ${{ steps.extract.outputs.specification_id }} | |
| PR_NUM: ${{ steps.check.outputs.pr_number }} | |
| QUALITY_SCORE: ${{ steps.quality.outputs.score }} | |
| run: | | |
| BODY="## :white_check_mark: ${LIBRARY} Complete | |
| **${LIBRARY}** implementation for \`${SPEC_ID}\` has been merged to main. | |
| **PR:** #${PR_NUM}" | |
| if [ -n "$QUALITY_SCORE" ]; then | |
| BODY="${BODY} | |
| **Quality Score:** ${QUALITY_SCORE}/100" | |
| fi | |
| BODY="${BODY} | |
| **Preview:** [View image](https://storage.googleapis.com/pyplots-images/plots/${SPEC_ID}/${LIBRARY}/plot.png) | |
| --- | |
| :robot: *[impl-merge](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" | |
| gh issue comment "$ISSUE" --body "$BODY" | |
| - name: Close issue if all libraries done | |
| if: steps.check.outputs.should_run == 'true' && steps.issue.outputs.number != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ISSUE: ${{ steps.issue.outputs.number }} | |
| SPEC_ID: ${{ steps.extract.outputs.specification_id }} | |
| run: | | |
| # All 9 supported libraries | |
| LIBRARIES="matplotlib seaborn plotly bokeh altair plotnine pygal highcharts letsplot" | |
| # Get current labels on the issue | |
| LABELS=$(gh issue view "$ISSUE" --json labels -q '.labels[].name' 2>/dev/null || echo "") | |
| # Count done and failed implementations | |
| DONE_COUNT=0 | |
| FAILED_COUNT=0 | |
| DONE_LIBS="" | |
| FAILED_LIBS="" | |
| for lib in $LIBRARIES; do | |
| if echo "$LABELS" | grep -q "^impl:${lib}:done$"; then | |
| DONE_COUNT=$((DONE_COUNT + 1)) | |
| DONE_LIBS="$DONE_LIBS $lib" | |
| elif echo "$LABELS" | grep -q "^impl:${lib}:failed$"; then | |
| FAILED_COUNT=$((FAILED_COUNT + 1)) | |
| FAILED_LIBS="$FAILED_LIBS $lib" | |
| fi | |
| done | |
| TOTAL=$((DONE_COUNT + FAILED_COUNT)) | |
| echo "::notice::Libraries: $DONE_COUNT done, $FAILED_COUNT failed, $TOTAL/9 total" | |
| # Close issue if all 9 libraries are done OR done+failed=9 | |
| if [ "$TOTAL" -eq 9 ]; then | |
| # Build status table | |
| TABLE="| Library | Status |\n|---------|--------|" | |
| for lib in $LIBRARIES; do | |
| if echo "$DONE_LIBS" | grep -w -q "$lib"; then | |
| TABLE="$TABLE\n| $lib | :white_check_mark: |" | |
| elif echo "$FAILED_LIBS" | grep -w -q "$lib"; then | |
| TABLE="$TABLE\n| $lib | :x: (not supported) |" | |
| fi | |
| done | |
| if [ "$FAILED_COUNT" -eq 0 ]; then | |
| TITLE=":tada: All Implementations Complete!" | |
| SUMMARY="All 9 library implementations for \`${SPEC_ID}\` have been successfully merged." | |
| else | |
| TITLE=":white_check_mark: Implementations Complete" | |
| SUMMARY="${DONE_COUNT}/9 implementations merged, ${FAILED_COUNT} libraries could not implement this plot type." | |
| fi | |
| gh issue comment "$ISSUE" --body "## ${TITLE} | |
| ${SUMMARY} | |
| $(echo -e "$TABLE") | |
| --- | |
| :robot: *[impl-merge](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" | |
| gh issue close "$ISSUE" | |
| echo "::notice::Closed issue #$ISSUE - all implementations complete ($DONE_COUNT done, $FAILED_COUNT failed)" | |
| fi | |
| - name: Trigger database sync | |
| if: steps.check.outputs.should_run == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh workflow run sync-postgres.yml | |
| echo "::notice::Triggered sync-postgres.yml" |