From 42c032915c630c936251751e63f4d21056057ef3 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 20 Oct 2024 02:33:13 +0300 Subject: [PATCH 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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" + } } From bca795fdc10f5d0b8266905c848fb1c0159bc0fa Mon Sep 17 00:00:00 2001 From: pavelperc Date: Thu, 7 Nov 2024 19:02:53 +0300 Subject: [PATCH 08/12] display variables and details --- .../plugins/network/intercept/NetworkData.kt | 16 +++++-- .../interceptor/ui/DetailsFragment.kt | 17 ++++++- .../interceptor/ui/components/OverviewStub.kt | 14 ++++++ .../pluto_network___fragment_details.xml | 46 +++++++++++++++++-- 4 files changed, 83 insertions(+), 10 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 d81c61df8..08eede274 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 @@ -17,6 +17,7 @@ class NetworkData { data class GraphqlData( val queryType: String, val queryName: String, + val variables: JSONObject, ) val graphqlData: GraphqlData? = parseGraphqlData() @@ -28,10 +29,12 @@ 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") ?: JSONObject() val match = graqphlQueryRegex.find(query)?.groupValues ?: return null return GraphqlData( queryType = match[1], queryName = match[2], + variables = variables, ) } @@ -49,7 +52,8 @@ class NetworkData { val protocol: String = "", val fromDiskCache: Boolean = false, ) { - val hasGraphqlErrors = parseHasGraphqlErrors() + val graphqlErrors: List = parseGraphqlErrors() + val hasGraphqlErrors get() = graphqlErrors.isNotEmpty() internal val status: Status get() = Status(statusCode, getStatusMessage()) @@ -61,13 +65,15 @@ class NetworkData { private fun getStatusMessage() = mapCode2Message(statusCode) + if (hasGraphqlErrors) ", Response with errors" else "" - private fun parseHasGraphqlErrors(): Boolean { + private fun parseGraphqlErrors(): List { if (request.graphqlData == null || body == null || !body.isLikelyJson - ) return false - val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return false - return json.has("errors") + ) return emptyList() + val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return emptyList() + val errors = json.optJSONArray("errors") ?: return emptyList() + return (0 until errors.length()) + .mapNotNull { errors.optJSONObject(it)?.optString("message") } } } diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt index 4e530c05b..99ac24939 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer @@ -125,8 +126,20 @@ internal class DetailsFragment : Fragment(R.layout.pluto_network___fragment_deta private val detailsObserver = Observer { setupStatusView(it.api) - binding.method.text = it.api.request.method.uppercase() - binding.url.text = Url(it.api.request.url).toString() + val graphqlData = it.api.request.graphqlData + val graphqlErrors = it.api.response?.graphqlErrors ?: emptyList() + binding.graphqlIcon.isVisible = graphqlData != null + if (graphqlData != null) { + binding.method.text = "${graphqlData.queryType.uppercase()} ${graphqlData.queryName}" + binding.url.text = graphqlData.variables.toString() + } else { + binding.method.text = it.api.request.method.uppercase() + binding.url.text = Url(it.api.request.url).toString() + } + binding.graphqlErrorsTitle.isVisible = graphqlErrors.isNotEmpty() + binding.graphqlErrors.isVisible = graphqlErrors.isNotEmpty() + binding.graphqlErrors.text = graphqlErrors.joinToString("\n") + binding.overview.apply { visibility = VISIBLE set(it.api) diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/components/OverviewStub.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/components/OverviewStub.kt index f66de721f..06369fc8d 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/components/OverviewStub.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/components/OverviewStub.kt @@ -66,6 +66,20 @@ internal class OverviewStub : ConstraintLayout { value = context.createSpan { append(semiBold(api.interceptorOption.name)) } ) ) + if (api.request.graphqlData != null) { + add( + KeyValuePairData( + key = context.getString(R.string.pluto_network___method_label), + value = api.request.method + ) + ) + add( + KeyValuePairData( + key = context.getString(R.string.pluto_network___url_label), + value = api.request.url + ) + ) + } } ) } diff --git a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml index 4c2677494..2c3f61d4a 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml +++ b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml @@ -108,16 +108,30 @@ android:layout_height="wrap_content" android:paddingBottom="@dimen/pluto___margin_medium"> + + + + + + Date: Thu, 7 Nov 2024 21:13:39 +0300 Subject: [PATCH 09/12] fixed graphql search --- .../plugins/network/internal/interceptor/ui/ListFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/ListFragment.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/ListFragment.kt index 21f43630d..f3a09735c 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/ListFragment.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/ListFragment.kt @@ -74,7 +74,8 @@ internal class ListFragment : Fragment(R.layout.pluto_network___fragment_list) { var list = emptyList() viewModel.apiCalls.value?.let { list = it.filter { api -> - api.request.url.toString().contains(search, true) + api.request.url.contains(search, true) || + api.request.graphqlData?.queryName?.contains(search, true) ?: false } } binding.noItemText.text = getString( From 31fdf2b9c5853bb6a99aa41555f8ebc3758f7421 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 24 Nov 2024 15:47:07 +0300 Subject: [PATCH 10/12] removed graphql error handling --- .../plugins/network/intercept/NetworkData.kt | 24 +++-------------- .../interceptor/ui/DetailsFragment.kt | 4 --- .../pluto_network___fragment_details.xml | 26 +------------------ .../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 +++++++--------- 10 files changed, 26 insertions(+), 81 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 08eede274..f5a8c809f 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 @@ -43,38 +43,20 @@ class NetworkData { } 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 graphqlErrors: List = parseGraphqlErrors() - val hasGraphqlErrors get() = graphqlErrors.isNotEmpty() - internal val status: Status - get() = Status(statusCode, getStatusMessage()) + get() = Status(statusCode, mapCode2Message(statusCode)) val isSuccessful: Boolean - get() = statusCode in 200..299 && !hasGraphqlErrors + get() = statusCode in 200..299 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 parseGraphqlErrors(): List { - if (request.graphqlData == null || - body == null || - !body.isLikelyJson - ) return emptyList() - val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return emptyList() - val errors = json.optJSONArray("errors") ?: return emptyList() - return (0 until errors.length()) - .mapNotNull { errors.optJSONObject(it)?.optString("message") } - } } data class Body( diff --git a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt index 99ac24939..457df78f0 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt +++ b/pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt @@ -127,7 +127,6 @@ internal class DetailsFragment : Fragment(R.layout.pluto_network___fragment_deta private val detailsObserver = Observer { setupStatusView(it.api) val graphqlData = it.api.request.graphqlData - val graphqlErrors = it.api.response?.graphqlErrors ?: emptyList() binding.graphqlIcon.isVisible = graphqlData != null if (graphqlData != null) { binding.method.text = "${graphqlData.queryType.uppercase()} ${graphqlData.queryName}" @@ -136,9 +135,6 @@ internal class DetailsFragment : Fragment(R.layout.pluto_network___fragment_deta binding.method.text = it.api.request.method.uppercase() binding.url.text = Url(it.api.request.url).toString() } - binding.graphqlErrorsTitle.isVisible = graphqlErrors.isNotEmpty() - binding.graphqlErrors.isVisible = graphqlErrors.isNotEmpty() - binding.graphqlErrors.text = graphqlErrors.joinToString("\n") binding.overview.apply { visibility = VISIBLE diff --git a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml index 2c3f61d4a..563b1a4a7 100644 --- a/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml +++ b/pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml @@ -148,37 +148,13 @@ app:layout_constraintTop_toBottomOf="@+id/method" tools:text="POST" /> - - - - ("ResponseBodySaved") fun HttpClient.addPlutoKtorInterceptor() { plugin(HttpSend).intercept { requestUnBuilt -> val request = requestUnBuilt.build() - val convertedRequest = request.convert() - val networkInterceptor = NetworkInterceptor.intercept(convertedRequest, NetworkInterceptor.Option(NAME)) + val networkInterceptor = NetworkInterceptor.intercept(request.convert(), NetworkInterceptor.Option(NAME)) val callResult = try { requestUnBuilt.url(networkInterceptor.actualOrMockRequestUrl) execute(requestUnBuilt) @@ -35,7 +34,7 @@ fun HttpClient.addPlutoKtorInterceptor() { newCall.attributes.put(saveAttributeKey, Unit) newCall } - networkInterceptor.onResponse(res.response.convert(convertedRequest)) + networkInterceptor.onResponse(res.response.convert()) 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 b201e74d9..888e1b05b 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,6 +1,5 @@ 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 @@ -10,16 +9,15 @@ import io.ktor.http.Headers import io.ktor.http.contentType internal object KtorResponseConverter : ResponseConverter { - override suspend fun HttpResponse.convert(request: NetworkData.Request): Response { + override suspend fun HttpResponse.convert(): 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 d40bdf41f..a6ec250dc 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,8 +1,7 @@ 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(request: NetworkData.Request): Response + suspend fun T.convert(): 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 4b709c360..fd85306dd 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,8 +24,7 @@ class PlutoOkhttpInterceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() - val convertedRequest = request.convert() - val networkInterceptor = NetworkInterceptor.intercept(convertedRequest, NetworkInterceptor.Option(NAME)) + val networkInterceptor = NetworkInterceptor.intercept(request.convert(), NetworkInterceptor.Option(NAME)) val response: Response = try { val builder = request.newBuilder().url(networkInterceptor.actualOrMockRequestUrl) chain.proceed(builder.build()) @@ -33,18 +32,18 @@ class PlutoOkhttpInterceptor { networkInterceptor.onError(e) throw e } - return response.processBody(convertedRequest) { networkInterceptor.onResponse(it) } + return response.processBody { networkInterceptor.onResponse(it) } } } } -private fun Response.processBody(request: NetworkData.Request, onComplete: (NetworkData.Response) -> Unit): Response { +private fun Response.processBody(onComplete: (NetworkData.Response) -> Unit): Response { if (!hasBody()) { - onComplete.invoke(convert(request, null)) + onComplete.invoke(convert(null)) return this } val responseBody: ResponseBody = body as ResponseBody - val sideStream = ReportingSink(PlutoInterface.files.createFile(), ResponseReportingSinkCallback(this, request, onComplete)) + val sideStream = ReportingSink(PlutoInterface.files.createFile(), ResponseReportingSinkCallback(this, 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 387405126..da21cafca 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,9 +36,8 @@ internal fun Request.headerMap(contentLength: Long): Map { return map } -internal fun Response.convert(request: NetworkData.Request, body: NetworkData.Body?): NetworkData.Response { +internal fun Response.convert(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 cb2f3021e..e94648bad 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,7 +12,6 @@ import java.io.IOException class ResponseReportingSinkCallback( private val response: Response, - private val request: NetworkData.Request, private val onComplete: (NetworkData.Response) -> Unit ) : ReportingSink.Callback { @@ -21,7 +20,7 @@ class ResponseReportingSinkCallback( readResponseBuffer(f, response.isGzipped)?.let { val responseBody = response.body ?: return val body = responseBody.processBody(it) - onComplete.invoke(response.convert(request, body)) + onComplete.invoke(response.convert(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 dc8ece7a2..24eee482a 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,23 +12,21 @@ 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( - request, + NetworkData.Request( + url = "https://google.com", + method = "GET", + body = NetworkData.Body( + body = "{\"message\": \"body\"}", + contentType = "application/json", + ), + headers = emptyMap(), + sentTimestamp = System.currentTimeMillis() + ) ) delay(5_000) networkInterceptor.onResponse( NetworkData.Response( - request = request, statusCode = 503, body = NetworkData.Body( body = "body", From 398e09f78ee81bee3779adaff5dd3359243e5e9d Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 24 Nov 2024 15:51:24 +0300 Subject: [PATCH 11/12] detect json by mediaType --- .../java/com/pluto/plugins/network/intercept/NetworkData.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 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 f5a8c809f..236ba76c9 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 @@ -25,7 +25,7 @@ class NetworkData { private fun parseGraphqlData(): GraphqlData? { if (method != "POST" || body == null || - !body.isLikelyJson + !body.isJson ) return null val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return null val query = json.optString("query") ?: return null @@ -69,7 +69,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('{') + val isJson get() = mediaTypeFull == "application/json" } companion object { From cfe1c6e360c1e9c8e467bc3b43d6c5b16455d898 Mon Sep 17 00:00:00 2001 From: pavelperc Date: Sun, 24 Nov 2024 15:55:46 +0300 Subject: [PATCH 12/12] fixed no-op --- .../main/java/com/pluto/plugins/network/intercept/NetworkData.kt | 1 - 1 file changed, 1 deletion(-) 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 395ac78b4..be3bb14ab 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,7 +11,6 @@ class NetworkData { ) data class Response( - val request: Request, val statusCode: Int, val body: Body?, val headers: Map,