15
15
*/
16
16
package com.google.firebase.gradle.plugins.report
17
17
18
- import kotlinx.serialization.json.Json
19
- import kotlinx.serialization.json.JsonArray
20
- import kotlinx.serialization.json.JsonElement
21
- import kotlinx.serialization.json.JsonObject
22
18
import java.io.FileWriter
23
19
import java.io.IOException
24
20
import java.net.URI
25
21
import java.net.http.HttpClient
26
22
import java.net.http.HttpRequest
27
23
import java.net.http.HttpResponse
28
24
import java.time.Duration
29
- import java.util.regex.Matcher
30
25
import java.util.regex.Pattern
31
- import org.gradle.internal.Pair
32
26
import 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
34
37
35
38
@SuppressWarnings(" NewApi" )
36
39
class UnitTestReport (private val apiToken : String ) {
37
40
private val client: HttpClient =
38
41
HttpClient .newBuilder().connectTimeout(Duration .ofSeconds(10 )).build()
39
42
40
43
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
+ )
42
50
val commits =
43
- response
51
+ response[" data" ]!!
52
+ .jsonObject[" repository" ]!!
53
+ .jsonObject[" ref" ]!!
54
+ .jsonObject[" target" ]!!
55
+ .jsonObject[" history" ]!!
56
+ .jsonObject[" nodes" ]!!
57
+ .jsonArray
44
58
.stream()
45
59
.limit(commitCount.toLong())
46
60
.map { el: JsonElement ->
47
61
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
+ )
55
71
}
56
72
.toList()
57
73
outputReport(commits)
@@ -174,9 +190,9 @@ class UnitTestReport(private val apiToken: String) {
174
190
val runs = request(" actions/runs?head_sha=" + commit)
175
191
for (el in runs[" workflow_runs" ] as JsonArray ) {
176
192
val run = el as JsonObject
177
- val name = run[" name" ].toString()
193
+ val name = run[" name" ]!! .jsonPrimitive.content
178
194
if (name == " CI Tests" ) {
179
- return parseCITests(run[" id" ].toString() , commit)
195
+ return parseCITests(run[" id" ]!! .jsonPrimitive.content , commit)
180
196
}
181
197
}
182
198
return listOf ()
@@ -187,7 +203,7 @@ class UnitTestReport(private val apiToken: String) {
187
203
val jobs = request(" actions/runs/" + id + " /jobs" )
188
204
for (el in jobs[" jobs" ] as JsonArray ) {
189
205
val job = el as JsonObject
190
- val jid = job[" name" ].toString()
206
+ val jid = job[" name" ]!! .jsonPrimitive.content
191
207
if (jid.startsWith(" Unit Tests (:" )) {
192
208
reports.add(parseJob(TestReport .Type .UNIT_TEST , job, commit))
193
209
} else if (jid.startsWith(" Instrumentation Tests (:" )) {
@@ -199,24 +215,61 @@ class UnitTestReport(private val apiToken: String) {
199
215
200
216
private fun parseJob (type : TestReport .Type , job : JsonObject , commit : String ): TestReport {
201
217
var name =
202
- job[" name" ]
203
- .toString()
218
+ job[" name" ]!!
219
+ .jsonPrimitive
220
+ .content
204
221
.split(" \\ (:" .toRegex())
205
222
.dropLastWhile { it.isEmpty() }
206
223
.toTypedArray()[1 ]
207
224
name = name.substring(0 , name.length - 1 ) // Remove trailing ")"
208
225
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" ) {
211
228
status = TestReport .Status .SUCCESS
212
229
} else {
213
230
status = TestReport .Status .FAILURE
214
231
}
215
232
}
216
- val url = job[" html_url" ].toString()
233
+ val url = job[" html_url" ]!! .jsonPrimitive.content
217
234
return TestReport (name, type, status, commit, url)
218
235
}
219
236
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
+
220
273
private fun request (path : String ): JsonObject {
221
274
return request(path, JsonObject ::class .java)
222
275
}
@@ -228,10 +281,15 @@ class UnitTestReport(private val apiToken: String) {
228
281
/* *
229
282
* Abstracts away paginated calling. Naively joins pages together by merging root level arrays.
230
283
*/
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
+ }
232
291
val request =
233
- HttpRequest .newBuilder()
234
- .GET ()
292
+ builder
235
293
.uri(uri)
236
294
.header(" Authorization" , " Bearer $apiToken " )
237
295
.header(" X-GitHub-Api-Version" , " 2022-11-28" )
@@ -243,39 +301,50 @@ class UnitTestReport(private val apiToken: String) {
243
301
System .err.println (response)
244
302
System .err.println (body)
245
303
}
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
+ }
251
310
if (json is JsonObject ) {
252
311
// 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
+ }
275
344
}
345
+ return @map json
276
346
}
277
- return @map json
278
- }.orElse(json) as T
347
+ .orElse(json) as T
279
348
}
280
349
return json as T
281
350
} catch (e: IOException ) {
0 commit comments