Skip to content

Commit e6ca06a

Browse files
committed
Change to approving only latest commit, stub methods
1 parent b49faeb commit e6ca06a

File tree

2 files changed

+103
-36
lines changed

2 files changed

+103
-36
lines changed

bot/src/dotty/tools/bot/PullRequestService.scala

Lines changed: 101 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,18 @@ trait PullRequestService {
7575
def postRequest(endpoint: Uri): Task[Request] =
7676
Request(uri = endpoint, method = Method.POST).putHeaders(authHeader).pure[Task]
7777

78-
def shutdownClient(client: Client): Unit =
79-
client.shutdownNow()
78+
def shutdownClient(client: Client): Task[Unit] =
79+
client.shutdownNow().pure[Task]
8080

8181
sealed trait CommitStatus {
8282
def commit: Commit
8383
def isValid: Boolean
8484
}
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 }
8686
final case class Invalid(user: String, commit: Commit) extends CommitStatus { def isValid = false }
8787
final case class CLAServiceDown(user: String, commit: Commit) extends CommitStatus { def isValid = false }
8888
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 }
8990

9091
/** Partitions invalid and valid commits */
9192
def checkCLA(xs: List[Commit], httpClient: Client): Task[List[CommitStatus]] = {
@@ -95,7 +96,7 @@ trait PullRequestService {
9596
claReq <- getRequest(endpoint)
9697
claRes <- httpClient.expect(claReq)(jsonOf[CLASignature])
9798
} yield { (commit: Commit) =>
98-
if (claRes.signed) Valid(user, commit)
99+
if (claRes.signed) Valid(Some(user), commit)
99100
else Invalid(user, commit)
100101
}
101102

@@ -117,33 +118,36 @@ trait PullRequestService {
117118
}.map(_.flatten)
118119
}
119120

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")
142139
}
143140

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
145146
}
146147

148+
def sendStatuses(xs: List[CommitStatus], httpClient: Client): Task[List[StatusResponse]] =
149+
Task.gatherUnordered(xs.map(setStatus(_, httpClient)))
150+
147151
private[this] val ExtractLink = """<([^>]+)>; rel="([^"]+)"""".r
148152
def findNext(header: Option[Header]): Option[String] = header.flatMap { header =>
149153
val value = header.value
@@ -157,6 +161,7 @@ trait PullRequestService {
157161
.headOption
158162
}
159163

164+
/** Ordered from earliest to latest */
160165
def getCommits(issueNbr: Int, httpClient: Client): Task[List[Commit]] = {
161166
def makeRequest(url: String): Task[List[Commit]] =
162167
for {
@@ -180,20 +185,80 @@ trait PullRequestService {
180185
res <- httpClient.expect(req)(jsonOf[List[Comment]])
181186
} yield res
182187

183-
def checkPullRequest(issue: Issue): Task[Response] = {
184-
val httpClient = PooledHttp1Client()
185188

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()
186197
for {
187-
// First get all the commits from the PR
188198
commits <- getCommits(issue.number, httpClient)
189-
190-
// Then check the CLA of each commit for both author and committer
191199
statuses <- checkCLA(commits, httpClient)
192200

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")
197255
} yield resp
198256
}
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+
}
199264
}

bot/src/dotty/tools/bot/model/Github.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ object Github {
99
)
1010

1111
case class Issue(
12+
action: String, // "opened", "reopened", "closed", "synchronize"
1213
number: Int,
1314
pull_request: Option[PullRequest]
1415
)
@@ -35,6 +36,7 @@ object Github {
3536
context: String = "CLA"
3637
)
3738

39+
// TODO: Can we get a `Commit` from `StatusResponse`?
3840
case class StatusResponse(
3941
url: String,
4042
id: Long,

0 commit comments

Comments
 (0)