refact: [NETWORK] format on data part to be able to filter and search… (#288)

Co-authored-by: Florent Champigny <florent@bere.al>
This commit is contained in:
Florent CHAMPIGNY 2025-10-02 14:32:56 +02:00 committed by GitHub
parent 8c6ce2e244
commit 08e58f60f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 4420 additions and 67 deletions

View file

@ -40,7 +40,7 @@ import io.github.openflocon.flocondesktop.common.db.converters.MapStringsConvert
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@Database( @Database(
version = 61, version = 62,
entities = [ entities = [
FloconNetworkCallEntity::class, FloconNetworkCallEntity::class,
FileEntity::class, FileEntity::class,

View file

@ -6,35 +6,6 @@ import io.github.openflocon.domain.network.models.responseByteSizeFormatted
import io.github.openflocon.flocondesktop.features.network.list.model.NetworkItemViewState import io.github.openflocon.flocondesktop.features.network.list.model.NetworkItemViewState
import io.ktor.http.Url import io.ktor.http.Url
fun extractDomain(url: String): String {
// Parse l'URL en un objet Url
val parsedUrl = Url(url)
// Utilise host pour le domaine et encodedPathAndQuery pour le chemin
val domainAndPath = parsedUrl.host
// Le code ci-dessous pourrait aussi fonctionner, mais host est plus précis pour le domaine
// return parsedUrl.hostWithPort + parsedUrl.fullPath
return domainAndPath.removePrefix("www.")
}
fun extractDomainAndPath(url: String): String {
// Parse l'URL en un objet Url
val parsedUrl = Url(url)
// Utilise host pour le domaine et encodedPathAndQuery pour le chemin
val domainAndPath = parsedUrl.host + parsedUrl.encodedPathAndQuery
// Le code ci-dessous pourrait aussi fonctionner, mais host est plus précis pour le domaine
// return parsedUrl.hostWithPort + parsedUrl.fullPath
return domainAndPath.removePrefix("www.")
}
fun extractPath(url: String): String {
val parsedUrl = Url(url)
return parsedUrl.encodedPathAndQuery
}
fun toUi( fun toUi(
networkCall: FloconNetworkCallDomainModel, networkCall: FloconNetworkCallDomainModel,
deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel? deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel?
@ -45,7 +16,7 @@ fun toUi(
timeFormatted = networkCall.response?.durationFormatted, timeFormatted = networkCall.response?.durationFormatted,
requestSize = networkCall.request.byteSizeFormatted, requestSize = networkCall.request.byteSizeFormatted,
responseSize = networkCall.responseByteSizeFormatted(), responseSize = networkCall.responseByteSizeFormatted(),
domain = getDomainUi(networkCall), domain = networkCall.request.domainFormatted,
type = toTypeUi(networkCall), type = toTypeUi(networkCall),
method = getMethodUi(networkCall), method = getMethodUi(networkCall),
status = getStatusUi(networkCall), status = getStatusUi(networkCall),
@ -53,9 +24,3 @@ fun toUi(
isFromOldAppInstance = deviceIdAndPackageName?.appInstance?.let { it != networkCall.appInstance } ?: false isFromOldAppInstance = deviceIdAndPackageName?.appInstance?.let { it != networkCall.appInstance } ?: false
) )
} }
fun getDomainUi(networkRequest: FloconNetworkCallDomainModel): String = when (networkRequest.request.specificInfos) {
is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl -> extractDomainAndPath(networkRequest.request.url)
is FloconNetworkCallDomainModel.Request.SpecificInfos.Http -> extractDomain(networkRequest.request.url)
is FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc -> networkRequest.request.url
}

View file

@ -5,20 +5,18 @@ import io.github.openflocon.flocondesktop.features.network.list.model.NetworkIte
fun toTypeUi(call: FloconNetworkCallDomainModel): NetworkItemViewState.NetworkTypeUi = when (val s = call.request.specificInfos) { fun toTypeUi(call: FloconNetworkCallDomainModel): NetworkItemViewState.NetworkTypeUi = when (val s = call.request.specificInfos) {
is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl -> NetworkItemViewState.NetworkTypeUi.GraphQl( is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl -> NetworkItemViewState.NetworkTypeUi.GraphQl(
queryName = s.query, queryName = call.request.queryFormatted,
) )
is FloconNetworkCallDomainModel.Request.SpecificInfos.Http -> { is FloconNetworkCallDomainModel.Request.SpecificInfos.Http -> {
val query = extractPath(call.request.url)
NetworkItemViewState.NetworkTypeUi.Url( NetworkItemViewState.NetworkTypeUi.Url(
query = query, query = call.request.queryFormatted,
method = call.request.method,
) )
} }
is FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc -> { is FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc -> {
NetworkItemViewState.NetworkTypeUi.Grpc( NetworkItemViewState.NetworkTypeUi.Grpc(
method = call.request.method, method = call.request.queryFormatted,
) )
} }
} }

View file

@ -32,7 +32,6 @@ data class NetworkItemViewState(
@Immutable @Immutable
data class Url( data class Url(
val method: String,
val query: String, val query: String,
) : NetworkTypeUi { ) : NetworkTypeUi {
override fun contains(text: String): Boolean = query.contains(text, ignoreCase = true) override fun contains(text: String): Boolean = query.contains(text, ignoreCase = true)
@ -68,7 +67,6 @@ fun previewNetworkItemViewState(): NetworkItemViewState = NetworkItemViewState(
status = NetworkStatusUi("200", NetworkStatusUi.Status.SUCCESS), status = NetworkStatusUi("200", NetworkStatusUi.Status.SUCCESS),
type = NetworkItemViewState.NetworkTypeUi.Url( type = NetworkItemViewState.NetworkTypeUi.Url(
query = "/search?q=test", query = "/search?q=test",
method = "get",
), ),
isMocked = false, isMocked = false,
isFromOldAppInstance = false, isFromOldAppInstance = false,

View file

@ -31,6 +31,9 @@ private fun FloconNetworkCallEntity.toRequestDomainModel(): FloconNetworkCallDom
isMocked = request.isMocked, isMocked = request.isMocked,
startTimeFormatted = request.startTimeFormatted, startTimeFormatted = request.startTimeFormatted,
byteSizeFormatted = request.byteSizeFormatted, byteSizeFormatted = request.byteSizeFormatted,
domainFormatted = request.domainFormatted,
queryFormatted = request.queryFormatted,
methodFormatted = request.methodFormatted,
specificInfos = when (type) { specificInfos = when (type) {
FloconNetworkCallType.HTTP -> FloconNetworkCallDomainModel.Request.SpecificInfos.Http FloconNetworkCallType.HTTP -> FloconNetworkCallDomainModel.Request.SpecificInfos.Http
FloconNetworkCallType.GRAPHQL -> FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl( FloconNetworkCallType.GRAPHQL -> FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl(
@ -49,6 +52,7 @@ private fun FloconNetworkResponseEmbedded.toDomainModel(): FloconNetworkCallDoma
durationMs = durationMs, durationMs = durationMs,
issue = responseError, issue = responseError,
durationFormatted = durationFormatted, durationFormatted = durationFormatted,
statusFormatted = statusFormatted,
) )
} else { } else {
FloconNetworkCallDomainModel.Response.Success( FloconNetworkCallDomainModel.Response.Success(
@ -60,6 +64,7 @@ private fun FloconNetworkResponseEmbedded.toDomainModel(): FloconNetworkCallDoma
durationFormatted = durationFormatted, durationFormatted = durationFormatted,
byteSizeFormatted = responseByteSizeFormatted ?: "", byteSizeFormatted = responseByteSizeFormatted ?: "",
isImage = isImage, isImage = isImage,
statusFormatted = statusFormatted,
specificInfos = when { specificInfos = when {
graphql != null -> FloconNetworkCallDomainModel.Response.Success.SpecificInfos.GraphQl( graphql != null -> FloconNetworkCallDomainModel.Response.Success.SpecificInfos.GraphQl(
httpCode = graphql.responseHttpCode, httpCode = graphql.responseHttpCode,

View file

@ -8,8 +8,6 @@ import io.github.openflocon.data.local.network.models.graphql.NetworkCallGraphQl
import io.github.openflocon.data.local.network.models.graphql.NetworkCallGraphQlResponseEmbedded import io.github.openflocon.data.local.network.models.graphql.NetworkCallGraphQlResponseEmbedded
import io.github.openflocon.data.local.network.models.grpc.NetworkCallGrpcResponseEmbedded import io.github.openflocon.data.local.network.models.grpc.NetworkCallGrpcResponseEmbedded
import io.github.openflocon.data.local.network.models.http.NetworkCallHttpResponseEmbedded import io.github.openflocon.data.local.network.models.http.NetworkCallHttpResponseEmbedded
import io.github.openflocon.domain.device.models.AppInstance
import io.github.openflocon.domain.device.models.DeviceId
import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
@ -43,7 +41,10 @@ fun FloconNetworkCallDomainModel.toEntity(
) )
else -> null else -> null
} },
domainFormatted = request.domainFormatted,
queryFormatted = request.queryFormatted,
methodFormatted = request.methodFormatted,
), ),
response = response?.let { networkResponse -> response = response?.let { networkResponse ->
when(networkResponse) { when(networkResponse) {
@ -61,6 +62,7 @@ fun FloconNetworkCallDomainModel.toEntity(
isImage = false, isImage = false,
durationFormatted = networkResponse.durationFormatted, durationFormatted = networkResponse.durationFormatted,
responseByteSizeFormatted = null, responseByteSizeFormatted = null,
statusFormatted = networkResponse.statusFormatted,
) )
} }
is FloconNetworkCallDomainModel.Response.Success -> { is FloconNetworkCallDomainModel.Response.Success -> {
@ -74,6 +76,7 @@ fun FloconNetworkCallDomainModel.toEntity(
responseByteSizeFormatted = networkResponse.byteSizeFormatted, responseByteSizeFormatted = networkResponse.byteSizeFormatted,
responseError = null, responseError = null,
isImage = networkResponse.isImage, isImage = networkResponse.isImage,
statusFormatted = networkResponse.statusFormatted,
graphql = when (val s = networkResponse.specificInfos) { graphql = when (val s = networkResponse.specificInfos) {
is FloconNetworkCallDomainModel.Response.Success.SpecificInfos.GraphQl -> NetworkCallGraphQlResponseEmbedded( is FloconNetworkCallDomainModel.Response.Success.SpecificInfos.GraphQl -> NetworkCallGraphQlResponseEmbedded(
responseHttpCode = s.httpCode, responseHttpCode = s.httpCode,

View file

@ -58,6 +58,10 @@ data class FloconNetworkRequestEmbedded(
val requestByteSize: Long, val requestByteSize: Long,
val isMocked: Boolean, val isMocked: Boolean,
val domainFormatted: String, // for sorting & filtering
val methodFormatted: String, // for sorting & filtering
val queryFormatted: String, // for sorting & filtering
@Embedded(prefix = "graphql_") @Embedded(prefix = "graphql_")
val graphql: NetworkCallGraphQlRequestEmbedded?, val graphql: NetworkCallGraphQlRequestEmbedded?,
) )
@ -72,6 +76,7 @@ data class FloconNetworkResponseEmbedded(
val responseByteSizeFormatted: String?, val responseByteSizeFormatted: String?,
val responseError: String?, val responseError: String?,
val isImage: Boolean, val isImage: Boolean,
val statusFormatted: String, // for sorting & filtering
@Embedded(prefix = "graphql_") @Embedded(prefix = "graphql_")
val graphql: NetworkCallGraphQlResponseEmbedded?, val graphql: NetworkCallGraphQlResponseEmbedded?,

View file

@ -57,6 +57,16 @@ fun toDomain(
val requestSize = decoded.requestSize ?: 0L val requestSize = decoded.requestSize ?: 0L
val startTime = decoded.startTime!! val startTime = decoded.startTime!!
val specificInfos = when {
graphQl != null -> FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl(
query = graphQl.request.requestBody.query,
operationType = graphQl.request.operationType,
)
decoded.floconNetworkType == "grpc" -> FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc
else -> FloconNetworkCallDomainModel.Request.SpecificInfos.Http
}
val request = FloconNetworkCallDomainModel.Request( val request = FloconNetworkCallDomainModel.Request(
url = decoded.url!!, url = decoded.url!!,
startTime = startTime, startTime = startTime,
@ -67,15 +77,20 @@ fun toDomain(
byteSize = requestSize, byteSize = requestSize,
byteSizeFormatted = ByteFormatter.formatBytes(requestSize), byteSizeFormatted = ByteFormatter.formatBytes(requestSize),
isMocked = decoded.isMocked ?: false, isMocked = decoded.isMocked ?: false,
specificInfos = when { specificInfos = specificInfos,
graphQl != null -> FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl( domainFormatted = extractDomain(
query = graphQl.request.requestBody.query, requestUrl = decoded.url,
operationType = graphQl.request.operationType, specificInfos = specificInfos,
) ),
methodFormatted = extractMethod(
decoded.floconNetworkType == "grpc" -> FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc requestMethod = decoded.method,
else -> FloconNetworkCallDomainModel.Request.SpecificInfos.Http specificInfos = specificInfos,
}, ),
queryFormatted = extractQueryFormatted(
requestUrl = decoded.url,
requestMethod = decoded.method,
specificInfos = specificInfos,
),
) )
FloconNetworkCallDomainModel( FloconNetworkCallDomainModel(

View file

@ -0,0 +1,42 @@
package com.flocon.data.remote.network.mapper
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
import io.ktor.http.Url
internal fun extractDomain(
requestUrl: String,
specificInfos: FloconNetworkCallDomainModel.Request.SpecificInfos,
): String = when (specificInfos) {
is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl -> extractDomainAndPath(requestUrl)
is FloconNetworkCallDomainModel.Request.SpecificInfos.Http -> extractDomain(requestUrl)
is FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc -> requestUrl
}
private fun extractDomain(url: String): String {
// Parse the URL into a Url object
val parsedUrl = Url(url)
// Use host for the domain
val domainAndPath = parsedUrl.host
// The code below could also work, but host is more precise for the domain
// return parsedUrl.hostWithPort + parsedUrl.fullPath
return domainAndPath.removePrefix("www.")
}
private fun extractDomainAndPath(url: String): String {
// Parse the URL into a Url object
val parsedUrl = Url(url)
// Use host for the domain and encodedPathAndQuery for the path
val domainAndPath = parsedUrl.host + parsedUrl.encodedPathAndQuery
// The code below could also work, but host is more precise for the domain
// return parsedUrl.hostWithPort + parsedUrl.fullPath
return domainAndPath.removePrefix("www.")
}
internal fun extractPath(url: String): String {
val parsedUrl = Url(url)
return parsedUrl.encodedPathAndQuery
}

View file

@ -0,0 +1,14 @@
package com.flocon.data.remote.network.mapper
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
internal fun extractMethod(
requestMethod: String,
specificInfos: FloconNetworkCallDomainModel.Request.SpecificInfos,
): String = when (specificInfos) {
is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl -> specificInfos.operationType.lowercase()
is FloconNetworkCallDomainModel.Request.SpecificInfos.Http -> toHttpMethodUi(requestMethod)
is FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc -> "grpc"
}
private fun toHttpMethodUi(httpMethod: String) = httpMethod.lowercase()

View file

@ -0,0 +1,13 @@
package com.flocon.data.remote.network.mapper
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
internal fun extractQueryFormatted(
requestUrl: String,
requestMethod: String,
specificInfos: FloconNetworkCallDomainModel.Request.SpecificInfos,
): String = when (val s = specificInfos) {
is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl -> s.query
is FloconNetworkCallDomainModel.Request.SpecificInfos.Http -> extractPath(requestUrl)
is FloconNetworkCallDomainModel.Request.SpecificInfos.Grpc -> requestMethod
}

View file

@ -0,0 +1,25 @@
package com.flocon.data.remote.network.mapper
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
// be sure this keep aligned with the ui mapper
internal fun failureStatus() = "Exception"
internal fun extractStatus(specificInfos: FloconNetworkCallDomainModel.Response.Success.SpecificInfos): String =
when (val s = specificInfos) {
is FloconNetworkCallDomainModel.Response.Success.SpecificInfos.GraphQl -> toGraphQlNetworkStatus(isSuccess = s.isSuccess)
is FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Http -> toNetworkStatus(s.httpCode)
is FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Grpc -> toGrpcNetworkStatus(specificInfos)
}
private fun toNetworkStatus(code: Int): String = code.toString()
private fun toGraphQlNetworkStatus(isSuccess: Boolean): String =
if (isSuccess) "Success" else "Error"
private fun toGrpcNetworkStatus(
specificInfos: FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Grpc
): String {
return specificInfos.grpcStatus
}

View file

@ -1,5 +1,7 @@
package com.flocon.data.remote.network.models package com.flocon.data.remote.network.models
import com.flocon.data.remote.network.mapper.failureStatus
import com.flocon.data.remote.network.mapper.extractStatus
import io.github.openflocon.domain.common.ByteFormatter import io.github.openflocon.domain.common.ByteFormatter
import io.github.openflocon.domain.common.time.formatDuration import io.github.openflocon.domain.common.time.formatDuration
import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel
@ -59,27 +61,32 @@ internal fun FloconNetworkResponseDataModel.toDomain(): FloconNetworkResponseOnl
durationMs = durationMs, durationMs = durationMs,
issue = responseError, issue = responseError,
durationFormatted = durationFormatted, durationFormatted = durationFormatted,
statusFormatted = failureStatus(),
) )
} else { } else {
val responseSize = responseSize ?: 0L val responseSize = responseSize ?: 0L
val specificInfos = when (floconNetworkType) {
"grpc" -> FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Grpc(
grpcStatus = responseGrpcStatus!!,
)
// otherwise tread like http
else -> FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Http(
httpCode = responseHttpCode!!,
)
}
FloconNetworkCallDomainModel.Response.Success( FloconNetworkCallDomainModel.Response.Success(
durationMs = durationMs, durationMs = durationMs,
contentType = responseContentType, contentType = responseContentType,
body = responseBody, body = responseBody,
headers = responseHeaders.orEmpty(), headers = responseHeaders.orEmpty(),
byteSize = responseSize, byteSize = responseSize,
specificInfos = when (floconNetworkType) { specificInfos = specificInfos,
"grpc" -> FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Grpc(
grpcStatus = responseGrpcStatus!!,
)
// otherwise tread like http
else -> FloconNetworkCallDomainModel.Response.Success.SpecificInfos.Http(
httpCode = responseHttpCode!!,
)
},
isImage = isImage, isImage = isImage,
durationFormatted = durationFormatted, durationFormatted = durationFormatted,
byteSizeFormatted = ByteFormatter.formatBytes(responseSize) byteSizeFormatted = ByteFormatter.formatBytes(responseSize),
statusFormatted = extractStatus(specificInfos)
) )
} }
FloconNetworkResponseOnlyDomainModel( FloconNetworkResponseOnlyDomainModel(

View file

@ -14,12 +14,15 @@ data class FloconNetworkCallDomainModel(
val startTime: Long, val startTime: Long,
val startTimeFormatted: String, val startTimeFormatted: String,
val method: String, val method: String,
val methodFormatted: String,
val headers: Map<String, String>, val headers: Map<String, String>,
val body: String?, val body: String?,
val byteSize: Long, val byteSize: Long,
val byteSizeFormatted: String, val byteSizeFormatted: String,
val isMocked: Boolean, val isMocked: Boolean,
val specificInfos: SpecificInfos, val specificInfos: SpecificInfos,
val domainFormatted: String, // extracted from url
val queryFormatted: String, // extracted from url
) { ) {
sealed interface SpecificInfos { sealed interface SpecificInfos {
data object Http: SpecificInfos data object Http: SpecificInfos
@ -35,6 +38,7 @@ data class FloconNetworkCallDomainModel(
val durationMs: Double val durationMs: Double
val durationFormatted: String val durationFormatted: String
val statusFormatted: String // extracted from response
data class Success( data class Success(
override val durationMs: Double, override val durationMs: Double,
@ -45,7 +49,8 @@ data class FloconNetworkCallDomainModel(
val byteSize: Long, val byteSize: Long,
val byteSizeFormatted: String, val byteSizeFormatted: String,
val specificInfos: SpecificInfos, val specificInfos: SpecificInfos,
val isImage: Boolean val isImage: Boolean,
override val statusFormatted: String, // extracted from response
) : Response { ) : Response {
sealed interface SpecificInfos { sealed interface SpecificInfos {
data class Http( data class Http(
@ -64,6 +69,7 @@ data class FloconNetworkCallDomainModel(
override val durationMs: Double, override val durationMs: Double,
override val durationFormatted: String, override val durationFormatted: String,
val issue: String, val issue: String,
override val statusFormatted: String, // extracted from response
) : Response ) : Response
} }
} }