Gen: Preview Images #8
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: Generate Plot Previews | |
| on: | |
| workflow_run: | |
| workflows: ["Plot Tests"] | |
| types: | |
| - completed | |
| jobs: | |
| generate-previews: | |
| name: Generate Previews and Post to Issue | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| actions: read | |
| steps: | |
| - name: Download test results | |
| uses: actions/download-artifact@v6 | |
| with: | |
| pattern: test-results-* | |
| path: all-test-results | |
| merge-multiple: true | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Parse test results | |
| id: test_results | |
| run: | | |
| echo "Parsing test results..." | |
| get_status() { | |
| local version=$1 | |
| local file="all-test-results/result-${version}.txt" | |
| if [ -f "$file" ]; then | |
| outcome=$(cut -d',' -f2 "$file") | |
| if [ "$outcome" == "success" ]; then | |
| echo "pass" | |
| elif [ "$outcome" == "skipped" ]; then | |
| echo "skip" | |
| else | |
| echo "fail" | |
| fi | |
| else | |
| echo "unknown" | |
| fi | |
| } | |
| echo "py314=$(get_status 3.14)" >> $GITHUB_OUTPUT | |
| echo "py313=$(get_status 3.13)" >> $GITHUB_OUTPUT | |
| echo "py312=$(get_status 3.12)" >> $GITHUB_OUTPUT | |
| echo "py311=$(get_status 3.11)" >> $GITHUB_OUTPUT | |
| echo "Results parsed:" | |
| cat all-test-results/*.txt 2>/dev/null || echo "No results found" | |
| - name: Get PR number from workflow run | |
| id: get_pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUMBER=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }} --jq '.pull_requests[0].number') | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "Found PR: #$PR_NUMBER" | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| fetch-depth: 0 | |
| - name: Set up Python 3.14 | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.14' | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Install dependencies | |
| run: | | |
| uv sync --all-extras | |
| uv pip install pillow | |
| - name: Find changed plot files | |
| id: changed_plots | |
| run: | | |
| # Get the base branch from the PR | |
| BASE_SHA=${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.pull_requests[0].base.sha || 'origin/main' }} | |
| CHANGED_FILES=$(git diff --name-only $BASE_SHA...${{ github.event.workflow_run.head_sha }} -- 'plots/**/*.py' || echo "") | |
| if [ -z "$CHANGED_FILES" ]; then | |
| echo "No plot files changed" | |
| echo "has_plots=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "has_plots=true" >> $GITHUB_OUTPUT | |
| CHANGED_ARRAY=$(echo "$CHANGED_FILES" | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| echo "changed_files=$CHANGED_ARRAY" >> $GITHUB_OUTPUT | |
| - name: Extract issue number from PR | |
| id: get_issue | |
| if: steps.changed_plots.outputs.has_plots == 'true' && steps.get_pr.outputs.pr_number != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_BODY=$(gh pr view ${{ steps.get_pr.outputs.pr_number }} --json body -q '.body') | |
| ISSUE_NUM=$(echo "$PR_BODY" | grep -oP '#\K\d+' | head -1 || echo "") | |
| if [ -z "$ISSUE_NUM" ]; then | |
| # Try to find issue by branch name | |
| BRANCH=$(gh pr view ${{ steps.get_pr.outputs.pr_number }} --json headRefName -q '.headRefName') | |
| SPEC_ID=$(echo "$BRANCH" | sed 's/auto\///') | |
| ISSUE_NUM=$(gh issue list --label plot-request --search "$SPEC_ID in:title" --json number -q '.[0].number' || echo "") | |
| fi | |
| echo "issue_num=$ISSUE_NUM" >> $GITHUB_OUTPUT | |
| echo "Found issue: #$ISSUE_NUM" | |
| - name: Setup Google Cloud authentication | |
| if: steps.changed_plots.outputs.has_plots == 'true' | |
| id: gcs_auth | |
| continue-on-error: true | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| credentials_json: ${{ secrets.GCS_CREDENTIALS }} | |
| - name: Setup gcloud CLI | |
| if: steps.changed_plots.outputs.has_plots == 'true' && steps.gcs_auth.outcome == 'success' | |
| uses: google-github-actions/setup-gcloud@v3 | |
| - name: Generate timestamp | |
| id: timestamp | |
| run: | | |
| TIMESTAMP=$(date -u +"%Y-%m-%dT%H%M%SZ") | |
| echo "value=$TIMESTAMP" >> $GITHUB_OUTPUT | |
| - name: Detect previous versions and generate plots | |
| id: generate_plots | |
| if: steps.changed_plots.outputs.has_plots == 'true' | |
| env: | |
| GCS_BUCKET: ${{ secrets.GCS_BUCKET }} | |
| TIMESTAMP: ${{ steps.timestamp.outputs.value }} | |
| GCS_AUTH_SUCCESS: ${{ steps.gcs_auth.outcome }} | |
| run: | | |
| mkdir -p plot_outputs | |
| echo "{}" > previous_versions.json | |
| echo '${{ steps.changed_plots.outputs.changed_files }}' | jq -r '.[]' | while read -r file; do | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "📊 Processing: $file" | |
| if [[ $file =~ ^plots/([^/]+)/([^/]+)/([^/]+)/([^/]+)\.py$ ]]; then | |
| LIBRARY="${BASH_REMATCH[1]}" | |
| PLOT_TYPE="${BASH_REMATCH[2]}" | |
| SPEC_ID="${BASH_REMATCH[3]}" | |
| VARIANT="${BASH_REMATCH[4]}" | |
| else | |
| echo "⚠️ Invalid file path format: $file" | |
| continue | |
| fi | |
| OUTPUT_DIR="plot_outputs/${SPEC_ID}/${LIBRARY}/${VARIANT}" | |
| mkdir -p "$OUTPUT_DIR" | |
| FULL_FILE="${OUTPUT_DIR}/v${TIMESTAMP}.png" | |
| THUMB_FILE="${OUTPUT_DIR}/v${TIMESTAMP}_thumb.png" | |
| PREVIOUS_URL="" | |
| VERSION_COUNT=0 | |
| if [ "$GCS_AUTH_SUCCESS" == "success" ] && [ -n "$GCS_BUCKET" ]; then | |
| GCS_PATH="gs://${GCS_BUCKET}/plots/${SPEC_ID}/${LIBRARY}/${VARIANT}/" | |
| VERSIONS=$(gsutil ls "${GCS_PATH}v*.png" 2>/dev/null | grep -v "_thumb" | sort || echo "") | |
| if [ -n "$VERSIONS" ]; then | |
| VERSION_COUNT=$(echo "$VERSIONS" | wc -l) | |
| PREVIOUS_GCS=$(echo "$VERSIONS" | tail -1) | |
| PREVIOUS_URL=$(echo "$PREVIOUS_GCS" | sed "s|gs://${GCS_BUCKET}|https://storage.googleapis.com/${GCS_BUCKET}|") | |
| jq --arg key "${SPEC_ID}_${LIBRARY}_${VARIANT}" \ | |
| --arg url "$PREVIOUS_URL" \ | |
| --arg count "$VERSION_COUNT" \ | |
| '. + {($key): {"url": $url, "count": ($count | tonumber)}}' \ | |
| previous_versions.json > tmp.json && mv tmp.json previous_versions.json | |
| fi | |
| fi | |
| echo "🎨 Generating plot..." | |
| MPLBACKEND=Agg uv run python "$file" 2>&1 | tee "${OUTPUT_DIR}/generation.log" | |
| FOUND_IMAGE=false | |
| for img in "plot.png" "test_output.png" "output.png" "${SPEC_ID}.png" "preview.png"; do | |
| if [ -f "$img" ]; then | |
| mv "$img" "$FULL_FILE" | |
| FOUND_IMAGE=true | |
| break | |
| fi | |
| done | |
| if [ "$FOUND_IMAGE" == "false" ]; then | |
| echo "⚠️ No output image found for $file" | |
| continue | |
| fi | |
| echo "🖼️ Processing image..." | |
| uv run python -m core.images process "$FULL_FILE" "$FULL_FILE" "$THUMB_FILE" "$SPEC_ID" | |
| done | |
| - name: Upload plots to GCS | |
| if: steps.changed_plots.outputs.has_plots == 'true' && steps.gcs_auth.outcome == 'success' | |
| continue-on-error: true | |
| run: | | |
| gsutil -m cp -r plot_outputs/* gs://${{ secrets.GCS_BUCKET }}/plots/ | |
| - name: Post results to Issue | |
| if: steps.changed_plots.outputs.has_plots == 'true' && steps.get_issue.outputs.issue_num != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GCS_BUCKET: ${{ secrets.GCS_BUCKET }} | |
| TIMESTAMP: ${{ steps.timestamp.outputs.value }} | |
| shell: bash | |
| run: | | |
| ISSUE_NUM="${{ steps.get_issue.outputs.issue_num }}" | |
| PR_NUM="${{ steps.get_pr.outputs.pr_number }}" | |
| touch plot_markdown.txt | |
| if [ "${{ steps.gcs_auth.outcome }}" == "success" ]; then | |
| PREV_VERSIONS=$(cat previous_versions.json) | |
| echo '${{ steps.changed_plots.outputs.changed_files }}' | jq -r '.[]' | while read -r file; do | |
| if [[ $file =~ ^plots/([^/]+)/([^/]+)/([^/]+)/([^/]+)\.py$ ]]; then | |
| LIBRARY="${BASH_REMATCH[1]}" | |
| SPEC_ID="${BASH_REMATCH[3]}" | |
| VARIANT="${BASH_REMATCH[4]}" | |
| KEY="${SPEC_ID}_${LIBRARY}_${VARIANT}" | |
| NEW_URL="https://storage.googleapis.com/${GCS_BUCKET}/plots/${SPEC_ID}/${LIBRARY}/${VARIANT}/v${TIMESTAMP}.png" | |
| HISTORY_URL="https://console.cloud.google.com/storage/browser/${GCS_BUCKET}/plots/${SPEC_ID}/${LIBRARY}/${VARIANT}" | |
| PREV_URL=$(echo "$PREV_VERSIONS" | jq -r --arg key "$KEY" '.[$key].url // empty') | |
| PREV_COUNT=$(echo "$PREV_VERSIONS" | jq -r --arg key "$KEY" '.[$key].count // 0') | |
| if [ -n "$PREV_URL" ] && [ "$PREV_URL" != "null" ]; then | |
| NEW_COUNT=$((PREV_COUNT + 1)) | |
| cat >> plot_markdown.txt << 'PLOTEOF' | |
| ### ${LIBRARY} (${VARIANT}) - UPDATE | |
| | Before | After | | |
| |--------|-------| | |
| |  |  | | |
| [View version history (${NEW_COUNT} versions)](${HISTORY_URL}) | |
| PLOTEOF | |
| sed -i "s|\${LIBRARY}|${LIBRARY}|g; s|\${VARIANT}|${VARIANT}|g; s|\${PREV_URL}|${PREV_URL}|g; s|\${NEW_URL}|${NEW_URL}|g; s|\${NEW_COUNT}|${NEW_COUNT}|g; s|\${HISTORY_URL}|${HISTORY_URL}|g" plot_markdown.txt | |
| else | |
| cat >> plot_markdown.txt << 'PLOTEOF' | |
| ### ${LIBRARY} (${VARIANT}) - NEW | |
|  | |
| PLOTEOF | |
| sed -i "s|\${LIBRARY}|${LIBRARY}|g; s|\${VARIANT}|${VARIANT}|g; s|\${NEW_URL}|${NEW_URL}|g" plot_markdown.txt | |
| fi | |
| echo "" >> plot_markdown.txt | |
| fi | |
| done | |
| fi | |
| PLOT_MARKDOWN=$(cat plot_markdown.txt 2>/dev/null || echo "") | |
| cat > comment_body.md << COMMENTEOF | |
| ## 🧪 Test Results (PR #${PR_NUM}) | |
| | Python | Status | Note | | |
| |--------|--------|------| | |
| | 3.14 | ✅ Pass | Primary (plot generated) | | |
| | 3.13 | ℹ️ See workflow | Compatibility test | | |
| | 3.12 | ℹ️ See workflow | Compatibility test | | |
| | 3.11 | ℹ️ See workflow | Compatibility test | | |
| **Note:** Code is optimized for Python 3.14. | |
| ## 📊 Plot Preview (Python 3.14) | |
| ${PLOT_MARKDOWN} | |
| --- | |
| 🤖 *Generated by [gen-preview workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})* | |
| COMMENTEOF | |
| gh issue comment "$ISSUE_NUM" --body-file comment_body.md | |
| - name: Save plot metadata | |
| if: steps.changed_plots.outputs.has_plots == 'true' | |
| env: | |
| GCS_BUCKET: ${{ secrets.GCS_BUCKET }} | |
| TIMESTAMP: ${{ steps.timestamp.outputs.value }} | |
| run: | | |
| cat > plot_metadata.json <<EOF | |
| { | |
| "pr_number": ${{ steps.get_pr.outputs.pr_number }}, | |
| "issue_number": "${{ steps.get_issue.outputs.issue_num }}", | |
| "bucket": "${GCS_BUCKET}", | |
| "base_path": "plots", | |
| "timestamp": "${TIMESTAMP}", | |
| "python_version": "3.14", | |
| "changed_files": ${{ steps.changed_plots.outputs.changed_files }} | |
| } | |
| EOF | |
| - name: Upload metadata artifact | |
| if: steps.changed_plots.outputs.has_plots == 'true' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: plot-metadata | |
| path: | | |
| plot_metadata.json | |
| previous_versions.json |