Skip to content

Instantly share code, notes, and snippets.

@rjsvaljean
Created August 24, 2017 08:08
Show Gist options
  • Select an option

  • Save rjsvaljean/183e3122180859da5de69a07b9a3c788 to your computer and use it in GitHub Desktop.

Select an option

Save rjsvaljean/183e3122180859da5de69a07b9a3c788 to your computer and use it in GitHub Desktop.
Script to find distribution of PR age
scalaVersion := "2.12.2"
name := "graphql-playground"
libraryDependencies ++= Seq(
"org.sangria-graphql" %% "sangria" % "1.3.0",
"org.http4s" %% "http4s-dsl" % "0.17.0-RC1",
"org.http4s" %% "http4s-blaze-client" % "0.17.0-RC1",
"org.http4s" %% "http4s-circe" % "0.17.0-RC1",
"io.circe" %% "circe-generic" % "0.6.1",
"io.circe" %% "circe-literal" % "0.6.1",
"io.circe" %% "circe-parser" % "0.6.1",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"joda-time" % "joda-time" % "2.9.9",
"org.typelevel" %% "cats-core" % "1.0.0-MF",
"com.quantifind" %% "wisp" % "0.0.4"
)
resolvers += Resolver.sonatypeRepo("releases")
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
package rxvl
import java.text.SimpleDateFormat
import org.http4s.client.blaze._
import java.util.Date
import cats.instances.map._
import cats.instances.int._
import cats.instances.tuple._
import cats.kernel.Semigroup
import cats.syntax.semigroup._
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import sangria._
import sangria.macros._
import sangria.renderer.QueryRenderer
import fs2._
import org.joda.time.{DateTime, Days}
object models {
private val is08601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")
implicit val dateEncoder: Encoder[Date] = Encoder.instance[Date](_.toString.asJson)
implicit val dateDecoder: Decoder[Date] = Decoder.decodeString.map(is08601.parse)
case class Commits(totalCount: Int)
case class PullRequestNode(createdAt: Date, mergedAt: Option[Date], commits: Commits)
case class PullRequestEdge(cursor: String, node: PullRequestNode)
case class PullRequest(edges: List[PullRequestEdge])
case class Repo(pullRequests: PullRequest)
case class Data(repository: Repo)
case class RepoGQLResponse(data: Data)
object Commits {implicit val encoder: Encoder[Commits] = deriveEncoder[Commits];implicit val decoder: Decoder[Commits] = deriveDecoder[Commits];implicit val entityEncoder: EntityEncoder[Commits] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[Commits] = jsonOf(decoder)}
object PullRequestNode {implicit val encoder: Encoder[PullRequestNode] = deriveEncoder[PullRequestNode];implicit val decoder: Decoder[PullRequestNode] = deriveDecoder[PullRequestNode];implicit val entityEncoder: EntityEncoder[PullRequestNode] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[PullRequestNode] = jsonOf(decoder)}
object PullRequestEdge {implicit val encoder: Encoder[PullRequestEdge] = deriveEncoder[PullRequestEdge];implicit val decoder: Decoder[PullRequestEdge] = deriveDecoder[PullRequestEdge];implicit val entityEncoder: EntityEncoder[PullRequestEdge] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[PullRequestEdge] = jsonOf(decoder)}
object PullRequest {implicit val encoder: Encoder[PullRequest] = deriveEncoder[PullRequest];implicit val decoder: Decoder[PullRequest] = deriveDecoder[PullRequest];implicit val entityEncoder: EntityEncoder[PullRequest] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[PullRequest] = jsonOf(decoder)}
object Repo {implicit val encoder: Encoder[Repo] = deriveEncoder[Repo];implicit val decoder: Decoder[Repo] = deriveDecoder[Repo];implicit val entityEncoder: EntityEncoder[Repo] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[Repo] = jsonOf(decoder)}
object Data {implicit val encoder: Encoder[Data] = deriveEncoder[Data];implicit val decoder: Decoder[Data] = deriveDecoder[Data];implicit val entityEncoder: EntityEncoder[Data] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[Data] = jsonOf(decoder)}
object RepoGQLResponse {implicit val encoder: Encoder[RepoGQLResponse] = deriveEncoder[RepoGQLResponse];implicit val decoder: Decoder[RepoGQLResponse] = deriveDecoder[RepoGQLResponse];implicit val entityEncoder: EntityEncoder[RepoGQLResponse] = jsonEncoderOf(encoder);implicit val entityDecoder: EntityDecoder[RepoGQLResponse] = jsonOf(decoder)}
}
case class Average(total: Int, n: Int) {
override def toString: String = "[Avg: " + (total.toDouble / n).formatted("%.2f") + "]"
}
object Average {
implicit val semigroup: Semigroup[Average] = (x: Average, y: Average) => Average(x.total + y.total, x.n + y.n)
}
object Main {
import models._
val GithubAccessToken = "nice try"
def main(args: Array[String]): Unit = (for {
buckets <- getPages.flatMap(toNodes).map(toBucket).reduce(_ |+| _).runLast.map(_.get)
_ <- client.shutdown
} yield buckets.toSeq.sortBy(_._1).foreach(println)).unsafeRun()
private def getPage(cursor: Option[String]): Task[RepoGQLResponse] = for {
reqWithBody <- prsStartingAt(cursor)
req = reqWithBody.withHeaders(Headers(
Header("Authorization", "bearer " + GithubAccessToken),
Header("Accept", "application/vnd.github.v4.raw+json")))
res <- client.fetchAs[RepoGQLResponse](req)
} yield res
private def getPages: Stream[Task, RepoGQLResponse] =
Stream.unfoldEval[Task, Option[String], RepoGQLResponse](Option.empty[String]) { cursorOpt =>
getPage(cursorOpt).map(res =>
if (res.data.repository.pullRequests.edges.isEmpty) None
else Some((res, Some(res.data.repository.pullRequests.edges.last.cursor))))
}
private val printStartEnd: Sink[Task, PullRequestNode] =
_.map(n => println("Created at: " + n.createdAt + " and Merged at: " + n.mergedAt.fold("NOT MERGED")(_.toString)))
def toBucket(node: PullRequestNode): Map[Int, (Int, Average)] = {
val days = node.mergedAt.map { mAt =>
val startAsLocalDate = new DateTime(node.createdAt).toLocalDate
val endAsLocalDate = new DateTime(mAt).toLocalDate
Days.daysBetween(startAsLocalDate, endAsLocalDate).getDays
}.getOrElse(999)
Map(days -> (1, Average(node.commits.totalCount, 1)))
}
private def toNodes(res: RepoGQLResponse): Stream[Task, PullRequestNode] =
Stream.emits(res.data.repository.pullRequests.edges.map(_.node))
private def prsStartingAt(cursor: Option[String]) = {
Request(Method.POST, Uri.uri("https://api.github.com/graphql")).withBody(GraphQLQuery(query, variables(cursor)))
}
val query =
graphql"""
query($$cursorPosition: String) {
repository(owner: "rjsvaljean", name: "some-repo") {
pullRequests(first: 10, after: $$cursorPosition) {
edges {
cursor
node {
createdAt
mergedAt
commits {
totalCount
}
}
}
}
}
}
"""
private def variables(cursor: Option[String]) = Json.obj("cursorPosition" -> cursor.fold(Json.Null)(_.asJson))
val client = PooledHttp1Client()
}
case class GraphQLQuery(query: ast.Document, variables: Json)
object GraphQLQuery {
implicit val queryEncoder: Encoder[ast.Document] =
Encoder.instance((doc: ast.Document) => QueryRenderer.render(doc).asJson)
implicit val encoder: Encoder[GraphQLQuery] = deriveEncoder[GraphQLQuery]
implicit val entityEncoder: EntityEncoder[GraphQLQuery] = jsonEncoderOf(encoder)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment