1515 */
1616package com.google.firebase.gradle.plugins.report
1717
18- import kotlinx.serialization.json.Json
19- import kotlinx.serialization.json.JsonArray
20- import kotlinx.serialization.json.JsonElement
21- import kotlinx.serialization.json.JsonObject
2218import java.io.FileWriter
2319import java.io.IOException
2420import java.net.URI
2521import java.net.http.HttpClient
2622import java.net.http.HttpRequest
2723import java.net.http.HttpResponse
2824import java.time.Duration
29- import java.util.regex.Matcher
3025import java.util.regex.Pattern
31- import org.gradle.internal.Pair
3226import java.util.stream.Stream
33- import kotlin.streams.toList
27+ import kotlinx.serialization.json.Json
28+ import kotlinx.serialization.json.JsonArray
29+ import kotlinx.serialization.json.JsonElement
30+ import kotlinx.serialization.json.JsonObject
31+ import kotlinx.serialization.json.JsonPrimitive
32+ import kotlinx.serialization.json.int
33+ import kotlinx.serialization.json.jsonArray
34+ import kotlinx.serialization.json.jsonObject
35+ import kotlinx.serialization.json.jsonPrimitive
36+ import org.gradle.internal.Pair
3437
3538@SuppressWarnings(" NewApi" )
3639class UnitTestReport (private val apiToken : String ) {
3740 private val client: HttpClient =
3841 HttpClient .newBuilder().connectTimeout(Duration .ofSeconds(10 )).build()
3942
4043 fun createReport (commitCount : Int ) {
41- val response = request(" commits?per_page=$commitCount " , JsonArray ::class .java)
44+ val response =
45+ request(
46+ URI .create(" https://api.github.com/graphql" ),
47+ JsonObject ::class .java,
48+ generateGraphQLQuery(commitCount),
49+ )
4250 val commits =
43- response
51+ response[" data" ]!!
52+ .jsonObject[" repository" ]!!
53+ .jsonObject[" ref" ]!!
54+ .jsonObject[" target" ]!!
55+ .jsonObject[" history" ]!!
56+ .jsonObject[" nodes" ]!!
57+ .jsonArray
4458 .stream()
4559 .limit(commitCount.toLong())
4660 .map { el: JsonElement ->
4761 val obj = el as JsonObject
48- var pr = - 1
49- val matcher: Matcher =
50- PR_NUMBER_MATCHER .matcher((obj[" commit" ] as JsonObject )[" message" ].toString())
51- if (matcher.find()) {
52- pr = matcher.group(1 ).toInt()
53- }
54- ReportCommit (obj[" sha" ].toString(), pr)
62+ ReportCommit (
63+ obj[" oid" ]!! .jsonPrimitive.content,
64+ obj[" associatedPullRequests" ]!!
65+ .jsonObject[" nodes" ]!!
66+ .jsonArray[0 ]
67+ .jsonObject[" number" ]!!
68+ .jsonPrimitive
69+ .int,
70+ )
5571 }
5672 .toList()
5773 outputReport(commits)
@@ -174,9 +190,9 @@ class UnitTestReport(private val apiToken: String) {
174190 val runs = request(" actions/runs?head_sha=" + commit)
175191 for (el in runs[" workflow_runs" ] as JsonArray ) {
176192 val run = el as JsonObject
177- val name = run[" name" ].toString()
193+ val name = run[" name" ]!! .jsonPrimitive.content
178194 if (name == " CI Tests" ) {
179- return parseCITests(run[" id" ].toString() , commit)
195+ return parseCITests(run[" id" ]!! .jsonPrimitive.content , commit)
180196 }
181197 }
182198 return listOf ()
@@ -187,7 +203,7 @@ class UnitTestReport(private val apiToken: String) {
187203 val jobs = request(" actions/runs/" + id + " /jobs" )
188204 for (el in jobs[" jobs" ] as JsonArray ) {
189205 val job = el as JsonObject
190- val jid = job[" name" ].toString()
206+ val jid = job[" name" ]!! .jsonPrimitive.content
191207 if (jid.startsWith(" Unit Tests (:" )) {
192208 reports.add(parseJob(TestReport .Type .UNIT_TEST , job, commit))
193209 } else if (jid.startsWith(" Instrumentation Tests (:" )) {
@@ -199,24 +215,61 @@ class UnitTestReport(private val apiToken: String) {
199215
200216 private fun parseJob (type : TestReport .Type , job : JsonObject , commit : String ): TestReport {
201217 var name =
202- job[" name" ]
203- .toString()
218+ job[" name" ]!!
219+ .jsonPrimitive
220+ .content
204221 .split(" \\ (:" .toRegex())
205222 .dropLastWhile { it.isEmpty() }
206223 .toTypedArray()[1 ]
207224 name = name.substring(0 , name.length - 1 ) // Remove trailing ")"
208225 var status = TestReport .Status .OTHER
209- if (job[" status" ].toString() == " completed" ) {
210- if (job[" conclusion" ].toString() == " success" ) {
226+ if (job[" status" ]!! .jsonPrimitive.content == " completed" ) {
227+ if (job[" conclusion" ]!! .jsonPrimitive.content == " success" ) {
211228 status = TestReport .Status .SUCCESS
212229 } else {
213230 status = TestReport .Status .FAILURE
214231 }
215232 }
216- val url = job[" html_url" ].toString()
233+ val url = job[" html_url" ]!! .jsonPrimitive.content
217234 return TestReport (name, type, status, commit, url)
218235 }
219236
237+ private fun generateGraphQLQuery (commitCount : Int ): JsonObject {
238+ return JsonObject (
239+ mapOf (
240+ Pair (
241+ " query" ,
242+ JsonPrimitive (
243+ """
244+ query {
245+ repository(owner: "firebase", name: "firebase-android-sdk") {
246+ ref(qualifiedName: "refs/heads/main") {
247+ target {
248+ ... on Commit {
249+ history(first: ${commitCount} ) {
250+ nodes {
251+ messageHeadline
252+ oid
253+ associatedPullRequests(first: 1) {
254+ nodes {
255+ number
256+ title
257+ }
258+ }
259+ }
260+ }
261+ }
262+ }
263+ }
264+ }
265+ }
266+ """
267+ ),
268+ )
269+ )
270+ )
271+ }
272+
220273 private fun request (path : String ): JsonObject {
221274 return request(path, JsonObject ::class .java)
222275 }
@@ -228,10 +281,15 @@ class UnitTestReport(private val apiToken: String) {
228281 /* *
229282 * Abstracts away paginated calling. Naively joins pages together by merging root level arrays.
230283 */
231- private fun <T > request (uri : URI , clazz : Class <T >): T {
284+ private fun <T > request (uri : URI , clazz : Class <T >, payload : JsonObject ? = null): T {
285+ val builder = HttpRequest .newBuilder()
286+ if (payload == null ) {
287+ builder.GET ()
288+ } else {
289+ builder.POST (HttpRequest .BodyPublishers .ofString(payload.toString()))
290+ }
232291 val request =
233- HttpRequest .newBuilder()
234- .GET ()
292+ builder
235293 .uri(uri)
236294 .header(" Authorization" , " Bearer $apiToken " )
237295 .header(" X-GitHub-Api-Version" , " 2022-11-28" )
@@ -243,39 +301,50 @@ class UnitTestReport(private val apiToken: String) {
243301 System .err.println (response)
244302 System .err.println (body)
245303 }
246- val json = when (clazz) {
247- JsonObject ::class .java -> Json .decodeFromString<JsonObject >(body)
248- JsonArray ::class .java -> Json .decodeFromString<JsonArray >(body)
249- else -> throw IllegalArgumentException ()
250- }
304+ val json =
305+ when (clazz) {
306+ JsonObject ::class .java -> Json .decodeFromString<JsonObject >(body)
307+ JsonArray ::class .java -> Json .decodeFromString<JsonArray >(body)
308+ else -> throw IllegalArgumentException ()
309+ }
251310 if (json is JsonObject ) {
252311 // Retrieve and merge objects from other pages, if present
253- return response.headers().firstValue(" Link" ).map { link: String ->
254- val parts = link.split(" ," .toRegex()).dropLastWhile { it.isEmpty() }
255- for (part in parts) {
256- if (part.endsWith(" rel=\" next\" " )) {
257- // <foo>; rel="next" -> foo
258- val url =
259- part
260- .split(" >;" .toRegex())
261- .dropLastWhile { it.isEmpty() }
262- .toTypedArray()[0 ]
263- .split(" <" .toRegex())
264- .dropLastWhile { it.isEmpty() }
265- .toTypedArray()[1 ]
266- val p = request<JsonObject >(URI .create(url), JsonObject ::class .java)
267- return @map JsonObject (json.keys.associateWith {
268- key: String ->
269-
270- if (json[key] is JsonArray && p.containsKey(key) && p[key] is JsonArray ) {
271- JsonArray (Stream .concat((json[key] as JsonArray ).stream(), (p[key] as JsonArray ).stream()).toList())
272- }
273- json[key]!!
274- })
312+ return response
313+ .headers()
314+ .firstValue(" Link" )
315+ .map { link: String ->
316+ val parts = link.split(" ," .toRegex()).dropLastWhile { it.isEmpty() }
317+ for (part in parts) {
318+ if (part.endsWith(" rel=\" next\" " )) {
319+ // <foo>; rel="next" -> foo
320+ val url =
321+ part
322+ .split(" >;" .toRegex())
323+ .dropLastWhile { it.isEmpty() }
324+ .toTypedArray()[0 ]
325+ .split(" <" .toRegex())
326+ .dropLastWhile { it.isEmpty() }
327+ .toTypedArray()[1 ]
328+ val p = request<JsonObject >(URI .create(url), JsonObject ::class .java)
329+ return @map JsonObject (
330+ json.keys.associateWith { key: String ->
331+ if (json[key] is JsonArray && p.containsKey(key) && p[key] is JsonArray ) {
332+ return @associateWith JsonArray (
333+ Stream .concat(
334+ (json[key] as JsonArray ).stream(),
335+ (p[key] as JsonArray ).stream(),
336+ )
337+ .toList()
338+ )
339+ }
340+ return @associateWith json[key]!!
341+ }
342+ )
343+ }
275344 }
345+ return @map json
276346 }
277- return @map json
278- }.orElse(json) as T
347+ .orElse(json) as T
279348 }
280349 return json as T
281350 } catch (e: IOException ) {
0 commit comments