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

chore: send telemetry when connecting to a cluster INTELLIJ-13 #9

Merged
merged 19 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ MongoDB plugin for IntelliJ IDEA.
## [Unreleased]

### Added
* [INTELLIJ-12](https://jira.mongodb.org/browse/INTELLIJ-11): Notify users about telemetry, and allow them to disable it.
* [INTELLIJ-13](https://jira.mongodb.org/browse/INTELLIJ-13): Send telemetry when successfully connected to a MongoDB Cluster.
* [INTELLIJ-12](https://jira.mongodb.org/browse/INTELLIJ-12): Notify users about telemetry, and allow them to disable it.
* [INTELLIJ-11](https://jira.mongodb.org/browse/INTELLIJ-11): Flush pending analytics events before closing the IDE.

### Changed
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ subprojects {
compileOnly(rootProject.libs.kotlin.coroutines.core)
compileOnly(rootProject.libs.kotlin.reflect)
testImplementation(rootProject.libs.testing.jupiter.engine)
testImplementation(rootProject.libs.testing.jupiter.params)
testImplementation(rootProject.libs.testing.jupiter.vintage.engine)
testImplementation(rootProject.libs.testing.mockito.core)
testImplementation(rootProject.libs.testing.mockito.kotlin)
Expand Down
4 changes: 4 additions & 0 deletions gradle/diktat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@
- name: LOCAL_VARIABLE_EARLY_DECLARATION # Allow declaring variables at the beginning of a function if they are mutable
enabled: false
- name: USE_DATA_CLASS # Do not force to use data classes, some intellij components won't work if they are data classes
enabled: false
- name: CLASS_SHOULD_NOT_BE_ABSTRACT # Allow abstract classes even if they don't have abstract methods
enabled: false
- name: BACKTICKS_PROHIBITED # does not work with @ParameterizedTest
enabled: false
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ owasp-encoder = { group = "org.owasp.encoder", name = "encoder", version.ref = "
######################################################
## Testing Libraries.
testing-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" }
testing-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "jupiter" }
testing-jupiter-vintage-engine = { group = "org.junit.vintage", name = "junit-vintage-engine", version.ref = "jupiter" }
testing-jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" }
testing-mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
Expand Down
42 changes: 23 additions & 19 deletions packages/jetbrains-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ dependencies {
testImplementation(libs.testing.remoteRobotDeps.okHttp)
testImplementation(libs.testing.remoteRobotDeps.retrofit)
testImplementation(libs.testing.remoteRobotDeps.retrofitGson)
testImplementation(libs.testing.testContainers.core)
testImplementation(libs.testing.testContainers.mongodb)
testImplementation(libs.testing.testContainers.jupiter)
}


jmh {
benchmarkMode.set(listOf("thrpt"))
iterations.set(10)
Expand Down Expand Up @@ -116,15 +118,14 @@ tasks {
"ide.show.tips.on.startup.default.value" to false,
"idea.is.internal" to true,
"robot-server.port" to "8082",
)
),
)
}

downloadRobotServerPlugin {
version.set(libs.versions.intellij.remoteRobot)
}


withType<ProcessResources> {
dependsOn("buildProperties")
}
Expand All @@ -134,15 +135,17 @@ tasks {
untilBuild.set("242.*")
version.set(rootProject.version.toString())

changeNotes.set(provider {
changelog.renderItem(
changelog
.getUnreleased()
.withHeader(false)
.withEmptySections(false),
Changelog.OutputType.HTML
)
})
changeNotes.set(
provider {
changelog.renderItem(
changelog
.getUnreleased()
.withHeader(false)
.withEmptySections(false),
Changelog.OutputType.HTML,
)
},
)
}

signPlugin {
Expand All @@ -152,11 +155,12 @@ tasks {
}

publishPlugin {
channels = when (System.getenv("JB_PUBLISH_CHANNEL")) {
"ga" -> listOf("Stable")
"beta" -> listOf("beta")
else -> listOf("eap")
}
channels =
when (System.getenv("JB_PUBLISH_CHANNEL")) {
"ga" -> listOf("Stable")
"beta" -> listOf("beta")
else -> listOf("eap")
}
token.set(System.getenv("JB_PUBLISH_TOKEN"))
}
}
Expand All @@ -169,12 +173,12 @@ changelog {
introduction.set(
"""
MongoDB plugin for IntelliJ IDEA.
""".trimIndent()
""".trimIndent(),
)
itemPrefix.set("-")
keepUnreleasedSection.set(true)
unreleasedTerm.set("[Unreleased]")
groups.set(listOf("Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"))
lineSeparator.set("\n")
combinePreReleases.set(true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@ import com.intellij.openapi.components.Service
internal class LogMessageBuilder(private val gson: Gson, message: String) {
private val properties: MutableMap<String, Any> = mutableMapOf("message" to message)

fun put(key: String, value: Any): LogMessageBuilder {
fun put(
key: String,
value: Any,
): LogMessageBuilder {
properties[key] = value
return this
}

inline fun <reified T : TelemetryEvent> mergeTelemetryEventProperties(event: T): LogMessageBuilder {
put("event", event.name)
properties.putAll(event.properties.mapKeys { it.key.publicName })
return this
}

fun build(): String = gson.toJson(properties)
}

Expand All @@ -50,9 +59,10 @@ internal class LogMessage {
private val gson = GsonBuilder().generateNonExecutableJson().disableJdkUnsafe().create()

fun message(key: String): LogMessageBuilder {
val runtimeInformationService = ApplicationManager.getApplication().getService(
RuntimeInformationService::class.java
)
val runtimeInformationService =
ApplicationManager.getApplication().getService(
RuntimeInformationService::class.java,
)
val runtimeInformation = runtimeInformationService.get()

return LogMessageBuilder(gson, key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ package com.mongodb.jbplugin.observability
*/
internal enum class TelemetryProperty(val publicName: String) {
IS_ATLAS("is_atlas"),
IS_LOCAL_ATLAS("is_local_atlas"),
IS_LOCALHOST("is_localhost"),
IS_ENTERPRISE("is_enterprise"),
IS_GENUINE("is_genuine"),
NON_GENUINE_SERVER_NAME("non_genuine_server_name"),
SERVER_OS_FAMILY("server_os_family"),
VERSION("version"),
;
;
}

/**
Expand All @@ -35,14 +36,14 @@ internal enum class TelemetryProperty(val publicName: String) {
*/
internal sealed class TelemetryEvent(
internal val name: String,
internal val properties: Map<TelemetryProperty, Any>
internal val properties: Map<TelemetryProperty, Any>,
) {
/**
* Represents the event that is emitted when the plugin is started.
*/
internal data object PluginActivated : TelemetryEvent(
data object PluginActivated : TelemetryEvent(
name = "plugin-activated",
properties = emptyMap()
properties = emptyMap(),
)

/**
Expand All @@ -56,25 +57,29 @@ internal sealed class TelemetryEvent(
* @param nonGenuineServerName
* @param serverOsFamily
* @param version
* @param isLocalAtlas
*/
internal class NewConnection(
class NewConnection(
isAtlas: Boolean,
isLocalAtlas: Boolean,
isLocalhost: Boolean,
isEnterprise: Boolean,
isGenuine: Boolean,
nonGenuineServerName: String?,
serverOsFamily: String?,
version: String?
version: String?,
) : TelemetryEvent(
name = "new-connection",
properties = mapOf(
TelemetryProperty.IS_ATLAS to isAtlas,
TelemetryProperty.IS_LOCALHOST to isLocalhost,
TelemetryProperty.IS_ENTERPRISE to isEnterprise,
TelemetryProperty.IS_GENUINE to isGenuine,
TelemetryProperty.NON_GENUINE_SERVER_NAME to (nonGenuineServerName ?: ""),
TelemetryProperty.SERVER_OS_FAMILY to (serverOsFamily ?: ""),
TelemetryProperty.VERSION to (version ?: "")
name = "new-connection",
properties =
mapOf(
TelemetryProperty.IS_ATLAS to isAtlas,
TelemetryProperty.IS_LOCAL_ATLAS to isLocalAtlas,
TelemetryProperty.IS_LOCALHOST to isLocalhost,
TelemetryProperty.IS_ENTERPRISE to isEnterprise,
TelemetryProperty.IS_GENUINE to isGenuine,
TelemetryProperty.NON_GENUINE_SERVER_NAME to (nonGenuineServerName ?: ""),
TelemetryProperty.SERVER_OS_FAMILY to (serverOsFamily ?: ""),
TelemetryProperty.VERSION to (version ?: ""),
),
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.mongodb.jbplugin.observability.probe

import com.intellij.database.console.client.VisibleDatabaseSessionClient
import com.intellij.database.console.session.DatabaseSession
import com.intellij.database.console.session.DatabaseSessionStateListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider
import com.mongodb.jbplugin.accessadapter.slice.BuildInfo
import com.mongodb.jbplugin.observability.LogMessage
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryService

private val logger: Logger = logger<NewConnectionActivatedProbe>()

/** This probe is emitted when a new connection happens through DataGrip. It connects
* directly into DataGrip extension points, so it shouldn't be instantiated directly.
*/
class NewConnectionActivatedProbe : DatabaseSessionStateListener {
override fun clientAttached(client: VisibleDatabaseSessionClient) {
}

override fun clientDetached(client: VisibleDatabaseSessionClient) {
}

override fun clientReattached(
client: VisibleDatabaseSessionClient,
source: DatabaseSession,
target: DatabaseSession,
) {
}

override fun renamed(session: DatabaseSession) {
}

override fun connected(session: DatabaseSession) {
val application = ApplicationManager.getApplication()
val logMessage = application.getService(LogMessage::class.java)
val telemetryService = application.getService(TelemetryService::class.java)

val readModelProvider = session.project.getService(DataGripBasedReadModelProvider::class.java)
val dataSource = session.connectionPoint.dataSource
val serverInfo = readModelProvider.slice(dataSource, BuildInfo.Slice)

val newConnectionEvent =
TelemetryEvent.NewConnection(
isAtlas = serverInfo.isAtlas,
isLocalAtlas = serverInfo.isLocalAtlas,
isLocalhost = serverInfo.isLocalhost,
isEnterprise = serverInfo.isEnterprise,
isGenuine = serverInfo.isGenuineMongoDb,
nonGenuineServerName = serverInfo.nonGenuineVariant,
serverOsFamily = serverInfo.buildEnvironment["target_os"],
version = serverInfo.version,
)

telemetryService.sendEvent(newConnectionEvent)

logger.info(
logMessage.message("New connection activated")
.mergeTelemetryEventProperties(newConnectionEvent)
.build(),
)
}

override fun disconnected(session: DatabaseSession) {
}

override fun stateChanged(event: DatabaseSessionStateListener.ChangeEvent) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@
</extensions>
<applicationListeners>
</applicationListeners>
<projectListeners>
<listener class="com.mongodb.jbplugin.observability.probe.NewConnectionActivatedProbe"
topic="com.intellij.database.console.session.DatabaseSessionStateListener"
activeInHeadlessMode="true"
activeInTestMode="true"/>
</projectListeners>
</idea-plugin>
Loading
Loading