Skip to content

Commit

Permalink
feat(telemetry): track when the run query button is clicked INTELLIJ-…
Browse files Browse the repository at this point in the history
…178 (#114)
  • Loading branch information
kmruiz authored Jan 10, 2025
1 parent 517c061 commit d3b4fa3
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ MongoDB plugin for IntelliJ IDEA.
## [Unreleased]

### Added
* [INTELLIJ-178](https://jira.mongodb.org/browse/INTELLIJ-178) Telemetry when the Run Query button is clicked.
It can be disabled in the Plugin settings.
* [INTELLIJ-153](https://jira.mongodb.org/browse/INTELLIJ-153) Add support for parsing, linting and
autocompleting fields in Accumulators.topN and Accumulators.bottomN
* [INTELLIJ-104](https://jira.mongodb.org/browse/INTELLIJ-104) Add support for Spring Criteria
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import com.mongodb.jbplugin.mql.parser.components.whenIsCommand
import com.mongodb.jbplugin.mql.parser.first
import com.mongodb.jbplugin.mql.parser.map
import com.mongodb.jbplugin.mql.parser.parse
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryEvent.QueryRunEvent.Console
import com.mongodb.jbplugin.observability.probe.QueryRunProbe
import kotlinx.coroutines.CoroutineScope

/**
Expand Down Expand Up @@ -78,11 +81,14 @@ internal object RunQueryCodeAction : MongoDbCodeAction {
if (shouldDelegateToIntelliJRunQuery(query)) {
delegateRunQueryToIntelliJ(query)
} else {
emitRunQueryEvent(query, dataSource)

coroutineScope.launchChildBackground {
val outputQuery = MongoshDialect.formatter.formatQuery(
query,
explain = false
)

if (dataSource?.isConnected() == true) {
coroutineScope.launchChildOnUi {
openDataGripConsole(query, dataSource, outputQuery.query)
Expand All @@ -103,6 +109,20 @@ internal object RunQueryCodeAction : MongoDbCodeAction {
)
}

private fun emitRunQueryEvent(
query: Node<PsiElement>,
dataSource: LocalDataSource?
) {
val hasConsole = DatagripConsoleEditor.isThereAnEditorForDataSource(dataSource)

val probe by service<QueryRunProbe>()
probe.queryRunRequested(
query,
if (hasConsole) Console.EXISTING else Console.NEW,
TelemetryEvent.QueryRunEvent.TriggerLocation.GUTTER
)
}

private fun shouldShowRunGutterIcon(node: Node<PsiElement>): Boolean {
return first(
whenIsCommand<PsiElement>(IsCommand.CommandType.AGGREGATE).map { false },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.mongodb.jbplugin.editor

import com.intellij.codeInsight.daemon.impl.EditorTracker
import com.intellij.database.dataSource.LocalDataSource
import com.intellij.database.editor.DatabaseEditorHelper
import com.intellij.database.util.DbUIUtil
import com.intellij.database.vfs.DbVFSUtils
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
Expand All @@ -14,19 +16,28 @@ import com.intellij.psi.PsiManager
import com.mongodb.jbplugin.accessadapter.datagrip.adapter.isConnected

object DatagripConsoleEditor {
fun isThereAnEditorForDataSource(dataSource: LocalDataSource?): Boolean {
if (dataSource == null) {
return false
}

return ApplicationManager.getApplication().runReadAction<Boolean> {
DatabaseEditorHelper.getConsoleVirtualFile(dataSource) != null
}
}

fun openConsoleForDataSource(project: Project, dataSource: LocalDataSource): Editor? {
if (!dataSource.isConnected()) {
return null
}

var activeEditor = allConsoleEditorsForDataSource(project, dataSource).firstOrNull()
if (activeEditor != null) {
val currentFile = PsiDocumentManager.getInstance(project)
.getPsiFile(activeEditor.document)!!.virtualFile

activeEditor?.let {
val currentFile = PsiDocumentManager.getInstance(
project
).getPsiFile(it.document)!!.virtualFile
FileEditorManager.getInstance(project).openFile(currentFile, true)
} ?: run {
} else {
activeEditor = openNewEmptyEditorForDataSource(project, dataSource)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
package com.mongodb.jbplugin.observability

import com.google.common.base.Objects
import com.mongodb.jbplugin.dialects.Dialect
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import org.intellij.lang.annotations.Pattern
import org.jetbrains.annotations.Nls

Expand Down Expand Up @@ -37,6 +37,8 @@ enum class TelemetryProperty(
AUTOCOMPLETE_TYPE("ac_type"),
AUTOCOMPLETE_COUNT("ac_count"),
COMMAND("command"),
CONSOLE("console"),
TRIGGER_LOCATION("trigger_location"),
}

/**
Expand Down Expand Up @@ -168,18 +170,42 @@ sealed class TelemetryEvent(
* @param count
*/
class AutocompleteGroupEvent(
dialect: Dialect<*, *>,
dialect: HasSourceDialect.DialectName,
autocompleteType: String,
command: String,
count: Int,
) : TelemetryEvent(
name = "AutocompleteSelected",
properties =
mapOf(
TelemetryProperty.DIALECT to dialect.javaClass.simpleName,
TelemetryProperty.DIALECT to dialect.name.lowercase(),
TelemetryProperty.AUTOCOMPLETE_TYPE to autocompleteType,
TelemetryProperty.COMMAND to command,
TelemetryProperty.AUTOCOMPLETE_COUNT to count,
),
)

class QueryRunEvent(
dialect: HasSourceDialect.DialectName,
console: Console,
triggerLocation: TriggerLocation,
) : TelemetryEvent(
name = "QueryRun",
properties =
mapOf(
TelemetryProperty.DIALECT to dialect.name.lowercase(),
TelemetryProperty.CONSOLE to console.name.lowercase(),
TelemetryProperty.TRIGGER_LOCATION to triggerLocation.name.lowercase(),
)
) {
enum class Console {
NEW,
EXISTING
}

enum class TriggerLocation {
GUTTER,
CONTEXT_MENU
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.mongodb.jbplugin.dialects.Dialect
import com.mongodb.jbplugin.dialects.javadriver.glossary.JavaDriverDialect
import com.mongodb.jbplugin.dialects.springcriteria.SpringCriteriaDialect
import com.mongodb.jbplugin.dialects.springquery.SpringAtQueryDialect
import com.mongodb.jbplugin.meta.service
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.mql.components.IsCommand.CommandType
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryService
Expand Down Expand Up @@ -104,7 +108,7 @@ class AutocompleteSuggestionAcceptedProbe(
.eachCount()
.map {
TelemetryEvent.AutocompleteGroupEvent(
it.key.first,
dialectName(it.key.first),
it.key.second.publicName,
it.key.third.canonical,
it.value
Expand All @@ -122,6 +126,18 @@ class AutocompleteSuggestionAcceptedProbe(
}
}

private fun dialectName(dialect: Dialect<*, *>): HasSourceDialect.DialectName {
if (dialect == JavaDriverDialect) {
return HasSourceDialect.DialectName.JAVA_DRIVER
} else if (dialect == SpringCriteriaDialect) {
return HasSourceDialect.DialectName.SPRING_CRITERIA
} else if (dialect == SpringAtQueryDialect) {
return HasSourceDialect.DialectName.SPRING_QUERY
}

return HasSourceDialect.DialectName.UNKNOWN
}

/**
* @property dialect
* @property type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mongodb.jbplugin.observability.probe

import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.intellij.psi.PsiElement
import com.mongodb.jbplugin.meta.service
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryService
import com.mongodb.jbplugin.observability.useLogMessage

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

/**
* This probe is emitted when the user requests to run a query through the gutter icon
* action.
*/
@Service
class QueryRunProbe {
fun queryRunRequested(
query: Node<PsiElement>,
console: TelemetryEvent.QueryRunEvent.Console,
trigger: TelemetryEvent.QueryRunEvent.TriggerLocation
) {
val telemetry by service<TelemetryService>()

var dialect = query.component<HasSourceDialect>() ?: return
val event = TelemetryEvent.QueryRunEvent(dialect.name, console, trigger)
telemetry.sendEvent(event)

logger.info(
useLogMessage("Query run requested.")
.mergeTelemetryEventProperties(event)
.build()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import java.lang.reflect.Method
import java.net.URI
import java.net.URL
import java.nio.file.Paths
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.io.path.Path
Expand Down Expand Up @@ -197,23 +194,6 @@ internal class CodeInsightTestExtension :
)
}
}

private fun pathToClassJarFile(javaClass: Class<*>): String {
val classResource: URL =
javaClass.getResource(javaClass.getSimpleName() + ".class")
?: throw RuntimeException("class resource is null")
val url: String = classResource.toString()
if (url.startsWith("jar:file:")) {
// extract 'file:......jarName.jar' part from the url string
val path = url.replace("^jar:(file:.*[.]jar)!/.*".toRegex(), "$1")
try {
return Paths.get(URI(path)).toString()
} catch (e: Exception) {
throw RuntimeException("Invalid Jar File URL String")
}
}
throw RuntimeException("Invalid Jar File URL String")
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.mongodb.jbplugin.dialects.javadriver.glossary.JavaDriverDialect
import com.mongodb.jbplugin.fixtures.IntegrationTest
import com.mongodb.jbplugin.fixtures.mockLogMessage
import com.mongodb.jbplugin.fixtures.withMockedService
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.mql.components.IsCommand.CommandType
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryService
Expand Down Expand Up @@ -45,7 +46,7 @@ class AutocompleteSuggestionAcceptedProbeTest {

verify(telemetryService).sendEvent(
TelemetryEvent.AutocompleteGroupEvent(
JavaDriverDialect,
HasSourceDialect.DialectName.JAVA_DRIVER,
"database",
CommandType.UNKNOWN.canonical,
5
Expand All @@ -54,7 +55,7 @@ class AutocompleteSuggestionAcceptedProbeTest {

verify(telemetryService).sendEvent(
TelemetryEvent.AutocompleteGroupEvent(
JavaDriverDialect,
HasSourceDialect.DialectName.JAVA_DRIVER,
"collection",
CommandType.UNKNOWN.canonical,
2
Expand All @@ -63,7 +64,7 @@ class AutocompleteSuggestionAcceptedProbeTest {

verify(telemetryService).sendEvent(
TelemetryEvent.AutocompleteGroupEvent(
JavaDriverDialect,
HasSourceDialect.DialectName.JAVA_DRIVER,
"field",
CommandType.FIND_ONE.canonical,
10
Expand All @@ -72,7 +73,7 @@ class AutocompleteSuggestionAcceptedProbeTest {

verify(telemetryService).sendEvent(
TelemetryEvent.AutocompleteGroupEvent(
JavaDriverDialect,
HasSourceDialect.DialectName.JAVA_DRIVER,
"field",
CommandType.AGGREGATE.canonical,
10
Expand All @@ -81,7 +82,7 @@ class AutocompleteSuggestionAcceptedProbeTest {

verify(telemetryService).sendEvent(
TelemetryEvent.AutocompleteGroupEvent(
JavaDriverDialect,
HasSourceDialect.DialectName.JAVA_DRIVER,
"field",
CommandType.UPDATE_MANY.canonical,
10
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.mongodb.jbplugin.observability.probe

import com.intellij.openapi.application.Application
import com.intellij.psi.PsiElement
import com.mongodb.jbplugin.fixtures.IntegrationTest
import com.mongodb.jbplugin.fixtures.mockLogMessage
import com.mongodb.jbplugin.fixtures.withMockedService
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryService
import org.junit.jupiter.api.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify

@IntegrationTest
internal class QueryRunProbeTest {
@Test
fun `should send a QueryRunEvent event`(application: Application) {
val telemetryService = mock<TelemetryService>()
val dialect = HasSourceDialect.DialectName.entries.toTypedArray().random()
val console = TelemetryEvent.QueryRunEvent.Console.entries.toTypedArray().random()
val triggerLocation = TelemetryEvent.QueryRunEvent.TriggerLocation.entries.toTypedArray().random()

val query = Node<PsiElement?>(null, listOf(HasSourceDialect(dialect))) as Node<PsiElement>

application.withMockedService(telemetryService)
.withMockedService(mockLogMessage())

val probe = QueryRunProbe()

probe.queryRunRequested(query, console, triggerLocation)

verify(telemetryService).sendEvent(
TelemetryEvent.QueryRunEvent(
dialect,
console,
triggerLocation
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ data class HasSourceDialect(val name: DialectName) : Component {
enum class DialectName {
JAVA_DRIVER,
SPRING_CRITERIA,
SPRING_QUERY
SPRING_QUERY,
UNKNOWN
}
}

0 comments on commit d3b4fa3

Please sign in to comment.