@@ -75,17 +75,18 @@ trait PullRequestService {
75
75
def postRequest (endpoint : Uri ): Task [Request ] =
76
76
Request (uri = endpoint, method = Method .POST ).putHeaders(authHeader).pure[Task ]
77
77
78
- def shutdownClient (client : Client ): Unit =
79
- client.shutdownNow()
78
+ def shutdownClient (client : Client ): Task [ Unit ] =
79
+ client.shutdownNow().pure[ Task ]
80
80
81
81
sealed trait CommitStatus {
82
82
def commit : Commit
83
83
def isValid : Boolean
84
84
}
85
- final case class Valid (user : String , commit : Commit ) extends CommitStatus { def isValid = true }
85
+ final case class Valid (user : Option [ String ] , commit : Commit ) extends CommitStatus { def isValid = true }
86
86
final case class Invalid (user : String , commit : Commit ) extends CommitStatus { def isValid = false }
87
87
final case class CLAServiceDown (user : String , commit : Commit ) extends CommitStatus { def isValid = false }
88
88
final case class MissingUser (commit : Commit ) extends CommitStatus { def isValid = false }
89
+ final case class InvalidPrevious (users : List [String ], commit : Commit ) extends CommitStatus { def isValid = false }
89
90
90
91
/** Partitions invalid and valid commits */
91
92
def checkCLA (xs : List [Commit ], httpClient : Client ): Task [List [CommitStatus ]] = {
@@ -95,7 +96,7 @@ trait PullRequestService {
95
96
claReq <- getRequest(endpoint)
96
97
claRes <- httpClient.expect(claReq)(jsonOf[CLASignature ])
97
98
} yield { (commit : Commit ) =>
98
- if (claRes.signed) Valid (user, commit)
99
+ if (claRes.signed) Valid (Some ( user) , commit)
99
100
else Invalid (user, commit)
100
101
}
101
102
@@ -117,33 +118,36 @@ trait PullRequestService {
117
118
}.map(_.flatten)
118
119
}
119
120
120
- def sendStatuses (xs : List [CommitStatus ], httpClient : Client ): Task [List [StatusResponse ]] = {
121
- def setStatus (cm : CommitStatus ): Task [StatusResponse ] = {
122
- val desc =
123
- if (cm.isValid) " User signed CLA"
124
- else " User needs to sign cla: https://www.lightbend.com/contribute/cla/scala"
125
-
126
- val stat = cm match {
127
- case Valid (user, commit) =>
128
- Status (" success" , claUrl(user), desc)
129
- case Invalid (user, commit) =>
130
- Status (" failure" , claUrl(user), desc)
131
- case MissingUser (commit) =>
132
- Status (" failure" , " " , " Missing valid github user for this PR" )
133
- case CLAServiceDown (user, commit) =>
134
- Status (" pending" , claUrl(user), " CLA Service is down" )
135
- }
136
-
137
- for {
138
- endpoint <- toUri(statusUrl(cm.commit.sha))
139
- req <- postRequest(endpoint).withBody(stat.asJson)
140
- res <- httpClient.expect(req)(jsonOf[StatusResponse ])
141
- } yield res
121
+ def setStatus (cm : CommitStatus , httpClient : Client ): Task [StatusResponse ] = {
122
+ val desc =
123
+ if (cm.isValid) " User signed CLA"
124
+ else " User needs to sign cla: https://www.lightbend.com/contribute/cla/scala"
125
+
126
+ val stat = cm match {
127
+ case Valid (Some (user), commit) =>
128
+ Status (" success" , claUrl(user), desc)
129
+ case Valid (None , commit) =>
130
+ Status (" success" , " " , " All contributors signed CLA" )
131
+ case Invalid (user, commit) =>
132
+ Status (" failure" , claUrl(user), desc)
133
+ case MissingUser (commit) =>
134
+ Status (" failure" , " " , " Missing valid github user for this PR" )
135
+ case CLAServiceDown (user, commit) =>
136
+ Status (" pending" , claUrl(user), " CLA Service is down" )
137
+ case InvalidPrevious (users, latestCommit) =>
138
+ Status (" failure" , " " , users.map(" @" + _).mkString(" , " ) + " have not signed the CLA" )
142
139
}
143
140
144
- Task .gatherUnordered(xs.map(setStatus))
141
+ for {
142
+ endpoint <- toUri(statusUrl(cm.commit.sha))
143
+ req <- postRequest(endpoint).withBody(stat.asJson)
144
+ res <- httpClient.expect(req)(jsonOf[StatusResponse ])
145
+ } yield res
145
146
}
146
147
148
+ def sendStatuses (xs : List [CommitStatus ], httpClient : Client ): Task [List [StatusResponse ]] =
149
+ Task .gatherUnordered(xs.map(setStatus(_, httpClient)))
150
+
147
151
private [this ] val ExtractLink = """ <([^>]+)>; rel="([^"]+)"""" .r
148
152
def findNext (header : Option [Header ]): Option [String ] = header.flatMap { header =>
149
153
val value = header.value
@@ -157,6 +161,7 @@ trait PullRequestService {
157
161
.headOption
158
162
}
159
163
164
+ /** Ordered from earliest to latest */
160
165
def getCommits (issueNbr : Int , httpClient : Client ): Task [List [Commit ]] = {
161
166
def makeRequest (url : String ): Task [List [Commit ]] =
162
167
for {
@@ -180,20 +185,80 @@ trait PullRequestService {
180
185
res <- httpClient.expect(req)(jsonOf[List [Comment ]])
181
186
} yield res
182
187
183
- def checkPullRequest (issue : Issue ): Task [Response ] = {
184
- val httpClient = PooledHttp1Client ()
185
188
189
+ private def usersFromInvalid (xs : List [CommitStatus ]) =
190
+ xs.collect { case Invalid (user, _) => user }
191
+
192
+ def sendInitialComment (invalidUsers : List [String ]): Task [Unit ] = ???
193
+
194
+ def checkFreshPR (issue : Issue ): Task [Response ] = {
195
+
196
+ val httpClient = PooledHttp1Client ()
186
197
for {
187
- // First get all the commits from the PR
188
198
commits <- getCommits(issue.number, httpClient)
189
-
190
- // Then check the CLA of each commit for both author and committer
191
199
statuses <- checkCLA(commits, httpClient)
192
200
193
- // Send statuses to Github and exit
194
- _ <- sendStatuses(statuses, httpClient)
195
- _ <- shutdownClient(httpClient).pure[Task ]
196
- resp <- Ok (" All statuses checked" )
201
+ (validStatuses, invalidStatuses) = statuses.partition(_.isValid)
202
+ invalidUsers = usersFromInvalid(invalidStatuses)
203
+
204
+ // Mark the invalid statuses:
205
+ _ <- sendStatuses(invalidStatuses, httpClient)
206
+
207
+ // Set status of last to indicate previous failures or all good:
208
+ _ <- {
209
+ if (invalidStatuses.nonEmpty)
210
+ setStatus(InvalidPrevious (invalidUsers, commits.last), httpClient)
211
+ else
212
+ setStatus(statuses.last, httpClient)
213
+ }
214
+
215
+ // Send positive comment:
216
+ _ <- sendInitialComment(invalidUsers)
217
+ _ <- shutdownClient(httpClient)
218
+ resp <- Ok (" Fresh PR checked" )
219
+ } yield resp
220
+
221
+ }
222
+
223
+ // TODO: Could this be done with `issueNbr` instead?
224
+ def getStatuses (commits : List [Commit ], client : Client ): Task [List [StatusResponse ]] =
225
+ ???
226
+
227
+ private def extractCommit (status : StatusResponse ): Task [Commit ] =
228
+ ???
229
+
230
+ def recheckCLA (statuses : List [StatusResponse ], client : Client ): Task [List [CommitStatus ]] =
231
+ for {
232
+ commits <- Task .gatherUnordered(statuses.map(extractCommit))
233
+ statuses <- checkCLA(commits, client)
234
+ } yield statuses
235
+
236
+ def checkSynchronize (issue : Issue ): Task [Response ] = {
237
+ val httpClient = PooledHttp1Client ()
238
+
239
+ for {
240
+ commits <- getCommits(issue.number, httpClient)
241
+ statuses <- getStatuses(commits, httpClient)
242
+ stillInvalid <- recheckCLA(statuses, httpClient)
243
+
244
+ // Set final commit status based on `stillInvalid`:
245
+ _ <- {
246
+ if (stillInvalid.nonEmpty)
247
+ setStatus(InvalidPrevious (usersFromInvalid(stillInvalid), commits.last), httpClient)
248
+ else {
249
+ val lastCommit = commits.last
250
+ setStatus(Valid (lastCommit.author.login, lastCommit), httpClient)
251
+ }
252
+ }
253
+ _ <- shutdownClient(httpClient)
254
+ resp <- Ok (" Updated PR checked" )
197
255
} yield resp
198
256
}
257
+
258
+ def checkPullRequest (issue : Issue ): Task [Response ] =
259
+ issue.action match {
260
+ case " opened" => checkFreshPR(issue)
261
+ case " synchronize" => checkSynchronize(issue)
262
+ case action => BadRequest (s " Unhandled action: $action" )
263
+ }
199
264
}
0 commit comments