diff --git a/lib/pr_checker.js b/lib/pr_checker.js index c4058f12..e28d1616 100644 --- a/lib/pr_checker.js +++ b/lib/pr_checker.js @@ -29,6 +29,30 @@ const FAST_TRACK_RE = /^Fast-track has been requested by @(.+?)\. Please 👍 to const FAST_TRACK_MIN_APPROVALS = 2; const GIT_CONFIG_GUIDE_URL = 'https://github.com/nodejs/node/blob/99b1ada/doc/guides/contributing/pull-requests.md#step-1-fork'; +function getLatestGitHubActionsCheckSuites(checkSuites) { + const latestByWorkflow = new Map(); + + for (const suite of checkSuites) { + const { app, workflowRun } = suite; + + if (app?.slug !== 'github-actions') { + continue; + } + + // Runs triggered by different events are independent and must not + // supersede one another. + const key = `${workflowRun.workflow.id}:${workflowRun.event}`; + const current = latestByWorkflow.get(key); + + if (!current || + workflowRun.runNumber > current.workflowRun.runNumber) { + latestByWorkflow.set(key, suite); + } + } + + return [...latestByWorkflow.values()]; +} + export const PR_CHECK_REASON_CODES = Object.freeze({ CANCELLED_GITHUB_CI: 'cancelled-github-ci', CLOSED: 'closed', @@ -459,14 +483,9 @@ export default class PRChecker { const pendingJobs = []; // GitHub new Check API - for (const { status, conclusion, app, checkRuns } of checkSuites.nodes) { - if (app.slug !== 'github-actions') { - // Ignore all non-github check suites, such as Dependabot and Codecov. - // They are expected to show up on PRs whose head branch is not on a - // fork and never complete. - continue; - } - + const latestCheckSuites = + getLatestGitHubActionsCheckSuites(checkSuites.nodes); + for (const { status, conclusion, app, checkRuns } of latestCheckSuites) { if (status !== 'COMPLETED') { pendingJobs.push({ app: app.slug, status, conclusion }); continue; diff --git a/lib/queries/PR.gql b/lib/queries/PR.gql index 824e5c5a..dabc79be 100644 --- a/lib/queries/PR.gql +++ b/lib/queries/PR.gql @@ -36,7 +36,14 @@ query PR($prid: Int!, $owner: String!, $repo: String!) { } conclusion, status, - checkRuns(first: 40) { + workflowRun { + event + runNumber + workflow { + id + } + } + checkRuns(first: 40, filterBy: { checkType: LATEST }) { nodes { name status diff --git a/test/fixtures/github-ci/both-apis-failure.json b/test/fixtures/github-ci/both-apis-failure.json index aa552cce..7b88192a 100644 --- a/test/fixtures/github-ci/both-apis-failure.json +++ b/test/fixtures/github-ci/both-apis-failure.json @@ -14,6 +14,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "FAILURE" } @@ -22,4 +23,3 @@ } } ] - diff --git a/test/fixtures/github-ci/both-apis-success.json b/test/fixtures/github-ci/both-apis-success.json index c58e7abc..b12fc607 100644 --- a/test/fixtures/github-ci/both-apis-success.json +++ b/test/fixtures/github-ci/both-apis-success.json @@ -14,6 +14,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "SUCCESS" } @@ -22,4 +23,3 @@ } } ] - diff --git a/test/fixtures/github-ci/check-suite-cancelled.json b/test/fixtures/github-ci/check-suite-cancelled.json index b42d16d6..db220db0 100644 --- a/test/fixtures/github-ci/check-suite-cancelled.json +++ b/test/fixtures/github-ci/check-suite-cancelled.json @@ -11,6 +11,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "CANCELLED", "checkRuns": { diff --git a/test/fixtures/github-ci/check-suite-failure.json b/test/fixtures/github-ci/check-suite-failure.json index d07172f0..519ef4a4 100644 --- a/test/fixtures/github-ci/check-suite-failure.json +++ b/test/fixtures/github-ci/check-suite-failure.json @@ -11,6 +11,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "FAILURE", "checkRuns": { diff --git a/test/fixtures/github-ci/check-suite-pending.json b/test/fixtures/github-ci/check-suite-pending.json index c09d11bf..2e53fadf 100644 --- a/test/fixtures/github-ci/check-suite-pending.json +++ b/test/fixtures/github-ci/check-suite-pending.json @@ -11,6 +11,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "IN_PROGRESS" } ] @@ -18,4 +19,3 @@ } } ] - diff --git a/test/fixtures/github-ci/check-suite-skipped.json b/test/fixtures/github-ci/check-suite-skipped.json index 6a2cb755..b7a021e7 100644 --- a/test/fixtures/github-ci/check-suite-skipped.json +++ b/test/fixtures/github-ci/check-suite-skipped.json @@ -11,6 +11,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "SKIPPED" } diff --git a/test/fixtures/github-ci/check-suite-success.json b/test/fixtures/github-ci/check-suite-success.json index 1dc5f395..4e65640c 100644 --- a/test/fixtures/github-ci/check-suite-success.json +++ b/test/fixtures/github-ci/check-suite-success.json @@ -11,6 +11,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "SUCCESS" } diff --git a/test/fixtures/github-ci/status-failure-check-suite-succeed.json b/test/fixtures/github-ci/status-failure-check-suite-succeed.json index 28faa58c..2f4805bc 100644 --- a/test/fixtures/github-ci/status-failure-check-suite-succeed.json +++ b/test/fixtures/github-ci/status-failure-check-suite-succeed.json @@ -14,6 +14,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "SUCCESS" } @@ -22,4 +23,3 @@ } } ] - diff --git a/test/fixtures/github-ci/status-succeed-check-suite-failure.json b/test/fixtures/github-ci/status-succeed-check-suite-failure.json index 6a336cd8..47d14b87 100644 --- a/test/fixtures/github-ci/status-succeed-check-suite-failure.json +++ b/test/fixtures/github-ci/status-succeed-check-suite-failure.json @@ -14,6 +14,7 @@ "nodes": [ { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "FAILURE" } @@ -22,4 +23,3 @@ } } ] - diff --git a/test/fixtures/github-ci/success-dependabot-queued.json b/test/fixtures/github-ci/success-dependabot-queued.json index 557452f5..1c6ddb9c 100644 --- a/test/fixtures/github-ci/success-dependabot-queued.json +++ b/test/fixtures/github-ci/success-dependabot-queued.json @@ -18,6 +18,7 @@ }, { "app": { "slug": "github-actions" }, + "workflowRun": { "event": "pull_request", "runNumber": 1, "workflow": { "id": "workflow-1" } }, "status": "COMPLETED", "conclusion": "SUCCESS" } @@ -26,4 +27,3 @@ } } ] - diff --git a/test/unit/graphql_queries.test.js b/test/unit/graphql_queries.test.js index 9b8d750e..c2fcfda7 100644 --- a/test/unit/graphql_queries.test.js +++ b/test/unit/graphql_queries.test.js @@ -17,7 +17,8 @@ describe('GraphQL queries', () => { assert.notStrictEqual(headCommitStart, -1); assert.notStrictEqual(headCommitEnd, -1); assert.match(headCommitQuery, /checkSuites\(first: 100\)/); - assert.match(headCommitQuery, /checkRuns\(first: 40\)/); + assert.match(headCommitQuery, + /checkRuns\(first: 40, filterBy: \{ checkType: LATEST \}\)/); assert.match(headCommitQuery, /status \{\s+state\s+\}/); assert.doesNotMatch(commitsQuery, /checkSuites/); assert.doesNotMatch(commitsQuery, /checkRuns/); diff --git a/test/unit/pr_checker.test.js b/test/unit/pr_checker.test.js index 109fc2cf..b3ce2d85 100644 --- a/test/unit/pr_checker.test.js +++ b/test/unit/pr_checker.test.js @@ -1825,6 +1825,45 @@ describe('PRChecker', () => { cli.assertCalledWith(expectedLogs); }); + it('should ignore a superseded Check suite', async() => { + const cli = new TestCLI(); + const commits = [{ + commit: { + checkSuites: { + nodes: [ + { + app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 2, + workflow: { id: 'workflow-1' } + }, + status: 'COMPLETED', + conclusion: 'SUCCESS' + }, + { + app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, + status: 'COMPLETED', + conclusion: 'FAILURE' + } + ] + } + } + }]; + const data = Object.assign({}, baseData, { commits }); + const checker = new PRChecker(cli, data, {}, testArgv); + + const status = await checker.checkCI(); + + assert(status); + cli.assertCalledWith({ ok: [['Last GitHub CI successful']] }); + }); + it('should error if commit status failed', async() => { const cli = new TestCLI(); @@ -2010,6 +2049,11 @@ describe('PRChecker', () => { checkSuites: { nodes: [{ app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, status: 'COMPLETED', conclusion: 'FAILURE', checkRuns: { @@ -2067,6 +2111,11 @@ describe('PRChecker', () => { checkSuites: { nodes: [{ app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, status: 'COMPLETED', conclusion: 'FAILURE', checkRuns: { @@ -2119,6 +2168,11 @@ describe('PRChecker', () => { checkSuites: { nodes: [{ app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, status: 'COMPLETED', conclusion: 'CANCELLED' // No checkRuns field @@ -2154,6 +2208,11 @@ describe('PRChecker', () => { checkSuites: { nodes: [{ app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, status: 'COMPLETED', conclusion: 'FAILURE', checkRuns: { nodes: [] } @@ -2189,6 +2248,11 @@ describe('PRChecker', () => { checkSuites: { nodes: [{ app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, status: 'COMPLETED', conclusion: 'FAILURE', checkRuns: { @@ -2231,6 +2295,11 @@ describe('PRChecker', () => { checkSuites: { nodes: [{ app: { slug: 'github-actions' }, + workflowRun: { + event: 'pull_request', + runNumber: 1, + workflow: { id: 'workflow-1' } + }, status: 'COMPLETED', conclusion: 'FAILURE', checkRuns: {