Skip to content

Commit 6cc2b1a

Browse files
feat(ncu-ci): check for request-ci label (#806)
When running `ncu-ci` without the `--certify-safe` CLI flag, if something was pushed to a PR since the last approving review, check if the last time the `request-ci` label was added, it was done by a Collaborator and after the last push event on the PR. Co-authored-by: Mathis Wiehl <[email protected]>
1 parent 9f8df53 commit 6cc2b1a

11 files changed

+209
-1
lines changed

lib/ci/run_ci.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class RunPRJob {
2727
this.certifySafe =
2828
certifySafe ||
2929
Promise.all([this.prData.getReviews(), this.prData.getPR()]).then(() =>
30-
new PRChecker(cli, this.prData, request, {}).checkCommitsAfterReview()
30+
new PRChecker(cli, this.prData, request, {}).checkCommitsAfterReviewOrLabel()
3131
);
3232
}
3333

lib/pr_checker.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,32 @@ export default class PRChecker {
523523
return true;
524524
}
525525

526+
async checkCommitsAfterReviewOrLabel() {
527+
if (this.checkCommitsAfterReview()) return true;
528+
529+
await Promise.all([this.data.getLabeledEvents(), this.data.getCollaborators()]);
530+
531+
const {
532+
cli, data, pr
533+
} = this;
534+
535+
const { updatedAt } = pr.timelineItems;
536+
const requestCiLabels = data.labeledEvents.findLast(
537+
({ createdAt, label: { name } }) => name === 'request-ci' && createdAt > updatedAt
538+
);
539+
if (requestCiLabels == null) return false;
540+
541+
const { actor: { login } } = requestCiLabels;
542+
const collaborators = Array.from(data.collaborators.values(),
543+
(c) => c.login.toLowerCase());
544+
if (collaborators.includes(login.toLowerCase())) {
545+
cli.info('request-ci label was added by a Collaborator after the last push event.');
546+
return true;
547+
}
548+
549+
return false;
550+
}
551+
526552
checkCommitsAfterReview() {
527553
const {
528554
commits, reviews, cli, argv

lib/pr_data.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from './user_status.js';
66

77
// lib/queries/*.gql file names
8+
const LABELED_EVENTS_QUERY = 'PRLabeledEvents';
89
const PR_QUERY = 'PR';
910
const REVIEWS_QUERY = 'Reviews';
1011
const COMMENTS_QUERY = 'PRComments';
@@ -33,6 +34,7 @@ export default class PRData {
3334
this.comments = [];
3435
this.commits = [];
3536
this.reviewers = [];
37+
this.labeledEvents = [];
3638
}
3739

3840
getThread() {
@@ -90,6 +92,14 @@ export default class PRData {
9092
]);
9193
}
9294

95+
async getLabeledEvents() {
96+
const { prid, owner, repo, cli, request, prStr } = this;
97+
const vars = { prid, owner, repo };
98+
cli.updateSpinner(`Getting labels from ${prStr}`);
99+
this.labeledEvents = (await request.gql(LABELED_EVENTS_QUERY, vars))
100+
.repository.pullRequest.timelineItems.nodes;
101+
}
102+
93103
async getComments() {
94104
const { prid, owner, repo, cli, request, prStr } = this;
95105
const vars = { prid, owner, repo };

lib/queries/PRLabeledEvents.gql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
query PRLabeledEvents($prid: Int!, $owner: String!, $repo: String!, $after: String) {
2+
repository(owner: $owner, name: $repo) {
3+
pullRequest(number: $prid) {
4+
timelineItems(itemTypes: LABELED_EVENT, after: $after, last: 100) {
5+
nodes {
6+
... on LabeledEvent {
7+
actor {
8+
login
9+
}
10+
label {
11+
name
12+
}
13+
createdAt
14+
}
15+
}
16+
}
17+
}
18+
}
19+
}

test/fixtures/data.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,12 @@ for (const subdir of readdirSync(path('./jenkins'))) {
134134
readJSON(`./jenkins/${subdir}/${item}`);
135135
}
136136
};
137+
138+
export const labeledEvents = {};
139+
140+
for (const item of readdirSync(path('./labeled_events'))) {
141+
if (!item.endsWith('.json')) {
142+
continue;
143+
}
144+
labeledEvents[basename(item, '.json')] = readJSON(`./labeled_events/${item}`);
145+
}

test/fixtures/first_timer_pr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
}
2323
]
2424
},
25+
"timelineItems": { "updatedAt": "2017-10-24T11:13:43Z" },
2526
"title": "test: awesome changes",
2627
"baseRefName": "main",
2728
"headRefName": "awesome-changes"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"actor": { "login": "nodejs-github-bot" },
4+
"label": { "name": "doc" },
5+
"createdAt": "2024-05-13T15:57:10Z"
6+
},
7+
{
8+
"actor": { "login": "nodejs-github-bot" },
9+
"label": { "name": "test_runner" },
10+
"createdAt": "2024-05-13T15:57:10Z"
11+
}
12+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"actor": { "login": "nodejs-github-bot" },
4+
"label": { "name": "doc" },
5+
"createdAt": "2024-05-13T15:57:10Z"
6+
},
7+
{
8+
"actor": { "login": "foo" },
9+
"label": { "name": "request-ci" },
10+
"createdAt": "1999-10-24T11:13:43Z"
11+
}
12+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"actor": { "login": "nodejs-github-bot" },
4+
"label": { "name": "doc" },
5+
"createdAt": "2024-05-13T15:57:10Z"
6+
},
7+
{
8+
"actor": { "login": "foo" },
9+
"label": { "name": "request-ci" },
10+
"createdAt": "2024-05-13T15:57:10Z"
11+
}
12+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"actor": { "login": "nodejs-github-bot" },
4+
"label": { "name": "doc" },
5+
"createdAt": "2024-05-13T15:57:10Z"
6+
},
7+
{
8+
"actor": { "login": "random-person" },
9+
"label": { "name": "request-ci" },
10+
"createdAt": "2024-05-13T15:57:10Z"
11+
}
12+
]

0 commit comments

Comments
 (0)