|
1 | 1 | name: Comment with mypy_primer diff
|
2 | 2 |
|
3 | 3 | on:
|
4 |
| - # pull_request_target gives us access to a write token which we need to post a comment |
5 |
| - # The presence of a write token means that we can't run any untrusted code (i.e. malicious PRs), |
6 |
| - # which is why this its own workflow. Github Actions doesn't make it easy for workflows to talk to |
7 |
| - # each other, so the approach here is to poll for workflow runs, find the mypy_primer run for our |
8 |
| - # commit, wait till it's completed, and download and post the diff. |
9 |
| - pull_request_target: |
10 |
| - paths-ignore: |
11 |
| - - 'docs/**' |
12 |
| - - '**/*.rst' |
13 |
| - - '**/*.md' |
14 |
| - - 'mypyc/**' |
| 4 | + workflow_run: |
| 5 | + workflows: |
| 6 | + - Run mypy_primer |
| 7 | + types: |
| 8 | + - completed |
| 9 | + |
| 10 | +permissions: |
| 11 | + contents: read |
| 12 | + pull-requests: write |
15 | 13 |
|
16 | 14 | jobs:
|
17 |
| - mypy_primer: |
18 |
| - name: Comment |
| 15 | + comment: |
| 16 | + name: Comment PR from mypy_primer |
19 | 17 | runs-on: ubuntu-latest
|
20 | 18 | steps:
|
21 |
| - - name: Install dependencies |
22 |
| - run: npm install adm-zip |
23 |
| - - name: Post comment |
| 19 | + - name: Download diffs |
24 | 20 | uses: actions/github-script@v3
|
25 | 21 | with:
|
26 |
| - github-token: ${{secrets.GITHUB_TOKEN}} |
27 | 22 | script: |
|
28 |
| - const AdmZip = require(`${process.env.GITHUB_WORKSPACE}/node_modules/adm-zip`) |
| 23 | + const fs = require('fs'); |
| 24 | + const artifacts = await github.actions.listWorkflowRunArtifacts({ |
| 25 | + owner: context.repo.owner, |
| 26 | + repo: context.repo.repo, |
| 27 | + run_id: ${{ github.event.workflow_run.id }}, |
| 28 | + }); |
| 29 | + const [matchArtifact] = artifacts.data.artifacts.filter((artifact) => |
| 30 | + artifact.name == "mypy_primer_diffs"); |
29 | 31 |
|
30 |
| - // Because of pull_request_target, context.sha is the PR base branch |
31 |
| - // So we need to ask Github for the SHA of the PR's head commit |
32 |
| - const pull_request = await github.pulls.get({ |
33 |
| - owner: context.repo.owner, |
34 |
| - repo: context.repo.repo, |
35 |
| - pull_number: context.issue.number, |
36 |
| - }) |
37 |
| - const pr_commit_sha = pull_request.data.head.sha |
38 |
| - console.log("Looking for mypy_primer run for commit:", pr_commit_sha) |
| 32 | + const download = await github.actions.downloadArtifact({ |
| 33 | + owner: context.repo.owner, |
| 34 | + repo: context.repo.repo, |
| 35 | + artifact_id: matchArtifact.id, |
| 36 | + archive_format: "zip", |
| 37 | + }); |
| 38 | + fs.writeFileSync("diff.zip", Buffer.from(download.data)); |
39 | 39 |
|
40 |
| - // Find the mypy_primer run for our commit and wait till it's completed |
41 |
| - // We wait up to an hour before timing out |
42 |
| - async function check_mypy_primer() { |
43 |
| - // We're only looking at the first page, so in theory if we open enough PRs around |
44 |
| - // the same time, this will fail to find the run. |
45 |
| - const response = await github.actions.listWorkflowRuns({ |
46 |
| - owner: context.repo.owner, |
47 |
| - repo: context.repo.repo, |
48 |
| - workflow_id: "mypy_primer.yml", |
49 |
| - }) |
50 |
| - if (response) { |
51 |
| - return response.data.workflow_runs.find(run => run.head_sha == pr_commit_sha) |
52 |
| - } |
53 |
| - return undefined |
54 |
| - } |
| 40 | + - run: unzip diff.zip |
55 | 41 |
|
56 |
| - const end_time = Number(new Date()) + 60 * 60 * 1000 |
57 |
| - let primer_run = await check_mypy_primer() |
58 |
| - while (!primer_run || primer_run.status != "completed") { |
59 |
| - if (Number(new Date()) > end_time) { |
60 |
| - throw Error("Timed out waiting for mypy_primer") |
61 |
| - } |
62 |
| - console.log("Waiting for mypy_primer to complete...") |
63 |
| - await new Promise(r => setTimeout(r, 10000)) |
64 |
| - primer_run = await check_mypy_primer() |
65 |
| - } |
66 |
| - console.log("Found mypy_primer run!") |
67 |
| - console.log(primer_run) |
| 42 | + # Based on https://github.com/kanga333/comment-hider |
| 43 | + - name: Hide old comments |
| 44 | + uses: actions/github-script@v3 |
| 45 | + with: |
| 46 | + github-token: ${{secrets.GITHUB_TOKEN}} |
| 47 | + script: | |
| 48 | + const fs = require('fs') |
68 | 49 |
|
69 |
| - // Download artifact(s) from the run |
70 |
| - const artifacts = await github.actions.listWorkflowRunArtifacts({ |
71 |
| - owner: context.repo.owner, |
72 |
| - repo: context.repo.repo, |
73 |
| - run_id: primer_run.id, |
| 50 | + const response = await github.issues.listComments({ |
| 51 | + issue_number: fs.readFileSync("pr_number.txt", { encoding: "utf8" }), |
| 52 | + owner: context.repo.owner, |
| 53 | + repo: context.repo.repo, |
74 | 54 | })
|
75 |
| - const filtered_artifacts = artifacts.data.artifacts.filter( |
76 |
| - a => a.name.startsWith("mypy_primer_diff") |
77 |
| - ) |
78 |
| - console.log("Artifacts from mypy_primer:") |
79 |
| - console.log(filtered_artifacts) |
| 55 | + const botCommentIds = response.data |
| 56 | + .filter(comment => comment.user.login === 'github-actions[bot]') |
| 57 | + .map(comment => comment.node_id) |
80 | 58 |
|
81 |
| - async function get_artifact_data(artifact) { |
82 |
| - const zip = await github.actions.downloadArtifact({ |
83 |
| - owner: context.repo.owner, |
84 |
| - repo: context.repo.repo, |
85 |
| - artifact_id: artifact.id, |
86 |
| - archive_format: "zip", |
87 |
| - }) |
88 |
| - const adm = new AdmZip(Buffer.from(zip.data)) |
89 |
| - return adm.readAsText(adm.getEntry("diff.txt")) |
| 59 | + for (const id of botCommentIds) { |
| 60 | + const resp = await github.graphql(` |
| 61 | + mutation { |
| 62 | + minimizeComment(input: {classifier: OUTDATED, subjectId: "${id}"}) { |
| 63 | + minimizedComment { |
| 64 | + isMinimized |
| 65 | + } |
| 66 | + } |
| 67 | + } |
| 68 | + `) |
| 69 | + if (resp.errors) { |
| 70 | + throw new Error(resp.errors) |
| 71 | + } |
90 | 72 | }
|
91 | 73 |
|
92 |
| - const all_data = await Promise.all(filtered_artifacts.map(get_artifact_data)) |
93 |
| - const data = all_data.join("\n") |
| 74 | + - name: Post comment |
| 75 | + uses: actions/github-script@v3 |
| 76 | + with: |
| 77 | + github-token: ${{secrets.GITHUB_TOKEN}} |
| 78 | + script: | |
| 79 | + const fs = require('fs') |
| 80 | + // Keep in sync with shards produced by mypy_primer workflow |
| 81 | + const data = ( |
| 82 | + ['diff_0.txt', 'diff_1.txt', 'diff_2.txt'] |
| 83 | + .map(fileName => fs.readFileSync(fileName, { encoding: 'utf8' })) |
| 84 | + .join('') |
| 85 | + .substr(0, 30000) // About 300 lines |
| 86 | + ) |
94 | 87 |
|
95 | 88 | console.log("Diff from mypy_primer:")
|
96 | 89 | console.log(data)
|
| 90 | +
|
97 | 91 | if (data.trim()) {
|
| 92 | + const body = 'Diff from [mypy_primer](https://github.com/hauntsaninja/mypy_primer), showing the effect of this PR on open source code:\n```diff\n' + data + '```' |
98 | 93 | await github.issues.createComment({
|
99 |
| - issue_number: context.issue.number, |
| 94 | + issue_number: fs.readFileSync("pr_number.txt", { encoding: "utf8" }), |
100 | 95 | owner: context.repo.owner,
|
101 | 96 | repo: context.repo.repo,
|
102 |
| - body: 'Diff from [mypy_primer](https://github.com/hauntsaninja/mypy_primer), showing the effect of this PR on open source code:\n```diff\n' + data + '```' |
| 97 | + body |
103 | 98 | })
|
104 | 99 | }
|
0 commit comments