Python PR Comment #34
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: Python PR Comment | |
| on: | |
| workflow_run: | |
| workflows: [Python] | |
| types: [completed] | |
| permissions: | |
| actions: read | |
| issues: write | |
| pull-requests: read | |
| jobs: | |
| comment: | |
| name: Update PR Comment | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download PR comment summary | |
| id: download_summary | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: python-pr-comment | |
| path: ${{ runner.temp }}/python-pr-comment | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ github.token }} | |
| - name: Create or update PR comment | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| SUMMARY_PATH: ${{ runner.temp }}/python-pr-comment/pr-comment-summary.json | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| const fs = require("fs"); | |
| const marker = "<!-- python-code-quality-check -->"; | |
| const run = context.payload.workflow_run; | |
| const pullRequests = run.pull_requests || []; | |
| if (run.event !== "pull_request" || pullRequests.length === 0) { | |
| core.info("No pull request is associated with this workflow run."); | |
| return; | |
| } | |
| const pr = pullRequests[0]; | |
| const issueNumber = pr.number; | |
| const summaryPath = process.env.SUMMARY_PATH; | |
| let summary = null; | |
| let statusLines = []; | |
| function assertBoolean(name, value) { | |
| if (typeof value !== "boolean") { | |
| throw new Error(`${name} must be a boolean`); | |
| } | |
| } | |
| function validateSummary(candidate) { | |
| if (candidate.schema_version !== 1) { | |
| throw new Error("Unexpected PR comment summary schema version"); | |
| } | |
| if (candidate.pr_number !== issueNumber) { | |
| throw new Error("PR number in summary does not match workflow_run payload"); | |
| } | |
| if (candidate.head_sha !== run.head_sha) { | |
| throw new Error("Head SHA in summary does not match workflow_run payload"); | |
| } | |
| if (String(candidate.workflow_run_id) !== String(run.id)) { | |
| throw new Error("Run ID in summary does not match workflow_run payload"); | |
| } | |
| assertBoolean("summary_complete", candidate.summary_complete); | |
| assertBoolean("black_failed", candidate.black_failed); | |
| assertBoolean("flake8_failed", candidate.flake8_failed); | |
| assertBoolean("python_files_checked", candidate.python_files_checked); | |
| if ( | |
| !Number.isInteger(candidate.flake8_issue_count) || | |
| candidate.flake8_issue_count < 0 || | |
| candidate.flake8_issue_count > 100000 | |
| ) { | |
| throw new Error("Invalid flake8 issue count in summary"); | |
| } | |
| } | |
| if (fs.existsSync(summaryPath)) { | |
| const stats = fs.statSync(summaryPath); | |
| if (stats.size > 8192) { | |
| throw new Error("PR comment summary artifact is too large"); | |
| } | |
| summary = JSON.parse(fs.readFileSync(summaryPath, "utf8")); | |
| validateSummary(summary); | |
| } | |
| if (summary && summary.summary_complete) { | |
| statusLines = [ | |
| `Black: **${summary.black_failed ? "failed" : "passed"}**`, | |
| summary.python_files_checked | |
| ? `Flake8: **${summary.flake8_failed ? "failed" : "passed"}** (${summary.flake8_issue_count} output line${summary.flake8_issue_count === 1 ? "" : "s"})` | |
| : "Flake8: **skipped** (no Python files changed)", | |
| ]; | |
| } else if (summary) { | |
| statusLines = [ | |
| "The Python workflow did not produce complete lint metadata.", | |
| `Workflow conclusion: **${run.conclusion || "unknown"}**`, | |
| ]; | |
| } else { | |
| statusLines = [ | |
| "The Python workflow did not upload PR comment metadata.", | |
| `Workflow conclusion: **${run.conclusion || "unknown"}**`, | |
| ]; | |
| } | |
| const details = []; | |
| if (summary?.black_failed) { | |
| details.push("Formatting diff is available in the workflow artifacts."); | |
| } | |
| if (summary?.flake8_failed) { | |
| details.push("Flake8 report is available in the workflow artifacts."); | |
| } | |
| const body = [ | |
| marker, | |
| "### Python Code Quality Check", | |
| "", | |
| ...statusLines, | |
| "", | |
| `[Workflow run](${run.html_url})`, | |
| ...details.map((detail) => `- ${detail}`), | |
| "", | |
| "_This comment is auto-updated with every commit._", | |
| ].join("\n"); | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| per_page: 100, | |
| }); | |
| const existing = comments.find((comment) => | |
| comment.user?.login === "github-actions[bot]" && comment.body?.includes(marker) | |
| ); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| body, | |
| }); | |
| } |