Skip to content

Gen: Preview Images

Gen: Preview Images #8

Workflow file for this run

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 |
|--------|-------|
| ![Before](${PREV_URL}) | ![After](${NEW_URL}) |
[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
![${LIBRARY} ${VARIANT}](${NEW_URL})
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