Skip to content

Commit

Permalink
feat(query-generation): modal for specifying default values INTELLIJ-…
Browse files Browse the repository at this point in the history
…198 (#133)
  • Loading branch information
kmruiz authored Jan 28, 2025
1 parent ee6c159 commit facd7e2
Show file tree
Hide file tree
Showing 40 changed files with 2,978 additions and 4,612 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/quality-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,11 @@ jobs:
echo "Installing docker"
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
- name: Run Test Suite
- name: Run Unit and Integration Tests
run: |
export DISPLAY=:99.0
Xvfb -ac :99 -screen 0 1920x1080x24 &
sleep 10
./gradlew --stacktrace --console=plain check
- name: Publish Test Report
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ MongoDB plugin for IntelliJ IDEA.
## [Unreleased]

### Added
* [INTELLIJ-198](https://jira.mongodb.org/browse/INTELLIJ-198) New modal to provide default values when generating queries with unknown runtime expressions.
* [INTELLIJ-175](https://jira.mongodb.org/browse/INTELLIJ-175) Add support for parsing, inspecting and autocompleting in a group stage written using `Aggregation.group` and chained `GroupOperation`s using `sum`, `avg`, `first`, `last`, `max`, `min`, `push` and `addToSet`.
* [INTELLIJ-196](https://jira.mongodb.org/browse/INTELLIJ-196) Add support for $sort when generating the query into DataGrip.
* [INTELLIJ-195](https://jira.mongodb.org/browse/INTELLIJ-195) Add support for $unwind when generating the query into DataGrip.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies {
testImplementation(libs.testing.spring.mongodb)
testImplementation(libs.testing.jsoup)
testImplementation(libs.testing.video.recorder)
testImplementation(libs.testing.assertj.swing)
testImplementation(libs.testing.remoteRobot)
testImplementation(libs.testing.remoteRobotDeps.remoteFixtures)
testImplementation(libs.testing.remoteRobotDeps.ideLauncher)
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ testContainers = "1.19.8"
spring-mongodb="4.3.2"
semver-parser="2.0.0"
snakeyaml="2.3"
assertj-swing="3.17.1"

[libraries]
## Kotlin compileOnly libraries. They must not be bundled because they are already part of the
Expand Down Expand Up @@ -75,7 +76,7 @@ testing-testContainers-core = { group = "org.testcontainers", name = "testcontai
testing-testContainers-mongodb = { group = "org.testcontainers", name = "mongodb", version.ref = "testContainers" }
testing-testContainers-jupiter = { group = "org.testcontainers", name = "junit-jupiter", version.ref = "testContainers" }
testing-spring-mongodb = { group = "org.springframework.data", name="spring-data-mongodb", version.ref="spring-mongodb" }

testing-assertj-swing = { group = "org.assertj", name="assertj-swing", version.ref="assertj-swing" }
######################################################
## Libraries and plugins only used for the buildScript.
buildScript-plugin-kotlin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin-stdlib" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.intellij.psi.PsiLiteralExpression
import com.mongodb.jbplugin.accessadapter.datagrip.adapter.isConnected
import com.mongodb.jbplugin.codeActions.AbstractMongoDbCodeActionBridge
import com.mongodb.jbplugin.codeActions.MongoDbCodeAction
import com.mongodb.jbplugin.codeActions.impl.runQuery.RunQueryModal
import com.mongodb.jbplugin.codeActions.sourceForMarker
import com.mongodb.jbplugin.dialects.DialectFormatter
import com.mongodb.jbplugin.dialects.OutputQuery
Expand All @@ -35,7 +36,6 @@ import com.mongodb.jbplugin.i18n.CodeActionsMessages
import com.mongodb.jbplugin.i18n.Icons
import com.mongodb.jbplugin.meta.service
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.TelemetryEvent.QueryRunEvent.Console
Expand Down Expand Up @@ -74,23 +74,35 @@ internal object RunQueryCodeAction : MongoDbCodeAction {
} else {
emitRunQueryEvent(query, dataSource)

coroutineScope.launchChildBackground {
val outputQuery = MongoshDialect.formatter.formatQuery(
query,
QueryContext.empty(prettyPrint = true)
)
if (dataSource == null || dataSource.isConnected() == false) {
return@LineMarkerInfo
}

if (dataSource?.isConnected() == true) {
coroutineScope.launchChildOnUi {
openDataGripConsole(query, dataSource, outputQuery.query)
}
} else {
openConsoleAfterSelection(
val queryContext = RunQueryModal(
query,
dataSource,
coroutineScope
).askForQueryContext()

if (queryContext != null) {
coroutineScope.launchChildBackground {
val outputQuery = MongoshDialect.formatter.formatQuery(
query,
outputQuery,
query.source.project,
coroutineScope
queryContext
)

if (dataSource.isConnected() == true) {
coroutineScope.launchChildOnUi {
openDataGripConsole(query, dataSource, outputQuery.query)
}
} else {
openConsoleAfterSelection(
query,
outputQuery,
query.source.project,
coroutineScope
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package com.mongodb.jbplugin.codeActions.impl.runQuery

import com.intellij.collaboration.ui.selectFirst
import com.intellij.database.dataSource.LocalDataSource
import com.intellij.openapi.project.Project
import com.intellij.openapi.rd.util.launchChildBackground
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.AnimatedIcon.ANIMATION_IN_RENDERER_ALLOWED
import com.intellij.ui.components.JBLabel
import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider
import com.mongodb.jbplugin.accessadapter.slice.ListCollections
import com.mongodb.jbplugin.accessadapter.slice.ListDatabases
import com.mongodb.jbplugin.i18n.Icons
import com.mongodb.jbplugin.i18n.Icons.scaledToText
import com.mongodb.jbplugin.meta.latest
import com.mongodb.jbplugin.meta.service
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import java.awt.Component
import javax.swing.DefaultComboBoxModel
import javax.swing.SwingConstants

class NamespaceSelector(
private val project: Project,
private val dataSource: LocalDataSource,
private val coroutineScope: CoroutineScope,
) {
sealed interface Event {
data object DatabasesLoading : Event
data class DatabasesLoaded(val databases: List<String>) : Event
data class DatabaseSelected(val database: String) : Event
data object CollectionsLoading : Event
data class CollectionsLoaded(val collections: List<String>) : Event
data class CollectionSelected(val collection: String) : Event
}

private val events: MutableSharedFlow<Event> = MutableSharedFlow(extraBufferCapacity = 1)

private val databaseModel = DefaultComboBoxModel<String>(emptyArray())
val databaseComboBox = ComboBox<String?>(databaseModel)
private val collectionModel = DefaultComboBoxModel<String>(emptyArray())
val collectionComboBox = ComboBox<String?>(collectionModel)

val selectedDatabase: String?
get() = databaseModel.selectedItem?.toString()

val selectedCollection: String?
get() = collectionModel.selectedItem?.toString()

private val loadingDatabases: Boolean by events.latest(
onNewEvent = { event, state ->
when (event) {
is Event.DatabasesLoading -> true
is Event.DatabasesLoaded -> false
else -> state
}
},
onChange = {},
defaultValue = true
)

private val loadingCollections: Boolean by events.latest(
onNewEvent = { event, state ->
when (event) {
is Event.CollectionsLoading -> true
is Event.CollectionsLoaded -> false
else -> state
}
},
onChange = {},
defaultValue = false
)

init {
databaseComboBox.isEnabled = false
collectionComboBox.isEnabled = false
databaseComboBox.name = "DatabaseComboBox"
collectionComboBox.name = "CollectionComboBox"

databaseComboBox.prototypeDisplayValue = "XXXXXXXXXXXXXXXXXXXXX"
databaseComboBox.putClientProperty(ANIMATION_IN_RENDERER_ALLOWED, true)

collectionComboBox.prototypeDisplayValue = "XXXXXXXXXXXXXXXXXXXXX"
collectionComboBox.putClientProperty(ANIMATION_IN_RENDERER_ALLOWED, true)

databaseComboBox.setRenderer { _, value, index, _, _ -> renderDatabaseItem(value, index) }
collectionComboBox.setRenderer { _, value, index, _, _ ->
renderCollectionItem(value, index)
}

coroutineScope.launchChildBackground {
events.collect(::handleEvent)
}

databaseComboBox.addItemListener {
coroutineScope.launchChildBackground {
events.emit(Event.DatabaseSelected(it.item.toString()))
}
}

collectionComboBox.addItemListener {
coroutineScope.launchChildBackground {
events.emit(Event.CollectionSelected(it.item.toString()))
}
}

loadDatabases()
}

private fun loadDatabases() {
coroutineScope.launchChildBackground {
events.emit(Event.DatabasesLoading)
val readModel by project.service<DataGripBasedReadModelProvider>()
val result = readModel.slice(
dataSource,
ListDatabases.Slice
)

events.emit(Event.DatabasesLoaded(result.databases.map { it.name }))
}
}

private fun handleEvent(event: Event) {
when (event) {
is Event.DatabasesLoading -> {
databaseComboBox.isEnabled = false
}

is Event.DatabasesLoaded -> {
databaseModel.removeAllElements()
collectionModel.removeAllElements()
databaseModel.addAll(event.databases)
databaseModel.selectFirst()
databaseComboBox.isEnabled = true
}
is Event.DatabaseSelected -> {
coroutineScope.launchChildBackground {
events.emit(Event.CollectionsLoading)
val readModel by project.service<DataGripBasedReadModelProvider>()
val result = readModel.slice(
dataSource,
ListCollections.Slice(event.database)
)

events.emit(Event.CollectionsLoaded(result.collections.map { it.name }))
}
}
is Event.CollectionsLoading -> {
collectionModel.removeAllElements()
collectionComboBox.isEnabled = false
}

is Event.CollectionsLoaded -> {
collectionModel.addAll(event.collections)
collectionModel.selectFirst()
collectionComboBox.isEnabled = true
}
else -> {}
}
}

private fun renderDatabaseItem(item: String?, index: Int): Component = if (item == null &&
index == -1 &&
loadingDatabases
) {
JBLabel("Loading databases...", Icons.loading.scaledToText(), SwingConstants.LEFT)
} else {
JBLabel(item ?: "", Icons.databaseAutocompleteEntry, SwingConstants.LEFT)
}

private fun renderCollectionItem(item: String?, index: Int): Component = if (item == null &&
index == -1 &&
loadingCollections
) {
JBLabel("Loading collections...", Icons.loading.scaledToText(), SwingConstants.LEFT)
} else {
JBLabel(item ?: "", Icons.collectionAutocompleteEntry, SwingConstants.LEFT)
}
}
Loading

0 comments on commit facd7e2

Please sign in to comment.