diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/GraphQLClient.kt b/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/GraphQLClient.kt index c3d1c0a969..8d52134645 100644 --- a/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/GraphQLClient.kt +++ b/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/GraphQLClient.kt @@ -67,20 +67,19 @@ class GraphQLClient( /** * Executes a GraphQL query - * @param queryFileName The .graphql file name + * @param query The GraphQL query string * @param variables Query variables * @param dataSerializer Kotlinx serialization serializer for the expected response data type * @return GraphQLResponse containing either the deserialized data or error information */ suspend fun execute( - queryFileName: String, + query: String, variables: Map = emptyMap(), dataSerializer: KSerializer, compress: Boolean = true ): GraphQLResponse = withContext(DispatcherProviderHolder.current.io) { var connection: HttpURLConnection? = null val response: GraphQLResponse = try { - val query = loadQuery(queryFileName) val request = GraphQLRequest( query = query, variables = variables @@ -143,17 +142,6 @@ class GraphQLClient( response } - /** - * Loads GraphQL query from resources - * @param queryFilepath The .graphql file path - * @return Query string - */ - private fun loadQuery(queryFilepath: String): String { - return this::class.java.classLoader?.getResourceAsStream(queryFilepath)?.bufferedReader()?.use { - it.readText() - } ?: throw IllegalStateException("Could not load GraphQL query file: $queryFilepath") - } - private fun gzip(data: ByteArray): ByteArray { val byteStream = ByteArrayOutputStream() GZIPOutputStream(byteStream).use { gzipStream -> diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/SamplingApiService.kt b/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/SamplingApiService.kt index ac9c55d2ca..1cbd0a8c30 100644 --- a/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/SamplingApiService.kt +++ b/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/network/SamplingApiService.kt @@ -11,7 +11,61 @@ class SamplingApiService( ) { companion object { - private const val GET_SAMPLING_CONFIG_QUERY_FILE_PATH = "graphql/GetSamplingConfigQuery.graphql" + private val GET_SAMPLING_CONFIG_QUERY = """ + fragment MatchParts on MatchConfig { + regexValue + matchValue + } + + query GetSamplingConfig(${'$'}organization_verbose_id: String!) { + sampling(organization_verbose_id: ${'$'}organization_verbose_id) { + spans { + name { + ...MatchParts + } + attributes { + key { + ...MatchParts + } + attribute { + ...MatchParts + } + } + events { + name { + ...MatchParts + } + attributes { + key { + ...MatchParts + } + attribute { + ...MatchParts + } + } + } + samplingRatio + } + logs { + message { + ...MatchParts + } + severityText { + ...MatchParts + } + attributes { + key { + ...MatchParts + } + attribute { + ...MatchParts + } + } + samplingRatio + } + } + } + """.trimIndent() } /** @@ -23,7 +77,7 @@ class SamplingApiService( try { val variables = mapOf("organization_verbose_id" to JsonPrimitive(organizationVerboseId)) val response = graphqlClient.execute( - queryFileName = GET_SAMPLING_CONFIG_QUERY_FILE_PATH, + query = GET_SAMPLING_CONFIG_QUERY, variables = variables, dataSerializer = SamplingResponse.serializer() ) diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayApiService.kt b/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayApiService.kt index 4e10fef841..161adc04b4 100644 --- a/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayApiService.kt +++ b/sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/exporter/SessionReplayApiService.kt @@ -26,9 +26,131 @@ class SessionReplayApiService( } companion object { - private const val INITIALIZE_REPLAY_SESSION_QUERY_FILE_PATH = "graphql/InitializeReplaySession.graphql" - private const val IDENTIFY_REPLAY_SESSION_QUERY_FILE_PATH = "graphql/IdentifyReplaySession.graphql" - private const val PUSH_PAYLOAD_QUERY_FILE_PATH = "graphql/PushPayload.graphql" + private val INITIALIZE_REPLAY_SESSION_QUERY = """ + fragment MatchParts on MatchConfig { + regexValue + matchValue + } + + mutation initializeSession( + ${'$'}session_secure_id: String! + ${'$'}organization_verbose_id: String! + ${'$'}enable_strict_privacy: Boolean! + ${'$'}privacy_setting: String! + ${'$'}enable_recording_network_contents: Boolean! + ${'$'}clientVersion: String! + ${'$'}firstloadVersion: String! + ${'$'}clientConfig: String! + ${'$'}environment: String! + ${'$'}id: String! + ${'$'}appVersion: String + ${'$'}serviceName: String! + ${'$'}client_id: String! + ${'$'}network_recording_domains: [String!] + ) { + initializeSession( + session_secure_id: ${'$'}session_secure_id + organization_verbose_id: ${'$'}organization_verbose_id + enable_strict_privacy: ${'$'}enable_strict_privacy + enable_recording_network_contents: ${'$'}enable_recording_network_contents + clientVersion: ${'$'}clientVersion + firstloadVersion: ${'$'}firstloadVersion + clientConfig: ${'$'}clientConfig + environment: ${'$'}environment + appVersion: ${'$'}appVersion + serviceName: ${'$'}serviceName + fingerprint: ${'$'}id + client_id: ${'$'}client_id + network_recording_domains: ${'$'}network_recording_domains + privacy_setting: ${'$'}privacy_setting + ) { + secure_id + project_id + sampling { + spans { + name { + ...MatchParts + } + attributes { + key { + ...MatchParts + } + attribute { + ...MatchParts + } + } + events { + name { + ...MatchParts + } + attributes { + key { + ...MatchParts + } + attribute { + ...MatchParts + } + } + } + samplingRatio + } + logs { + message { + ...MatchParts + } + severityText { + ...MatchParts + } + attributes { + key { + ...MatchParts + } + attribute { + ...MatchParts + } + } + samplingRatio + } + } + } + } + """.trimIndent() + + private val IDENTIFY_REPLAY_SESSION_QUERY = """ + mutation identifySession( + ${'$'}session_secure_id: String! + ${'$'}user_identifier: String! + ${'$'}user_object: Any + ) { + identifySession( + session_secure_id: ${'$'}session_secure_id + user_identifier: ${'$'}user_identifier + user_object: ${'$'}user_object + ) + } + """.trimIndent() + + private val PUSH_PAYLOAD_QUERY = """ + mutation PushPayload( + ${'$'}session_secure_id: String! + ${'$'}payload_id: ID! + ${'$'}events: ReplayEventsInput! + ${'$'}messages: String! + ${'$'}resources: String! + ${'$'}web_socket_events: String! + ${'$'}errors: [ErrorObjectInput]! + ) { + pushPayload( + session_secure_id: ${'$'}session_secure_id + payload_id: ${'$'}payload_id + events: ${'$'}events + messages: ${'$'}messages + resources: ${'$'}resources + web_socket_events: ${'$'}web_socket_events + errors: ${'$'}errors + ) + } + """.trimIndent() } /** @@ -54,7 +176,7 @@ class SessionReplayApiService( "id" to JsonPrimitive("") // TODO: O11Y-631 - remove hardcoded params ) val response = graphqlClient.execute( - queryFileName = INITIALIZE_REPLAY_SESSION_QUERY_FILE_PATH, + query = INITIALIZE_REPLAY_SESSION_QUERY, variables = variables, dataSerializer = InitializeReplaySessionResponse.serializer() ) @@ -78,7 +200,7 @@ class SessionReplayApiService( "user_object" to userObject ) val response = graphqlClient.execute( - queryFileName = IDENTIFY_REPLAY_SESSION_QUERY_FILE_PATH, + query = IDENTIFY_REPLAY_SESSION_QUERY, variables = variables, dataSerializer = IdentifySessionResponse.serializer() ) @@ -124,7 +246,7 @@ class SessionReplayApiService( ) val response = graphqlClient.execute( - queryFileName = PUSH_PAYLOAD_QUERY_FILE_PATH, + query = PUSH_PAYLOAD_QUERY, variables = variables, dataSerializer = PushPayloadResponse.serializer() ) diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/GetSamplingConfigQuery.graphql b/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/GetSamplingConfigQuery.graphql deleted file mode 100644 index 5d5d7ac053..0000000000 --- a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/GetSamplingConfigQuery.graphql +++ /dev/null @@ -1,53 +0,0 @@ -fragment MatchParts on MatchConfig { - regexValue - matchValue -} - -query GetSamplingConfig($organization_verbose_id: String!) { - sampling(organization_verbose_id: $organization_verbose_id) { - spans { - name { - ...MatchParts - } - attributes { - key { - ...MatchParts - } - attribute { - ...MatchParts - } - } - events { - name { - ...MatchParts - } - attributes { - key { - ...MatchParts - } - attribute { - ...MatchParts - } - } - } - samplingRatio - } - logs { - message { - ...MatchParts - } - severityText { - ...MatchParts - } - attributes { - key { - ...MatchParts - } - attribute { - ...MatchParts - } - } - samplingRatio - } - } -} diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/IdentifyReplaySession.graphql b/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/IdentifyReplaySession.graphql deleted file mode 100644 index a3ac9c2618..0000000000 --- a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/IdentifyReplaySession.graphql +++ /dev/null @@ -1,11 +0,0 @@ -mutation identifySession( - $session_secure_id: String! - $user_identifier: String! - $user_object: Any -) { - identifySession( - session_secure_id: $session_secure_id - user_identifier: $user_identifier - user_object: $user_object - ) -} diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/InitializeReplaySession.graphql b/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/InitializeReplaySession.graphql deleted file mode 100644 index b0274b197a..0000000000 --- a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/InitializeReplaySession.graphql +++ /dev/null @@ -1,87 +0,0 @@ -fragment MatchParts on MatchConfig { - regexValue - matchValue -} - -mutation initializeSession( - $session_secure_id: String! - $organization_verbose_id: String! - $enable_strict_privacy: Boolean! - $privacy_setting: String! - $enable_recording_network_contents: Boolean! - $clientVersion: String! - $firstloadVersion: String! - $clientConfig: String! - $environment: String! - $id: String! - $appVersion: String - $serviceName: String! - $client_id: String! - $network_recording_domains: [String!] -) { - initializeSession( - session_secure_id: $session_secure_id - organization_verbose_id: $organization_verbose_id - enable_strict_privacy: $enable_strict_privacy - enable_recording_network_contents: $enable_recording_network_contents - clientVersion: $clientVersion - firstloadVersion: $firstloadVersion - clientConfig: $clientConfig - environment: $environment - appVersion: $appVersion - serviceName: $serviceName - fingerprint: $id - client_id: $client_id - network_recording_domains: $network_recording_domains - privacy_setting: $privacy_setting - ) { - secure_id - project_id - sampling { - spans { - name { - ...MatchParts - } - attributes { - key { - ...MatchParts - } - attribute { - ...MatchParts - } - } - events { - name { - ...MatchParts - } - attributes { - key { - ...MatchParts - } - attribute { - ...MatchParts - } - } - } - samplingRatio - } - logs { - message { - ...MatchParts - } - severityText { - ...MatchParts - } - attributes { - key { - ...MatchParts - } - attribute { - ...MatchParts - } - } - samplingRatio - } - } - } -} diff --git a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/PushPayload.graphql b/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/PushPayload.graphql deleted file mode 100644 index 08a0039f10..0000000000 --- a/sdk/@launchdarkly/observability-android/lib/src/main/resources/graphql/PushPayload.graphql +++ /dev/null @@ -1,19 +0,0 @@ -mutation PushPayload( - $session_secure_id: String! - $payload_id: ID! - $events: ReplayEventsInput! - $messages: String! - $resources: String! - $web_socket_events: String! - $errors: [ErrorObjectInput]! -) { - pushPayload( - session_secure_id: $session_secure_id - payload_id: $payload_id - events: $events - messages: $messages - resources: $resources - web_socket_events: $web_socket_events - errors: $errors - ) -} diff --git a/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/GraphQLClientTest.kt b/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/GraphQLClientTest.kt index 1069397c57..72cb99ef4e 100644 --- a/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/GraphQLClientTest.kt +++ b/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/GraphQLClientTest.kt @@ -9,7 +9,6 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonPrimitive import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertNotNull @@ -23,6 +22,12 @@ class GraphQLClientTest { @Serializable data class TestData(val id: String, val name: String) + private val testQuery = """ + query EmptyQuery { + fakeField + } + """.trimIndent() + private lateinit var graphQLClient: GraphQLClient private lateinit var mockConnection: HttpURLConnection private var openConnectionWasCalled: Pair = Pair(false, "url") @@ -51,19 +56,6 @@ class GraphQLClientTest { unmockkAll() } - @Test - fun `execute should handle missing GraphQL query file`() = runTest { - val result = graphQLClient.execute( - queryFileName = "nonexistent-query.graphql", - dataSerializer = TestData.serializer() - ) - - assertNull(result.data) - assertNotNull(result.errors) - assertEquals(1, result.errors.size) - assertTrue(result.errors.first().message.contains("Could not load GraphQL query file")) - } - @Test fun `execute should make successful GraphQL request and return a valid response`() = runTest { val responseJson = """{"data": {"id": "123", "name": "John Doe"}}""" @@ -71,7 +63,7 @@ class GraphQLClientTest { every { mockConnection.inputStream } returns ByteArrayInputStream(responseJson.toByteArray()) val result = graphQLClient.execute( - queryFileName = "test-query.graphql", + query = testQuery, variables = mapOf("test_variable" to JsonPrimitive("567")), dataSerializer = TestData.serializer() ) @@ -91,7 +83,7 @@ class GraphQLClientTest { every { mockConnection.inputStream } returns ByteArrayInputStream(responseJson.toByteArray()) val result = graphQLClient.execute( - queryFileName = "test-query.graphql", + query = testQuery, dataSerializer = TestData.serializer() ) @@ -112,7 +104,7 @@ class GraphQLClientTest { every { mockConnection.errorStream } returns ByteArrayInputStream(errorResponse.toByteArray()) val result = graphQLClient.execute( - queryFileName = "test-query.graphql", + query = testQuery, dataSerializer = TestData.serializer() ) @@ -127,7 +119,7 @@ class GraphQLClientTest { every { mockConnection.errorStream } returns null val result = graphQLClient.execute( - queryFileName = "test-query.graphql", + query = testQuery, dataSerializer = TestData.serializer() ) @@ -142,7 +134,7 @@ class GraphQLClientTest { every { mockConnection.outputStream } throws IOException("Connection failed") val result = graphQLClient.execute( - queryFileName = "test-query.graphql", + query = testQuery, dataSerializer = TestData.serializer() ) @@ -159,7 +151,7 @@ class GraphQLClientTest { every { mockConnection.inputStream } returns ByteArrayInputStream(responseJson.toByteArray()) graphQLClient.execute( - queryFileName = "test-query.graphql", + query = testQuery, dataSerializer = TestData.serializer() ) diff --git a/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/SamplingApiServiceTest.kt b/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/SamplingApiServiceTest.kt index 4e3f24ff0b..4f193bef37 100644 --- a/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/SamplingApiServiceTest.kt +++ b/sdk/@launchdarkly/observability-android/lib/src/test/kotlin/com/launchdarkly/observability/network/SamplingApiServiceTest.kt @@ -54,7 +54,7 @@ class SamplingApiServiceTest { coEvery { mockGraphqlClient.execute( - "graphql/GetSamplingConfigQuery.graphql", + any(), mapOf("organization_verbose_id" to JsonPrimitive(organizationId)), SamplingResponse.serializer() ) @@ -67,7 +67,7 @@ class SamplingApiServiceTest { coVerify(exactly = 1) { mockGraphqlClient.execute( - "graphql/GetSamplingConfigQuery.graphql", + any(), mapOf("organization_verbose_id" to JsonPrimitive(organizationId)), SamplingResponse.serializer() ) diff --git a/sdk/@launchdarkly/observability-android/lib/src/test/resources/test-query.graphql b/sdk/@launchdarkly/observability-android/lib/src/test/resources/test-query.graphql deleted file mode 100644 index 06bf9c8fd2..0000000000 --- a/sdk/@launchdarkly/observability-android/lib/src/test/resources/test-query.graphql +++ /dev/null @@ -1,4 +0,0 @@ -# Fake graphql query file used in GraphQLClientTest -query EmptyQuery { - fakeField -}