feat(intellij): add anonymous usage event IntelliJInitFailed. (#365)
* feat(intellij): add anonymous usage event plugin activated. * fix(intellij): post event IntelliJinitFailed only when failed instead of PluginActivated.release-0.0
parent
b1ad936033
commit
bdda8a534f
|
|
@ -1,11 +1,11 @@
|
|||
# Tabby Plugin for IntelliJ Platform
|
||||
|
||||
## Requirements
|
||||
- Tabby plugin is compatiple with all IntelliJ Platform IDEs build 2022.2.5 or later versions, including Idea, Pycharm, Android Studio and so on.
|
||||
- Tabby plugin requires [Node.js](https://nodejs.org) 16.0+ installed and added into `PATH` enviroment variable.
|
||||
- Tabby plugin works with all IntelliJ Platform IDEs that have build 2022.2.5 or later versions, such as Idea, PyCharm, Android Studio, and more.
|
||||
- Tabby plugin requires [Node.js](https://nodejs.org) 16.0+ to be installed and added into the `PATH` environment variable.
|
||||
|
||||
## Installation
|
||||
Tabby plugin could be installed from the IntelliJ Platform [plugin marketplace](https://plugins.jetbrains.com/plugin/22379-tabby).
|
||||
You can install Tabby plugin from the IntelliJ Platform [plugin marketplace](https://plugins.jetbrains.com/plugin/22379-tabby).
|
||||
|
||||
## Development and Build
|
||||
Please clone this directory and open it with IntelliJ Idea.
|
||||
To develop and build Tabby plugin, please clone this directory and open it with IntelliJ Idea.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -10,7 +10,6 @@ import com.intellij.execution.process.ProcessAdapter
|
|||
import com.intellij.execution.process.ProcessEvent
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.openapi.application.ApplicationInfo
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import com.intellij.openapi.util.Key
|
||||
|
|
@ -26,8 +25,8 @@ import java.io.OutputStreamWriter
|
|||
class Agent : ProcessAdapter() {
|
||||
private val logger = Logger.getInstance(Agent::class.java)
|
||||
private val gson = Gson()
|
||||
private val process: KillableProcessHandler
|
||||
private val streamWriter: OutputStreamWriter
|
||||
private lateinit var process: KillableProcessHandler
|
||||
private lateinit var streamWriter: OutputStreamWriter
|
||||
|
||||
enum class Status {
|
||||
NOT_INITIALIZED,
|
||||
|
|
@ -41,7 +40,9 @@ class Agent : ProcessAdapter() {
|
|||
private val authRequiredEventFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
val authRequiredEvent = authRequiredEventFlow.asSharedFlow()
|
||||
|
||||
init {
|
||||
open class AgentException(message: String) : Exception(message)
|
||||
|
||||
fun open() {
|
||||
logger.info("Environment variables: PATH: ${EnvironmentUtil.getValue("PATH")}")
|
||||
|
||||
val node = PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("node")
|
||||
|
|
@ -49,7 +50,7 @@ class Agent : ProcessAdapter() {
|
|||
logger.info("Node bin path: ${node.absolutePath}")
|
||||
} else {
|
||||
logger.error("Node bin not found")
|
||||
throw Error("Node bin not found")
|
||||
throw AgentException("Node bin not found")
|
||||
}
|
||||
|
||||
val script =
|
||||
|
|
@ -59,7 +60,7 @@ class Agent : ProcessAdapter() {
|
|||
logger.info("Node script path: ${script.absolutePath}")
|
||||
} else {
|
||||
logger.error("Node script not found")
|
||||
throw Error("Node script not found")
|
||||
throw AgentException("Node script not found")
|
||||
}
|
||||
|
||||
val cmd = GeneralCommandLine(node.absolutePath, script.absolutePath)
|
||||
|
|
@ -97,15 +98,12 @@ class Agent : ProcessAdapter() {
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun initialize(config: Config): Boolean {
|
||||
val appInfo = ApplicationInfo.getInstance().fullApplicationName
|
||||
val pluginId = "com.tabbyml.intellij-tabby"
|
||||
val pluginVersion = PluginManagerCore.getPlugin(PluginId.getId(pluginId))?.version
|
||||
suspend fun initialize(config: Config, client: String): Boolean {
|
||||
return request(
|
||||
"initialize", listOf(
|
||||
mapOf(
|
||||
"config" to config,
|
||||
"client" to "$appInfo $pluginId $pluginVersion",
|
||||
"client" to client,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -142,8 +140,11 @@ class Agent : ProcessAdapter() {
|
|||
@SerializedName("choice_index") val choiceIndex: Int,
|
||||
) {
|
||||
enum class EventType {
|
||||
@SerializedName("view") VIEW,
|
||||
@SerializedName("select") SELECT,
|
||||
@SerializedName("view")
|
||||
VIEW,
|
||||
|
||||
@SerializedName("select")
|
||||
SELECT,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,30 @@
|
|||
package com.tabbyml.intellijtabby.agent
|
||||
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.lang.Language
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.notification.Notifications
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.ApplicationInfo
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.tabbyml.intellijtabby.actions.OpenAuthPage
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import com.tabbyml.intellijtabby.usage.AnonymousUsageLogger
|
||||
import io.ktor.util.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -36,12 +38,23 @@ class AgentService : Disposable {
|
|||
|
||||
init {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
val anonymousUsageLogger = service<AnonymousUsageLogger>()
|
||||
scope.launch {
|
||||
val appInfo = ApplicationInfo.getInstance().fullApplicationName
|
||||
val pluginId = "com.tabbyml.intellij-tabby"
|
||||
val pluginVersion = PluginManagerCore.getPlugin(PluginId.getId(pluginId))?.version
|
||||
val client = "$appInfo $pluginId $pluginVersion"
|
||||
|
||||
try {
|
||||
agent.initialize(createAgentConfig(settings.data))
|
||||
agent.open()
|
||||
agent.initialize(createAgentConfig(settings.data), client)
|
||||
logger.info("Agent init done.")
|
||||
} catch (e: Error) {
|
||||
} catch (e: Exception) {
|
||||
logger.error("Agent init failed: $e")
|
||||
anonymousUsageLogger.event("IntelliJInitFailed", mapOf(
|
||||
"client" to client,
|
||||
"error" to e.stackTraceToString()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tabbyml.intellijtabby.usage
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
|
||||
@Service
|
||||
class AnonymousUsageLogger {
|
||||
private val logger = Logger.getInstance(AnonymousUsageLogger::class.java)
|
||||
private val gson = Gson()
|
||||
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
private lateinit var anonymousId: String
|
||||
private val disabled: Boolean
|
||||
get() {
|
||||
return service<ApplicationSettingsState>().isAnonymousUsageTrackingDisabled
|
||||
}
|
||||
private val initialized = MutableStateFlow(false)
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
try {
|
||||
val home = System.getProperty("user.home")
|
||||
logger.info("User home: $home")
|
||||
val datafile = Path(home).resolve(".tabby/agent/data.json").toFile()
|
||||
var data: Map<*, *>? = null
|
||||
try {
|
||||
val dataJson = datafile.inputStream().bufferedReader().use { it.readText() }
|
||||
data = gson.fromJson(dataJson, Map::class.java)
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to load anonymous ID: ${e.message}")
|
||||
}
|
||||
if (data?.get("anonymousId") != null) {
|
||||
anonymousId = data["anonymousId"].toString()
|
||||
logger.info("Saved anonymous ID: $anonymousId")
|
||||
} else {
|
||||
anonymousId = UUID.randomUUID().toString()
|
||||
val newData = data?.toMutableMap() ?: mutableMapOf()
|
||||
newData["anonymousId"] = anonymousId
|
||||
val newDataJson = gson.toJson(newData)
|
||||
datafile.writeText(newDataJson)
|
||||
logger.info("Create new anonymous ID: $anonymousId")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed when init anonymous ID: ${e.message}")
|
||||
anonymousId = UUID.randomUUID().toString()
|
||||
} finally {
|
||||
initialized.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UsageRequest(
|
||||
val distinctId: String,
|
||||
val event: String,
|
||||
val properties: Map<String, String>,
|
||||
)
|
||||
|
||||
suspend fun event(event: String, properties: Map<String, String>) {
|
||||
initialized.first { it }
|
||||
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
val request = UsageRequest(
|
||||
distinctId = anonymousId,
|
||||
event = event,
|
||||
properties = properties,
|
||||
)
|
||||
val requestString = gson.toJson(request)
|
||||
|
||||
withContext(scope.coroutineContext) {
|
||||
try {
|
||||
val connection = URL(ENDPOINT).openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.setRequestProperty("Content-Type", "application/json")
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.doInput = true
|
||||
connection.doOutput = true
|
||||
|
||||
val outputStreamWriter = OutputStreamWriter(connection.outputStream)
|
||||
outputStreamWriter.write(requestString)
|
||||
outputStreamWriter.flush()
|
||||
|
||||
val responseCode = connection.responseCode
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
logger.info("Usage event sent successfully.")
|
||||
} else {
|
||||
logger.error("Usage event failed to send: $responseCode")
|
||||
}
|
||||
connection.disconnect()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Usage event failed to send: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ENDPOINT = "https://app.tabbyml.com/api/usage"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue