Skip to content

Commit

Permalink
[IJ plugin] Use apollo-debug-server to retrieve normalized caches (#5348
Browse files Browse the repository at this point in the history
)
  • Loading branch information
BoD authored Nov 6, 2023
1 parent 347baf3 commit 9c1fd7b
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 45 deletions.
20 changes: 20 additions & 0 deletions intellij-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ plugins {
id("org.jetbrains.kotlin.jvm")
id("org.jetbrains.intellij")
id("maven-publish")
alias(libs.plugins.apollo.published)
}

commonSetup()
Expand Down Expand Up @@ -219,6 +220,25 @@ dependencies {
implementation(project(":apollo-tooling"))
implementation(project(":apollo-normalized-cache-sqlite"))
implementation(libs.sqlite.jdbc)
implementation(libs.apollo.runtime.published)
}

fun isSnapshotBuild() = System.getenv("COM_APOLLOGRAPHQL_IJ_PLUGIN_SNAPSHOT").toBoolean()

apollo {
service("apolloDebug") {
packageName.set("com.apollographql.apollo3.debug")
schemaFile.set(file("../libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls"))
introspection {
endpointUrl.set("http://localhost:12200/")
schemaFile.set(file("../libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls"))
}
}
}

// We're using project(":apollo-gradle-plugin-external") and the published "apollo-runtime" which do not have the same version
tasks.configureEach {
if (name == "checkApolloVersions") {
enabled = false
}
}
24 changes: 24 additions & 0 deletions intellij-plugin/src/main/graphql/operations.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
query GetApolloClients {
apolloClients {
id
displayName
normalizedCaches {
id
displayName
recordCount
}
}
}

query GetNormalizedCache($apolloClientId: ID!, $normalizedCacheId: ID!) {
apolloClient(id: $apolloClientId) {
normalizedCache(id: $normalizedCacheId) {
displayName
records {
key
size
fields
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.apollographql.ijplugin.apollodebugserver

import com.android.ddmlib.IDevice
import com.android.tools.idea.adb.AdbShellCommandsUtil
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.debug.GetApolloClientsQuery
import com.apollographql.apollo3.debug.GetNormalizedCacheQuery
import com.apollographql.ijplugin.util.logw
import java.io.Closeable

private const val SOCKET_NAME_PREFIX = "apollo_debug_"
private const val BASE_PORT = 12200

class ApolloDebugClient(
private val device: IDevice,
val packageName: String,
) : Closeable {
companion object {
private var uniquePort = 0

private fun getUniquePort(): Int {
return BASE_PORT + uniquePort++
}

private fun IDevice.getApolloDebugPackageList(): Result<List<String>> {
val commandResult = runCatching {
AdbShellCommandsUtil.create(this).executeCommandBlocking("cat /proc/net/unix | grep $SOCKET_NAME_PREFIX | cat")
}
if (commandResult.isFailure) {
val e = commandResult.exceptionOrNull()!!
logw(e, "Could not list Apollo Debug packages")
return Result.failure(e)
}
val result = commandResult.getOrThrow()
if (result.isError) {
val message = "Could not list Apollo Debug packages: ${result.output.joinToString()}"
logw(message)
return Result.failure(Exception(message))
}
// Results are in the form:
// 0000000000000000: 00000002 00000000 00010000 0001 01 116651 @apollo_debug_com.example.myapplication
return Result.success(
result.output
.filter { it.contains(SOCKET_NAME_PREFIX) }
.map { it.substringAfterLast(SOCKET_NAME_PREFIX) }
.sorted()
)
}

fun IDevice.getApolloDebugClients(): Result<List<ApolloDebugClient>> {
return getApolloDebugPackageList().map { packageNames ->
packageNames.map { packageName ->
ApolloDebugClient(this, packageName)
}
}
}
}

private val port = getUniquePort()
private var hasPortForward: Boolean = false

private val apolloClient = ApolloClient.Builder()
.serverUrl("http://localhost:$port")
.build()

private fun createPortForward() {
device.createForward(port, "$SOCKET_NAME_PREFIX$packageName", IDevice.DeviceUnixSocketNamespace.ABSTRACT)
hasPortForward = true
}

private fun removePortForward() {
device.removeForward(port)
hasPortForward = false
}

private fun ensurePortForward() {
if (!hasPortForward) {
createPortForward()
}
}

suspend fun getApolloClients(): Result<List<GetApolloClientsQuery.ApolloClient>> = runCatching {
ensurePortForward()
apolloClient.query(GetApolloClientsQuery()).execute().dataOrThrow().apolloClients
}

suspend fun getNormalizedCache(
apolloClientId: String,
normalizedCacheId: String,
): Result<GetNormalizedCacheQuery.NormalizedCache> = runCatching {
ensurePortForward()
apolloClient.query(GetNormalizedCacheQuery(apolloClientId, normalizedCacheId)).execute().dataOrThrow().apolloClient?.normalizedCache
?: error("No normalized cache returned by server")
}

override fun close() {
if (hasPortForward) {
removePortForward()
}
apolloClient.close()
}
}

val String.normalizedCacheSimpleName: String
get() = when (this) {
"com.apollographql.apollo3.cache.normalized.api.MemoryCache" -> "MemoryCache"
"com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCache" -> "SqlNormalizedCache"
else -> this
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ data class NormalizedCache(
data class Record(
val key: String,
val fields: List<Field>,
val size: Int,
)

data class Field(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.apollographql.ijplugin.normalizedcache

import com.apollographql.ijplugin.ApolloBundle
import com.apollographql.ijplugin.apollodebugserver.ApolloDebugClient
import com.apollographql.ijplugin.apollodebugserver.normalizedCacheSimpleName
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.BooleanValue
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.CompositeValue
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.ListValue
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.Null
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.NumberValue
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.Reference
import com.apollographql.ijplugin.normalizedcache.NormalizedCache.FieldValue.StringValue
import com.apollographql.ijplugin.normalizedcache.provider.ApolloDebugNormalizedCacheProvider
import com.apollographql.ijplugin.normalizedcache.provider.DatabaseNormalizedCacheProvider
import com.apollographql.ijplugin.telemetry.TelemetryEvent
import com.apollographql.ijplugin.telemetry.telemetryService
import com.apollographql.ijplugin.util.logw
Expand Down Expand Up @@ -64,6 +68,7 @@ import com.intellij.util.ui.ColumnInfo
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.ListUiUtil
import com.intellij.util.ui.UIUtil
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.sqlite.SQLiteException
import java.awt.Color
Expand Down Expand Up @@ -136,7 +141,7 @@ class NormalizedCacheToolWindowFactory : ToolWindowFactory, DumbAware, Disposabl
class NormalizedCacheWindowPanel(
private val project: Project,
private val setTabName: (tabName: String) -> Unit,
) : SimpleToolWindowPanel(false, true) {
) : SimpleToolWindowPanel(false, true), Disposable {
private lateinit var normalizedCache: NormalizedCache

private lateinit var recordList: JBList<NormalizedCache.Record>
Expand All @@ -148,6 +153,11 @@ class NormalizedCacheWindowPanel(
private val history = History<NormalizedCache.Record>()
private var updateHistory = true

private var apolloDebugClient: ApolloDebugClient? = null
private var apolloDebugApolloClientId: String? = null
private var apolloDebugNormalizedCacheId: String? = null
private var isRefreshing = false

init {
setContent(createEmptyContent())
}
Expand All @@ -162,11 +172,12 @@ class NormalizedCacheWindowPanel(
emptyText.appendLine(ApolloBundle.message("normalizedCacheViewer.empty.pullFromDevice"), SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES) {
PullFromDeviceDialog(
project,
onFilePullError = { throwable ->
onPullError = { throwable ->
showNotification(project, title = ApolloBundle.message("normalizedCacheViewer.pullFromDevice.pull.error"), content = throwable.message
?: "", type = NotificationType.ERROR)
},
onFilePullSuccess = ::openFile,
onApolloDebugCacheSelected = ::openApolloDebugNormalizedCache,
).show()
}
}
Expand Down Expand Up @@ -260,6 +271,25 @@ class NormalizedCacheWindowPanel(
add(CommonActionsManager.getInstance().createCollapseAllAction(fieldTreeExpander, this@NormalizedCacheWindowPanel).apply {
getTemplatePresentation().setDescription(ApolloBundle.message("normalizedCacheViewer.toolbar.collapseAll"))
})
addSeparator()
add(object : DumbAwareAction(ApolloBundle.messagePointer("normalizedCacheViewer.toolbar.refresh"), AllIcons.Actions.Refresh) {
init {
ActionUtil.copyFrom(this, IdeActions.ACTION_REFRESH)
registerCustomShortcutSet(this.shortcutSet, this@NormalizedCacheWindowPanel)
}

override fun actionPerformed(e: AnActionEvent) {
refreshApolloDebugNormalizedCache()
}

override fun update(e: AnActionEvent) {
e.presentation.isVisible = apolloDebugNormalizedCacheId != null && apolloDebugClient != null
e.presentation.isEnabled = !isRefreshing
}

override fun getActionUpdateThread() = ActionUpdateThread.BGT
})

}

val actionToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, group, false)
Expand Down Expand Up @@ -550,9 +580,76 @@ class NormalizedCacheWindowPanel(
showNotification(project, title = ApolloBundle.message("normalizedCacheViewer.openFileError.title"), content = details, type = NotificationType.ERROR)
}

private fun openApolloDebugNormalizedCache(apolloDebugClient: ApolloDebugClient, apolloClientId: String, normalizedCacheId: String) {
project.telemetryService.logEvent(TelemetryEvent.ApolloIjNormalizedCacheOpenApolloDebugCache())
setContent(createLoadingContent())
object : Task.Backgroundable(
project,
ApolloBundle.message("normalizedCacheViewer.loading.message"),
false,
) {
override fun run(indicator: ProgressIndicator) {
var tabName = ""
val normalizedCacheResult = runBlocking {
apolloDebugClient.getNormalizedCache(apolloClientId = apolloClientId, normalizedCacheId = normalizedCacheId)
}.mapCatching { apolloDebugNormalizedCache ->
val tabNamePrefix = apolloClientId.takeIf { it != "client" }?.let { "$it - " } ?: ""
tabName = tabNamePrefix + apolloDebugNormalizedCache.displayName.normalizedCacheSimpleName
ApolloDebugNormalizedCacheProvider().provide(apolloDebugNormalizedCache).getOrThrow()
}
invokeLater {
if (normalizedCacheResult.isFailure) {
showOpenFileError(normalizedCacheResult.exceptionOrNull()!!)
setContent(createEmptyContent())
return@invokeLater
}
this@NormalizedCacheWindowPanel.apolloDebugClient = apolloDebugClient
this@NormalizedCacheWindowPanel.apolloDebugApolloClientId = apolloClientId
this@NormalizedCacheWindowPanel.apolloDebugNormalizedCacheId = normalizedCacheId
normalizedCache = normalizedCacheResult.getOrThrow().sorted()
setContent(createNormalizedCacheContent())
toolbar = createToolbar()
setTabName(tabName)
}
}
}.queue()
}

private fun refreshApolloDebugNormalizedCache() {
object : Task.Backgroundable(
project,
ApolloBundle.message("normalizedCacheViewer.loading.message"),
false,
) {
override fun run(indicator: ProgressIndicator) {
isRefreshing = true
val normalizedCacheResult = runBlocking {
apolloDebugClient!!.getNormalizedCache(apolloClientId = apolloDebugApolloClientId!!, normalizedCacheId = apolloDebugNormalizedCacheId!!)
}.mapCatching { apolloDebugNormalizedCache ->
ApolloDebugNormalizedCacheProvider().provide(apolloDebugNormalizedCache).getOrThrow()
}
isRefreshing = false
invokeLater {
if (normalizedCacheResult.isFailure) {
showOpenFileError(normalizedCacheResult.exceptionOrNull()!!)
return@invokeLater
}
normalizedCache = normalizedCacheResult.getOrThrow().sorted()
setContent(createNormalizedCacheContent())
toolbar = null
toolbar = createToolbar()
}
}
}.queue()
}

private class NormalizedCacheFieldTreeNode(val field: NormalizedCache.Field) : DefaultMutableTreeNode() {
init {
userObject = field.name
}
}

override fun dispose() {
apolloDebugClient?.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ fun IDevice.getDatabaseList(packageName: String, databasesDir: String): Result<L
return Result.success(result.output.filter { it.isDatabaseFileName() }.sorted())
}


fun pullFileAsync(
project: Project,
device: IDevice,
Expand Down Expand Up @@ -100,7 +99,7 @@ fun pullFileAsync(

private fun pullFile(device: IDevice, appPackageName: String, remoteDirName: String, remoteFileName: String): Result<File> {
val remoteFilePath = "$remoteDirName/$remoteFileName"
val localFile = File.createTempFile(remoteFileName.substringBeforeLast(".")+"-tmp", ".db")
val localFile = File.createTempFile(remoteFileName.substringBeforeLast(".") + "-tmp", ".db")
logd("Pulling $remoteFilePath to ${localFile.absolutePath}")
val intermediateRemoteFilePath = "/data/local/tmp/${localFile.name}"
val shellCommandsUtil = AdbShellCommandsUtil.create(device)
Expand Down
Loading

0 comments on commit 9c1fd7b

Please sign in to comment.