@@ -4,8 +4,8 @@ const SECOND = 1000;
44const MINUTE = SECOND * 60 ;
55const 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
1010const {
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 ) {
0 commit comments