diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModal.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModal.kt index 44f5c1c4..f8f0c5bc 100644 --- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModal.kt +++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModal.kt @@ -110,6 +110,7 @@ class RunQueryModal( fieldsForContext += Triple(fieldName.fieldName, type, input) + input.name = fieldName.fieldName builder.addLabeledComponent(label, input, 8) if (toolTip != null) { builder.addTooltip(toolTip) @@ -193,7 +194,7 @@ class RunQueryModal( } } - private fun buildQueryContextFromModal(): QueryContext { + internal fun buildQueryContextFromModal(): QueryContext { val localVariables = fieldsForContext.groupBy { it.first } .mapValues { it.value.first().let { it.second to it.third } } .mapValues { (_, value) -> diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/NamespaceSelectorTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/NamespaceSelectorTest.kt index 9021417f..b7ae0690 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/NamespaceSelectorTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/NamespaceSelectorTest.kt @@ -117,6 +117,7 @@ class NamespaceSelectorTest { selector.databaseComboBox.isVisible = true selector.collectionComboBox.isVisible = true frame.isVisible = true + frame.pack() FrameFixture(robot, frame) to selector } diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModalTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModalTest.kt index 47bbc589..db583a94 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModalTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/codeActions/impl/runQuery/RunQueryModalTest.kt @@ -3,9 +3,14 @@ package com.mongodb.jbplugin.codeActions.impl.runQuery import com.intellij.database.dataSource.LocalDataSource import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import com.mongodb.jbplugin.accessadapter.slice.ListCollections +import com.mongodb.jbplugin.accessadapter.slice.ListDatabases import com.mongodb.jbplugin.fixtures.IntegrationTest import com.mongodb.jbplugin.fixtures.mockDataSource +import com.mongodb.jbplugin.fixtures.mockReadModelProvider import com.mongodb.jbplugin.fixtures.parseJavaQuery +import com.mongodb.jbplugin.mql.BsonBoolean +import com.mongodb.jbplugin.mql.BsonString import com.mongodb.jbplugin.mql.Node import kotlinx.coroutines.CoroutineScope import org.assertj.swing.core.Robot @@ -13,9 +18,19 @@ import org.assertj.swing.core.matcher.JLabelMatcher import org.assertj.swing.edt.GuiActionRunner import org.assertj.swing.exception.ComponentLookupException import org.assertj.swing.fixture.FrameFixture +import org.bson.types.ObjectId +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.kotlin.whenever +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* import javax.swing.JFrame +import kotlin.reflect.KClass @IntegrationTest class RunQueryModalTest { @@ -24,9 +39,9 @@ class RunQueryModalTest { val dataSource = mockDataSource() val query = project.parseJavaQuery( - code = """ + """ public Document find() { - return this.client.getDatabase("prod").getCollection("books").find(eq("_id", id)).first(); + return this.client.getDatabase("prod").getCollection("books").find(eq("_id", 1234)).first(); } """ ) @@ -50,9 +65,9 @@ class RunQueryModalTest { val dataSource = mockDataSource() val query = project.parseJavaQuery( - code = """ + """ public Document find() { - return this.client.getDatabase("prod").getCollection("books").find(eq("_id", id)).first(); + return this.client.getDatabase("prod").getCollection("books").find(eq("_id", 1234)).first(); } """ ) @@ -64,35 +79,185 @@ class RunQueryModalTest { } @Test - fun `if the collection can not be inferred it does show the namespace selector`( + fun `if the collection can not be inferred it does show the namespace selector and gathers the values for the query context`( robot: Robot, project: Project, coroutineScope: CoroutineScope ) { val dataSource = mockDataSource() + val readModel = project.mockReadModelProvider() + + whenever(readModel.slice(dataSource, ListDatabases.Slice)).thenReturn( + ListDatabases( + listOf( + ListDatabases.Database("db1") + ) + ) + ) + + whenever(readModel.slice(dataSource, ListCollections.Slice("db1"))).thenReturn( + ListCollections( + listOf( + ListCollections.Collection("coll1", "collection"), + ) + ) + ) val query = project.parseJavaQuery( code = """ public Document find(String db, String coll) { - return this.client.getDatabase(db).getCollection(coll).find(eq("_id", id)).first(); + return this.client.getDatabase(db).getCollection(coll).find(eq("_id", 1234)).first(); } - """, + """ ) - val (fixture, _) = render(robot, query, dataSource, coroutineScope) + val (fixture, modal) = render(robot, query, dataSource, coroutineScope) fixture.comboBox("DatabaseComboBox").requireVisible() fixture.comboBox("CollectionComboBox").requireVisible() + + val queryContext = modal.buildQueryContextFromModal() + val dbInput = queryContext.expansions.getValue("database") + val collInput = queryContext.expansions.getValue("collection") + + assertEquals("db1", dbInput.defaultValue) + assertEquals(BsonString, dbInput.type) + + assertEquals("coll1", collInput.defaultValue) + assertEquals(BsonString, collInput.type) + } + + @Test + fun `should show runtime values as inputs and build the context from there`( + robot: Robot, + project: Project, + coroutineScope: CoroutineScope + ) { + val dataSource = mockDataSource() + + val query = project.parseJavaQuery( + """ + public Document find(String id) { + return this.client.getDatabase("prod").getCollection("books").find(eq("_id", id)).first(); + } + """ + ) + + val (fixture, modal) = render(robot, query, dataSource, coroutineScope) + fixture.textBox("_id").requireVisible().focus().setText("myId") + + val queryContext = modal.buildQueryContextFromModal() + val idInput = queryContext.expansions.getValue("_id") + + assertEquals("myId", idInput.defaultValue) + assertEquals(BsonString, idInput.type) + } + + @ParameterizedTest + @MethodSource("javaTypeToFormatHint") + fun `should show hints for the specified types`( + javaType: KClass<*>, + expectedHint: String, + robot: Robot, + project: Project, + coroutineScope: CoroutineScope, + ) { + val dataSource = mockDataSource() + + val query = project.parseJavaQuery( + """ + public Document find(${javaType.simpleName} id) { + return this.client.getDatabase("prod").getCollection("books").find(eq("_id", id)).first(); + } + """ + ) + + val (fixture, _) = render(robot, query, dataSource, coroutineScope) + fixture.label(JLabelMatcher.withText(expectedHint)).requireVisible() + } + + @ParameterizedTest + @MethodSource("complexTypeSample") + fun `should show the warning message for complex types`( + javaType: KClass<*>, + robot: Robot, + project: Project, + coroutineScope: CoroutineScope + ) { + val dataSource = mockDataSource() + + val query = project.parseJavaQuery( + """ + public Document find(${javaType.qualifiedName} id) { + return this.client.getDatabase("prod").getCollection("books").find(eq("_id", id)).first(); + } + """ + ) + + val (fixture, _) = render(robot, query, dataSource, coroutineScope) + fixture.label( + JLabelMatcher.withText( + "Unable to specify. Please fill it after generating the query." + ) + ).requireVisible() + } + + @Test + fun `should show boolean runtime values as checkboxes`( + robot: Robot, + project: Project, + coroutineScope: CoroutineScope + ) { + val dataSource = mockDataSource() + + val query = project.parseJavaQuery( + """ + public Document find(boolean id) { + return this.client.getDatabase("prod").getCollection("books").find(eq("_id", id)).first(); + } + """ + ) + + val (fixture, modal) = render(robot, query, dataSource, coroutineScope) + fixture.checkBox("_id") + .requireVisible() + .focus() + .check(true) + + val queryContext = modal.buildQueryContextFromModal() + val idInput = queryContext.expansions.getValue("_id") + + assertEquals(true, idInput.defaultValue) + assertEquals(BsonBoolean, idInput.type) + } + + companion object { + @JvmStatic + fun javaTypeToFormatHint(): Array> = arrayOf( + arrayOf(ObjectId::class, "Hexadecimal ObjectId representation"), + arrayOf(Date::class, "ISO 8601 Date: yyyy-MM-dd'T'HH:mm:ss"), + arrayOf(LocalDate::class, "ISO 8601 Date: yyyy-MM-dd'T'HH:mm:ss"), + arrayOf(LocalDateTime::class, "ISO 8601 Date: yyyy-MM-dd'T'HH:mm:ss"), + arrayOf(Instant::class, "ISO 8601 Date: yyyy-MM-dd'T'HH:mm:ss"), + ) + + @JvmStatic + fun complexTypeSample(): Array = arrayOf( + List::class, + Array::class, + Map::class, + StringBuffer::class, + ) } private fun render(robot: Robot, query: Node, dataSource: LocalDataSource, coroutineScope: CoroutineScope): Pair { return GuiActionRunner.execute> { val frame = JFrame() - val modal = RunQueryModal(query, dataSource, coroutineScope) frame.add(modal.createCenterPanel()) frame.isVisible = true + frame.pack() FrameFixture(robot, frame) to modal } } diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/IntegrationTestExtensions.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/IntegrationTestExtensions.kt index 845f571d..531246a8 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/IntegrationTestExtensions.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/IntegrationTestExtensions.kt @@ -90,8 +90,8 @@ import com.mongodb.client.model.*; import org.bson.Document; import org.bson.conversions.Bson; import org.bson.types.ObjectId; -import java.util.List; -import java.util.Arrays; +import java.util.*; +import java.time.*; import static com.mongodb.client.model.Filters.*; import static com.mongodb.client.model.Accumulators.*; import static com.mongodb.client.model.Projections.*; diff --git a/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/PsiMdbTreeUtil.kt b/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/PsiMdbTreeUtil.kt index f455aab0..976b11d8 100644 --- a/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/PsiMdbTreeUtil.kt +++ b/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/PsiMdbTreeUtil.kt @@ -416,6 +416,7 @@ fun String.toBsonType(): BsonType { ) { return BsonAnyOf(BsonString, BsonNull) } else if (this == ("java.util.Date") || + this == ("java.time.Instant") || this == ("java.time.LocalDate") || this == ("java.time.LocalDateTime") ) { @@ -428,6 +429,10 @@ fun String.toBsonType(): BsonType { val baseType = this.substring(0, this.length - 2) return BsonArray(baseType.toBsonType()) } else if (this.contains("List") || this.contains("Set")) { + if (!this.contains("<")) { // not passing the generic types, so assume an array of BsonAny + return BsonArray(BsonAny) + } + val baseType = this.substringAfter("<").substringBeforeLast(">") return BsonArray(baseType.toBsonType()) } else if (this == ("java.util.UUID")) {