diff --git a/etc/inspections.sh b/etc/inspections.sh new file mode 100755 index 00000000..ef349d23 --- /dev/null +++ b/etc/inspections.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +./etc/plugin-logs.sh |\ + grep 'inspection_id' |\ + jq '{ inspection: .inspection_type, id: .inspection_id, status: .error_status, meta: { error_field_type: .error_field_type, actual_field_type: .actual_field_type } }' |\ + jq -s |\ + jq 'group_by(.id) | map({ id: .[0].id, inspection: .[0].inspection, status: map(.status), meta: map(.meta) })' | + jq '.[]' + diff --git a/etc/plugin-logs.sh b/etc/plugin-logs.sh new file mode 100755 index 00000000..56692efd --- /dev/null +++ b/etc/plugin-logs.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./packages/jetbrains-plugin/build/idea-sandbox/system/log/idea.log | grep "pluginVersion" diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/AbstractMongoDbInspectionBridge.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/AbstractMongoDbInspectionBridge.kt index c3761b80..2a3246a7 100644 --- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/AbstractMongoDbInspectionBridge.kt +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/AbstractMongoDbInspectionBridge.kt @@ -30,12 +30,19 @@ abstract class AbstractMongoDbInspectionBridge( private val coroutineScope: CoroutineScope, private val inspection: MongoDbInspection, ) : AbstractBaseJavaLocalInspectionTool() { + protected abstract fun emitFinishedInspectionTelemetryEvent(problemsHolder: ProblemsHolder) + override fun buildVisitor( holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession, ): PsiElementVisitor = object : JavaElementVisitor() { + override fun visitJavaFile(file: PsiJavaFile) { + super.visitJavaFile(file) + emitFinishedInspectionTelemetryEvent(holder) + } + override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { dispatchIfValidMongoDbQuery(expression) } @@ -53,8 +60,10 @@ abstract class AbstractMongoDbInspectionBridge( val dialect = expression.containingFile.dialect ?: return@runReadAction val queryService by expression.project.service() - queryService.queryAt(expression)?.let { query -> - fileInExpression.virtualFile?.let { + val query = queryService.queryAt(expression) + + if (query != null) { + if (fileInExpression.virtualFile != null) { inspection.visitMongoDbQuery( coroutineScope, dataSource?.localDataSource, @@ -62,13 +71,15 @@ abstract class AbstractMongoDbInspectionBridge( query, dialect.formatter, ) - } ?: inspection.visitMongoDbQuery( - coroutineScope, - null, - holder, - query, - dialect.formatter - ) + } else { + inspection.visitMongoDbQuery( + coroutineScope, + null, + holder, + query, + dialect.formatter + ) + } } } } diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/FieldCheckInspectionBridge.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/FieldCheckInspectionBridge.kt index 40d38500..a4e8bcd9 100644 --- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/FieldCheckInspectionBridge.kt +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/FieldCheckInspectionBridge.kt @@ -21,17 +21,29 @@ import com.mongodb.jbplugin.linting.FieldCheckingLinter import com.mongodb.jbplugin.meta.service import com.mongodb.jbplugin.mql.Node import com.mongodb.jbplugin.mql.components.HasCollectionReference +import com.mongodb.jbplugin.observability.TelemetryEvent +import com.mongodb.jbplugin.observability.probe.InspectionStatusChangedProbe import kotlinx.coroutines.CoroutineScope -/** - * @param coroutineScope - */ -@Suppress("MISSING_KDOC_TOP_LEVEL") class FieldCheckInspectionBridge(coroutineScope: CoroutineScope) : AbstractMongoDbInspectionBridge( coroutineScope, FieldCheckLinterInspection, - ) + ) { + override fun emitFinishedInspectionTelemetryEvent(problemsHolder: ProblemsHolder) { + val probe by service() + + probe.finishedProcessingInspections( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.FIELD_DOES_NOT_EXIST, + problemsHolder + ) + + probe.finishedProcessingInspections( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.TYPE_MISMATCH, + problemsHolder + ) + } +} /** * This inspection object calls the linting engine and transforms the result so they can be rendered in the IntelliJ @@ -67,10 +79,11 @@ internal object FieldCheckLinterInspection : MongoDbInspection { is FieldCheckWarning.FieldDoesNotExist -> registerFieldDoesNotExistProblem( coroutineScope, problems, - it + it, + query ) is FieldCheckWarning.FieldValueTypeMismatch -> - registerFieldValueTypeMismatch(coroutineScope, problems, it, formatter) + registerFieldValueTypeMismatch(coroutineScope, problems, it, formatter, query) } } } @@ -121,6 +134,7 @@ internal object FieldCheckLinterInspection : MongoDbInspection { coroutineScope: CoroutineScope, problems: ProblemsHolder, warningInfo: FieldCheckWarning.FieldDoesNotExist, + query: Node, ) { val problemDescription = InspectionsAndInlaysMessages.message( "inspection.field.checking.error.message", @@ -138,6 +152,12 @@ internal object FieldCheckLinterInspection : MongoDbInspection { ), ), ) + + val probe by service() + probe.inspectionChanged( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.FIELD_DOES_NOT_EXIST, + query + ) } private fun registerFieldValueTypeMismatch( @@ -145,11 +165,15 @@ internal object FieldCheckLinterInspection : MongoDbInspection { problems: ProblemsHolder, warningInfo: FieldCheckWarning.FieldValueTypeMismatch, formatter: DialectFormatter, + query: Node ) { + val expectedType = formatter.formatType(warningInfo.fieldType) + val actualType = formatter.formatType(warningInfo.valueType) + val problemDescription = InspectionsAndInlaysMessages.message( "inspection.field.checking.error.message.value.type.mismatch", - formatter.formatType(warningInfo.valueType), - formatter.formatType(warningInfo.fieldType), + actualType, + expectedType, warningInfo.field, ) problems.registerProblem( @@ -163,5 +187,12 @@ internal object FieldCheckLinterInspection : MongoDbInspection { ), ), ) + + val probe by service() + probe.typeMismatchInspectionActive( + query, + actualType, + expectedType, + ) } } diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/IndexCheckInspectionBridge.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/IndexCheckInspectionBridge.kt index 651f6f60..6cfe68eb 100644 --- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/IndexCheckInspectionBridge.kt +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/IndexCheckInspectionBridge.kt @@ -21,17 +21,24 @@ import com.mongodb.jbplugin.linting.IndexCheckWarning import com.mongodb.jbplugin.linting.IndexCheckingLinter import com.mongodb.jbplugin.meta.service import com.mongodb.jbplugin.mql.Node +import com.mongodb.jbplugin.observability.TelemetryEvent import com.mongodb.jbplugin.observability.probe.CreateIndexIntentionProbe +import com.mongodb.jbplugin.observability.probe.InspectionStatusChangedProbe import kotlinx.coroutines.CoroutineScope -/** - * @param coroutineScope - */ class IndexCheckInspectionBridge(coroutineScope: CoroutineScope) : AbstractMongoDbInspectionBridge( coroutineScope, IndexCheckLinterInspection, - ) + ) { + override fun emitFinishedInspectionTelemetryEvent(problemsHolder: ProblemsHolder) { + val probe by service() + probe.finishedProcessingInspections( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.FIELD_DOES_NOT_EXIST, + problemsHolder, + ) + } +} /** * This inspection object calls the linting engine and transforms the result so they can be rendered in the IntelliJ @@ -70,6 +77,12 @@ internal object IndexCheckLinterInspection : MongoDbInspection { "inspection.index.checking.error.query.not.covered.by.index", ) + val probe by service() + probe.inspectionChanged( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.QUERY_NOT_COVERED_BY_INDEX, + query + ) + problems.registerProblem( query.source, problemDescription, diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/NamespaceCheckInspectionBridge.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/NamespaceCheckInspectionBridge.kt index aed229bf..e4eb1947 100644 --- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/NamespaceCheckInspectionBridge.kt +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/inspections/impl/NamespaceCheckInspectionBridge.kt @@ -20,17 +20,36 @@ import com.mongodb.jbplugin.linting.NamespaceCheckWarning import com.mongodb.jbplugin.linting.NamespaceCheckingLinter import com.mongodb.jbplugin.meta.service import com.mongodb.jbplugin.mql.Node +import com.mongodb.jbplugin.observability.TelemetryEvent +import com.mongodb.jbplugin.observability.probe.InspectionStatusChangedProbe import kotlinx.coroutines.CoroutineScope /** * @param coroutineScope */ -@Suppress("MISSING_KDOC_TOP_LEVEL") class NamespaceCheckInspectionBridge(coroutineScope: CoroutineScope) : AbstractMongoDbInspectionBridge( coroutineScope, NamespaceCheckingLinterInspection, - ) + ) { + override fun emitFinishedInspectionTelemetryEvent(problemsHolder: ProblemsHolder) { + val probe by service() + probe.finishedProcessingInspections( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.NO_NAMESPACE_INFERRED, + problemsHolder + ) + + probe.finishedProcessingInspections( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.DATABASE_DOES_NOT_EXIST, + problemsHolder + ) + + probe.finishedProcessingInspections( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.COLLECTION_DOES_NOT_EXIST, + problemsHolder + ) + } +} /** * This inspection object calls the linting engine and transforms the result so they can be rendered in the IntelliJ @@ -59,17 +78,24 @@ internal object NamespaceCheckingLinterInspection : MongoDbInspection { result.warnings.forEach { when (it) { is NamespaceCheckWarning.NoNamespaceInferred -> - registerNoNamespaceInferred(coroutineScope, problems, it.source) + registerNoNamespaceInferred(coroutineScope, problems, it.source, query) is NamespaceCheckWarning.CollectionDoesNotExist -> registerCollectionDoesNotExist( coroutineScope, problems, it.source, it.database, - it.collection + it.collection, + query ) is NamespaceCheckWarning.DatabaseDoesNotExist -> - registerDatabaseDoesNotExist(coroutineScope, problems, it.source, it.database) + registerDatabaseDoesNotExist( + coroutineScope, + problems, + it.source, + it.database, + query + ) } } } @@ -77,8 +103,15 @@ internal object NamespaceCheckingLinterInspection : MongoDbInspection { private fun registerNoNamespaceInferred( coroutineScope: CoroutineScope, problems: ProblemsHolder, - source: PsiElement + source: PsiElement, + query: Node ) { + val probe by service() + probe.inspectionChanged( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.NO_NAMESPACE_INFERRED, + query + ) + val problemDescription = InspectionsAndInlaysMessages.message( "inspection.namespace.checking.error.message", ) @@ -100,7 +133,14 @@ internal object NamespaceCheckingLinterInspection : MongoDbInspection { problems: ProblemsHolder, source: PsiElement, dbName: String, + query: Node ) { + val probe by service() + probe.inspectionChanged( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.DATABASE_DOES_NOT_EXIST, + query + ) + val problemDescription = InspectionsAndInlaysMessages.message( "inspection.namespace.checking.error.message.database.missing", dbName @@ -123,8 +163,15 @@ internal object NamespaceCheckingLinterInspection : MongoDbInspection { problems: ProblemsHolder, source: PsiElement, dbName: String, - collName: String + collName: String, + query: Node ) { + val probe by service() + probe.inspectionChanged( + TelemetryEvent.InspectionStatusChangeEvent.InspectionType.COLLECTION_DOES_NOT_EXIST, + query + ) + val problemDescription = InspectionsAndInlaysMessages.message( "inspection.namespace.checking.error.message.collection.missing", collName, diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/TelemetryEvent.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/TelemetryEvent.kt index e4bbbaf2..5335ce0c 100644 --- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/TelemetryEvent.kt +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/TelemetryEvent.kt @@ -40,6 +40,10 @@ enum class TelemetryProperty( CONSOLE("console"), TRIGGER_LOCATION("trigger_location"), SIGNAL_TYPE("signal_type"), + INSPECTION_TYPE("inspection_type"), + ERROR_FIELD_TYPE("error_field_type"), + ACTUAL_ERROR_TYPE("actual_field_type"), + ERROR_STATUS("error_status") } enum class SignalType( @@ -227,4 +231,36 @@ sealed class TelemetryEvent( TelemetryProperty.SIGNAL_TYPE to SignalType.MISSING_INDEX.publicName, ) ) + + class InspectionStatusChangeEvent( + dialect: HasSourceDialect.DialectName, + inspectionType: InspectionType, + inspectionStatus: InspectionStatus, + actualFieldType: String?, + expectedFieldType: String?, + ) : TelemetryEvent( + name = "Inspection", + properties = + mapOf( + TelemetryProperty.DIALECT to dialect.name.lowercase(), + TelemetryProperty.INSPECTION_TYPE to inspectionType.name.lowercase(), + TelemetryProperty.ERROR_STATUS to inspectionStatus.name.lowercase(), + TelemetryProperty.ERROR_FIELD_TYPE to (actualFieldType ?: ""), + TelemetryProperty.ACTUAL_ERROR_TYPE to (expectedFieldType ?: ""), + ) + ) { + enum class InspectionType { + FIELD_DOES_NOT_EXIST, + TYPE_MISMATCH, + QUERY_NOT_COVERED_BY_INDEX, + NO_NAMESPACE_INFERRED, + COLLECTION_DOES_NOT_EXIST, + DATABASE_DOES_NOT_EXIST + } + + enum class InspectionStatus { + ACTIVE, + RESOLVED + } + } } diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/probe/InspectionStatusChangedProbe.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/probe/InspectionStatusChangedProbe.kt new file mode 100644 index 00000000..31c98246 --- /dev/null +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/probe/InspectionStatusChangedProbe.kt @@ -0,0 +1,179 @@ +package com.mongodb.jbplugin.observability.probe + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.database.util.common.containsElements +import com.intellij.openapi.application.readAction +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.TelemetryEvent.InspectionStatusChangeEvent.InspectionType +import com.mongodb.jbplugin.observability.TelemetryService +import com.mongodb.jbplugin.observability.useLogMessage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.lang.ref.WeakReference +import java.util.Collections +import java.util.UUID + +private val inspectionTelemetry = Dispatchers.IO +private val logger: Logger = logger() + +@Service +class InspectionStatusChangedProbe( + private val cs: CoroutineScope +) { + private data class UniqueInspection(val id: UUID, val on: WeakReference>) { + companion object { + fun new(query: Node): UniqueInspection { + return UniqueInspection(UUID.randomUUID(), WeakReference(query)) + } + } + } + + private val problemsByInspectionType: MutableMap> = + Collections.synchronizedMap(mutableMapOf()) + + fun inspectionChanged(inspectionType: InspectionType, query: Node) { + val dialect = query.component() ?: return + val psiElement = query.source + + cs.launch(inspectionTelemetry) { + val elementsWithProblems = problemsByInspectionType(inspectionType) + + // check if the element is already in the list + if (isElementRegistered(elementsWithProblems, psiElement)) { + // do nothing, it's already registered + return@launch + } + + // it's a new error, send a telemetry event and store it + val inspection = UniqueInspection.new(query) + elementsWithProblems.add(inspection) + + val telemetry by service() + val event = TelemetryEvent.InspectionStatusChangeEvent( + dialect = dialect.name, + inspectionType = inspectionType, + inspectionStatus = TelemetryEvent.InspectionStatusChangeEvent.InspectionStatus.ACTIVE, + null, + null + ) + + telemetry.sendEvent(event) + logger.info( + useLogMessage("New inspection triggered") + .put("inspection_id", inspection.id.toString()) + .mergeTelemetryEventProperties(event) + .build() + ) + + problemsByInspectionType[inspectionType] = elementsWithProblems + } + } + + fun typeMismatchInspectionActive(query: Node, actualType: String, expectedType: String) { + val inspectionType = InspectionType.TYPE_MISMATCH + val dialect = query.component() ?: return + val psiElement = query.source + + cs.launch(inspectionTelemetry) { + val elementsWithProblems = problemsByInspectionType(inspectionType) + + if (isElementRegistered(elementsWithProblems, psiElement)) { + // do nothing, it's already registered + return@launch + } + + // it's a new error, send a telemetry event and store it + val inspection = UniqueInspection.new(query) + elementsWithProblems.add(inspection) + + val telemetry by service() + val event = TelemetryEvent.InspectionStatusChangeEvent( + dialect = dialect.name, + inspectionType = inspectionType, + inspectionStatus = TelemetryEvent.InspectionStatusChangeEvent.InspectionStatus.ACTIVE, + actualFieldType = actualType, + expectedFieldType = expectedType, + ) + + telemetry.sendEvent(event) + logger.info( + useLogMessage("New inspection triggered") + .put("inspection_id", inspection.id.toString()) + .mergeTelemetryEventProperties(event) + .build() + ) + + problemsByInspectionType[inspectionType] = elementsWithProblems + } + } + + fun finishedProcessingInspections(inspectionType: InspectionType, problemsHolder: ProblemsHolder) { + cs.launch(inspectionTelemetry) { + val elementsWithProblems = problemsByInspectionType(inspectionType) + + val results = problemsHolder.results + // check all our registered problems + // if at the end of the processing cycle it's empty + // we will assume they are + for (loopResult in results) { + for ((idx, elementWithProblem) in elementsWithProblems.withIndex()) { + if (isElementRegistered(elementsWithProblems, loopResult.psiElement)) { + // the problem is still there, so don't do anything + // do nothing, it's already registered + break + } + + elementsWithProblems.removeAt(idx) + + val dialect = + elementWithProblem.on.get()?.component() ?: continue + val telemetry by service() + val event = TelemetryEvent.InspectionStatusChangeEvent( + dialect = dialect.name, + inspectionType = inspectionType, + inspectionStatus = TelemetryEvent.InspectionStatusChangeEvent.InspectionStatus.RESOLVED, + null, + null + ) + + telemetry.sendEvent(event) + logger.info( + useLogMessage("Inspection resolved") + .put("inspection_id", elementWithProblem.id.toString()) + .mergeTelemetryEventProperties(event) + .build() + ) + } + } + } + } + + private suspend fun isElementRegistered( + elementsWithProblems: MutableList, + psiElement: PsiElement + ): Boolean = runCatching { + readAction { + elementsWithProblems.containsElements { + it.on.get()?.source == psiElement || + it.on.get()?.source?.isEquivalentTo(psiElement) == true + } + } + }.getOrDefault(false) + + private fun problemsByInspectionType(inspectionType: InspectionType): MutableList { + val result = problemsByInspectionType.computeIfAbsent(inspectionType) { + Collections.synchronizedList(mutableListOf()) + } + + result.removeAll { it.on.get() == null } + return result + } +} diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/CodeInsightTestExtensions.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/CodeInsightTestExtensions.kt index 3873b4db..3ba4f756 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/CodeInsightTestExtensions.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/CodeInsightTestExtensions.kt @@ -11,6 +11,7 @@ import com.intellij.database.dataSource.LocalDataSource import com.intellij.database.dataSource.localDataSource import com.intellij.database.psi.DbDataSource import com.intellij.database.psi.DbPsiFacade +import com.intellij.openapi.application.Application import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.module.Module @@ -37,6 +38,7 @@ import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider import com.mongodb.jbplugin.dialects.Dialect import com.mongodb.jbplugin.editor.MongoDbVirtualFileDataSourceProvider +import com.mongodb.jbplugin.observability.TelemetryService import kotlinx.coroutines.runBlocking import kotlinx.coroutines.suspendCancellableCoroutine import org.intellij.lang.annotations.Language @@ -135,6 +137,7 @@ internal class CodeInsightTestExtension : ).get(testFixtureKey) as CodeInsightTestFixture val dumbService = DumbService.getInstance(fixture.project) + ApplicationManager.getApplication().withMockedService(mock(TelemetryService::class.java)) // Run only when the code has been analysed runBlocking { suspendCancellableCoroutine { callback -> @@ -174,7 +177,8 @@ internal class CodeInsightTestExtension : parameterContext.parameter.type == Project::class.java || parameterContext.parameter.type == CodeInsightTestFixture::class.java || parameterContext.parameter.type == PsiFile::class.java || - parameterContext.parameter.type == JavaPsiFacade::class.java + parameterContext.parameter.type == JavaPsiFacade::class.java || + parameterContext.parameter.type == Application::class.java override fun resolveParameter( parameterContext: ParameterContext, @@ -189,6 +193,7 @@ internal class CodeInsightTestExtension : CodeInsightTestFixture::class.java -> fixture PsiFile::class.java -> fixture.file JavaPsiFacade::class.java -> JavaPsiFacade.getInstance(fixture.project) + Application::class.java -> ApplicationManager.getApplication() else -> TODO( "Parameter of type ${parameterContext.parameter.type.canonicalName} is not supported." ) diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt index b8fef1ca..cdb3e7d4 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt @@ -1,5 +1,6 @@ package com.mongodb.jbplugin.inspections.impl +import com.intellij.openapi.application.Application import com.intellij.testFramework.fixtures.CodeInsightTestFixture import com.mongodb.jbplugin.accessadapter.slice.GetCollectionSchema import com.mongodb.jbplugin.dialects.javadriver.glossary.JavaDriverDialect @@ -12,9 +13,11 @@ import com.mongodb.jbplugin.mql.BsonObject import com.mongodb.jbplugin.mql.BsonString import com.mongodb.jbplugin.mql.CollectionSchema import com.mongodb.jbplugin.mql.Namespace +import com.mongodb.jbplugin.observability.TelemetryService import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.eq +import org.mockito.kotlin.verify @CodeInsightTest class JavaDriverFieldCheckLinterInspectionTest { @@ -78,8 +81,11 @@ public class Repository { """, ) fun `shows an inspection when the field does not exist in the current namespace`( + app: Application, fixture: CodeInsightTestFixture, ) { + val telemetryService = app.getService(TelemetryService::class.java) + val (dataSource, readModelProvider) = fixture.setupConnection() fixture.specifyDialect(JavaDriverDialect) @@ -93,6 +99,8 @@ public class Repository { fixture.enableInspections(FieldCheckInspectionBridge::class.java) fixture.testHighlighting() + + verify(telemetryService).sendEvent(any()) } @ParsingTest( diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverIndexCheckLinterInspectionTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverIndexCheckLinterInspectionTest.kt index 23c6adee..e03a6dce 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverIndexCheckLinterInspectionTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverIndexCheckLinterInspectionTest.kt @@ -1,5 +1,6 @@ package com.mongodb.jbplugin.inspections.impl +import com.intellij.openapi.application.Application import com.intellij.testFramework.fixtures.CodeInsightTestFixture import com.mongodb.jbplugin.accessadapter.ExplainPlan import com.mongodb.jbplugin.accessadapter.slice.ExplainQuery @@ -8,9 +9,11 @@ import com.mongodb.jbplugin.fixtures.CodeInsightTest import com.mongodb.jbplugin.fixtures.ParsingTest import com.mongodb.jbplugin.fixtures.setupConnection import com.mongodb.jbplugin.fixtures.specifyDialect +import com.mongodb.jbplugin.observability.TelemetryService import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.eq +import org.mockito.kotlin.verify @CodeInsightTest @Suppress("TOO_LONG_FUNCTION", "LONG_LINE") @@ -43,8 +46,11 @@ public class Repository { """, ) fun `shows an inspection when the query is a collscan`( + app: Application, fixture: CodeInsightTestFixture, ) { + val telemetryService = app.getService(TelemetryService::class.java) + val (dataSource, readModelProvider) = fixture.setupConnection() fixture.specifyDialect(JavaDriverDialect) @@ -54,5 +60,7 @@ public class Repository { fixture.enableInspections(IndexCheckInspectionBridge::class.java) fixture.testHighlighting() + + verify(telemetryService).sendEvent(any()) } } diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverNamespaceCheckLinterInspectionTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverNamespaceCheckLinterInspectionTest.kt index 9b154dcd..35f3f872 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverNamespaceCheckLinterInspectionTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverNamespaceCheckLinterInspectionTest.kt @@ -1,5 +1,6 @@ package com.mongodb.jbplugin.inspections.impl +import com.intellij.openapi.application.Application import com.intellij.testFramework.fixtures.CodeInsightTestFixture import com.mongodb.jbplugin.accessadapter.slice.ListCollections import com.mongodb.jbplugin.accessadapter.slice.ListDatabases @@ -8,9 +9,11 @@ import com.mongodb.jbplugin.fixtures.CodeInsightTest import com.mongodb.jbplugin.fixtures.ParsingTest import com.mongodb.jbplugin.fixtures.setupConnection import com.mongodb.jbplugin.fixtures.specifyDialect +import com.mongodb.jbplugin.observability.TelemetryService import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.eq +import org.mockito.kotlin.verify @CodeInsightTest @Suppress("TOO_LONG_FUNCTION", "LONG_LINE") @@ -80,8 +83,11 @@ public class Repository { """, ) fun `shows an inspection when the collection does not exist in the current data source`( + app: Application, fixture: CodeInsightTestFixture, ) { + val telemetryService = app.getService(TelemetryService::class.java) + val (dataSource, readModelProvider) = fixture.setupConnection() fixture.specifyDialect(JavaDriverDialect) @@ -95,5 +101,7 @@ public class Repository { fixture.enableInspections(NamespaceCheckInspectionBridge::class.java) fixture.testHighlighting() + + verify(telemetryService).sendEvent(any()) } } diff --git a/packages/jetbrains-plugin/src/test/resources/project-fixtures/basic-java-project-with-mongodb/src/main/java/alt/mongodb/javadriver/JavaDriverRepository.java b/packages/jetbrains-plugin/src/test/resources/project-fixtures/basic-java-project-with-mongodb/src/main/java/alt/mongodb/javadriver/JavaDriverRepository.java index 5e8dc079..4d897b23 100644 --- a/packages/jetbrains-plugin/src/test/resources/project-fixtures/basic-java-project-with-mongodb/src/main/java/alt/mongodb/javadriver/JavaDriverRepository.java +++ b/packages/jetbrains-plugin/src/test/resources/project-fixtures/basic-java-project-with-mongodb/src/main/java/alt/mongodb/javadriver/JavaDriverRepository.java @@ -30,7 +30,7 @@ public Document findMovieById(String id) { .first(); } - public List findMoviesByYear(String year) { + public List findMoviesByYear(int year) { return client .getDatabase("sample_mflix") .getCollection("movies")