Skip to content

Commit f66e405

Browse files
chore: Android Move graphql queries in the code (#507)
## Summary Move graphql queries into code files not load them every time in runtime, weirdly it helps to make code compatibkle with .NET10 and usable in the Java server code. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it changes the public `GraphQLClient.execute` signature and shifts query text from resources to code, where string formatting/escaping mistakes could break production GraphQL calls. > > **Overview** > **GraphQL queries are now inlined in code instead of loaded from resources at runtime.** `GraphQLClient.execute` now takes a `query: String` and the internal `loadQuery` resource loader is removed. > > `SamplingApiService` and `SessionReplayApiService` embed their GraphQL operations as Kotlin multiline strings and pass them directly to `execute`, and the corresponding `.graphql` resource files (plus the test query resource) are deleted. Tests are updated accordingly, including removing the missing-file test case and adjusting mocks to accept the new `execute` parameter. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ffa3cc6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent a9907a7 commit f66e405

10 files changed

Lines changed: 200 additions & 218 deletions

File tree

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/GraphQLClient.kt

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,19 @@ class GraphQLClient(
6767

6868
/**
6969
* Executes a GraphQL query
70-
* @param queryFileName The .graphql file name
70+
* @param query The GraphQL query string
7171
* @param variables Query variables
7272
* @param dataSerializer Kotlinx serialization serializer for the expected response data type
7373
* @return GraphQLResponse containing either the deserialized data or error information
7474
*/
7575
suspend fun <T> execute(
76-
queryFileName: String,
76+
query: String,
7777
variables: Map<String, JsonElement> = emptyMap(),
7878
dataSerializer: KSerializer<T>,
7979
compress: Boolean = true
8080
): GraphQLResponse<T> = withContext(DispatcherProviderHolder.current.io) {
8181
var connection: HttpURLConnection? = null
8282
val response: GraphQLResponse<T> = try {
83-
val query = loadQuery(queryFileName)
8483
val request = GraphQLRequest(
8584
query = query,
8685
variables = variables
@@ -143,17 +142,6 @@ class GraphQLClient(
143142
response
144143
}
145144

146-
/**
147-
* Loads GraphQL query from resources
148-
* @param queryFilepath The .graphql file path
149-
* @return Query string
150-
*/
151-
private fun loadQuery(queryFilepath: String): String {
152-
return this::class.java.classLoader?.getResourceAsStream(queryFilepath)?.bufferedReader()?.use {
153-
it.readText()
154-
} ?: throw IllegalStateException("Could not load GraphQL query file: $queryFilepath")
155-
}
156-
157145
private fun gzip(data: ByteArray): ByteArray {
158146
val byteStream = ByteArrayOutputStream()
159147
GZIPOutputStream(byteStream).use { gzipStream ->

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/SamplingApiService.kt

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,61 @@ class SamplingApiService(
1111
) {
1212

1313
companion object {
14-
private const val GET_SAMPLING_CONFIG_QUERY_FILE_PATH = "graphql/GetSamplingConfigQuery.graphql"
14+
private val GET_SAMPLING_CONFIG_QUERY = """
15+
fragment MatchParts on MatchConfig {
16+
regexValue
17+
matchValue
18+
}
19+
20+
query GetSamplingConfig(${'$'}organization_verbose_id: String!) {
21+
sampling(organization_verbose_id: ${'$'}organization_verbose_id) {
22+
spans {
23+
name {
24+
...MatchParts
25+
}
26+
attributes {
27+
key {
28+
...MatchParts
29+
}
30+
attribute {
31+
...MatchParts
32+
}
33+
}
34+
events {
35+
name {
36+
...MatchParts
37+
}
38+
attributes {
39+
key {
40+
...MatchParts
41+
}
42+
attribute {
43+
...MatchParts
44+
}
45+
}
46+
}
47+
samplingRatio
48+
}
49+
logs {
50+
message {
51+
...MatchParts
52+
}
53+
severityText {
54+
...MatchParts
55+
}
56+
attributes {
57+
key {
58+
...MatchParts
59+
}
60+
attribute {
61+
...MatchParts
62+
}
63+
}
64+
samplingRatio
65+
}
66+
}
67+
}
68+
""".trimIndent()
1569
}
1670

1771
/**
@@ -23,7 +77,7 @@ class SamplingApiService(
2377
try {
2478
val variables = mapOf("organization_verbose_id" to JsonPrimitive(organizationVerboseId))
2579
val response = graphqlClient.execute(
26-
queryFileName = GET_SAMPLING_CONFIG_QUERY_FILE_PATH,
80+
query = GET_SAMPLING_CONFIG_QUERY,
2781
variables = variables,
2882
dataSerializer = SamplingResponse.serializer()
2983
)

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayApiService.kt

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,131 @@ class SessionReplayApiService(
2626
}
2727

2828
companion object {
29-
private const val INITIALIZE_REPLAY_SESSION_QUERY_FILE_PATH = "graphql/InitializeReplaySession.graphql"
30-
private const val IDENTIFY_REPLAY_SESSION_QUERY_FILE_PATH = "graphql/IdentifyReplaySession.graphql"
31-
private const val PUSH_PAYLOAD_QUERY_FILE_PATH = "graphql/PushPayload.graphql"
29+
private val INITIALIZE_REPLAY_SESSION_QUERY = """
30+
fragment MatchParts on MatchConfig {
31+
regexValue
32+
matchValue
33+
}
34+
35+
mutation initializeSession(
36+
${'$'}session_secure_id: String!
37+
${'$'}organization_verbose_id: String!
38+
${'$'}enable_strict_privacy: Boolean!
39+
${'$'}privacy_setting: String!
40+
${'$'}enable_recording_network_contents: Boolean!
41+
${'$'}clientVersion: String!
42+
${'$'}firstloadVersion: String!
43+
${'$'}clientConfig: String!
44+
${'$'}environment: String!
45+
${'$'}id: String!
46+
${'$'}appVersion: String
47+
${'$'}serviceName: String!
48+
${'$'}client_id: String!
49+
${'$'}network_recording_domains: [String!]
50+
) {
51+
initializeSession(
52+
session_secure_id: ${'$'}session_secure_id
53+
organization_verbose_id: ${'$'}organization_verbose_id
54+
enable_strict_privacy: ${'$'}enable_strict_privacy
55+
enable_recording_network_contents: ${'$'}enable_recording_network_contents
56+
clientVersion: ${'$'}clientVersion
57+
firstloadVersion: ${'$'}firstloadVersion
58+
clientConfig: ${'$'}clientConfig
59+
environment: ${'$'}environment
60+
appVersion: ${'$'}appVersion
61+
serviceName: ${'$'}serviceName
62+
fingerprint: ${'$'}id
63+
client_id: ${'$'}client_id
64+
network_recording_domains: ${'$'}network_recording_domains
65+
privacy_setting: ${'$'}privacy_setting
66+
) {
67+
secure_id
68+
project_id
69+
sampling {
70+
spans {
71+
name {
72+
...MatchParts
73+
}
74+
attributes {
75+
key {
76+
...MatchParts
77+
}
78+
attribute {
79+
...MatchParts
80+
}
81+
}
82+
events {
83+
name {
84+
...MatchParts
85+
}
86+
attributes {
87+
key {
88+
...MatchParts
89+
}
90+
attribute {
91+
...MatchParts
92+
}
93+
}
94+
}
95+
samplingRatio
96+
}
97+
logs {
98+
message {
99+
...MatchParts
100+
}
101+
severityText {
102+
...MatchParts
103+
}
104+
attributes {
105+
key {
106+
...MatchParts
107+
}
108+
attribute {
109+
...MatchParts
110+
}
111+
}
112+
samplingRatio
113+
}
114+
}
115+
}
116+
}
117+
""".trimIndent()
118+
119+
private val IDENTIFY_REPLAY_SESSION_QUERY = """
120+
mutation identifySession(
121+
${'$'}session_secure_id: String!
122+
${'$'}user_identifier: String!
123+
${'$'}user_object: Any
124+
) {
125+
identifySession(
126+
session_secure_id: ${'$'}session_secure_id
127+
user_identifier: ${'$'}user_identifier
128+
user_object: ${'$'}user_object
129+
)
130+
}
131+
""".trimIndent()
132+
133+
private val PUSH_PAYLOAD_QUERY = """
134+
mutation PushPayload(
135+
${'$'}session_secure_id: String!
136+
${'$'}payload_id: ID!
137+
${'$'}events: ReplayEventsInput!
138+
${'$'}messages: String!
139+
${'$'}resources: String!
140+
${'$'}web_socket_events: String!
141+
${'$'}errors: [ErrorObjectInput]!
142+
) {
143+
pushPayload(
144+
session_secure_id: ${'$'}session_secure_id
145+
payload_id: ${'$'}payload_id
146+
events: ${'$'}events
147+
messages: ${'$'}messages
148+
resources: ${'$'}resources
149+
web_socket_events: ${'$'}web_socket_events
150+
errors: ${'$'}errors
151+
)
152+
}
153+
""".trimIndent()
32154
}
33155

34156
/**
@@ -54,7 +176,7 @@ class SessionReplayApiService(
54176
"id" to JsonPrimitive("") // TODO: O11Y-631 - remove hardcoded params
55177
)
56178
val response = graphqlClient.execute(
57-
queryFileName = INITIALIZE_REPLAY_SESSION_QUERY_FILE_PATH,
179+
query = INITIALIZE_REPLAY_SESSION_QUERY,
58180
variables = variables,
59181
dataSerializer = InitializeReplaySessionResponse.serializer()
60182
)
@@ -78,7 +200,7 @@ class SessionReplayApiService(
78200
"user_object" to userObject
79201
)
80202
val response = graphqlClient.execute(
81-
queryFileName = IDENTIFY_REPLAY_SESSION_QUERY_FILE_PATH,
203+
query = IDENTIFY_REPLAY_SESSION_QUERY,
82204
variables = variables,
83205
dataSerializer = IdentifySessionResponse.serializer()
84206
)
@@ -124,7 +246,7 @@ class SessionReplayApiService(
124246
)
125247

126248
val response = graphqlClient.execute(
127-
queryFileName = PUSH_PAYLOAD_QUERY_FILE_PATH,
249+
query = PUSH_PAYLOAD_QUERY,
128250
variables = variables,
129251
dataSerializer = PushPayloadResponse.serializer()
130252
)

sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/GetSamplingConfigQuery.graphql

Lines changed: 0 additions & 53 deletions
This file was deleted.

sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/IdentifyReplaySession.graphql

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)