Skip to content

Commit 80bf14a

Browse files
committed
lib: simply PR checking logic
1 parent 6255af7 commit 80bf14a

File tree

4 files changed

+181
-226
lines changed

4 files changed

+181
-226
lines changed

lib/collaborators.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Collaborator {
3131
}
3232

3333
getName() {
34-
return `${this.name}(${this.login})`;
34+
return `${this.name} (@${this.login})`;
3535
}
3636

3737
getContact() {

lib/pr_checker.js

Lines changed: 74 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ const SECOND = 1000;
44
const MINUTE = SECOND * 60;
55
const HOUR = MINUTE * 60;
66

7-
const WAIT_TIME = 48;
8-
const WAIT_TIME_SINGLE_APPROVAL = 168;
7+
const WAIT_TIME_MULTI_APPROVAL = 24 * 2;
8+
const WAIT_TIME_SINGLE_APPROVAL = 24 * 7;
99

1010
const {
1111
REVIEW_SOURCES: { FROM_COMMENT, FROM_REVIEW_COMMENT }
@@ -49,12 +49,11 @@ class PRChecker {
4949
);
5050
}
5151

52-
checkAll(comments = false) {
52+
checkAll(checkComments = false) {
5353
const status = [
54-
this.checkReviews(comments),
5554
this.checkCommitsAfterReview(),
5655
this.checkCI(),
57-
this.checkPRWait(new Date()),
56+
this.checkReviewsAndWait(new Date(), checkComments),
5857
this.checkMergeableState(),
5958
this.checkPRState(),
6059
this.checkGitConfig()
@@ -70,132 +69,104 @@ class PRChecker {
7069
return status.every((i) => i);
7170
}
7271

73-
getTSCHint(people) {
74-
const tsc = people
72+
getTSC(people) {
73+
return people
7574
.filter((p) => p.reviewer.isTSC())
7675
.map((p) => p.reviewer.login);
76+
}
77+
78+
formatReview(reviewer, review) {
7779
let hint = '';
78-
if (tsc.length > 0) {
79-
const list = `(${tsc.join(', ')})`;
80-
hint = `, ${tsc.length} from TSC ${list}`;
80+
if (reviewer.isTSC()) {
81+
hint = ' (TSC)';
8182
}
82-
return hint;
83+
return `- ${reviewer.getName()}${hint}: ${review.ref}`;
8384
}
8485

85-
checkReviews(comments = false) {
86-
const {
87-
pr, cli, reviewers: { requestedChanges, approved }
88-
} = this;
89-
let status = true;
90-
91-
if (requestedChanges.length === 0) {
92-
cli.ok(`Requested Changes: 0`);
93-
} else {
94-
status = false;
95-
let hint = this.getTSCHint(requestedChanges);
96-
cli.error(`Requested Changes: ${requestedChanges.length}${hint}`);
86+
displayReviews(checkComments) {
87+
const { cli, reviewers: { requestedChanges, approved } } = this;
88+
if (requestedChanges.length > 0) {
89+
cli.error(`Requested Changes: ${requestedChanges.length}`);
9790
for (const { reviewer, review } of requestedChanges) {
98-
cli.error(`- ${reviewer.getName()}: ${review.ref}`);
91+
cli.error(this.formatReview(reviewer, review));
9992
}
10093
}
94+
10195
if (approved.length === 0) {
102-
status = false;
10396
cli.error(`Approvals: 0`);
104-
} else {
105-
let hint = this.getTSCHint(approved);
106-
cli.ok(`Approvals: ${approved.length}${hint}`);
107-
108-
if (comments) {
109-
for (const { reviewer, review } of approved) {
110-
if (review.source === FROM_COMMENT ||
111-
review.source === FROM_REVIEW_COMMENT) {
112-
cli.info(
113-
`- ${reviewer.getName()} approved in via LGTM in comments`);
114-
}
115-
}
116-
}
97+
return;
98+
}
11799

118-
const labels = pr.labels.nodes.map((l) => l.name);
119-
if (labels.includes('semver-major')) {
120-
const tscApproval = approved.filter((p) => p.reviewer.isTSC()).length;
121-
if (tscApproval < 2) {
122-
status = false;
123-
cli.error('semver-major requires at least two TSC approvals');
124-
}
100+
cli.ok(`Approvals: ${approved.length}`);
101+
for (const { reviewer, review } of approved) {
102+
cli.ok(this.formatReview(reviewer, review));
103+
if (checkComments &&
104+
[FROM_COMMENT, FROM_REVIEW_COMMENT].includes(review.source)) {
105+
cli.info(`- ${reviewer.getName()} approved in via LGTM in comments`);
125106
}
126107
}
127-
128-
return status;
129108
}
130109

131-
/**
132-
* @param {Date} now
133-
*/
134-
getWait(now) {
110+
checkReviewsAndWait(now, checkComments) {
111+
const {
112+
pr, cli, reviewers
113+
} = this;
114+
const { requestedChanges, approved } = reviewers;
115+
const labels = pr.labels.nodes.map((l) => l.name);
116+
117+
const isFastTracked = labels.includes('fast-track');
118+
const isSemverMajor = labels.includes('semver-major');
119+
120+
const dateStr = new Date(pr.createdAt).toUTCString();
121+
cli.info(`This PR was created on ${dateStr}`);
122+
this.displayReviews(checkComments);
123+
// NOTE: a semver-major PR with fast-track should have either one of
124+
// these labels removed because that doesn't make sense
125+
if (isFastTracked) {
126+
cli.info('This PR is being fast-tracked');
127+
}
128+
129+
if (approved.length === 0 || requestedChanges.length > 0) {
130+
return false;
131+
}
132+
133+
if (isSemverMajor) {
134+
const tscApproved = approved
135+
.filter((p) => p.reviewer.isTSC())
136+
.map((p) => p.reviewer.login);
137+
if (tscApproved.length < 2) {
138+
cli.error('semver-major requires at least 2 TSC approvals');
139+
return false; // 7 day rule doesn't matter here
140+
}
141+
}
142+
135143
const createTime = new Date(this.pr.createdAt);
136144
const hoursFromCreateTime = Math.ceil(
137145
(now.getTime() - createTime.getTime()) / HOUR
138146
);
139-
const timeLeft = WAIT_TIME - hoursFromCreateTime;
140-
const timeLeftSingleApproval =
141-
WAIT_TIME_SINGLE_APPROVAL - hoursFromCreateTime;
142-
143-
return {
144-
timeLeft,
145-
timeLeftSingleApproval
146-
};
147-
}
147+
let timeLeftMulti = WAIT_TIME_MULTI_APPROVAL - hoursFromCreateTime;
148+
const timeLeftSingle = WAIT_TIME_SINGLE_APPROVAL - hoursFromCreateTime;
148149

149-
// TODO: skip some PRs...we might need a label for that
150-
/**
151-
* @param {Date} now
152-
*/
153-
checkPRWait(now) {
154-
const {
155-
pr, cli, reviewers, CIStatus
156-
} = this;
157-
const { approved } = reviewers;
158-
const labels = pr.labels.nodes;
159-
160-
const fast =
161-
labels.some((l) => ['fast-track'].includes(l.name));
162-
if (fast) {
163-
if (approved.length > 1 && CIStatus) {
164-
cli.info('This PR is being fast-tracked');
150+
if (approved.length >= 2) {
151+
if (isFastTracked) {
165152
return true;
166-
} else {
167-
const msg = ['This PR is being fast-tracked, but awaiting '];
168-
if (approved.length < 2) msg.push('approvals of 2 contributors');
169-
if (!CIStatus) msg.push('a CI run');
170-
171-
let warnMsg = msg.length === 2
172-
? msg.join('') : `${msg[0] + msg[1]} and ${msg[2]}`;
173-
cli.warn(warnMsg);
174153
}
175-
154+
if (timeLeftMulti < 0) {
155+
return true;
156+
}
157+
cli.error(`This PR needs to wait ${timeLeftMulti} more hours to land`);
176158
return false;
177159
}
178160

179-
const dateStr = new Date(pr.createdAt).toDateString();
180-
cli.info(`This PR was created on ${dateStr}`);
181-
182-
const wait = this.getWait(now);
183-
if (approved.length > 1 && wait.timeLeft > 0) {
184-
cli.warn(`Wait at least ${wait.timeLeft} more hours before landing`);
185-
return false;
186-
} else if (approved.length === 1 && wait.timeLeftSingleApproval > 0) {
187-
const waitMsg = wait.timeLeft > 0
188-
? ` and ${wait.timeLeft} more hours`
189-
: '';
190-
cli.warn('Wait at one of the following:');
191-
cli.warn(`* another approval${waitMsg}`);
192-
cli.warn(`* ${wait.timeLeftSingleApproval} more hours` +
193-
' with existing single approval');
161+
if (approved.length === 1) {
162+
if (timeLeftSingle < 0) {
163+
return true;
164+
}
165+
timeLeftMulti = timeLeftMulti < 0 ? 0 : timeLeftMulti;
166+
cli.error(`This PR needs to wait ${timeLeftSingle} more hours to land ` +
167+
`(or ${timeLeftMulti} hours if there is one more approval)`);
194168
return false;
195169
}
196-
197-
cli.info('No more waiting required');
198-
return true;
199170
}
200171

201172
hasFullOrLiteCI(ciMap) {

test/unit/collaborators.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('collaborators', function() {
6262
collaborators.forEach(collaborator => {
6363
assert.strictEqual(
6464
collaborator.getName(),
65-
`${collaborator.name}(${collaborator.login})`);
65+
`${collaborator.name} (@${collaborator.login})`);
6666
});
6767
});
6868
});

0 commit comments

Comments
 (0)