Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve transaction list for GraphQL requests #805

Merged
merged 11 commits into from
Sep 10, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Please add your entries according to this format.
* Added ability to export list of transactions as .har file.
* Added ability to save single transaction as .har file.
* Added ability to export transactions to a file programmatically.
* GraphlQL OperationName header to transaction title [#69], [#116]

### Fixed

Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ buildscript {
ktLintGradleVersion = '11.0.0'
leakcanaryVersion = '2.9.1'

// Apollo
apolloVersion = '3.2.1'

// Testing
androidxTestCoreVersion = '1.4.0'
junitGradlePluignVersion = '1.8.2.1'
Expand All @@ -50,6 +53,7 @@ buildscript {
classpath "com.android.tools.build:gradle:$androidGradleVersion"
classpath "de.mannodermaus.gradle.plugins:android-junit5:$junitGradlePluignVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.apollographql.apollo3:apollo-gradle-plugin:$apolloVersion"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion"
classpath "org.jlleitschuh.gradle:ktlint-gradle:$ktLintGradleVersion"
classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binaryCompatibilityValidator"
Expand Down
11 changes: 11 additions & 0 deletions downloadApolloSchema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

# GraphQL Schema Update
cortinico marked this conversation as resolved.
Show resolved Hide resolved
# Open Android Studio terminal
# Run command `./downloadApolloSchema.sh`
# Format the file (`ctrl+option+i` && `option+cmd+L`), if not formatted
# Replace `"null"` to `null`
# Go to the top of the file and click the play icon ▢️ "Generate GraphQL SDL schema file". (It normally can be visible on Android Studio and IntelliJ IDE)
# Be happy.

./gradlew downloadApolloSchema --endpoint="https://rickandmortyapi.com/graphql" --schema="sample/src/main/graphql/com/chuckerteam/chucker/sample/schema.json"
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ internal class HttpTransaction(
@ColumnInfo(name = "responseHeadersSize") var responseHeadersSize: Long?,
@ColumnInfo(name = "responseBody") var responseBody: String?,
@ColumnInfo(name = "isResponseBodyEncoded") var isResponseBodyEncoded: Boolean = false,
@ColumnInfo(name = "responseImageData") var responseImageData: ByteArray?
@ColumnInfo(name = "responseImageData") var responseImageData: ByteArray?,
@ColumnInfo(name = "graphQlOperationName") var graphQlOperationName: String?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but wouldn't we need to include a check for this parameter in the fun hasTheSameContent()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I've added it πŸ‘

) {

@Ignore
Expand Down Expand Up @@ -82,7 +83,8 @@ internal class HttpTransaction(
responseHeaders = null,
responseHeadersSize = null,
responseBody = null,
responseImageData = null
responseImageData = null,
graphQlOperationName = null
)

enum class Status {
Expand Down Expand Up @@ -156,6 +158,11 @@ internal class HttpTransaction(
requestHeaders = JsonConverter.instance.toJson(headers)
}

fun setGraphQlOperationName(headers: Headers) {
graphQlOperationName = toHttpHeaderList(headers)
.find { it.name.lowercase().contains("operation-name") }?.value
}

cortinico marked this conversation as resolved.
Show resolved Hide resolved
fun getParsedRequestHeaders(): List<HttpHeader>? {
return JsonConverter.instance.fromJson<List<HttpHeader>>(
requestHeaders,
Expand Down Expand Up @@ -285,6 +292,7 @@ internal class HttpTransaction(
(responseHeadersSize == other.responseHeadersSize) &&
(responseBody == other.responseBody) &&
(isResponseBodyEncoded == other.isResponseBodyEncoded) &&
(responseImageData?.contentEquals(other.responseImageData ?: byteArrayOf()) != false)
(responseImageData?.contentEquals(other.responseImageData ?: byteArrayOf()) != false) &&
(graphQlOperationName == other.graphQlOperationName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ internal data class HttpTransactionTuple(
@ColumnInfo(name = "responseCode") var responseCode: Int?,
@ColumnInfo(name = "requestPayloadSize") var requestPayloadSize: Long?,
@ColumnInfo(name = "responsePayloadSize") var responsePayloadSize: Long?,
@ColumnInfo(name = "error") var error: String?
@ColumnInfo(name = "error") var error: String?,
@ColumnInfo(name = "graphQlOperationName") var graphQlOperationName: String?,
) {
val isSsl: Boolean get() = scheme.equals("https", ignoreCase = true)

Expand All @@ -45,7 +46,6 @@ internal data class HttpTransactionTuple(
private fun formatBytes(bytes: Long): String {
return FormatUtils.formatByteCount(bytes, true)
}

fun getFormattedPath(encode: Boolean): String {
val path = this.path ?: return ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction

@Database(entities = [HttpTransaction::class], version = 7, exportSchema = false)
@Database(entities = [HttpTransaction::class], version = 8, exportSchema = false)
internal abstract class ChuckerDatabase : RoomDatabase() {

abstract fun transactionDao(): HttpTransactionDao
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import com.chuckerteam.chucker.internal.data.entity.HttpTransactionTuple
internal interface HttpTransactionDao {

@Query(
"SELECT id, requestDate, tookMs, protocol, method, host, " +
"path, scheme, responseCode, requestPayloadSize, responsePayloadSize, error FROM " +
"SELECT id, requestDate, tookMs, protocol, method, host, path, scheme, responseCode, " +
"requestPayloadSize, responsePayloadSize, error, graphQlOperationName FROM " +
"transactions ORDER BY requestDate DESC"
)
fun getSortedTuples(): LiveData<List<HttpTransactionTuple>>

@Query(
"SELECT id, requestDate, tookMs, protocol, method, host, " +
"path, scheme, responseCode, requestPayloadSize, responsePayloadSize, error FROM " +
"SELECT id, requestDate, tookMs, protocol, method, host, path, scheme, responseCode, " +
"requestPayloadSize, responsePayloadSize, error, graphQlOperationName FROM " +
"transactions WHERE responseCode LIKE :codeQuery AND path LIKE :pathQuery " +
"ORDER BY requestDate DESC"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ internal class RequestProcessor(
private fun processMetadata(request: Request, transaction: HttpTransaction) {
transaction.apply {
requestHeadersSize = request.headers.byteCount()
setRequestHeaders(request.headers.redact(headersToRedact))
request.headers.redact(headersToRedact).let {
setRequestHeaders(it)
setGraphQlOperationName(it)
}
populateUrl(request.url)

requestDate = System.currentTimeMillis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
Expand All @@ -21,17 +22,26 @@ import javax.net.ssl.HttpsURLConnection
internal class TransactionAdapter internal constructor(
context: Context,
private val onTransactionClick: (Long) -> Unit,
) : ListAdapter<HttpTransactionTuple, TransactionAdapter.TransactionViewHolder>(TransactionDiffCallback) {
) : ListAdapter<HttpTransactionTuple, TransactionAdapter.TransactionViewHolder>(
TransactionDiffCallback
) {

private val colorDefault: Int = ContextCompat.getColor(context, R.color.chucker_status_default)
private val colorRequested: Int = ContextCompat.getColor(context, R.color.chucker_status_requested)
private val colorRequested: Int = ContextCompat.getColor(
context,
R.color.chucker_status_requested
)
private val colorError: Int = ContextCompat.getColor(context, R.color.chucker_status_error)
private val color500: Int = ContextCompat.getColor(context, R.color.chucker_status_500)
private val color400: Int = ContextCompat.getColor(context, R.color.chucker_status_400)
private val color300: Int = ContextCompat.getColor(context, R.color.chucker_status_300)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
val viewBinding = ChuckerListItemTransactionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val viewBinding = ChuckerListItemTransactionBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return TransactionViewHolder(viewBinding)
}

Expand All @@ -57,6 +67,7 @@ internal class TransactionAdapter internal constructor(
transactionId = transaction.id

itemBinding.apply {
displayGraphQlFields(transaction.graphQlOperationName)
path.text = "${transaction.method} ${transaction.getFormattedPath(encode = false)}"
host.text = transaction.host
timeStart.text = DateFormat.getTimeInstance().format(transaction.requestDate)
Expand All @@ -81,7 +92,12 @@ internal class TransactionAdapter internal constructor(
}

private fun setProtocolImage(resources: ProtocolResources) {
itemBinding.ssl.setImageDrawable(AppCompatResources.getDrawable(itemView.context, resources.icon))
itemBinding.ssl.setImageDrawable(
AppCompatResources.getDrawable(
itemView.context,
resources.icon
)
)
ImageViewCompat.setImageTintList(
itemBinding.ssl,
ColorStateList.valueOf(ContextCompat.getColor(itemView.context, resources.color))
Expand All @@ -103,3 +119,15 @@ internal class TransactionAdapter internal constructor(
}
}
}

private fun ChuckerListItemTransactionBinding.displayGraphQlFields(graphQlOperationName: String?) {
if (graphQlOperationName != null) {
graphqlIcon.visibility = View.VISIBLE
graphqlPath.visibility = View.VISIBLE

graphqlPath.text = graphQlOperationName
} else {
graphqlIcon.visibility = View.GONE
graphqlPath.visibility = View.GONE
}
}
27 changes: 27 additions & 0 deletions library/src/main/res/drawable/chucker_ic_graphql.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<vector xmlns:android="http:https://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="29.999"
android:viewportHeight="30">
<path
android:pathData="M4.08,22.864l-1.1,-0.636L15.248,0.98l1.1,0.636z"
android:fillColor="#e10098"/>
<path
android:pathData="M2.727,20.53h24.538v1.272H2.727z"
android:fillColor="#e10098"/>
<path
android:pathData="M15.486,28.332L3.213,21.246l0.636,-1.1 12.273,7.086zM26.148,9.862L13.874,2.777l0.636,-1.1 12.273,7.086z"
android:fillColor="#e10098"/>
<path
android:pathData="M3.852,9.858l-0.636,-1.1L15.5,1.67l0.636,1.1z"
android:fillColor="#e10098"/>
<path
android:pathData="M25.922,22.864l-12.27,-21.25 1.1,-0.636 12.27,21.25zM3.7,7.914h1.272v14.172L3.7,22.086zM25.028,7.914L26.3,7.914v14.172h-1.272z"
android:fillColor="#e10098"/>
<path
android:pathData="M15.27,27.793l-0.555,-0.962 10.675,-6.163 0.555,0.962z"
android:fillColor="#e10098"/>
<path
android:pathData="M27.985,22.5a2.68,2.68 0,0 1,-3.654 0.981,2.68 2.68,0 0,1 -0.981,-3.654 2.68,2.68 0,0 1,3.654 -0.981c1.287,0.743 1.724,2.375 0.98,3.654M6.642,10.174a2.68,2.68 0,0 1,-3.654 0.981A2.68,2.68 0,0 1,2.007 7.5a2.68,2.68 0,0 1,3.654 -0.981,2.68 2.68,0 0,1 0.981,3.654M2.015,22.5a2.68,2.68 0,0 1,0.981 -3.654,2.68 2.68,0 0,1 3.654,0.981 2.68,2.68 0,0 1,-0.981 3.654c-1.287,0.735 -2.92,0.3 -3.654,-0.98m21.343,-12.326a2.68,2.68 0,0 1,0.981 -3.654,2.68 2.68,0 0,1 3.654,0.981 2.68,2.68 0,0 1,-0.981 3.654,2.68 2.68,0 0,1 -3.654,-0.981M15,30a2.674,2.674 0,1 1,2.674 -2.673A2.68,2.68 0,0 1,15 30m0,-24.652a2.67,2.67 0,0 1,-2.674 -2.674,2.67 2.67,0 1,1 5.347,0A2.67,2.67 0,0 1,15 5.347"
android:fillColor="#e10098"/>
</vector>
40 changes: 33 additions & 7 deletions library/src/main/res/layout/chucker_list_item_transaction.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http:https://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http:https://schemas.android.com/apk/res/android"
xmlns:app="http:https://schemas.android.com/apk/res-auto"
xmlns:tools="http:https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:focusable="true"
android:padding="@dimen/chucker_base_grid">

<androidx.constraintlayout.widget.Guideline
Expand All @@ -24,8 +25,8 @@
android:textAppearance="@style/Chucker.TextAppearance.ListItem"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="200"
tools:ignore="RtlSymmetry" />
tools:ignore="RtlSymmetry"
tools:text="200" />

<TextView
android:id="@+id/path"
Expand All @@ -40,27 +41,52 @@
tools:text="GET /path/to/some/resource?goes=here" />

<TextView
android:id="@+id/host"
android:id="@+id/graphqlPath"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/chucker_half_grid"
android:ellipsize="end"
android:maxLines="2"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ssl"
app:layout_constraintTop_toBottomOf="@+id/path"
tools:text="getBatchDetails"
tools:visibility="visible" />

<ImageView
android:id="@+id/graphqlIcon"
android:layout_width="@dimen/chucker_doub_grid"
android:layout_height="@dimen/chucker_doub_grid"
android:contentDescription="@string/chucker_ssl"
android:src="@drawable/chucker_ic_graphql"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/graphqlPath"
app:layout_constraintStart_toStartOf="@+id/ssl"
app:layout_constraintTop_toTopOf="@+id/graphqlPath"
tools:visibility="visible" />

<TextView
android:id="@+id/host"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/chucker_half_grid"
android:ellipsize="end"
android:maxLines="2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ssl"
app:layout_constraintTop_toBottomOf="@+id/graphqlPath"
app:layout_goneMarginStart="0dp"
app:layout_goneMarginTop="@dimen/chucker_doub_grid"
tools:text="example.com" />

<ImageView
android:id="@+id/ssl"
android:layout_width="@dimen/chucker_doub_grid"
android:layout_height="@dimen/chucker_doub_grid"
android:contentDescription="@string/chucker_ssl"
app:layout_constraintBottom_toBottomOf="@id/host"
app:layout_constraintStart_toStartOf="@+id/path"
app:layout_constraintTop_toTopOf="@+id/host"
app:layout_constraintBottom_toBottomOf="@id/host"
tools:src="@drawable/chucker_ic_https" />

<TextView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,21 @@ internal class HttpTransactionTupleTest {
responseCode: Int? = null,
requestPayloadSize: Long? = null,
responsePayloadSize: Long? = null,
error: String? = null
error: String? = null,
graphQlOperationName: String? = null,
) = HttpTransactionTuple(
id,
requestDate,
tookMs,
protocol,
method,
host,
path,
scheme,
responseCode,
requestPayloadSize,
responsePayloadSize,
error
id = id,
requestDate = requestDate,
tookMs = tookMs,
protocol = protocol,
method = method,
host = host,
path = path,
scheme = scheme,
responseCode = responseCode,
requestPayloadSize = requestPayloadSize,
responsePayloadSize = responsePayloadSize,
error = error,
graphQlOperationName = graphQlOperationName
)
}
Loading