Skip to content

Commit

Permalink
Merge pull request microsoft#106 from maartenba/183-mb-dbexplorer-con…
Browse files Browse the repository at this point in the history
…nect

Add "Connect to server/database" actions
  • Loading branch information
maartenba authored Nov 14, 2018
2 parents 15253d9 + ab19ec2 commit b8fd9aa
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ idea_version=IC-183-SNAPSHOT
rider_version=RD-2018.3-SNAPSHOT
build_common_code_with=rider
dep_plugins=org.intellij.scala:2018.3.1
dep_plugins_rider=com.intellij.database:183.4139.35
applicationinsights.key=57cc111a-36a8-44b3-b044-25d293b8b77c
updateVersionRange=true
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ intellij {
pluginName = 'azure-toolkit-for-rider'
version = rider_version

plugins 'terminal'
plugins 'terminal', dep_plugins_rider
}

instrumentCode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<idea-version since-build="181.*" until-build="181.*"/>

<depends>com.intellij.modules.rider</depends>
<depends>com.intellij.database</depends>
<depends>org.jetbrains.plugins.terminal</depends>

<extensions defaultExtensionNs="com.intellij">
Expand All @@ -34,6 +35,10 @@
<rider.publishConfigurationProvider implementation="com.microsoft.intellij.runner.webapp.AzureDotNetWebAppContextPublishProvider" order="last" />
</extensions>

<extensions defaultExtensionNs="com.microsoft.intellij">
<nodeActionsMap implementation="com.microsoft.intellij.serviceexplorer.RiderNodeActionsMap" />
</extensions>

<application-components>
<component>
<implementation-class>com.microsoft.intellij.AzureRiderActionsComponent</implementation-class>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2018 JetBrains s.r.o.
* <p/>
* All rights reserved.
* <p/>
* MIT License
* <p/>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p/>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
* <p/>
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.microsoft.intellij.serviceexplorer

import com.google.common.collect.ImmutableList
import com.microsoft.intellij.serviceexplorer.azure.database.actions.*
import com.microsoft.tooling.msservices.serviceexplorer.Node
import com.microsoft.tooling.msservices.serviceexplorer.NodeActionListener
import com.microsoft.tooling.msservices.serviceexplorer.azure.database.sqldatabase.SqlDatabaseNode
import com.microsoft.tooling.msservices.serviceexplorer.azure.database.sqlserver.SqlServerNode
import java.util.*

class RiderNodeActionsMap : NodeActionsMap() {
override fun getMap(): Map<Class<out Node>, ImmutableList<Class<out NodeActionListener>>> {
return node2Actions
}

companion object {
private val node2Actions = HashMap<Class<out Node>, ImmutableList<Class<out NodeActionListener>>>()

init {
node2Actions[SqlServerNode::class.java] = ImmutableList.Builder<Class<out NodeActionListener>>()
.add(ConnectServerAction::class.java)
.build()

node2Actions[SqlDatabaseNode::class.java] = ImmutableList.Builder<Class<out NodeActionListener>>()
.add(ConnectDatabaseAction::class.java)
.build()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright (c) 2018 JetBrains s.r.o.
* <p/>
* All rights reserved.
* <p/>
* MIT License
* <p/>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p/>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
* <p/>
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.microsoft.intellij.serviceexplorer.azure.database.actions

import com.intellij.database.autoconfig.DataSourceDetector
import com.intellij.database.autoconfig.DataSourceRegistry
import com.intellij.openapi.project.Project
import com.microsoft.azuretools.authmanage.AuthMethodManager
import com.microsoft.azuretools.ijidea.actions.AzureSignInAction
import com.microsoft.intellij.AzurePlugin
import com.microsoft.tooling.msservices.serviceexplorer.Node
import com.microsoft.tooling.msservices.serviceexplorer.NodeActionEvent
import com.microsoft.tooling.msservices.serviceexplorer.NodeActionListener

abstract class ConnectDataSourceAction(protected val node: Node) : NodeActionListener() {

public override fun actionPerformed(e: NodeActionEvent) {
val project = node.project as Project ?: return

try {
if (!AzureSignInAction.doSignIn(AuthMethodManager.getInstance(), project)) return

val registry = DataSourceRegistry(project)

val builder = populateConnectionBuilder(registry.builder)

if (builder != null) {
builder.commit()
registry.showDialog()
}
} catch (ex: Throwable) {
AzurePlugin.log("Error connecting to database", ex)
throw RuntimeException("Error connecting to database", ex)
}
}

abstract fun populateConnectionBuilder(builder: DataSourceDetector.Builder): DataSourceDetector.Builder?

protected class AzureSqlConnectionStringBuilder {
companion object {
const val defaultDriverClass = "com.microsoft.sqlserver.jdbc.SQLServerDriver"

fun build(host: String) : String {
return "jdbc:sqlserver://$host:1433;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;"
}

fun build(host: String, database: String) : String {
return "jdbc:sqlserver://$host:1433;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;database=$database;"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2018 JetBrains s.r.o.
* <p/>
* All rights reserved.
* <p/>
* MIT License
* <p/>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p/>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
* <p/>
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.microsoft.intellij.serviceexplorer.azure.database.actions

import com.intellij.database.autoconfig.DataSourceDetector
import com.microsoft.intellij.runner.db.AzureDatabaseMvpModel
import com.microsoft.tooling.msservices.helpers.Name
import com.microsoft.tooling.msservices.serviceexplorer.azure.database.sqldatabase.SqlDatabaseNode
import com.microsoft.tooling.msservices.serviceexplorer.azure.database.sqlserver.SqlServerNode


@Name("Connect to database")
class ConnectDatabaseAction(private val databaseNode: SqlDatabaseNode)
: ConnectDataSourceAction(databaseNode) {

override fun populateConnectionBuilder(builder: DataSourceDetector.Builder): DataSourceDetector.Builder? {
val databaseServerNode = databaseNode.parent as SqlServerNode

val sqlServer = AzureDatabaseMvpModel.getSqlServerById(databaseServerNode.subscriptionId, databaseServerNode.sqlServerId)

return builder
.withName("Azure SQL Database - " + databaseServerNode.sqlServerName + " - " + databaseNode.sqlDatabaseName)
.withDriverClass(AzureSqlConnectionStringBuilder.defaultDriverClass)
.withUrl(AzureSqlConnectionStringBuilder.build(
sqlServer.fullyQualifiedDomainName(),
databaseNode.sqlDatabaseName
))
.withUser(sqlServer.administratorLogin())
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2018 JetBrains s.r.o.
* <p/>
* All rights reserved.
* <p/>
* MIT License
* <p/>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p/>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
* <p/>
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.microsoft.intellij.serviceexplorer.azure.database.actions

import com.intellij.database.autoconfig.DataSourceDetector
import com.microsoft.intellij.runner.db.AzureDatabaseMvpModel
import com.microsoft.tooling.msservices.helpers.Name
import com.microsoft.tooling.msservices.serviceexplorer.azure.database.sqlserver.SqlServerNode

@Name("Connect to server")
class ConnectServerAction(private val databaseServerNode: SqlServerNode)
: ConnectDataSourceAction(databaseServerNode) {

override fun populateConnectionBuilder(builder: DataSourceDetector.Builder): DataSourceDetector.Builder? {
val sqlServer = AzureDatabaseMvpModel.getSqlServerById(databaseServerNode.subscriptionId, databaseServerNode.sqlServerId)

return builder
.withName("Azure SQL Database Server - " + databaseServerNode.sqlServerName)
.withDriverClass(AzureSqlConnectionStringBuilder.defaultDriverClass)
.withUrl(AzureSqlConnectionStringBuilder.build(
sqlServer.fullyQualifiedDomainName()
))
.withUser(sqlServer.administratorLogin())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2018 JetBrains s.r.o.
* <p/>
* All rights reserved.
* <p/>
* MIT License
* <p/>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p/>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
* <p/>
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.microsoft.intellij.helpers.base

import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.jetbrains.rider.util.idea.application
import com.jetbrains.rider.util.idea.getLogger
import com.jetbrains.rider.util.lifetime.Lifetime
import com.jetbrains.rider.util.reactive.Signal
import com.jetbrains.rider.util.reactive.adviseOnce
import com.microsoft.azuretools.core.mvp.ui.base.MvpPresenter
import com.microsoft.azuretools.core.mvp.ui.base.MvpView
import com.microsoft.tooling.msservices.components.DefaultLoader

open class AzureMvpPresenter<V : MvpView> : MvpPresenter<V>() {

companion object {
private val LOG = getLogger(this)
}

fun <T>subscribe(lifetime: Lifetime,
signal: Signal<T>,
taskName: String,
errorMessage: String,
callableFunc: () -> T,
invokeLaterCallback: (T) -> Unit) {

signal.adviseOnce(lifetime) {
application.invokeLater {
if (lifetime.isTerminated) return@invokeLater
invokeLaterCallback(it)
}
}

ProgressManager.getInstance().run(object : Task.Backgroundable(null, taskName, false) {
override fun run(indicator: ProgressIndicator) {
signal.fire(callableFunc())
}

override fun onThrowable(e: Throwable) {
LOG.error(e)
errorHandler(errorMessage, e as Exception)
super.onThrowable(e)
}
})
}

private fun errorHandler(message: String, e: Exception) {
DefaultLoader.getIdeHelper().invokeLater {
if (isViewDetached) return@invokeLater
mvpView.onErrorWithException(message, e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.microsoft.intellij.helpers.validator

import com.intellij.execution.configurations.RuntimeConfigurationError

open class ConfigurationValidator {

/**
* Validate Azure resource name against Azure requirements
* Please see for details - https://docs.microsoft.com/en-us/azure/architecture/best-practices/naming-conventions
*
* @throws [RuntimeConfigurationError] in case name does not match requirements
*/
@Throws(RuntimeConfigurationError::class)
fun validateResourceName(name: String,
nameLengthMin: Int,
nameLengthMax: Int,
nameLengthErrorMessage: String,
nameRegex: Regex,
nameInvalidCharsMessage: String) {

validateResourceNameRegex(name, nameRegex, nameInvalidCharsMessage)

if (name.length !in nameLengthMin..nameLengthMax)
throw RuntimeConfigurationError(nameLengthErrorMessage)
}

@Throws(RuntimeConfigurationError::class)
fun validateResourceNameRegex(name: String,
nameRegex: Regex,
nameInvalidCharsMessage: String) {
val matches = nameRegex.findAll(name)
if (matches.count() > 0) {
val invalidChars = matches.map { it.value }.distinct().joinToString("', '", "'", "'")
throw RuntimeConfigurationError(String.format(nameInvalidCharsMessage, invalidChars))
}
}

/**
* Validate the field is set in a configuration
*
* @param value filed value to validate
* @param message failure message to show to a user
*
* @throws [RuntimeConfigurationError] if field value is not set
*/
@Throws(RuntimeConfigurationError::class)
fun checkValueIsSet(value: String, message: String) {
if (value.isEmpty()) throw RuntimeConfigurationError(message)
}

@Throws(RuntimeConfigurationError::class)
fun checkValueIsSet(value: CharArray, message: String) {
if (value.isEmpty()) throw RuntimeConfigurationError(message)
}
}
Loading

0 comments on commit b8fd9aa

Please sign in to comment.