Skip to content

Commit

Permalink
[andr] Upgrade graphql apollo inegration from v3 to v4 (#176)
Browse files Browse the repository at this point in the history
* [andr] Upgrade graphql apollo inegration from v3 to v4

* Make it backwards compatible with apollo3

* Fix headers and span_name

* Update namespace to remove 3

* Use uppercase
  • Loading branch information
murki authored Jan 10, 2025
1 parent 9198a84 commit ff903aa
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ plugins {
group = "io.bitdrift"

android {
namespace = "io.bitdrift.capture.apollo3"
namespace = "io.bitdrift.capture.apollo"
compileSdk = 34

defaultConfig {
Expand Down Expand Up @@ -70,7 +70,7 @@ dependencies {
mavenPublishing {
pom {
name.set("CaptureApollo")
description.set("Official Capture integration for Apollo v3 (GraphQL).")
description.set("Official Capture integration for Apollo (GraphQL).")
url.set("https://bitdrift.io")
licenses {
license {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture.apollo3
package io.bitdrift.capture.apollo

import com.apollographql.apollo3.api.ApolloRequest
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.Mutation
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.api.Subscription
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.apollographql.apollo3.interceptor.ApolloInterceptorChain
import com.apollographql.apollo.api.ApolloRequest
import com.apollographql.apollo.api.ApolloResponse
import com.apollographql.apollo.api.Mutation
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.Query
import com.apollographql.apollo.api.Subscription
import com.apollographql.apollo.interceptor.ApolloInterceptor
import com.apollographql.apollo.interceptor.ApolloInterceptorChain
import io.bitdrift.capture.Capture
import kotlinx.coroutines.flow.Flow

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ data class HttpRequestInfo @JvmOverloads constructor(
internal val commonFields: InternalFieldsMap by lazy {
buildMap {
putAll(extraFields.toFields())
put(SpanField.Key.NAME, FieldValue.StringField("_http"))
putOptionalHeaderSpanFields(headers)
putOptionalGraphQlHeaders(headers)
put(SpanField.Key.ID, FieldValue.StringField(spanId.toString()))
put(SpanField.Key.TYPE, FieldValue.StringField(SpanField.Value.TYPE_START))
put("_method", FieldValue.StringField(method))
Expand Down Expand Up @@ -77,6 +79,7 @@ data class HttpRequestInfo @JvmOverloads constructor(
headers?.get("x-capture-span-key")?.let { spanKey ->
val prefix = "x-capture-span-$spanKey"
val spanName = "_" + headers["$prefix-name"]
// override _span_name
put(SpanField.Key.NAME, FieldValue.StringField(spanName))
val fieldPrefix = "$prefix-field"
headers.forEach { (key, value) ->
Expand All @@ -85,9 +88,26 @@ data class HttpRequestInfo @JvmOverloads constructor(
put(fieldKey, FieldValue.StringField(value))
}
}
} ?: run {
// Default span name is simply http
put(SpanField.Key.NAME, FieldValue.StringField("_http"))
}
}

/**
* Best effort to extract graphQL operation name from the headers, this is specific to apollo3 kotlin client
*
* @param headers The map of headers from which fields are extracted.
*/
private fun MutableMap<String, FieldValue>.putOptionalGraphQlHeaders(headers: Map<String, String>?) {
headers?.get("X-APOLLO-OPERATION-NAME")?.let { gqlOperationName ->
put(HttpFieldKey.PATH_TEMPLATE, FieldValue.StringField("gql-$gqlOperationName"))
put("_operation_name", FieldValue.StringField(gqlOperationName))
headers["X-APOLLO-OPERATION-TYPE"]?.let { gqlOperationKey ->
put("_operation_type", FieldValue.StringField(gqlOperationKey))
}
headers["X-APOLLO-OPERATION-ID"]?.let { gqlOperationId ->
put("_operation_id", FieldValue.StringField(gqlOperationId))
}
// override _span_name
put(SpanField.Key.NAME, FieldValue.StringField("_graphql"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -401,4 +401,64 @@ class CaptureOkHttpEventListenerFactoryTest {
// validate all request fields are present in response
assertThat(httpResponseInfo.fields.mapValues { it.value.toString() }.entries.containsAll(expectedFields.entries)).isTrue()
}

@Test
fun testApolloHeadersSendGraphQlSpans() {
// ARRANGE
val headerFields = mapOf(
"X-APOLLO-OPERATION-NAME" to "myOperationName",
"X-APOLLO-OPERATION-ID" to "myOperationId",
"X-APOLLO-OPERATION-TYPE" to "query",
)
val expectedSpanName = "_graphql"
val expectedFields = mapOf(
"_operation_name" to "myOperationName",
"_operation_id" to "myOperationId",
"_operation_type" to "query",
"_path_template" to "gql-myOperationName",
)

val request = Request.Builder()
.url(endpoint)
.post("test".toRequestBody())
.headers(headerFields.toHeaders())
.build()

val response = Response.Builder()
.request(request)
.protocol(Protocol.HTTP_2)
.code(200)
.message("message")
.header("response_header", "response_header_value")
.build()

val call: Call = mock()
whenever(call.request()).thenReturn(request)

// ACT
val factory = CaptureOkHttpEventListenerFactory(null, logger, clock)
val listener = factory.create(call)

listener.callStart(call)

listener.responseHeadersEnd(call, response)
listener.responseBodyEnd(call, 234)

listener.callEnd(call)

// ASSERT
val httpRequestInfoCapture = argumentCaptor<HttpRequestInfo>()
verify(logger).log(httpRequestInfoCapture.capture())
val httpResponseInfoCapture = argumentCaptor<HttpResponseInfo>()
verify(logger).log(httpResponseInfoCapture.capture())

val httpRequestInfo = httpRequestInfoCapture.firstValue
val httpResponseInfo = httpResponseInfoCapture.firstValue

assertThat(httpRequestInfo.fields["_span_name"].toString()).isEqualTo(expectedSpanName)
// validate all the extra headers are present as properly formatted fields
assertThat(httpRequestInfo.fields.mapValues { it.value.toString() }.entries.containsAll(expectedFields.entries)).isTrue()
// validate all request fields are present in response
assertThat(httpResponseInfo.fields.mapValues { it.value.toString() }.entries.containsAll(expectedFields.entries)).isTrue()
}
}
6 changes: 3 additions & 3 deletions platform/jvm/gradle-test-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

dependencies {
implementation(project(":capture"))
implementation(project(":capture-apollo3"))
implementation(project(":capture-apollo"))
implementation(project(":capture-timber"))
implementation(libs.androidx.material3.android)
implementation("androidx.core:core-ktx:1.9.0")
Expand All @@ -16,8 +16,8 @@ dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("com.apollographql.apollo3:apollo-runtime:3.8.3")
implementation("com.apollographql.apollo3:apollo-runtime:3.8.3")
implementation("com.apollographql.apollo:apollo-runtime:4.1.0")
implementation("com.apollographql.apollo:apollo-runtime:4.1.0")
implementation("com.jakewharton.timber:timber:5.0.1")
implementation("com.google.android.material:material:1.8.0")
implementation("com.squareup.papa:papa:0.26")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import androidx.compose.material.Text
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.network.okHttpClient
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.network.okHttpClient
import com.example.rocketreserver.BookTripsMutation
import com.example.rocketreserver.LaunchListQuery
import com.example.rocketreserver.LoginMutation
Expand All @@ -39,7 +39,7 @@ import io.bitdrift.capture.Capture.Logger
import io.bitdrift.capture.CaptureJniLibrary
import io.bitdrift.capture.LogLevel
import io.bitdrift.capture.LoggerImpl
import io.bitdrift.capture.apollo3.CaptureApolloInterceptor
import io.bitdrift.capture.apollo.CaptureApolloInterceptor
import io.bitdrift.capture.network.okhttp.CaptureOkHttpEventListenerFactory
import io.bitdrift.gradletestapp.databinding.FragmentFirstBinding
import kotlinx.coroutines.MainScope
Expand Down
6 changes: 3 additions & 3 deletions platform/jvm/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
androidBenchkmarkPlugin = "1.2.4"
androidGradlePlugin = "8.2.2" # have to pin to this version due to 8.3.x breaking our lint https://issuetracker.google.com/issues/332755363
apollo = "3.8.3"
apollo = "4.1.0"
appcompat = "1.5.1"
assertjCore = "3.22.0"
androidxCore = "1.9.0"
Expand Down Expand Up @@ -37,7 +37,7 @@ androidx-core = { group = "androidx.core", name = "core", version.ref = "android
androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" }
androidx-ui = { module = "androidx.compose.ui:ui", version.ref = "ui" }
apollo-runtime = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollo" }
apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" }
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertjCore" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
Expand All @@ -57,7 +57,7 @@ truth = { module = "com.google.truth:truth", version.ref = "truth" }
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-benchmark = { id = "androidx.benchmark", version.ref = "androidBenchkmarkPlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
apollo-graphql = { id = "com.apollographql.apollo3", version.ref = "apollo" }
apollo-graphql = { id = "com.apollographql.apollo", version.ref = "apollo" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detektPlugin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaPlugin" }
maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublishPlugin" }
Expand Down
2 changes: 1 addition & 1 deletion platform/jvm/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pluginManagement {
}

include(":capture")
include(":capture-apollo3")
include(":capture-apollo")
include(":capture-timber")
include(":common")
include(":replay")
Expand Down

0 comments on commit ff903aa

Please sign in to comment.