Skip to content

Commit 1751287

Browse files
authored
Merge pull request #25 from codacy/par-pag
Make paginated requests parallel
2 parents be876e7 + 5f77801 commit 1751287

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

src/main/scala/com/codacy/client/bitbucket/client/BitbucketClient.scala

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.codacy.client.bitbucket.client
22

3+
import java.net.URI
34
import java.util.concurrent.{SynchronousQueue, ThreadPoolExecutor, TimeUnit}
45

56
import com.codacy.client.bitbucket.util.HTTPStatusCodes
7+
import com.codacy.client.bitbucket.util.Implicits.URIQueryParam
68
import com.ning.http.client.AsyncHttpClientConfig
79
import play.api.http.{ContentTypeOf, Writeable}
810
import play.api.libs.json._
@@ -26,7 +28,7 @@ class BitbucketClient(key: String, secretKey: String, token: String, secretToken
2628
*/
2729
def execute[T](request: Request[T])(implicit reader: Reads[T]): RequestResponse[T] = {
2830
get(request.url) match {
29-
case Right(json) => json.validate[T].fold(_ => FailedResponse(s"Failed to parse json: $json"), a => SuccessfulResponse(a))
31+
case Right(json) => json.validate[T].fold(e => FailedResponse(s"Failed to parse json ($e): $json"), a => SuccessfulResponse(a))
3032
case Left(error) => FailedResponse(error.detail)
3133
}
3234
}
@@ -35,16 +37,33 @@ class BitbucketClient(key: String, secretKey: String, token: String, secretToken
3537
* Does a paginated API request and parses the json output into a sequence of classes
3638
*/
3739
def executePaginated[T](request: Request[Seq[T]])(implicit reader: Reads[T]): RequestResponse[Seq[T]] = {
40+
val FIRST_PAGE = 1
41+
42+
def extractValues(json: JsValue): RequestResponse[Seq[T]] =
43+
(json \ "values").validate[Seq[T]].fold(e => FailedResponse(s"Failed to parse json ($e): $json"), a => SuccessfulResponse(a))
44+
3845
get(request.url) match {
3946
case Right(json) =>
40-
val nextPage = (json \ "next").asOpt[String]
41-
val nextRepos = nextPage.map {
42-
nextUrl =>
43-
executePaginated(Request(nextUrl, request.classType))
44-
}.getOrElse(SuccessfulResponse(Seq.empty))
45-
46-
val values = (json \ "values").validate[Seq[T]].fold(_ => FailedResponse(s"Failed to parse json: $json"), a => SuccessfulResponse(a))
47-
RequestResponse.apply(values, nextRepos)
47+
val nextPages = (for {
48+
size <- (json \ "size").asOpt[Double]
49+
pagelen <- (json \ "pagelen").asOpt[Double]
50+
} yield {
51+
val lastPage = math.ceil(size / pagelen).toInt
52+
(FIRST_PAGE + 1 to lastPage).par.map { page =>
53+
val nextUrl = new URI(request.url).addQuery(s"page=$page").toString
54+
get(nextUrl) match {
55+
case Right(json) => extractValues(json)
56+
case Left(error) => FailedResponse(error.detail)
57+
}
58+
}.to[Seq]
59+
}).getOrElse(Seq(SuccessfulResponse(Seq.empty)))
60+
61+
val values = extractValues(json)
62+
63+
(values +: nextPages).foldLeft[RequestResponse[Seq[T]]](SuccessfulResponse(Seq.empty[T])) {
64+
(a, b) =>
65+
RequestResponse.apply(a, b)
66+
}
4867

4968
case Left(error) =>
5069
FailedResponse(error.detail)
@@ -153,7 +172,7 @@ class BitbucketClient(key: String, secretKey: String, token: String, secretToken
153172
case Success(jsValue) =>
154173
jsValue
155174
case Failure(e) =>
156-
Left(ResponseError("Failed to parse json",e.getStackTrace.mkString(System.lineSeparator),e.getMessage))
175+
Left(ResponseError("Failed to parse json", e.getStackTrace.mkString(System.lineSeparator), e.getMessage))
157176
}
158177
}
159178

src/main/scala/com/codacy/client/bitbucket/service/PullRequestServices.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class PullRequestServices(client: BitbucketClient) {
1313
*
1414
*/
1515
def getPullRequests(owner: String, repository: String, states: Seq[String] = Seq("OPEN")): RequestResponse[Seq[PullRequest]] = {
16-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests?state=${states.mkString("&state=")}"
16+
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests?pagelen=50&state=${states.mkString("&state=")}"
1717

1818
client.executePaginated(Request(url, classOf[Seq[PullRequest]]))
1919
}
@@ -23,7 +23,7 @@ class PullRequestServices(client: BitbucketClient) {
2323
*
2424
*/
2525
def getPullRequestCommits(owner: String, repository: String, prId: Long): RequestResponse[Seq[SimpleCommit]] = {
26-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/commits"
26+
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/commits?pagelen=100"
2727

2828
client.executePaginated(Request(url, classOf[Seq[SimpleCommit]]))
2929
}
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
package com.codacy.client.bitbucket.util
22

3+
import java.net.URI
4+
35
import play.api.data.validation.ValidationError
46
import play.api.libs.json.{Json, Reads, Writes}
7+
58
import scala.language.implicitConversions
69

710
object Implicits {
811

912
implicit def enumWrites[E <: Enumeration#Value]: Writes[E] = Writes((e: E) => Json.toJson(e.toString))
1013

1114
implicit def enumReads[E <: Enumeration](e: E): Reads[e.Value] = {
12-
Reads.StringReads.map { case value => e.values.find(_.toString == value) }.
15+
Reads.StringReads.map { value => e.values.find(_.toString == value) }.
1316
collect(ValidationError("Invalid enumeration value")) { case Some(v) => v }
1417
}
15-
}
18+
19+
implicit class URIQueryParam(uri: URI) {
20+
def addQuery(q: String): URI = {
21+
val newQuery = if (Option.apply(uri.getQuery).isEmpty) {
22+
q
23+
} else {
24+
uri.getQuery + "&" + q
25+
}
26+
27+
new URI(uri.getScheme, uri.getAuthority, uri.getPath, newQuery, uri.getFragment)
28+
}
29+
}
30+
31+
}

0 commit comments

Comments
 (0)