From 42c032915c630c936251751e63f4d21056057ef3 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 20 Oct 2024 02:33:13 +0300 Subject: [PATCH 1/7] added graphql examples --- .../functions/network/DemoNetworkFragment.kt | 4 ++ .../network/internal/okhttp/ApiService.kt | 3 + .../internal/okhttp/OkhttpViewModel.kt | 57 +++++++++++++++ .../main/res/layout/fragment_container.xml | 2 +- .../main/res/layout/fragment_demo_network.xml | 70 +++++++++++++++++++ 5 files changed, 135 insertions(+), 1 deletion(-) diff --git a/sample/src/main/java/com/sampleapp/functions/network/DemoNetworkFragment.kt b/sample/src/main/java/com/sampleapp/functions/network/DemoNetworkFragment.kt index e0825e9f4..be36d2a6f 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/DemoNetworkFragment.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/DemoNetworkFragment.kt @@ -34,6 +34,10 @@ class DemoNetworkFragment : Fragment(R.layout.fragment_demo_network) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding.graphqlQuery.setOnClickListener { okhttpViewModel.graphqlQuery() } + binding.graphqlQueryError.setOnClickListener { okhttpViewModel.graphqlQueryError() } + binding.graphqlMutation.setOnClickListener { okhttpViewModel.graphqlMutation() } + binding.graphqlMutationError.setOnClickListener { okhttpViewModel.graphqlMutationError() } binding.postCall.setOnClickListener { okhttpViewModel.post() } binding.getCall.setOnClickListener { okhttpViewModel.get() } binding.getCallKtor.setOnClickListener { ktorViewModel.get() } diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt index cc7e62580..751b401a0 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt @@ -30,4 +30,7 @@ interface ApiService { ) @POST("xml") suspend fun xml(@Body hashMapOf: RequestBody): Any + + @POST("https://spacex-production.up.railway.app/") + suspend fun graphql(@Body body: Any): Any } diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt index f6a0584f5..d76f88173 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt @@ -25,6 +25,63 @@ class OkhttpViewModel : ViewModel() { } } + fun graphqlQuery() { + viewModelScope.launch { + // todo, better mock requests + enqueue { + apiService.graphql( + mapOf( + "query" to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", + "variables" to mapOf("limit" to 3), + "operationName" to "Launches", + ) + ) + } + } + } + + fun graphqlQueryError() { + viewModelScope.launch { + enqueue { + apiService.graphql( + mapOf( + "query" to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", + "variables" to mapOf("limit" to -1111), + "operationName" to "Launches", + ) + ) + } + } + } + + fun graphqlMutation() { + viewModelScope.launch { + enqueue { + apiService.graphql( + mapOf( + "query" to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users(objects: \$objects) {affected_rows}}", + "variables" to mapOf("objects" to emptyList()), + "operationName" to "Insert_users", + ) + ) + } + } + } + + fun graphqlMutationError() { + viewModelScope.launch { + enqueue { + apiService.graphql( + mapOf( + "query" to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users112231321(objects: \$objects) {affected_rows}}", + "variables" to mapOf("objects" to emptyList()), + "operationName" to "Insert_users", + ) + ) + } + } + } + fun post() { val label = "POST call" viewModelScope.launch { diff --git a/sample/src/main/res/layout/fragment_container.xml b/sample/src/main/res/layout/fragment_container.xml index e9de71c64..e1c135205 100644 --- a/sample/src/main/res/layout/fragment_container.xml +++ b/sample/src/main/res/layout/fragment_container.xml @@ -14,7 +14,7 @@ + + + + + + + + + \ No newline at end of file From b10f9bbebbeea4c31c8fbcb22c018f1aa5e6c243 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 20 Oct 2024 04:53:12 +0300 Subject: [PATCH 2/7] show graphql query names. --- .../plugins/network/intercept/NetworkData.kt | 50 ++++++++++++++++-- .../interceptor/ui/list/ApiItemHolder.kt | 10 +++- .../drawable/pluto_network___ic_graphql.xml | 51 +++++++++++++++++++ .../layout/pluto_network___item_network.xml | 9 ++++ .../plugins/network/ktor/PlutoKtorHelper.kt | 5 +- .../ktor/internal/KtorResponseConverter.kt | 6 ++- .../ktor/internal/ResponseConverter.kt | 3 +- .../network/okhttp/PlutoOkhttpInterceptor.kt | 11 ++-- .../network/okhttp/internal/DataConvertor.kt | 3 +- .../internal/ResponseReportingSinkCallback.kt | 3 +- .../internal/custom/CustomViewModel.kt | 22 ++++---- .../internal/okhttp/OkhttpViewModel.kt | 6 +-- 12 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 pluto-plugins/plugins/network/core/lib/src/main/res/drawable/pluto_network___ic_graphql.xml diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt index d9572b050..071a1be2f 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt @@ -3,6 +3,7 @@ package com.pluto.plugins.network.intercept import com.pluto.plugins.network.internal.Status import com.pluto.plugins.network.internal.interceptor.logic.mapCode2Message import io.ktor.http.ContentType +import org.json.JSONObject class NetworkData { @@ -11,32 +12,70 @@ class NetworkData { val method: String, val body: Body?, val headers: Map, - val sentTimestamp: Long + val sentTimestamp: Long, ) { + data class GraphqlData( + val queryType: String, + val queryName: String, + ) + + val graphqlData: GraphqlData? = parseGraphqlData() + + private fun parseGraphqlData(): GraphqlData? { + if (method != "POST" || + body == null || + body.isBinary || + !body.body.startsWith("{") + ) return null + val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return null + val query = json.optString("query") ?: return null + val match = graqphlQueryRegex.find(query)?.groupValues ?: return null + return GraphqlData( + queryType = match[1], + queryName = match[2], + ) + } + internal val isGzipped: Boolean get() = headers["Content-Encoding"].equals("gzip", ignoreCase = true) } data class Response( + val request: Request, private val statusCode: Int, val body: Body?, val headers: Map, val sentTimestamp: Long, val receiveTimestamp: Long, val protocol: String = "", - val fromDiskCache: Boolean = false + val fromDiskCache: Boolean = false, ) { + val hasGraphqlErrors = parseHasGraphqlError() + internal val status: Status - get() = Status(statusCode, mapCode2Message(statusCode)) + get() = Status(statusCode, getStatusMessage()) val isSuccessful: Boolean - get() = statusCode in 200..299 + get() = statusCode in 200..299 && !hasGraphqlErrors internal val isGzipped: Boolean get() = headers["Content-Encoding"].equals("gzip", ignoreCase = true) + + private fun getStatusMessage() = mapCode2Message(statusCode) + + if (hasGraphqlErrors) ", Response with errors" else "" + + private fun parseHasGraphqlError(): Boolean { + if (request.graphqlData == null || + body == null || + body.isBinary || + !body.body.startsWith("{") + ) return false + val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return false + return json.has("errors") + } } data class Body( val body: CharSequence, - val contentType: String + val contentType: String, ) { private val contentTypeInternal: ContentType = ContentType.parse(contentType) private val mediaType: String = contentTypeInternal.contentType @@ -48,5 +87,6 @@ class NetworkData { companion object { internal val BINARY_MEDIA_TYPES = listOf("audio", "video", "image", "font") + private val graqphlQueryRegex = Regex("""\b(query|mutation)\s+(\w+)""") } } diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt index c9dea7ecb..5ac960f7e 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt @@ -4,6 +4,7 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup +import androidx.core.view.isVisible import com.pluto.plugins.network.R import com.pluto.plugins.network.databinding.PlutoNetworkItemNetworkBinding import com.pluto.plugins.network.intercept.NetworkData.Response @@ -30,6 +31,7 @@ internal class ApiItemHolder(parent: ViewGroup, actionListener: DiffAwareAdapter private val error = binding.error private val timeElapsed = binding.timeElapsed private val proxyIndicator = binding.proxyIndicator + private val graphqlIcon = binding.graphqlIcon override fun onBind(item: ListItem) { if (item is ApiCallData) { @@ -37,9 +39,13 @@ internal class ApiItemHolder(parent: ViewGroup, actionListener: DiffAwareAdapter timeElapsed.text = item.request.sentTimestamp.asTimeElapsed() binding.root.setBackgroundColor(context.color(R.color.pluto___transparent)) + val method = (item.request.graphqlData?.queryType ?: item.request.method).uppercase() + val urlOrQuery = item.request.graphqlData?.queryName ?: Url(item.request.url).encodedPath + graphqlIcon.isVisible = item.request.graphqlData != null + url.setSpan { - append(fontColor(item.request.method.uppercase(), context.color(R.color.pluto___text_dark_60))) - append(" ${Url(item.request.url).encodedPath}") + append(fontColor(method, context.color(R.color.pluto___text_dark_60))) + append(" ${urlOrQuery}") } progress.visibility = VISIBLE status.visibility = INVISIBLE diff --git a/pluto-plugins/plugins/network/core/lib/src/main/res/drawable/pluto_network___ic_graphql.xml b/pluto-plugins/plugins/network/core/lib/src/main/res/drawable/pluto_network___ic_graphql.xml new file mode 100644 index 000000000..e1f692542 --- /dev/null +++ b/pluto-plugins/plugins/network/core/lib/src/main/res/drawable/pluto_network___ic_graphql.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml index 8f3bc2bfe..f7c87dd80 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml +++ b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml @@ -71,6 +71,15 @@ android:layout_marginEnd="@dimen/pluto___margin_small" android:layout_marginRight="@dimen/pluto___margin_small" android:src="@drawable/pluto_network___ic_proxy_indicator" + app:layout_constraintEnd_toStartOf="@id/graphqlIcon" + app:layout_constraintTop_toTopOf="@+id/url" /> + + diff --git a/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/PlutoKtorHelper.kt b/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/PlutoKtorHelper.kt index 542a995e3..a8e5c42b0 100644 --- a/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/PlutoKtorHelper.kt +++ b/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/PlutoKtorHelper.kt @@ -19,7 +19,8 @@ private val saveAttributeKey = AttributeKey("ResponseBodySaved") fun HttpClient.addPlutoKtorInterceptor() { plugin(HttpSend).intercept { requestUnBuilt -> val request = requestUnBuilt.build() - val networkInterceptor = NetworkInterceptor.intercept(request.convert(), NetworkInterceptor.Option(NAME)) + val convertedRequest = request.convert() + val networkInterceptor = NetworkInterceptor.intercept(convertedRequest, NetworkInterceptor.Option(NAME)) val callResult = try { requestUnBuilt.url(networkInterceptor.actualOrMockRequestUrl) execute(requestUnBuilt) @@ -34,7 +35,7 @@ fun HttpClient.addPlutoKtorInterceptor() { newCall.attributes.put(saveAttributeKey, Unit) newCall } - networkInterceptor.onResponse(res.response.convert()) + networkInterceptor.onResponse(res.response.convert(convertedRequest)) res } } diff --git a/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/KtorResponseConverter.kt b/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/KtorResponseConverter.kt index 888e1b05b..b201e74d9 100644 --- a/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/KtorResponseConverter.kt +++ b/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/KtorResponseConverter.kt @@ -1,5 +1,6 @@ package com.pluto.plugins.network.ktor.internal +import com.pluto.plugins.network.intercept.NetworkData import com.pluto.plugins.network.intercept.NetworkData.Body import com.pluto.plugins.network.intercept.NetworkData.Response import io.ktor.client.statement.HttpResponse @@ -9,15 +10,16 @@ import io.ktor.http.Headers import io.ktor.http.contentType internal object KtorResponseConverter : ResponseConverter { - override suspend fun HttpResponse.convert(): Response { + override suspend fun HttpResponse.convert(request: NetworkData.Request): Response { return Response( + request = request, statusCode = status.value, body = extractBody(), protocol = version.name, fromDiskCache = false, headers = headersMap(headers), sentTimestamp = requestTime.timestamp, - receiveTimestamp = responseTime.timestamp + receiveTimestamp = responseTime.timestamp, ) } diff --git a/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/ResponseConverter.kt b/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/ResponseConverter.kt index a6ec250dc..d40bdf41f 100644 --- a/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/ResponseConverter.kt +++ b/pluto-plugins/plugins/network/interceptor-ktor/lib/src/main/kotlin/com/pluto/plugins/network/ktor/internal/ResponseConverter.kt @@ -1,7 +1,8 @@ package com.pluto.plugins.network.ktor.internal +import com.pluto.plugins.network.intercept.NetworkData import com.pluto.plugins.network.intercept.NetworkData.Response internal interface ResponseConverter { - suspend fun T.convert(): Response + suspend fun T.convert(request: NetworkData.Request): Response } diff --git a/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/PlutoOkhttpInterceptor.kt b/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/PlutoOkhttpInterceptor.kt index fd85306dd..4b709c360 100644 --- a/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/PlutoOkhttpInterceptor.kt +++ b/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/PlutoOkhttpInterceptor.kt @@ -24,7 +24,8 @@ class PlutoOkhttpInterceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() - val networkInterceptor = NetworkInterceptor.intercept(request.convert(), NetworkInterceptor.Option(NAME)) + val convertedRequest = request.convert() + val networkInterceptor = NetworkInterceptor.intercept(convertedRequest, NetworkInterceptor.Option(NAME)) val response: Response = try { val builder = request.newBuilder().url(networkInterceptor.actualOrMockRequestUrl) chain.proceed(builder.build()) @@ -32,18 +33,18 @@ class PlutoOkhttpInterceptor { networkInterceptor.onError(e) throw e } - return response.processBody { networkInterceptor.onResponse(it) } + return response.processBody(convertedRequest) { networkInterceptor.onResponse(it) } } } } -private fun Response.processBody(onComplete: (NetworkData.Response) -> Unit): Response { +private fun Response.processBody(request: NetworkData.Request, onComplete: (NetworkData.Response) -> Unit): Response { if (!hasBody()) { - onComplete.invoke(convert(null)) + onComplete.invoke(convert(request, null)) return this } val responseBody: ResponseBody = body as ResponseBody - val sideStream = ReportingSink(PlutoInterface.files.createFile(), ResponseReportingSinkCallback(this, onComplete)) + val sideStream = ReportingSink(PlutoInterface.files.createFile(), ResponseReportingSinkCallback(this, request, onComplete)) val processedResponseBody: ResponseBody = DepletingSource(TeeSource(responseBody.source(), sideStream)) .buffer() .asResponseBody(responseBody) diff --git a/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/DataConvertor.kt b/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/DataConvertor.kt index da21cafca..387405126 100644 --- a/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/DataConvertor.kt +++ b/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/DataConvertor.kt @@ -36,8 +36,9 @@ internal fun Request.headerMap(contentLength: Long): Map { return map } -internal fun Response.convert(body: NetworkData.Body?): NetworkData.Response { +internal fun Response.convert(request: NetworkData.Request, body: NetworkData.Body?): NetworkData.Response { return NetworkData.Response( + request = request, statusCode = code, body = body, protocol = protocol.name, diff --git a/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/ResponseReportingSinkCallback.kt b/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/ResponseReportingSinkCallback.kt index e94648bad..cb2f3021e 100644 --- a/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/ResponseReportingSinkCallback.kt +++ b/pluto-plugins/plugins/network/interceptor-okhttp/lib/src/main/kotlin/com/pluto/plugins/network/okhttp/internal/ResponseReportingSinkCallback.kt @@ -12,6 +12,7 @@ import java.io.IOException class ResponseReportingSinkCallback( private val response: Response, + private val request: NetworkData.Request, private val onComplete: (NetworkData.Response) -> Unit ) : ReportingSink.Callback { @@ -20,7 +21,7 @@ class ResponseReportingSinkCallback( readResponseBuffer(f, response.isGzipped)?.let { val responseBody = response.body ?: return val body = responseBody.processBody(it) - onComplete.invoke(response.convert(body)) + onComplete.invoke(response.convert(request, body)) } f.delete() } diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/custom/CustomViewModel.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/custom/CustomViewModel.kt index 24eee482a..dc8ece7a2 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/custom/CustomViewModel.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/custom/CustomViewModel.kt @@ -12,21 +12,23 @@ class CustomViewModel : ViewModel() { @SuppressWarnings("MagicNumber") fun customTrace() { viewModelScope.launch { + val request = NetworkData.Request( + url = "https://google.com", + method = "GET", + body = NetworkData.Body( + body = "{\"message\": \"body\"}", + contentType = "application/json", + ), + headers = emptyMap(), + sentTimestamp = System.currentTimeMillis() + ) val networkInterceptor = NetworkInterceptor.intercept( - NetworkData.Request( - url = "https://google.com", - method = "GET", - body = NetworkData.Body( - body = "{\"message\": \"body\"}", - contentType = "application/json", - ), - headers = emptyMap(), - sentTimestamp = System.currentTimeMillis() - ) + request, ) delay(5_000) networkInterceptor.onResponse( NetworkData.Response( + request = request, statusCode = 503, body = NetworkData.Body( body = "body", diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt index d76f88173..404a31535 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt @@ -27,13 +27,12 @@ class OkhttpViewModel : ViewModel() { fun graphqlQuery() { viewModelScope.launch { - // todo, better mock requests + // todo, better mock responses enqueue { apiService.graphql( mapOf( "query" to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", "variables" to mapOf("limit" to 3), - "operationName" to "Launches", ) ) } @@ -47,7 +46,6 @@ class OkhttpViewModel : ViewModel() { mapOf( "query" to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", "variables" to mapOf("limit" to -1111), - "operationName" to "Launches", ) ) } @@ -61,7 +59,6 @@ class OkhttpViewModel : ViewModel() { mapOf( "query" to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users(objects: \$objects) {affected_rows}}", "variables" to mapOf("objects" to emptyList()), - "operationName" to "Insert_users", ) ) } @@ -75,7 +72,6 @@ class OkhttpViewModel : ViewModel() { mapOf( "query" to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users112231321(objects: \$objects) {affected_rows}}", "variables" to mapOf("objects" to emptyList()), - "operationName" to "Insert_users", ) ) } From 4e53181baffa488d2af8f923a0afdc00d5e12d66 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 20 Oct 2024 04:55:03 +0300 Subject: [PATCH 3/7] added variables --- .../com/pluto/plugins/network/intercept/NetworkData.kt | 9 +++++++-- .../internal/interceptor/ui/list/ApiItemHolder.kt | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt index 071a1be2f..3eae4cb7a 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt @@ -16,7 +16,7 @@ class NetworkData { ) { data class GraphqlData( val queryType: String, - val queryName: String, + val queryNameWithVariables: String, ) val graphqlData: GraphqlData? = parseGraphqlData() @@ -29,13 +29,18 @@ class NetworkData { ) return null val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return null val query = json.optString("query") ?: return null + val variables = json.optJSONObject("variables") val match = graqphlQueryRegex.find(query)?.groupValues ?: return null return GraphqlData( queryType = match[1], - queryName = match[2], + queryNameWithVariables = match[2] + variables?.formatVariables().orEmpty(), ) } + private fun JSONObject.formatVariables() = + keys().asSequence().joinToString { "$it: ${get(it)}" }.let { " ($it)" } + + internal val isGzipped: Boolean get() = headers["Content-Encoding"].equals("gzip", ignoreCase = true) } diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt index 5ac960f7e..9337d7317 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt @@ -40,7 +40,7 @@ internal class ApiItemHolder(parent: ViewGroup, actionListener: DiffAwareAdapter binding.root.setBackgroundColor(context.color(R.color.pluto___transparent)) val method = (item.request.graphqlData?.queryType ?: item.request.method).uppercase() - val urlOrQuery = item.request.graphqlData?.queryName ?: Url(item.request.url).encodedPath + val urlOrQuery = item.request.graphqlData?.queryNameWithVariables ?: Url(item.request.url).encodedPath graphqlIcon.isVisible = item.request.graphqlData != null url.setSpan { From ba09369c7866b70d0b266ffa62a4b30aee4ab5d8 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 3 Nov 2024 22:38:28 +0400 Subject: [PATCH 4/7] Revert "added variables" This reverts commit 4e53181baffa488d2af8f923a0afdc00d5e12d66. --- .../com/pluto/plugins/network/intercept/NetworkData.kt | 9 ++------- .../internal/interceptor/ui/list/ApiItemHolder.kt | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt index 3eae4cb7a..071a1be2f 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt @@ -16,7 +16,7 @@ class NetworkData { ) { data class GraphqlData( val queryType: String, - val queryNameWithVariables: String, + val queryName: String, ) val graphqlData: GraphqlData? = parseGraphqlData() @@ -29,18 +29,13 @@ class NetworkData { ) return null val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return null val query = json.optString("query") ?: return null - val variables = json.optJSONObject("variables") val match = graqphlQueryRegex.find(query)?.groupValues ?: return null return GraphqlData( queryType = match[1], - queryNameWithVariables = match[2] + variables?.formatVariables().orEmpty(), + queryName = match[2], ) } - private fun JSONObject.formatVariables() = - keys().asSequence().joinToString { "$it: ${get(it)}" }.let { " ($it)" } - - internal val isGzipped: Boolean get() = headers["Content-Encoding"].equals("gzip", ignoreCase = true) } diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt index 9337d7317..5ac960f7e 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt @@ -40,7 +40,7 @@ internal class ApiItemHolder(parent: ViewGroup, actionListener: DiffAwareAdapter binding.root.setBackgroundColor(context.color(R.color.pluto___transparent)) val method = (item.request.graphqlData?.queryType ?: item.request.method).uppercase() - val urlOrQuery = item.request.graphqlData?.queryNameWithVariables ?: Url(item.request.url).encodedPath + val urlOrQuery = item.request.graphqlData?.queryName ?: Url(item.request.url).encodedPath graphqlIcon.isVisible = item.request.graphqlData != null url.setSpan { From 583bc9bdc3b1adbe3318f389d95887b2183b0230 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 3 Nov 2024 23:07:13 +0400 Subject: [PATCH 5/7] better icon --- .../layout/pluto_network___item_network.xml | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml index f7c87dd80..eb626f644 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml +++ b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml @@ -51,18 +51,17 @@ android:id="@+id/url" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/pluto___margin_small" + android:layout_marginStart="@dimen/pluto___margin_mini" + app:layout_goneMarginStart="@dimen/pluto___margin_small" android:layout_marginTop="@dimen/pluto___margin_medium" - android:layout_marginLeft="@dimen/pluto___margin_small" android:fontFamily="@font/muli_semibold" android:textColor="@color/pluto___text_dark" android:textSize="@dimen/pluto___text_small" android:layout_marginEnd="@dimen/pluto___margin_mini" - app:layout_constraintStart_toEndOf="@+id/status" + app:layout_constraintStart_toEndOf="@+id/graphqlIcon" app:layout_constraintTop_toTopOf="parent" - android:layout_marginRight="@dimen/pluto___margin_mini" app:layout_constraintEnd_toStartOf="@+id/proxyIndicator" - tools:text="api endpoint" /> + tools:text="POST /api/v2" /> + app:layout_constraintBottom_toBottomOf="@id/url" + app:layout_constraintStart_toEndOf="@id/status" + app:layout_constraintTop_toTopOf="@id/url" /> From 39c661318b15a0dffdad15e0ddaa20762fadb8cd Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 3 Nov 2024 23:58:29 +0400 Subject: [PATCH 6/7] renamed function --- .../java/com/pluto/plugins/network/intercept/NetworkData.kt | 4 ++-- .../functions/network/internal/okhttp/OkhttpViewModel.kt | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt index 071a1be2f..c77c11df1 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt @@ -50,7 +50,7 @@ class NetworkData { val protocol: String = "", val fromDiskCache: Boolean = false, ) { - val hasGraphqlErrors = parseHasGraphqlError() + val hasGraphqlErrors = parseHasGraphqlErrors() internal val status: Status get() = Status(statusCode, getStatusMessage()) @@ -62,7 +62,7 @@ class NetworkData { private fun getStatusMessage() = mapCode2Message(statusCode) + if (hasGraphqlErrors) ", Response with errors" else "" - private fun parseHasGraphqlError(): Boolean { + private fun parseHasGraphqlErrors(): Boolean { if (request.graphqlData == null || body == null || body.isBinary || diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt index 404a31535..4e185d90c 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt @@ -27,7 +27,6 @@ class OkhttpViewModel : ViewModel() { fun graphqlQuery() { viewModelScope.launch { - // todo, better mock responses enqueue { apiService.graphql( mapOf( From e9d42a9434bc8771cfde38afe48fa53058240d74 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Mon, 4 Nov 2024 00:26:02 +0400 Subject: [PATCH 7/7] fixed ktlint and detekt issues. --- .../plugins/network/intercept/NetworkData.kt | 1 + .../plugins/network/intercept/NetworkData.kt | 9 ++++---- .../interceptor/ui/list/ApiItemHolder.kt | 2 +- .../network/internal/okhttp/ApiService.kt | 1 + .../internal/okhttp/OkhttpViewModel.kt | 23 ++++++++++++------- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/pluto-plugins/plugins/network/core/lib-no-op/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt b/pluto-plugins/plugins/network/core/lib-no-op/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt index be3bb14ab..395ac78b4 100644 --- a/pluto-plugins/plugins/network/core/lib-no-op/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt +++ b/pluto-plugins/plugins/network/core/lib-no-op/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt @@ -11,6 +11,7 @@ class NetworkData { ) data class Response( + val request: Request, val statusCode: Int, val body: Body?, val headers: Map, diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt index c77c11df1..d81c61df8 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt @@ -24,8 +24,7 @@ class NetworkData { private fun parseGraphqlData(): GraphqlData? { if (method != "POST" || body == null || - body.isBinary || - !body.body.startsWith("{") + !body.isLikelyJson ) return null val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return null val query = json.optString("query") ?: return null @@ -60,13 +59,12 @@ class NetworkData { get() = headers["Content-Encoding"].equals("gzip", ignoreCase = true) private fun getStatusMessage() = mapCode2Message(statusCode) + - if (hasGraphqlErrors) ", Response with errors" else "" + if (hasGraphqlErrors) ", Response with errors" else "" private fun parseHasGraphqlErrors(): Boolean { if (request.graphqlData == null || body == null || - body.isBinary || - !body.body.startsWith("{") + !body.isLikelyJson ) return false val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return false return json.has("errors") @@ -83,6 +81,7 @@ class NetworkData { internal val isBinary: Boolean = BINARY_MEDIA_TYPES.contains(mediaType) val sizeInBytes: Long = body.length.toLong() internal val mediaTypeFull: String = "$mediaType/$mediaSubtype" + val isLikelyJson get() = !isBinary && body.startsWith('{') } companion object { diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt index 5ac960f7e..efeacf82d 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt @@ -45,7 +45,7 @@ internal class ApiItemHolder(parent: ViewGroup, actionListener: DiffAwareAdapter url.setSpan { append(fontColor(method, context.color(R.color.pluto___text_dark_60))) - append(" ${urlOrQuery}") + append(" $urlOrQuery") } progress.visibility = VISIBLE status.visibility = INVISIBLE diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt index 751b401a0..556d070d5 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt @@ -31,6 +31,7 @@ interface ApiService { @POST("xml") suspend fun xml(@Body hashMapOf: RequestBody): Any + // https://studio.apollographql.com/public/SpaceX-pxxbxen/variant/current/home @POST("https://spacex-production.up.railway.app/") suspend fun graphql(@Body body: Any): Any } diff --git a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt index 4e185d90c..b03e58af3 100644 --- a/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt +++ b/sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/OkhttpViewModel.kt @@ -30,8 +30,8 @@ class OkhttpViewModel : ViewModel() { enqueue { apiService.graphql( mapOf( - "query" to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", - "variables" to mapOf("limit" to 3), + GQL_QUERY to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", + GQL_VARIABLES to mapOf("limit" to GQL_LIMIT_VALID), ) ) } @@ -43,8 +43,8 @@ class OkhttpViewModel : ViewModel() { enqueue { apiService.graphql( mapOf( - "query" to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", - "variables" to mapOf("limit" to -1111), + GQL_QUERY to "query Launches(\$limit: Int){launches(limit: \$limit){mission_name}}", + GQL_VARIABLES to mapOf("limit" to GQL_LIMIT_INVALID), ) ) } @@ -56,8 +56,8 @@ class OkhttpViewModel : ViewModel() { enqueue { apiService.graphql( mapOf( - "query" to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users(objects: \$objects) {affected_rows}}", - "variables" to mapOf("objects" to emptyList()), + GQL_QUERY to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users(objects: \$objects) {affected_rows}}", + GQL_VARIABLES to mapOf("objects" to emptyList()), ) ) } @@ -69,8 +69,8 @@ class OkhttpViewModel : ViewModel() { enqueue { apiService.graphql( mapOf( - "query" to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users112231321(objects: \$objects) {affected_rows}}", - "variables" to mapOf("objects" to emptyList()), + GQL_QUERY to "mutation Insert_users(\$objects: [users_insert_input!]!) {insert_users112231321(objects: \$objects) {affected_rows}}", + GQL_VARIABLES to mapOf("objects" to emptyList()), ) ) } @@ -131,4 +131,11 @@ class OkhttpViewModel : ViewModel() { ) } } + + companion object { + private const val GQL_QUERY = "query" + private const val GQL_LIMIT_VALID = 3 + private const val GQL_LIMIT_INVALID = -1111 + private const val GQL_VARIABLES = "variables" + } }