Skip to content

Commit e7aad71

Browse files
authored
feature: Basic GraphQL support (#328)
* added graphql examples * show graphql query names. * added variables * Revert "added variables" This reverts commit 4e53181. * better icon * renamed function * fixed ktlint and detekt issues. * display variables and details * fixed graphql search * removed graphql error handling * detect json by mediaType * fixed no-op
1 parent 333bbe8 commit e7aad71

File tree

13 files changed

+287
-16
lines changed

13 files changed

+287
-16
lines changed

pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/intercept/NetworkData.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.pluto.plugins.network.intercept
33
import com.pluto.plugins.network.internal.Status
44
import com.pluto.plugins.network.internal.interceptor.logic.mapCode2Message
55
import io.ktor.http.ContentType
6+
import org.json.JSONObject
67

78
class NetworkData {
89

@@ -11,8 +12,32 @@ class NetworkData {
1112
val method: String,
1213
val body: Body?,
1314
val headers: Map<String, String?>,
14-
val sentTimestamp: Long
15+
val sentTimestamp: Long,
1516
) {
17+
data class GraphqlData(
18+
val queryType: String,
19+
val queryName: String,
20+
val variables: JSONObject,
21+
)
22+
23+
val graphqlData: GraphqlData? = parseGraphqlData()
24+
25+
private fun parseGraphqlData(): GraphqlData? {
26+
if (method != "POST" ||
27+
body == null ||
28+
!body.isJson
29+
) return null
30+
val json = runCatching { JSONObject(body!!.body.toString()) }.getOrNull() ?: return null
31+
val query = json.optString("query") ?: return null
32+
val variables = json.optJSONObject("variables") ?: JSONObject()
33+
val match = graqphlQueryRegex.find(query)?.groupValues ?: return null
34+
return GraphqlData(
35+
queryType = match[1],
36+
queryName = match[2],
37+
variables = variables,
38+
)
39+
}
40+
1641
internal val isGzipped: Boolean
1742
get() = headers["Content-Encoding"].equals("gzip", ignoreCase = true)
1843
}
@@ -36,17 +61,19 @@ class NetworkData {
3661

3762
data class Body(
3863
val body: CharSequence,
39-
val contentType: String
64+
val contentType: String,
4065
) {
4166
private val contentTypeInternal: ContentType = ContentType.parse(contentType)
4267
private val mediaType: String = contentTypeInternal.contentType
4368
internal val mediaSubtype: String = contentTypeInternal.contentSubtype
4469
internal val isBinary: Boolean = BINARY_MEDIA_TYPES.contains(mediaType)
4570
val sizeInBytes: Long = body.length.toLong()
4671
internal val mediaTypeFull: String = "$mediaType/$mediaSubtype"
72+
val isJson get() = mediaTypeFull == "application/json"
4773
}
4874

4975
companion object {
5076
internal val BINARY_MEDIA_TYPES = listOf("audio", "video", "image", "font")
77+
private val graqphlQueryRegex = Regex("""\b(query|mutation)\s+(\w+)""")
5178
}
5279
}

pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/DetailsFragment.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.view.View
55
import android.view.View.GONE
66
import android.view.View.VISIBLE
77
import androidx.core.os.bundleOf
8+
import androidx.core.view.isVisible
89
import androidx.fragment.app.Fragment
910
import androidx.fragment.app.activityViewModels
1011
import androidx.lifecycle.Observer
@@ -125,8 +126,16 @@ internal class DetailsFragment : Fragment(R.layout.pluto_network___fragment_deta
125126

126127
private val detailsObserver = Observer<DetailContentData> {
127128
setupStatusView(it.api)
128-
binding.method.text = it.api.request.method.uppercase()
129-
binding.url.text = Url(it.api.request.url).toString()
129+
val graphqlData = it.api.request.graphqlData
130+
binding.graphqlIcon.isVisible = graphqlData != null
131+
if (graphqlData != null) {
132+
binding.method.text = "${graphqlData.queryType.uppercase()} ${graphqlData.queryName}"
133+
binding.url.text = graphqlData.variables.toString()
134+
} else {
135+
binding.method.text = it.api.request.method.uppercase()
136+
binding.url.text = Url(it.api.request.url).toString()
137+
}
138+
130139
binding.overview.apply {
131140
visibility = VISIBLE
132141
set(it.api)

pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/ListFragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ internal class ListFragment : Fragment(R.layout.pluto_network___fragment_list) {
7474
var list = emptyList<ApiCallData>()
7575
viewModel.apiCalls.value?.let {
7676
list = it.filter { api ->
77-
api.request.url.toString().contains(search, true)
77+
api.request.url.contains(search, true) ||
78+
api.request.graphqlData?.queryName?.contains(search, true) ?: false
7879
}
7980
}
8081
binding.noItemText.text = getString(

pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/components/OverviewStub.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ internal class OverviewStub : ConstraintLayout {
6666
value = context.createSpan { append(semiBold(api.interceptorOption.name)) }
6767
)
6868
)
69+
if (api.request.graphqlData != null) {
70+
add(
71+
KeyValuePairData(
72+
key = context.getString(R.string.pluto_network___method_label),
73+
value = api.request.method
74+
)
75+
)
76+
add(
77+
KeyValuePairData(
78+
key = context.getString(R.string.pluto_network___url_label),
79+
value = api.request.url
80+
)
81+
)
82+
}
6983
}
7084
)
7185
}

pluto-plugins/plugins/network/core/lib/src/main/java/com/pluto/plugins/network/internal/interceptor/ui/list/ApiItemHolder.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.view.View.GONE
44
import android.view.View.INVISIBLE
55
import android.view.View.VISIBLE
66
import android.view.ViewGroup
7+
import androidx.core.view.isVisible
78
import com.pluto.plugins.network.R
89
import com.pluto.plugins.network.databinding.PlutoNetworkItemNetworkBinding
910
import com.pluto.plugins.network.intercept.NetworkData.Response
@@ -30,16 +31,21 @@ internal class ApiItemHolder(parent: ViewGroup, actionListener: DiffAwareAdapter
3031
private val error = binding.error
3132
private val timeElapsed = binding.timeElapsed
3233
private val proxyIndicator = binding.proxyIndicator
34+
private val graphqlIcon = binding.graphqlIcon
3335

3436
override fun onBind(item: ListItem) {
3537
if (item is ApiCallData) {
3638
host.text = Url(item.request.url).host
3739
timeElapsed.text = item.request.sentTimestamp.asTimeElapsed()
3840
binding.root.setBackgroundColor(context.color(R.color.pluto___transparent))
3941

42+
val method = (item.request.graphqlData?.queryType ?: item.request.method).uppercase()
43+
val urlOrQuery = item.request.graphqlData?.queryName ?: Url(item.request.url).encodedPath
44+
graphqlIcon.isVisible = item.request.graphqlData != null
45+
4046
url.setSpan {
41-
append(fontColor(item.request.method.uppercase(), context.color(R.color.pluto___text_dark_60)))
42-
append(" ${Url(item.request.url).encodedPath}")
47+
append(fontColor(method, context.color(R.color.pluto___text_dark_60)))
48+
append(" $urlOrQuery")
4349
}
4450
progress.visibility = VISIBLE
4551
status.visibility = INVISIBLE
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="400"
5+
android:viewportHeight="400">
6+
<path
7+
android:pathData="M57.47,302.66l-14.38,-8.3l160.15,-277.38l14.38,8.3z"
8+
android:fillColor="#E535AB"/>
9+
<path
10+
android:pathData="M39.8,272.2h320.3v16.6h-320.3z"
11+
android:fillColor="#E535AB"/>
12+
<path
13+
android:pathData="M206.35,374.03l-160.21,-92.5l8.3,-14.38l160.21,92.5z"
14+
android:fillColor="#E535AB"/>
15+
<path
16+
android:pathData="M345.52,132.95l-160.21,-92.5l8.3,-14.38l160.21,92.5z"
17+
android:fillColor="#E535AB"/>
18+
<path
19+
android:pathData="M54.48,132.88l-8.3,-14.38l160.21,-92.5l8.3,14.38z"
20+
android:fillColor="#E535AB"/>
21+
<path
22+
android:pathData="M342.57,302.66l-160.15,-277.38l14.38,-8.3l160.15,277.38z"
23+
android:fillColor="#E535AB"/>
24+
<path
25+
android:pathData="M52.5,107.5h16.6v185h-16.6z"
26+
android:fillColor="#E535AB"/>
27+
<path
28+
android:pathData="M330.9,107.5h16.6v185h-16.6z"
29+
android:fillColor="#E535AB"/>
30+
<path
31+
android:pathData="M203.52,367l-7.25,-12.56l139.34,-80.45l7.25,12.56z"
32+
android:fillColor="#E535AB"/>
33+
<path
34+
android:pathData="M369.5,297.9c-9.6,16.7 -31,22.4 -47.7,12.8c-16.7,-9.6 -22.4,-31 -12.8,-47.7c9.6,-16.7 31,-22.4 47.7,-12.8C373.5,259.9 379.2,281.2 369.5,297.9"
35+
android:fillColor="#E535AB"/>
36+
<path
37+
android:pathData="M90.9,137c-9.6,16.7 -31,22.4 -47.7,12.8c-16.7,-9.6 -22.4,-31 -12.8,-47.7c9.6,-16.7 31,-22.4 47.7,-12.8C94.8,99 100.5,120.3 90.9,137"
38+
android:fillColor="#E535AB"/>
39+
<path
40+
android:pathData="M30.5,297.9c-9.6,-16.7 -3.9,-38 12.8,-47.7c16.7,-9.6 38,-3.9 47.7,12.8c9.6,16.7 3.9,38 -12.8,47.7C61.4,320.3 40.1,314.6 30.5,297.9"
41+
android:fillColor="#E535AB"/>
42+
<path
43+
android:pathData="M309.1,137c-9.6,-16.7 -3.9,-38 12.8,-47.7c16.7,-9.6 38,-3.9 47.7,12.8c9.6,16.7 3.9,38 -12.8,47.7C340.1,159.4 318.7,153.7 309.1,137"
44+
android:fillColor="#E535AB"/>
45+
<path
46+
android:pathData="M200,395.8c-19.3,0 -34.9,-15.6 -34.9,-34.9c0,-19.3 15.6,-34.9 34.9,-34.9c19.3,0 34.9,15.6 34.9,34.9C234.9,380.1 219.3,395.8 200,395.8"
47+
android:fillColor="#E535AB"/>
48+
<path
49+
android:pathData="M200,74c-19.3,0 -34.9,-15.6 -34.9,-34.9c0,-19.3 15.6,-34.9 34.9,-34.9c19.3,0 34.9,15.6 34.9,34.9C234.9,58.4 219.3,74 200,74"
50+
android:fillColor="#E535AB"/>
51+
</vector>

pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___fragment_details.xml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,30 @@
108108
android:layout_height="wrap_content"
109109
android:paddingBottom="@dimen/pluto___margin_medium">
110110

111+
<ImageView
112+
android:id="@+id/graphqlIcon"
113+
android:layout_width="@dimen/pluto___text_small"
114+
android:layout_height="@dimen/pluto___text_small"
115+
android:layout_marginStart="@dimen/pluto___margin_medium"
116+
android:src="@drawable/pluto_network___ic_graphql"
117+
app:layout_constraintBottom_toBottomOf="@id/method"
118+
app:layout_constraintStart_toStartOf="parent"
119+
app:layout_constraintTop_toTopOf="@id/method" />
120+
111121
<TextView
112122
android:id="@+id/method"
113-
android:layout_width="match_parent"
123+
android:layout_width="0dp"
114124
android:layout_height="wrap_content"
115-
android:layout_marginHorizontal="@dimen/pluto___margin_medium"
125+
android:layout_marginStart="@dimen/pluto___margin_mini"
116126
android:layout_marginTop="@dimen/pluto___margin_medium"
127+
android:layout_marginEnd="@dimen/pluto___margin_medium"
117128
android:fontFamily="@font/muli_bold"
118129
android:textColor="@color/pluto___dark"
119130
android:textSize="@dimen/pluto___text_xmedium"
131+
app:layout_constraintEnd_toEndOf="parent"
132+
app:layout_constraintStart_toEndOf="@+id/graphqlIcon"
120133
app:layout_constraintTop_toTopOf="parent"
134+
app:layout_goneMarginStart="@dimen/pluto___margin_medium"
121135
tools:text="POST" />
122136

123137
<TextView
@@ -126,7 +140,9 @@
126140
android:layout_height="wrap_content"
127141
android:layout_marginHorizontal="@dimen/pluto___margin_medium"
128142
android:layout_marginTop="@dimen/pluto___margin_micro"
143+
android:ellipsize="end"
129144
android:fontFamily="@font/muli"
145+
android:maxLines="5"
130146
android:textColor="@color/pluto___dark_60"
131147
android:textSize="@dimen/pluto___text_xmedium"
132148
app:layout_constraintTop_toBottomOf="@+id/method"

pluto-plugins/plugins/network/core/lib/src/main/res/layout/pluto_network___item_network.xml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,17 @@
5151
android:id="@+id/url"
5252
android:layout_width="0dp"
5353
android:layout_height="wrap_content"
54-
android:layout_marginStart="@dimen/pluto___margin_small"
54+
android:layout_marginStart="@dimen/pluto___margin_mini"
55+
app:layout_goneMarginStart="@dimen/pluto___margin_small"
5556
android:layout_marginTop="@dimen/pluto___margin_medium"
56-
android:layout_marginLeft="@dimen/pluto___margin_small"
5757
android:fontFamily="@font/muli_semibold"
5858
android:textColor="@color/pluto___text_dark"
5959
android:textSize="@dimen/pluto___text_small"
6060
android:layout_marginEnd="@dimen/pluto___margin_mini"
61-
app:layout_constraintStart_toEndOf="@+id/status"
61+
app:layout_constraintStart_toEndOf="@+id/graphqlIcon"
6262
app:layout_constraintTop_toTopOf="parent"
63-
android:layout_marginRight="@dimen/pluto___margin_mini"
6463
app:layout_constraintEnd_toStartOf="@+id/proxyIndicator"
65-
tools:text="api endpoint" />
64+
tools:text="POST /api/v2" />
6665

6766
<ImageView
6867
android:id="@+id/proxyIndicator"
@@ -74,20 +73,31 @@
7473
app:layout_constraintEnd_toEndOf="parent"
7574
app:layout_constraintTop_toTopOf="@+id/url" />
7675

76+
<ImageView
77+
android:id="@+id/graphqlIcon"
78+
android:layout_width="@dimen/pluto___text_small"
79+
android:layout_height="@dimen/pluto___text_small"
80+
android:layout_marginStart="@dimen/pluto___margin_small"
81+
android:src="@drawable/pluto_network___ic_graphql"
82+
app:layout_constraintBottom_toBottomOf="@id/url"
83+
app:layout_constraintStart_toEndOf="@id/status"
84+
app:layout_constraintTop_toTopOf="@id/url" />
85+
7786
<TextView
7887
android:id="@+id/host"
7988
android:layout_width="0dp"
8089
android:layout_height="wrap_content"
8190
android:layout_marginTop="@dimen/pluto___margin_micro"
8291
android:layout_marginEnd="@dimen/pluto___margin_small"
8392
android:layout_marginBottom="@dimen/pluto___margin_medium"
93+
android:layout_marginStart="@dimen/pluto___margin_small"
8494
android:ellipsize="end"
8595
android:fontFamily="@font/muli"
8696
android:textColor="@color/pluto___text_dark_60"
8797
android:textSize="@dimen/pluto___text_xsmall"
8898
app:layout_constraintBottom_toBottomOf="parent"
8999
app:layout_constraintEnd_toStartOf="@+id/timeElapsed"
90-
app:layout_constraintStart_toStartOf="@+id/url"
100+
app:layout_constraintStart_toEndOf="@+id/status"
91101
app:layout_constraintTop_toBottomOf="@+id/url"
92102
android:layout_marginRight="@dimen/pluto___margin_small"
93103
tools:text="https host" />

sample/src/main/java/com/sampleapp/functions/network/DemoNetworkFragment.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class DemoNetworkFragment : Fragment(R.layout.fragment_demo_network) {
3434

3535
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
3636
super.onViewCreated(view, savedInstanceState)
37+
binding.graphqlQuery.setOnClickListener { okhttpViewModel.graphqlQuery() }
38+
binding.graphqlQueryError.setOnClickListener { okhttpViewModel.graphqlQueryError() }
39+
binding.graphqlMutation.setOnClickListener { okhttpViewModel.graphqlMutation() }
40+
binding.graphqlMutationError.setOnClickListener { okhttpViewModel.graphqlMutationError() }
3741
binding.postCall.setOnClickListener { okhttpViewModel.post() }
3842
binding.getCall.setOnClickListener { okhttpViewModel.get() }
3943
binding.getCallKtor.setOnClickListener { ktorViewModel.get() }

sample/src/main/java/com/sampleapp/functions/network/internal/okhttp/ApiService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ interface ApiService {
3030
)
3131
@POST("xml")
3232
suspend fun xml(@Body hashMapOf: RequestBody): Any
33+
34+
// https://studio.apollographql.com/public/SpaceX-pxxbxen/variant/current/home
35+
@POST("https://spacex-production.up.railway.app/")
36+
suspend fun graphql(@Body body: Any): Any
3337
}

0 commit comments

Comments
 (0)