diff --git a/.gitignore b/.gitignore index f2db4d08b45..fa05dc8a554 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,5 @@ pom.xml.versionsBackup # Bicep Language Server **/bicep-langserver/** **/azure-intellij-plugin-bicep/downloaded.zip +*.log +deps.txt diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..56304c79d04 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,125 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Azure Toolkits for Java — IDE plugins for IntelliJ IDEA and Eclipse that help Java developers create, develop, configure, test, and deploy applications to Azure. The primary focus of active development is the IntelliJ plugin. + +## Build Commands + +### Prerequisites +- Java 17 (required for IntelliJ plugin; set `JAVA_HOME` accordingly) +- The shared toolkit libraries from [azure-maven-plugins](https://github.com/microsoft/azure-maven-plugins) must be installed to local Maven repo first + +### Build shared toolkit libs (one-time or when libs change) +```bash +# Clone and build azure-toolkit-libs into local Maven repo +git clone https://github.com/microsoft/azure-maven-plugins.git +cd azure-maven-plugins/azure-toolkit-libs +mvn clean install -B -T 4 -Dmaven.test.skip=true +``` + +### Build Utils (Maven modules in Utils/) +```bash +# From repo root — builds the IDE-specific wrapper libraries +mvn clean install -f Utils/pom.xml +# Or via the root Gradle task: +./gradlew buildUtils -x buildToolkitsLib +``` + +### Build IntelliJ plugin +```bash +cd PluginsAndFeatures/azure-toolkit-for-intellij +./gradlew clean buildPlugin -s +``` +Output ZIP: `PluginsAndFeatures/azure-toolkit-for-intellij/build/distributions/` + +### Build Eclipse plugin +```bash +mvn clean install -f Utils/pom.xml +mvn clean install -f PluginsAndFeatures/AddLibrary/AzureLibraries/pom.xml +mvn clean install -f PluginsAndFeatures/azure-toolkit-for-eclipse/pom.xml +``` + +### Build everything (from repo root) +```bash +./gradlew buildAll +``` + +### Run/Debug IntelliJ plugin locally +Open `PluginsAndFeatures/azure-toolkit-for-intellij` in IntelliJ and use the Gradle `runIde` task. Remote debug port: 5005. + +### Tests +```bash +# IntelliJ plugin tests (run from the IntelliJ plugin dir) +cd PluginsAndFeatures/azure-toolkit-for-intellij +./gradlew test + +# Utils tests (Maven) +mvn test -f Utils/pom.xml + +# Skip tests during build +./gradlew buildAll -PskipTest=true +# Or for Maven modules: +mvn install -f Utils/pom.xml -Dmaven.test.skip=true +``` + +### Checkstyle +Checkstyle runs by default on Maven builds. Skip with: +```bash +mvn install -f Utils/pom.xml -Dcheckstyle.skip=true +``` +IntelliJ plugin checkstyle config: `PluginsAndFeatures/azure-toolkit-for-intellij/config/checkstyle/checkstyle.xml` + +## Architecture + +### Three-Layer Dependency Stack + +1. **azure-toolkit-libs** (external repo: `azure-maven-plugins`) — Core SDK wrappers for Azure services. IDE-agnostic. Must be `mvn install`ed to local repo before building this project. + +2. **Utils/** (this repo, Maven) — IDE-specific wrapper libraries (`azure-toolkit-ide-libs` and `azure-toolkit-ide-hdinsight-libs`). Each Azure service has a corresponding `azure-toolkit-ide-*-lib` module (e.g., `azure-toolkit-ide-appservice-lib`, `azure-toolkit-ide-cosmos-lib`). Built with Maven and published to local repo for the Gradle IntelliJ build to consume. + +3. **PluginsAndFeatures/** (this repo) — IDE plugin implementations: + - `azure-toolkit-for-intellij/` — IntelliJ plugin (Gradle + Kotlin DSL, ~40 submodules) + - `azure-toolkit-for-eclipse/` — Eclipse plugin (Maven/Tycho) + +### IntelliJ Plugin Module Structure + +The IntelliJ plugin is a single composite plugin built from ~40 Gradle submodules, each declared in `settings.gradle.kts`. Key module categories: + +- **`azure-intellij-plugin-lib`** — Core IntelliJ integration: UI components (AzureComboBox, AzureDialog, Tree), action framework, auth, messager, task manager, telemetry. This is the foundation module. +- **`azure-intellij-plugin-lib-java`** — Java-specific IntelliJ utilities (run configs, artifact handling). +- **`azure-intellij-plugin-base`** — Plugin entry point (`AzurePlugin` as `StartupActivity`), lifecycle management. +- **`azure-intellij-plugin-service-explorer`** — Azure Explorer tool window. +- **`azure-intellij-resource-connector-lib`** — Resource connection framework (connecting Azure resources to project modules). +- **Service modules** — One per Azure service (e.g., `azure-intellij-plugin-appservice`, `azure-intellij-plugin-cosmos`, `azure-intellij-plugin-database`, `azure-intellij-plugin-storage`, etc.) +- **`-java` suffix modules** — Java-language-specific features for that service (e.g., `azure-intellij-plugin-appservice-java`, `azure-intellij-plugin-database-java`). + +### Extension Point Pattern + +The plugin uses an IntelliJ extension point `com.microsoft.tooling.msservices.intellij.azure.actions` with the `IActionsContributor` interface. Each service module registers its own `ActionsContributor` implementation via its `META-INF/*.xml` descriptor. These are composed into the main `plugin.xml` using `xi:include`. + +To add a new service module: +1. Create the module directory under `PluginsAndFeatures/azure-toolkit-for-intellij/` +2. Add it to `settings.gradle.kts` +3. Add an `implementation(project(":module-name"))` dependency in the root `build.gradle.kts` +4. Create a `META-INF/.xml` descriptor +5. Include it in `src/main/resources/META-INF/plugin.xml` via `xi:include` + +### Build System Details + +- **Root Gradle** (`build.gradle` at repo root) — Orchestrates cross-project builds. Uses Groovy DSL with Gradle 8.8. +- **IntelliJ Gradle** (`PluginsAndFeatures/azure-toolkit-for-intellij/build.gradle.kts`) — Uses Kotlin DSL with IntelliJ Platform Gradle Plugin 2.3.0. Targets IntelliJ IDEA Ultimate for compilation. Version catalog at `gradle/libs.versions.toml`. +- **Maven** (`Utils/pom.xml`) — Builds the IDE wrapper libraries. Uses AspectJ for cross-cutting concerns (via `azure-toolkit-common-lib`). +- AspectJ weaving is used at both Maven (aspectj-maven-plugin) and Gradle (io.freefair.aspectj.post-compile-weaving) levels. + +### Key Conventions + +- Java 17 source/target for all modules. +- Lombok is used project-wide (`@Data`, `@Builder`, etc.). +- All Java source files require the MIT license header (enforced by checkstyle). +- LF line endings required (no CRLF). +- No tabs — spaces only. +- TODOs must be named: `TODO (name): description`. +- Base package: `com.microsoft.azure.toolkit.intellij` for IntelliJ modules, `com.microsoft.azure.toolkit.lib` for library modules. diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-bicep/build.gradle.kts b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-bicep/build.gradle.kts index 6309b2736b6..a8ef64b4d8d 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-bicep/build.gradle.kts +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-bicep/build.gradle.kts @@ -8,6 +8,8 @@ dependencies { implementation("com.vladsch.flexmark:flexmark:0.64.0") implementation("com.vladsch.flexmark:flexmark-util:0.64.0") implementation("org.apache.commons:commons-lang3:3.12.0") + // ComparableVersion is no longer on the platform classpath in 261 + implementation("org.apache.maven:maven-artifact:3.9.11") testImplementation("junit:junit:4.13.2") testImplementation("org.mockito:mockito-core:3.9.0") testImplementation("org.powermock:powermock-api-mockito2:2.0.9") diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-cosmos/src/main/java/com/microsoft/azure/toolkit/intellij/cosmos/dbtools/AzureCosmosDbAccountParamEditor.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-cosmos/src/main/java/com/microsoft/azure/toolkit/intellij/cosmos/dbtools/AzureCosmosDbAccountParamEditor.java index 22947591ec6..86f17c4aeb7 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-cosmos/src/main/java/com/microsoft/azure/toolkit/intellij/cosmos/dbtools/AzureCosmosDbAccountParamEditor.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-cosmos/src/main/java/com/microsoft/azure/toolkit/intellij/cosmos/dbtools/AzureCosmosDbAccountParamEditor.java @@ -209,7 +209,9 @@ private void setUsername(String user) { @SneakyThrows private void setUseSsl(boolean useSsl) { final DataSourceConfigurable configurable = this.getDataSourceConfigurable(); - final JBCheckBox useSSLCheckBox = (JBCheckBox) FieldUtils.readField(configurable.getSshSslPanel(), "myUseSSLJBCheckBox", true); + // getSshSslPanel() was removed in IntelliJ 261; use reflection to access the panel field directly + final Object sshSslPanel = FieldUtils.readField(configurable, "mySshSslPanel", true); + final JBCheckBox useSSLCheckBox = (JBCheckBox) FieldUtils.readField(sshSslPanel, "myUseSSLJBCheckBox", true); useSSLCheckBox.setSelected(useSsl); } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-hdinsight/src/main/java/com/microsoft/azure/hdinsight/projects/SbtVersionOptionsPanel.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-hdinsight/src/main/java/com/microsoft/azure/hdinsight/projects/SbtVersionOptionsPanel.java index 7ff14936b86..d186a228cea 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-hdinsight/src/main/java/com/microsoft/azure/hdinsight/projects/SbtVersionOptionsPanel.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-hdinsight/src/main/java/com/microsoft/azure/hdinsight/projects/SbtVersionOptionsPanel.java @@ -8,11 +8,12 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.ui.ComboBox; import com.microsoft.azure.hdinsight.common.logger.ILogger; -import org.jetbrains.plugins.scala.project.Versions; +import scala.collection.immutable.Seq; import scala.reflect.ClassTag; import javax.swing.*; import java.awt.*; +import java.lang.reflect.Method; public class SbtVersionOptionsPanel extends JPanel implements ILogger { private ComboBox sbtVersionComboBox; @@ -36,7 +37,35 @@ public SbtVersionOptionsPanel() { public void updateSbtVersions() { final String[][] versions = new String[1][1]; ProgressManager.getInstance().runProcess(() -> { - versions[0] = (String[]) Versions.SBT$.MODULE$.loadVersionsWithProgress(null).versions().toArray(ClassTag.apply(String.class)); + try { + // In newer Scala plugin versions, the API changed. Use reflection for cross-version compatibility. + // Old: Versions.SBT$.MODULE$.loadVersionsWithProgress(null).versions() -> Seq + // New: Versions.loadSbtVersions(false, null) -> Seq + final Class versionsClass = Class.forName("org.jetbrains.plugins.scala.project.Versions"); + try { + // Try new API first + final Method loadSbtVersionsMethod = versionsClass.getMethod("loadSbtVersions", boolean.class, com.intellij.openapi.progress.ProgressIndicator.class); + final Seq sbtVersions = (Seq) loadSbtVersionsMethod.invoke(null, false, null); + final int size = sbtVersions.size(); + final String[] result = new String[size]; + for (int i = 0; i < size; i++) { + result[i] = sbtVersions.apply(i).toString(); + } + versions[0] = result; + } catch (final NoSuchMethodException e) { + // Fallback to old API + final Class versionsSbtClass = Class.forName("org.jetbrains.plugins.scala.project.Versions$SBT$"); + final Object module = versionsSbtClass.getField("MODULE$").get(null); + final Method loadMethod = module.getClass().getMethod("loadVersionsWithProgress", com.intellij.openapi.progress.ProgressIndicator.class); + final Object loadedVersions = loadMethod.invoke(module, (Object) null); + final Method versionsMethod = loadedVersions.getClass().getMethod("versions"); + final Seq versionSeq = (Seq) versionsMethod.invoke(loadedVersions); + versions[0] = (String[]) versionSeq.toArray(ClassTag.apply(String.class)); + } + } catch (final Exception e) { + log().warn("Failed to get SBT versions from scala plugin.", e); + versions[0] = new String[0]; + } }, null); for (String version : versions[0]) { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureActionButton.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureActionButton.java index 96e2afda28d..18c1026b9cb 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureActionButton.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureActionButton.java @@ -20,11 +20,20 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Objects; import java.util.Optional; +/** + * A JButton subclass that bridges Azure Toolkit {@link Action} with Swing buttons. + *

+ * Overrides {@link AbstractButton#setAction(javax.swing.Action)} to prevent the inherited + * Swing method from clearing the button text/icon when the IntelliJ form runtime or + * Look-and-Feel calls it unexpectedly. This ensures compatibility with IntelliJ's New UI + * (2026.1+) where DarculaButtonUI may interact with the Swing Action property. + */ public class AzureActionButton extends JButton { public static final DataKey ACTION_EVENT_KEY = DataKey.create("AzureActionButton.actionEvent"); private final ActionListener listener = this::onActionPerformed; @@ -44,6 +53,25 @@ public AzureActionButton(@Nonnull final Action action) { this.registerActionListener(); } + /** + * Override the inherited {@link AbstractButton#setAction(javax.swing.Action)} to prevent + * the Swing Action mechanism from clearing button text and icon. In IntelliJ's New UI, + * the form runtime or LAF may call this method, which by default calls + * {@code configurePropertiesFromAction()} and resets all button properties (text, icon, + * tooltip, enabled) from the javax.swing.Action — wiping out values set by the form + * instrumentation or programmatic code. + *

+ * This no-op override preserves the button's text and icon as set by the GUI form + * or by calling {@link #setText(String)} / {@link #setIcon(Icon)} directly. + */ + @Override + public void setAction(javax.swing.Action a) { + // Intentionally do NOT call super.setAction(a). + // The Swing default would call configurePropertiesFromAction() which clears + // text/icon/tooltip if the javax.swing.Action has no corresponding properties. + // Our buttons get their text from the .form file and icons from Java code. + } + public void setAction(@Nonnull final Action.Id actionId) { setAction(actionId, null); } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/streaminglog/StreamingLogsConsoleView.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/streaminglog/StreamingLogsConsoleView.java index 0596ea516af..673f62e0b65 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/streaminglog/StreamingLogsConsoleView.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/streaminglog/StreamingLogsConsoleView.java @@ -43,6 +43,11 @@ public boolean isActive() { return subscription != null && !subscription.isDisposed(); } + /** + * Returns whether this console view has been disposed. + * Note: In IntelliJ 261+, ConsoleViewImpl.isDisposed() is private final, + * so this is NOT an override but a new public method for callers. + */ public boolean isDisposed() { return this.isDisposed; } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/build.gradle.kts b/PluginsAndFeatures/azure-toolkit-for-intellij/build.gradle.kts index 696be472581..437d80e9239 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/build.gradle.kts +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/build.gradle.kts @@ -1,12 +1,12 @@ import io.freefair.gradle.plugins.aspectj.AjcAction import org.apache.tools.ant.filters.ReplaceTokens import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType -import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask +import java.net.URI import java.time.LocalDateTime import java.time.format.DateTimeFormatter -import java.net.URL fun properties(key: String) = providers.gradleProperty(key) + fun environment(key: String) = providers.environmentVariable(key) plugins { @@ -60,7 +60,16 @@ allprojects { dependencies { intellijPlatform { - intellijIdeaUltimate(properties("platformVersion").get(), useInstaller = false) + intellijIdeaUltimate(properties("platformVersion").get()) { + useInstaller = false + } + // JBR 25 required to run IntelliJ 2026.1 (PathClassLoader is JBR-only) + jetbrainsRuntime() + // MavenId/MavenCoordinate classes moved from maven plugin to repository-search plugin in 261 + bundledPlugin("org.jetbrains.idea.reposearch") + // Test framework classes moved to separate modules in 261 + testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform) + testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Plugin.Java) } implementation(platform("com.microsoft.azure:azure-toolkit-libs:0.52.2")) @@ -72,6 +81,8 @@ allprojects { annotationProcessor("org.projectlombok:lombok:1.18.32") implementation("com.microsoft.azure:azure-toolkit-common-lib:0.52.2") aspect("com.microsoft.azure:azure-toolkit-common-lib:0.52.2") + // junit was removed from IntelliJ platform bundled libs in 261 + testImplementation("junit:junit:4.13.2") } configurations { @@ -114,6 +125,17 @@ allprojects { duplicatesStrategy = DuplicatesStrategy.WARN } + // Gradle 9 requires explicit dependency declaration for shared sandbox outputs + withType { + dependsOn(rootProject.tasks.named("prepareTestSandbox")) + // Each subproject's test sandbox may be produced by other subproject tasks + rootProject.subprojects.forEach { sub -> + sub.tasks.matching { it.name == "prepareTestSandbox" }.configureEach { + this@withType.dependsOn(this) + } + } + } + sourceSets { main { java.srcDirs("src/main/java") @@ -125,6 +147,10 @@ allprojects { java.srcDir("src/test/java") kotlin.srcDirs("src/test/kotlin") resources.srcDir("src/test/resources") + // Exclude legacy duplicate hdinsight test files from root module; + // they are properly maintained in azure-intellij-plugin-hdinsight-base + java.exclude("com/microsoft/azure/hdinsight/**") + kotlin.exclude("com/microsoft/azure/hdinsight/**") } } } @@ -146,8 +172,14 @@ intellijPlatform { pluginVerification { ides { - ide(IntelliJPlatformType.IntellijIdeaCommunity, properties("platformVersion").get()) + // IC (Community) no longer published since 253; use IU (Ultimate) for verification + create(IntelliJPlatformType.IntellijIdeaUltimate, properties("platformVersion").get()) } + // Suppress known structural warnings — plugin ID/name historically contain "intellij" + freeArgs = listOf( + "-mute", "TemplateWordInPluginId", + "-mute", "TemplateWordInPluginName" + ) } } @@ -214,6 +246,11 @@ dependencies { implementation("com.microsoft.azure:azure-toolkit-auth-lib") implementation("com.microsoft.azure:azure-toolkit-ide-common-lib") implementation("com.microsoft.azure:azure-toolkit-ide-appservice-lib") + + // Test dependencies for root module tests (cucumber, assertj) + testImplementation("io.cucumber:cucumber-java:7.0.0") + testImplementation("io.cucumber:cucumber-junit:7.0.0") + testImplementation("org.assertj:assertj-core:3.19.0") } tasks { @@ -256,7 +293,7 @@ tasks { if (!langServerDir.exists()) { logger.info("Downloading bicep language server ...") val zipFile = file("azure-intellij-plugin-bicep/downloaded.zip") - URL("https://aka.ms/java-toolkit-bicep-ls").openStream().use { input -> + URI("https://aka.ms/java-toolkit-bicep-ls").toURL().openStream().use { input -> zipFile.outputStream().use { it.write(input.readBytes()) } } logger.info("Unzipping bicep language server ...") @@ -301,4 +338,4 @@ tasks { // publishPlugin { // dependsOn(patchChangelog) // } -} \ No newline at end of file +} diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/gradle.properties b/PluginsAndFeatures/azure-toolkit-for-intellij/gradle.properties index 35f6e586dce..eb1a694780f 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/gradle.properties +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/gradle.properties @@ -1,12 +1,12 @@ pluginVersion=3.97.6 -intellijDisplayVersion=2025.3 -intellij_version=253-EAP-SNAPSHOT -platformVersion=253-EAP-SNAPSHOT +intellijDisplayVersion=2026.1 +intellij_version=261-EAP-SNAPSHOT +platformVersion=261-EAP-SNAPSHOT # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html -pluginSinceBuild=253 -pluginUntilBuild=253.* +pluginSinceBuild=261 +pluginUntilBuild=261.* # Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP -platformPlugins=org.intellij.scala:2025.3.12 +platformPlugins=org.intellij.scala:2026.1.8 # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension platformType=IU needPatchVersion=true @@ -22,7 +22,7 @@ platformDownloadSources=true # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib kotlin.stdlib.default.dependency=false # Gradle Releases -> https://github.com/gradle/gradle/releases -gradleVersion=8.14.1 +gradleVersion=9.0 # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html org.gradle.configuration-cache=true org.gradle.configuration-cache.problems=warn @@ -36,4 +36,3 @@ org.gradle.jvmargs='-Duser.language=en' org.jetbrains.intellij.platform.buildFeature.useBinaryReleases=false - diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/libs.versions.toml b/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/libs.versions.toml index fa3446576dd..6527cbeaabe 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/libs.versions.toml +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/libs.versions.toml @@ -5,12 +5,12 @@ kotlin = "2.2.20" changelog = "2.4.0" #intellijPlatform = "2.10.1" -intellijPlatform = "2.10.1" +intellijPlatform = "2.12.0" detekt = "1.23.6" ktlint = "12.1.1" #gradleIntelliJPlugin = "1.17.3" #qodana = "2024.1.5" -aspectj = "8.6" +aspectj = "9.2.0" springDependencyManagement = "1.1.6" serialization = "1.9.24" diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/wrapper/gradle-wrapper.properties b/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/wrapper/gradle-wrapper.properties index 002b867c48b..2dcec856bd0 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/wrapper/gradle-wrapper.properties +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkSubmitModelScenario.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkSubmitModelScenario.kt index 4a3bc9661fb..6d3630dcf48 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkSubmitModelScenario.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkSubmitModelScenario.kt @@ -40,15 +40,17 @@ class SparkSubmitModelScenario { @Given("^set SparkSubmitModel properties as following$") fun initSparkSubmitModel(properties: Map) { - properties.forEach { key, value -> when (key) { - "cluster_name" -> submitModel.clusterName = value - "is_local_artifact" -> submitModel.isLocalArtifact = value.toBoolean() - "local_artifact_path" -> submitModel.localArtifactPath = value - "classname" -> submitModel.mainClassName = value - "cmd_line_args" -> submitModel.commandLineArgs = value.split(" ") - "ref_jars" -> submitModel.referenceJars = value.split(";") - "ref_files" -> submitModel.referenceFiles = value.split(";") - } } + properties.forEach { key, value -> + when (key) { + "cluster_name" -> submitModel.clusterName = value + "is_local_artifact" -> submitModel.isLocalArtifact = value.toBoolean() + "local_artifact_path" -> submitModel.localArtifactPath = value + "classname" -> submitModel.mainClassName = value + "cmd_line_args" -> submitModel.commandLineArgs = value.split(" ") + "ref_jars" -> submitModel.referenceJars = value.split(";") + "ref_files" -> submitModel.referenceFiles = value.split(";") + } + } } @Then("^checking XML serialized should to be '(.*)'$") @@ -57,7 +59,6 @@ class SparkSubmitModelScenario { val actual = XMLOutputter().outputString(element) assertEquals(expect, actual) - } @Given("^the SparkSubmitModel XML input '(.*)' to deserialize$") @@ -69,14 +70,16 @@ class SparkSubmitModelScenario { @Then("^check SparkSubmitModel properties as following$") fun checkSparkSubmitMode(expect: Map) { - expect.forEach { key, value -> when (key) { - "cluster_name" -> assertEquals(value, submitModel.clusterName) - "is_local_artifact" -> assertEquals(value.toBoolean(), submitModel.isLocalArtifact) - "local_artifact_path" -> assertEquals(value, submitModel.localArtifactPath) - "classname" -> assertEquals(value, submitModel.mainClassName) - "cmd_line_args" -> assertEquals(value, submitModel.commandLineArgs.joinToString(" ")) - "ref_jars" -> assertEquals(value, submitModel.referenceJars.joinToString(";")) - "ref_files" -> assertEquals(value, submitModel.referenceFiles.joinToString(";")) - } } + expect.forEach { key, value -> + when (key) { + "cluster_name" -> assertEquals(value, submitModel.clusterName) + "is_local_artifact" -> assertEquals(value.toBoolean(), submitModel.isLocalArtifact) + "local_artifact_path" -> assertEquals(value, submitModel.localArtifactPath) + "classname" -> assertEquals(value, submitModel.mainClassName) + "cmd_line_args" -> assertEquals(value, submitModel.commandLineArgs.joinToString(" ")) + "ref_jars" -> assertEquals(value, submitModel.referenceJars.joinToString(";")) + "ref_files" -> assertEquals(value, submitModel.referenceFiles.joinToString(";")) + } + } } -} \ No newline at end of file +} diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkUITest.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkUITest.kt index 8b5c5d9745e..0187aa13f06 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkUITest.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/common/SparkUITest.kt @@ -51,4 +51,4 @@ open class SparkUITest : LightJavaCodeInsightFixtureTestCase() { isVisible = true } } -} \ No newline at end of file +} diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/ImmutableComboBoxModelTest.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/ImmutableComboBoxModelTest.kt index 8e1d8bf2c57..7d6f792109d 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/ImmutableComboBoxModelTest.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/ImmutableComboBoxModelTest.kt @@ -25,10 +25,10 @@ package com.microsoft.azure.hdinsight.spark.ui import com.microsoft.intellij.ui.util.findFirst import com.microsoft.intellij.ui.util.iterator import junit.framework.TestCase -import org.assertj.core.api.Assertions.* +import org.assertj.core.api.Assertions.assertThat import org.junit.Test -class ImmutableComboBoxModelTest: TestCase() { +class ImmutableComboBoxModelTest : TestCase() { @Test fun testIterator() { val data = listOf(3, 5, 4, 2) @@ -51,4 +51,4 @@ class ImmutableComboBoxModelTest: TestCase() { val emptyModel: ImmutableComboBoxModel? = null assertNull(emptyModel.findFirst { true }) } -} \ No newline at end of file +} diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/SparkJobTableTest.kt b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/SparkJobTableTest.kt index b482f9cd4cf..f294523a2ae 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/SparkJobTableTest.kt +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/test/java/com/microsoft/azure/hdinsight/spark/ui/SparkJobTableTest.kt @@ -42,40 +42,48 @@ import java.awt.event.WindowEvent import java.util.concurrent.TimeUnit class KillLivyJobAction : AzureAnAction(AllIcons.Actions.Cancel) { - override fun onActionPerformed(anActionEvent: AnActionEvent, operation: Operation?): Boolean { + override fun onActionPerformed( + anActionEvent: AnActionEvent, + operation: Operation?, + ): Boolean { System.out.println("Clicked ${anActionEvent.place} kill job button") return true } } class RestartLivyJobAction : AzureAnAction(AllIcons.Actions.Restart) { - override fun onActionPerformed(anActionEvent: AnActionEvent, operation: Operation?): Boolean { + override fun onActionPerformed( + anActionEvent: AnActionEvent, + operation: Operation?, + ): Boolean { System.out.println("Clicked ${anActionEvent.place} restart job button") return true } } -const val killActionColName = "KillAction" -const val restartActionColName = "RestartAction" -const val idColName = "ID" -const val appIdColName = "AppID" -const val stateColName = "State" - -class MockSparkLivyJobsTableSchema - : UniqueColumnNameTableSchema(arrayOf( - ActionColumnInfo(killActionColName), - ActionColumnInfo(restartActionColName), - PlainColumnInfo(idColName), - PlainColumnInfo(appIdColName), - PlainColumnInfo(stateColName))) { +const val KILL_ACTION_COL_NAME = "KillAction" +const val RESTART_ACTION_COL_NAME = "RestartAction" +const val ID_COL_NAME = "ID" +const val APP_ID_COL_NAME = "AppID" +const val STATE_COL_NAME = "State" +class MockSparkLivyJobsTableSchema : + UniqueColumnNameTableSchema( + arrayOf( + ActionColumnInfo(KILL_ACTION_COL_NAME), + ActionColumnInfo(RESTART_ACTION_COL_NAME), + PlainColumnInfo(ID_COL_NAME), + PlainColumnInfo(APP_ID_COL_NAME), + PlainColumnInfo(STATE_COL_NAME), + ), + ) { inner class MockSparkJobDescriptor(val jobStatus: SparkSubmitResponse) : RowDescriptor( - killActionColName to KillLivyJobAction(), - restartActionColName to RestartLivyJobAction(), - idColName to jobStatus.id, - appIdColName to jobStatus.appId, - stateColName to jobStatus.state) - + KILL_ACTION_COL_NAME to KillLivyJobAction(), + RESTART_ACTION_COL_NAME to RestartLivyJobAction(), + ID_COL_NAME to jobStatus.id, + APP_ID_COL_NAME to jobStatus.appId, + STATE_COL_NAME to jobStatus.state, + ) } class MockSparkBatchJobViewerControl(private val view: MockSparkBatchJobViewer) : LivyBatchJobViewer.Control { @@ -84,26 +92,28 @@ class MockSparkBatchJobViewerControl(private val view: MockSparkBatchJobViewer) } override fun onJobSelected(jobSelected: UniqueColumnNameTableSchema.RowDescriptor?) { - val sparkJobDesc = (jobSelected as? MockSparkLivyJobsTableSchema.MockSparkJobDescriptor)?.let { arrayOf(it)} - ?: emptyArray() + val sparkJobDesc = + (jobSelected as? MockSparkLivyJobsTableSchema.MockSparkJobDescriptor)?.let { arrayOf(it) } + ?: emptyArray() Observable.from(sparkJobDesc) - .delay(500, TimeUnit.MILLISECONDS) - .subscribe { view.getModel(LivyBatchJobViewer.Model::class.java).apply { - jobDetail = if (it.jobStatus.id == 1) { - // Unclosed JSON string - """{"message":"A broken response for ${it.jobStatus.appId}!","error no": 0, "id": ${it.jobStatus.id}""" - - } else { - """{"message":"hello ${it.jobStatus.appId}!","error no": 0, "id": ${it.jobStatus.id}}""" - } + .delay(500, TimeUnit.MILLISECONDS) + .subscribe { + view.getModel(LivyBatchJobViewer.Model::class.java).apply { + jobDetail = + if (it.jobStatus.id == 1) { + // Unclosed JSON string + """{"message":"A broken response for ${it.jobStatus.appId}!","error no": 0, "id": ${it.jobStatus.id}""" + } else { + """{"message":"hello ${it.jobStatus.appId}!","error no": 0, "id": ${it.jobStatus.id}}""" + } view.setData(this) - }} + } + } } } - class MockSparkBatchJobViewer : LivyBatchJobViewer() { override val jobViewerControl: Control by lazy { MockSparkBatchJobViewerControl(this@MockSparkBatchJobViewer) } } @@ -119,100 +129,180 @@ fun getJobListPage(pageLink: String?): JobPage? { println("Get job list from $pageLink") return when (pageLink) { - "http://page1" -> object : JobPage { - override fun nextPageLink(): String? { - return "http://page2" - } + "http://page1" -> + object : JobPage { + override fun nextPageLink(): String? { + return "http://page2" + } - override fun items(): List? { - return listOf( - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 1, - "appId": "application-134124194-1", - "state": "running" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 2, - "appId": null, - "state": "dead" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 3, - "state": "success" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 4, - "appId": "application-134124194-4" - }""".trimIndent())) - ) - } - } - "http://page2" -> object : JobPage { - override fun nextPageLink(): String? { - return "http://page3" + override fun items(): List? { + return listOf( + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 1, + "appId": "application-134124194-1", + "state": "running" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 2, + "appId": null, + "state": "dead" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 3, + "state": "success" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 4, + "appId": "application-134124194-4" + } + """.trimIndent(), + ), + ), + ) + } } + "http://page2" -> + object : JobPage { + override fun nextPageLink(): String? { + return "http://page3" + } - override fun items(): List? { - return listOf( - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 5, - "appId": "application-134124194-5", - "state": "running" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 6, - "appId": null, - "state": "dead" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 7, - "state": "success" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 8, - "appId": "application-134124194-8" - }""".trimIndent())) - ) - } - } - "http://page3" -> object : JobPage { - override fun nextPageLink(): String? { - return null + override fun items(): List? { + return listOf( + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 5, + "appId": "application-134124194-5", + "state": "running" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 6, + "appId": null, + "state": "dead" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 7, + "state": "success" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 8, + "appId": "application-134124194-8" + } + """.trimIndent(), + ), + ), + ) + } } + "http://page3" -> + object : JobPage { + override fun nextPageLink(): String? { + return null + } - override fun items(): List? { - return listOf( - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 9, - "appId": "application-134124194-9", - "state": "running" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 10, - "appId": null, - "state": "dead" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 11, - "state": "success" - }""".trimIndent())), - tableSchema.MockSparkJobDescriptor(parseJSON("""{ - "id": 12, - "appId": "application-134124194-12" - }""".trimIndent())) - ) + override fun items(): List? { + return listOf( + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 9, + "appId": "application-134124194-9", + "state": "running" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 10, + "appId": null, + "state": "dead" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 11, + "state": "success" + } + """.trimIndent(), + ), + ), + tableSchema.MockSparkJobDescriptor( + parseJSON( + """ + { + "id": 12, + "appId": "application-134124194-12" + } + """.trimIndent(), + ), + ), + ) + } } - } else -> null } } + @Ignore class SparkJobTableTest : SparkUITest() { - @Test fun testLivyTable() { - val model = LivyBatchJobViewer.Model(LivyBatchJobTableViewport.Model( - LivyBatchJobTableModel(tableSchema), getJobListPage("http://page1"))) + val model = + LivyBatchJobViewer.Model( + LivyBatchJobTableViewport.Model( + LivyBatchJobTableModel(tableSchema), + getJobListPage("http://page1"), + ), + ) jobView.setData(model) @@ -220,11 +310,13 @@ class SparkJobTableTest : SparkUITest() { contentPane.add(jobView.component) pack() - addWindowListener(object: WindowAdapter() { - override fun windowClosing(e: WindowEvent?) { - jobView.dispose() - } - }) + addWindowListener( + object : WindowAdapter() { + override fun windowClosing(e: WindowEvent?) { + jobView.dispose() + } + }, + ) isVisible = true } } diff --git a/Utils/azure-toolkit-ide-libs/azure-toolkit-ide-common-lib/src/main/resources/bundles/com/microsoft/azure/toolkit/operation.properties b/Utils/azure-toolkit-ide-libs/azure-toolkit-ide-common-lib/src/main/resources/bundles/com/microsoft/azure/toolkit/operation.properties index b32b9bd5f4a..1ddda68a956 100644 --- a/Utils/azure-toolkit-ide-libs/azure-toolkit-ide-common-lib/src/main/resources/bundles/com/microsoft/azure/toolkit/operation.properties +++ b/Utils/azure-toolkit-ide-libs/azure-toolkit-ide-common-lib/src/main/resources/bundles/com/microsoft/azure/toolkit/operation.properties @@ -1012,4 +1012,5 @@ internal/guidance.create_resource_group=create Resource Group internal/guidance.open_container_app_in_browser=open Container App in browser user/guidance.open_container_app_log_streaming=open Container App log streaming -actions.common.deploy_to_azure=deploy to Azure \ No newline at end of file +actions.common.deploy_to_azure=deploy to Azure +user/appmod.refresh_migrate_node=Refresh Migrate to Azure Node diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491770..e6441136f3d 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew b/gradlew index 1aa94a42690..b740cf13397 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f135..25da30dbdee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail