From 3d0a368aee9409353d58be73fbaf54b7a49bf374 Mon Sep 17 00:00:00 2001 From: Jonathan Breedlove Date: Fri, 21 Nov 2025 18:23:48 -0800 Subject: [PATCH 01/60] build: migrate Core to a shadow JAR dependency (#6112) --- gradle/libs.versions.toml | 2 + plugins/amazonq/build.gradle.kts | 5 +-- .../chat/jetbrains-community/build.gradle.kts | 14 +++++-- .../jetbrains-community/build.gradle.kts | 4 +- .../jetbrains-community/build.gradle.kts | 4 +- .../jetbrains-ultimate/build.gradle.kts | 4 +- .../jetbrains-community/build.gradle.kts | 3 +- .../src/main/resources/META-INF/plugin.xml | 17 +++++++- .../PluginCoreJvmBinaryCompatabilityTest.kt | 0 plugins/core/build.gradle.kts | 28 ++++++++++++- .../resources/META-INF/module-core.xml | 2 - .../src/main/resources/META-INF/plugin.xml | 3 +- .../main/resources/META-INF/pluginIcon.svg | 41 ------------------- .../resources/META-INF/pluginIcon_dark.svg | 41 ------------------- .../intellij-standalone/build.gradle.kts | 8 +--- .../resources/META-INF/plugin-shim.xml | 15 ++++++- .../toolkit/jetbrains-core/build.gradle.kts | 13 +++++- .../META-INF/inactive/plugin-gateway.xml | 4 ++ .../jetbrains-gateway/build.gradle.kts | 4 ++ .../META-INF/plugin-shim.xml | 5 --- .../src/AWS.Psi/Protocol/ModelZoneMarket.cs | 16 -------- .../toolkit/jetbrains-rider/build.gradle.kts | 1 - .../jetbrains-ultimate/build.gradle.kts | 15 +++++-- sandbox-all/build.gradle.kts | 1 - settings.gradle.kts | 4 ++ tmp-all/build.gradle.kts | 1 - ui-tests-starter/build.gradle.kts | 2 - 27 files changed, 113 insertions(+), 144 deletions(-) rename plugins/{core => amazonq}/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt (100%) delete mode 100644 plugins/core/src/main/resources/META-INF/pluginIcon.svg delete mode 100644 plugins/core/src/main/resources/META-INF/pluginIcon_dark.svg delete mode 100644 plugins/toolkit/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Protocol/ModelZoneMarket.cs diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2a32f799b5..db032e1029e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ node-gradle = "7.0.2" telemetryGenerator = "1.0.338" testLogger = "4.0.0" testRetry = "1.5.10" +shadow = "9.2.2" # test-only; platform provides slf4j transitively at runtime slf4j = "2.0.16" sshd = "2.13.2" @@ -128,5 +129,6 @@ mockito = ["mockito-core", "mockito-junit-jupiter", "mockito-kotlin"] sshd = ["sshd-core", "sshd-scp", "sshd-sftp"] [plugins] +gradleup-shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } node-gradle = { id = "com.github.node-gradle.node", version.ref = "node-gradle" } diff --git a/plugins/amazonq/build.gradle.kts b/plugins/amazonq/build.gradle.kts index c312b50dcdc..03750601bad 100644 --- a/plugins/amazonq/build.gradle.kts +++ b/plugins/amazonq/build.gradle.kts @@ -34,10 +34,7 @@ tasks.jar { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } - + implementation(project(path = ":plugin-core", configuration = "shadow")) implementation(project(":plugin-amazonq:chat")) implementation(project(":plugin-amazonq:codetransform")) implementation(project(":plugin-amazonq:codewhisperer")) diff --git a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts b/plugins/amazonq/chat/jetbrains-community/build.gradle.kts index 147d481b482..f8c1617119e 100644 --- a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/chat/jetbrains-community/build.gradle.kts @@ -12,9 +12,7 @@ intellijToolkit { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } + implementation(project(path = ":plugin-core", configuration = "shadow")) implementation(project(":plugin-amazonq:shared:jetbrains-community")) // everything references codewhisperer, which is not ideal @@ -26,3 +24,13 @@ dependencies { testImplementation(testFixtures(project(":plugin-core:jetbrains-community"))) } + +// hack because our test structure currently doesn't make complete sense +tasks.prepareTestSandbox { + val pluginXmlJar = project(":plugin-amazonq").tasks.jar + + dependsOn(pluginXmlJar) + from(pluginXmlJar) { + into(intellijPlatform.projectName.map { "$it/lib" }) + } +} diff --git a/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts b/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts index cb64e7d4c9d..22ce14c36b6 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts @@ -12,9 +12,7 @@ intellijToolkit { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } + implementation(project(path = ":plugin-core", configuration = "shadow")) implementation(project(":plugin-amazonq:shared:jetbrains-community")) // hack because transform has a chat entrypoint diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts b/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts index 824d0f016ff..73593eaae51 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts @@ -12,9 +12,7 @@ intellijToolkit { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } + implementation(project(path = ":plugin-core", configuration = "shadow")) compileOnly(project(":plugin-core:jetbrains-community")) diff --git a/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts b/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts index fdb619c58ba..8c88ac4128d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts +++ b/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts @@ -12,9 +12,7 @@ intellijToolkit { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } + implementation(project(path = ":plugin-core", configuration = "shadow")) compileOnly(project(":plugin-amazonq:codewhisperer:jetbrains-community")) compileOnly(project(":plugin-amazonq:shared:jetbrains-ultimate")) diff --git a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts index 36313073d9b..e75036713b6 100644 --- a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts @@ -14,10 +14,11 @@ intellijToolkit { dependencies { intellijPlatform { - localPlugin(project(":plugin-core")) platformDependency(Coordinates(groupId = "com.jetbrains.intellij.rd", artifactId = "rd-platform")) } + implementation(project(path = ":plugin-core", configuration = "shadow")) + compileOnlyApi(project(":plugin-core:jetbrains-community")) // CodeWhispererTelemetryService uses a CircularFifoQueue diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 96e2d30afd1..989e48ab717 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -2,6 +2,9 @@ + + + amazon.q Amazon Q org.jetbrains.idea.maven software.aws.toolkits.resources.MessagesBundle - aws.toolkit.core + aws.toolkit.core + com.intellij.modules.lang com.jetbrains.codeWithMe @@ -104,4 +108,15 @@ + + + + + + + + + + + diff --git a/plugins/core/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt b/plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt similarity index 100% rename from plugins/core/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt rename to plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt diff --git a/plugins/core/build.gradle.kts b/plugins/core/build.gradle.kts index d703efc96c5..2bf25ac620a 100644 --- a/plugins/core/build.gradle.kts +++ b/plugins/core/build.gradle.kts @@ -2,10 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 plugins { - id("toolkit-publishing-conventions") - id("toolkit-publish-root-conventions") id("toolkit-jvm-conventions") id("toolkit-testing") + alias(libs.plugins.gradleup.shadow) } dependencies { @@ -19,9 +18,34 @@ dependencies { implementation(libs.slf4j.jdk14) } +configurations { + configureEach { + // IDE provides netty + exclude("io.netty") + } + + // Make sure we exclude stuff we either A) ships with IDE, B) we don't use to cut down on size + runtimeClasspath { + exclude(group = "com.google.code.gson") + exclude(group = "org.jetbrains.kotlin") + exclude(group = "org.jetbrains.kotlinx") + } +} + tasks.check { val coreProject = project(":plugin-core").subprojects coreProject.forEach { dependsOn(":plugin-core:${it.name}:check") } } + +tasks.shadowJar { + archiveBaseName.set("plugin-core-shadow") + archiveVersion.set(rootProject.version.toString()) + archiveClassifier.set("") + + exclude("/META-INF/plugin.xml") + + configurations = project.configurations.runtimeClasspath.map { listOf(it) } + destinationDirectory.set(layout.buildDirectory.dir("libs")) +} diff --git a/plugins/core/jetbrains-community/resources/META-INF/module-core.xml b/plugins/core/jetbrains-community/resources/META-INF/module-core.xml index e03f58657d8..fdcd84ba8ee 100644 --- a/plugins/core/jetbrains-community/resources/META-INF/module-core.xml +++ b/plugins/core/jetbrains-community/resources/META-INF/module-core.xml @@ -2,8 +2,6 @@ - - diff --git a/plugins/core/src/main/resources/META-INF/plugin.xml b/plugins/core/src/main/resources/META-INF/plugin.xml index 9703141c302..9a4cde9f80f 100644 --- a/plugins/core/src/main/resources/META-INF/plugin.xml +++ b/plugins/core/src/main/resources/META-INF/plugin.xml @@ -1,4 +1,4 @@ - + @@ -16,6 +16,7 @@ com.intellij.jetbrains.client com.intellij.gateway + diff --git a/plugins/core/src/main/resources/META-INF/pluginIcon.svg b/plugins/core/src/main/resources/META-INF/pluginIcon.svg deleted file mode 100644 index 19453f558b7..00000000000 --- a/plugins/core/src/main/resources/META-INF/pluginIcon.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/plugins/core/src/main/resources/META-INF/pluginIcon_dark.svg b/plugins/core/src/main/resources/META-INF/pluginIcon_dark.svg deleted file mode 100644 index cbc43434bdd..00000000000 --- a/plugins/core/src/main/resources/META-INF/pluginIcon_dark.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/plugins/toolkit/intellij-standalone/build.gradle.kts b/plugins/toolkit/intellij-standalone/build.gradle.kts index 22dd4c0cf47..8d3b1deb553 100644 --- a/plugins/toolkit/intellij-standalone/build.gradle.kts +++ b/plugins/toolkit/intellij-standalone/build.gradle.kts @@ -19,15 +19,11 @@ intellijPlatform { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } + implementation(project(path = ":plugin-core", configuration = "shadow")) } tasks.prepareJarSearchableOptions { - val pluginXmlJar = project(":plugin-core").tasks.jar - dependsOn(pluginXmlJar) - composedJarFile.set(pluginXmlJar.flatMap { it.archiveFile }) + enabled = false } tasks.check { diff --git a/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml b/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml index 6429c47cead..c6d9995c08d 100644 --- a/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml +++ b/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml @@ -1,6 +1,17 @@ - - aws.toolkit.core + + + + + aws.toolkit.core + + + + + + + + diff --git a/plugins/toolkit/jetbrains-core/build.gradle.kts b/plugins/toolkit/jetbrains-core/build.gradle.kts index dc32bdd361c..8fc2f13b9a6 100644 --- a/plugins/toolkit/jetbrains-core/build.gradle.kts +++ b/plugins/toolkit/jetbrains-core/build.gradle.kts @@ -33,11 +33,10 @@ intellijToolkit { dependencies { intellijPlatform { - localPlugin(project(":plugin-core")) - bundledModule("intellij.platform.vcs.dvcs.impl") bundledModule("intellij.libraries.microba") } + implementation(project(path = ":plugin-core", configuration = "shadow")) } val changelog = tasks.register("pluginChangeLog") { @@ -212,3 +211,13 @@ fun transformXml(document: Document, path: Path) { path.writeText(text = it.toString()) } } + +// hack because our test structure currently doesn't make complete sense +tasks.prepareTestSandbox { + val pluginXmlJar = project(":plugin-core").tasks.jar + + dependsOn(pluginXmlJar) + from(pluginXmlJar) { + into(intellijPlatform.projectName.map { "$it/lib" }) + } +} diff --git a/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml b/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml index 72b3034fc63..ec19995f8bd 100644 --- a/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml +++ b/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml @@ -14,8 +14,12 @@ com.intellij.modules.python com.intellij.java + + + + diff --git a/plugins/toolkit/jetbrains-gateway/build.gradle.kts b/plugins/toolkit/jetbrains-gateway/build.gradle.kts index 6064b6b4a11..ee695f4e38a 100644 --- a/plugins/toolkit/jetbrains-gateway/build.gradle.kts +++ b/plugins/toolkit/jetbrains-gateway/build.gradle.kts @@ -167,6 +167,10 @@ tasks.buildPlugin { archiveClassifier.set(classifier) } +tasks.buildSearchableOptions { + enabled = false +} + tasks.integrationTest { val testToken = RandomString.make(32) environment("CWM_HOST_STATUS_OVER_HTTP_TOKEN", testToken) diff --git a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml index 1d662155edf..b887dcdb460 100644 --- a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml +++ b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml @@ -3,9 +3,4 @@ - - - - - diff --git a/plugins/toolkit/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Protocol/ModelZoneMarket.cs b/plugins/toolkit/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Protocol/ModelZoneMarket.cs deleted file mode 100644 index 6c3a4493fb9..00000000000 --- a/plugins/toolkit/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Protocol/ModelZoneMarket.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JetBrains.Application.BuildScript.Application.Zones; -using JetBrains.Rider.Model; - -// https://github.com/JetBrains/resharper-unity/blob/cfd107d555a0a13a4b37273340bf930621d0a3dd/resharper/resharper-unity/src/Unity.Rider/ModelZoneMarker.cs - -// ReSharper disable once CheckNamespace -namespace AWS.Toolkit.Rider.Model -{ - // Zone marker for the generated models. Required to keep zone inspections happy because the generated code uses a - // type from a namespace that requires IRiderModelZone. In practice, this doesn't cause issues - // TODO: Look at moving the models to a better location or namespace? - [ZoneMarker] - public class ZoneMarker : IRequire - { - } -} diff --git a/plugins/toolkit/jetbrains-rider/build.gradle.kts b/plugins/toolkit/jetbrains-rider/build.gradle.kts index 6e624db252c..8ee731a4aa2 100644 --- a/plugins/toolkit/jetbrains-rider/build.gradle.kts +++ b/plugins/toolkit/jetbrains-rider/build.gradle.kts @@ -75,7 +75,6 @@ configurations { dependencies { intellijPlatform { - localPlugin(project(":plugin-core")) testFramework(TestFrameworkType.Bundled) // FIX_WHEN_MIN_IS_251: https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1774 diff --git a/plugins/toolkit/jetbrains-ultimate/build.gradle.kts b/plugins/toolkit/jetbrains-ultimate/build.gradle.kts index bee30edb03a..85023835d81 100644 --- a/plugins/toolkit/jetbrains-ultimate/build.gradle.kts +++ b/plugins/toolkit/jetbrains-ultimate/build.gradle.kts @@ -16,9 +16,8 @@ intellijToolkit { } dependencies { - intellijPlatform { - localPlugin(project(":plugin-core")) - } + implementation(project(path = ":plugin-core", configuration = "shadow")) + compileOnlyApi(project(":plugin-toolkit:jetbrains-core")) compileOnlyApi(project(":plugin-core:jetbrains-ultimate")) @@ -31,3 +30,13 @@ dependencies { // delete when fully split testRuntimeOnly(project(":plugin-core:jetbrains-ultimate")) } + +// hack because our test structure currently doesn't make complete sense +tasks.prepareTestSandbox { + val pluginXmlJar = project(":plugin-core").tasks.jar + + dependsOn(pluginXmlJar) + from(pluginXmlJar) { + into(intellijPlatform.projectName.map { "$it/lib" }) + } +} diff --git a/sandbox-all/build.gradle.kts b/sandbox-all/build.gradle.kts index c7c65b24859..44419b687bf 100644 --- a/sandbox-all/build.gradle.kts +++ b/sandbox-all/build.gradle.kts @@ -32,7 +32,6 @@ intellijPlatform { dependencies { intellijPlatform { - localPlugin(project(":plugin-core")) localPlugin(project(":plugin-amazonq")) localPlugin(project(":plugin-toolkit:intellij-standalone")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 3c13798d46c..aee098fcb3e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,6 +61,10 @@ dependencyResolutionManagement { defaultRepositories() jetbrainsRuntime() } + + maven { + url = uri("https://d2s4y8xcwt8bet.cloudfront.net/") + } } } diff --git a/tmp-all/build.gradle.kts b/tmp-all/build.gradle.kts index 2917ef4f54a..3f29176499b 100644 --- a/tmp-all/build.gradle.kts +++ b/tmp-all/build.gradle.kts @@ -24,7 +24,6 @@ dependencies { create(type, version, useInstaller = false) jetbrainsRuntime() - localPlugin(project(":plugin-core")) localPlugin(project(":plugin-amazonq")) plugin(toolkitIntelliJ.ideProfile().map { "aws.toolkit:2.19-${it.shortName}" }) diff --git a/ui-tests-starter/build.gradle.kts b/ui-tests-starter/build.gradle.kts index 5bd6f620958..a9db8285a99 100644 --- a/ui-tests-starter/build.gradle.kts +++ b/ui-tests-starter/build.gradle.kts @@ -62,7 +62,6 @@ dependencies { val version = ideProfile.community.sdkVersion intellijIdeaCommunity(version, !version.contains("SNAPSHOT")) - localPlugin(project(":plugin-core")) testImplementation(project(":plugin-core:core")) testImplementation(project(":plugin-core:jetbrains-community")) testImplementation(testFixtures(project(":plugin-core:jetbrains-community"))) @@ -74,7 +73,6 @@ dependencies { } testPlugins(project(":plugin-amazonq", "pluginZip")) - testPlugins(project(":plugin-core", "pluginZip")) } tasks.test { From 72bff44933993e15080eb4af7e896037b871a475 Mon Sep 17 00:00:00 2001 From: Jonathan Breedlove Date: Tue, 2 Dec 2025 10:44:15 -0800 Subject: [PATCH 02/60] refactor: remove Core plugin version checker logic (#6121) --- .../resources/META-INF/aws.toolkit.core.xml | 4 - .../jetbrains/PluginVersionChecker.kt | 13 -- .../jetbrains/PluginVersionChecker.kt | 12 -- .../jetbrains/PluginVersionCheckerImpl.kt | 115 ------------------ .../resources/MessagesBundle.properties | 3 - 5 files changed, 147 deletions(-) delete mode 100644 plugins/core/jetbrains-community/src-242/software/aws/toolkits/jetbrains/PluginVersionChecker.kt delete mode 100644 plugins/core/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/PluginVersionChecker.kt delete mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/PluginVersionCheckerImpl.kt diff --git a/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml b/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml index 88b37350a68..3eb636bb0de 100644 --- a/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml +++ b/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml @@ -11,7 +11,6 @@ - @@ -81,9 +80,6 @@ - - - diff --git a/plugins/core/jetbrains-community/src-242/software/aws/toolkits/jetbrains/PluginVersionChecker.kt b/plugins/core/jetbrains-community/src-242/software/aws/toolkits/jetbrains/PluginVersionChecker.kt deleted file mode 100644 index 92e527a6a61..00000000000 --- a/plugins/core/jetbrains-community/src-242/software/aws/toolkits/jetbrains/PluginVersionChecker.kt +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains - -import com.intellij.ide.ApplicationInitializedListener -import kotlinx.coroutines.CoroutineScope - -class PluginVersionChecker : ApplicationInitializedListener { - override suspend fun execute(asyncScope: CoroutineScope) { - PluginVersionCheckerImpl.execute() - } -} diff --git a/plugins/core/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/PluginVersionChecker.kt b/plugins/core/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/PluginVersionChecker.kt deleted file mode 100644 index 46da3856f27..00000000000 --- a/plugins/core/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/PluginVersionChecker.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains - -import com.intellij.ide.ApplicationInitializedListener - -class PluginVersionChecker : ApplicationInitializedListener { - override suspend fun execute() { - PluginVersionCheckerImpl.execute() - } -} diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/PluginVersionCheckerImpl.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/PluginVersionCheckerImpl.kt deleted file mode 100644 index 9d21e80d40f..00000000000 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/PluginVersionCheckerImpl.kt +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains - -import com.intellij.ide.plugins.IdeaPluginDescriptor -import com.intellij.ide.plugins.PluginEnabler -import com.intellij.notification.NotificationAction -import com.intellij.notification.NotificationType -import com.intellij.notification.SingletonNotificationManager -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ex.ApplicationManagerEx -import com.intellij.openapi.progress.EmptyProgressIndicator -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.tryOrNull -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.AwsToolkit.TOOLKIT_PLUGIN_ID -import software.aws.toolkits.jetbrains.core.plugin.PluginUpdateManager -import software.aws.toolkits.resources.AwsCoreBundle -import javax.swing.SwingUtilities - -object PluginVersionCheckerImpl { - fun execute() { - if (ApplicationManager.getApplication().isHeadlessEnvironment) { - LOG.info { "Skipping due to headless environment" } - return - } - - val core = AwsToolkit.PLUGINS_INFO.get(AwsPlugin.CORE) ?: return - val mismatch = AwsToolkit.PLUGINS_INFO.values.filter { it.descriptor?.isEnabled == true && it.version != core.version } - - if (mismatch.isEmpty()) { - return - } - - LOG.info { "Mismatch between core version: ${core.version} and plugins: $mismatch" } - - val updated = mismatch.filter { - val descriptor = it.descriptor as? IdeaPluginDescriptor ?: return@filter false - - return@filter try { - PluginUpdateManager.updatePlugin(descriptor, EmptyProgressIndicator()) - } catch (e: Exception) { - LOG.error(e) { "Failed to update $descriptor" } - false - } - } - - // defensively disable the old toolkit if we couldn't update it because we might deadlock during project open - val toolkit = mismatch.firstOrNull { it.id == TOOLKIT_PLUGIN_ID && it.version?.startsWith("2.") == true } - - if (shouldDisableToolkit(toolkit, updated) || updated.isNotEmpty()) { - LOG.info { "Restarting due to forced update of plugins" } - - // IDE invokeLater is not initialized yet - SwingUtilities.invokeAndWait { - ApplicationManagerEx.getApplicationEx().restart(true) - } - return - } - - val notificationGroup = SingletonNotificationManager("aws.plugin.version.mismatch", NotificationType.WARNING) - notificationGroup.notify( - AwsCoreBundle.message("plugin.incompatible.title"), - AwsCoreBundle.message("plugin.incompatible.message"), - null - ) { - it.isImportant = true - it.addAction( - NotificationAction.createSimpleExpiring(AwsCoreBundle.message("plugin.incompatible.fix")) { - // try update core and disable everything else - val coreDescriptor = core.descriptor as? IdeaPluginDescriptor - tryOrNull { - coreDescriptor?.let { descriptor -> PluginUpdateManager.updatePlugin(descriptor, EmptyProgressIndicator()) } - } - - PluginEnabler.HEADLESS.disable( - AwsToolkit.PLUGINS_INFO.values.mapNotNull { - val descriptor = it.descriptor as? IdeaPluginDescriptor - if (descriptor != null && descriptor != core.descriptor) { - descriptor - } else { - null - } - } - ) - } - ) - } - } - - private fun shouldDisableToolkit(toolkit: PluginInfo?, updated: List): Boolean { - if (toolkit != null && updated.none { it == toolkit }) { - LOG.info { "Attempting to disable aws.toolkit due to known incompatibility" } - val descriptor = toolkit.descriptor as? IdeaPluginDescriptor ?: run { - LOG.warn { "Expected toolkit descriptor to be IdeaPluginDescriptor, but was ${toolkit.descriptor}" } - return false - } - - if (!descriptor.isEnabled) { - LOG.info { "Does not need to disable toolkit since it is already disabled" } - return false - } - - PluginEnabler.HEADLESS.disable(listOf(descriptor)) - return true - } - - return false - } - - private val LOG = getLogger() -} diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index a3cac32bd40..8a7beee4573 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -1641,9 +1641,6 @@ notification.changelog=Changelog notification.expand=Expand notification.learn_more=Learn more notification.update=Update -plugin.incompatible.fix=Disable incompatible plugins and restart IDE -plugin.incompatible.message=The plugin versions for Amazon Q, AWS Toolkit, and AWS Toolkit Core must match or conflicts may occur. -plugin.incompatible.title=AWS Plugin Incompatibility q.connection.disconnected=You don't have access to Amazon Q. Please authenticate to get started. q.connection.expired=Your Amazon Q session has timed out. Re-authenticate to continue. q.connection.invalid=You don't have access to Amazon Q. Please authenticate to get started. From 472b61401862ac66a5e5d113dbe3608cbcd43bd2 Mon Sep 17 00:00:00 2001 From: Jonathan Breedlove Date: Tue, 2 Dec 2025 13:46:40 -0800 Subject: [PATCH 03/60] refactor: remove more Core plugin references --- .../src/software/aws/toolkits/jetbrains/AwsToolkit.kt | 3 --- .../aws/toolkits/jetbrains/core/notifications/RulesEngine.kt | 1 - 2 files changed, 4 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/AwsToolkit.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/AwsToolkit.kt index 350dcb9442b..ec357774de2 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/AwsToolkit.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/AwsToolkit.kt @@ -12,16 +12,13 @@ import java.util.EnumMap object AwsToolkit { const val TOOLKIT_PLUGIN_ID = "aws.toolkit" const val Q_PLUGIN_ID = "amazon.q" - const val CORE_PLUGIN_ID = "aws.toolkit.core" private val TOOLKIT_PLUGIN_INFO = PluginInfo(TOOLKIT_PLUGIN_ID, "AWS Toolkit") private val Q_PLUGIN_INFO = PluginInfo(Q_PLUGIN_ID, "Amazon Q") - private val CORE_PLUGIN_INFO = PluginInfo(CORE_PLUGIN_ID, "AWS Plugin Core") val PLUGINS_INFO = EnumMap(AwsPlugin::class.java).apply { put(AwsPlugin.TOOLKIT, TOOLKIT_PLUGIN_INFO) put(AwsPlugin.Q, Q_PLUGIN_INFO) - put(AwsPlugin.CORE, CORE_PLUGIN_INFO) } const val GITHUB_URL = "https://github.com/aws/aws-toolkit-jetbrains" diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt index 4f5c174409e..c3d602f2bc6 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt @@ -150,7 +150,6 @@ fun createPluginVersionMap(): Map { val pluginVersionMap = mutableMapOf() val pluginIds = listOf( "amazon.q", - "aws.toolkit.core", "aws.toolkit" ) pluginIds.forEach { pluginId -> From 8e5e8dd46f69ce4344e1ff63e6bba3ccfa4d4e94 Mon Sep 17 00:00:00 2001 From: Jonathan Breedlove Date: Tue, 23 Dec 2025 16:15:47 -0800 Subject: [PATCH 04/60] feat: seperate Toolkit Core and Q Core into separate modules that can run concurrently --- plugins/amazonq/build.gradle.kts | 4 +- .../chat/jetbrains-community/build.gradle.kts | 6 +- .../resources/META-INF/plugin-chat.xml | 2 +- .../services/amazonq/GetAmazonQLogsAction.kt | 6 +- .../services/amazonq/QLoginWebview.kt | 40 +- .../services/amazonq/auth/AuthController.kt | 16 +- .../explorerActions/ReauthenticateWithQ.kt | 2 +- .../explorerActions/SignInToQAction.kt | 12 +- .../gettingstarted/QGettingStartedContent.kt | 4 +- .../gettingstarted/QGettingStartedPanel.kt | 2 +- .../amazonq/startup/AmazonQStartupActivity.kt | 8 +- .../amazonq/toolwindow/AmazonQPanel.kt | 10 +- .../toolwindow/AmazonQToolWindowFactory.kt | 34 +- .../services/amazonq/webview/Browser.kt | 4 +- .../amazonq/webview/BrowserConnector.kt | 8 +- .../amazonq/webview/FqnWebviewAdapter.kt | 4 +- .../webview/theme/EditorThemeAdapter.kt | 4 +- .../amazonqCodeScan/CodeScanChatApp.kt | 16 +- .../amazonqCodeScan/auth/CodeScanAuthUtils.kt | 6 +- .../controller/CodeScanChatController.kt | 4 +- .../toolkits/jetbrains/services/cwc/App.kt | 2 +- .../cwc/clients/chat/v1/ChatSessionV1.kt | 6 +- .../services/cwc/controller/ChatController.kt | 8 +- .../chat/messenger/ChatPromptHandler.kt | 4 +- .../chat/telemetry/TelemetryHelper.kt | 18 +- .../context/file/FileContextExtractor.kt | 4 +- .../context/file/util/MatchPolicyExtractor.kt | 4 +- .../focusArea/FocusAreaContextExtractor.kt | 8 +- .../cwc/inline/InlineChatController.kt | 14 +- .../cwc/inline/InlineChatPopupFactory.kt | 2 +- .../listeners/InlineChatSelectionListener.kt | 2 +- .../services/amazonq/TelemetryHelperTest.kt | 18 +- .../services/amazonq/TelemetryHelperTest.kt | 16 +- .../services/amazonq/AmazonQTestBase.kt | 18 +- .../clients/AmazonQStreamingClientTest.kt | 16 +- .../amazonq/webview/BrowserConnectorTest.kt | 2 +- .../jetbrains-community/build.gradle.kts | 6 +- .../META-INF/codetransform-ext-java.xml | 4 +- .../codemodernizer/ArtifactHandler.kt | 18 +- .../codemodernizer/CodeModernizerManager.kt | 18 +- .../codemodernizer/CodeModernizerSession.kt | 14 +- .../codemodernizer/CodeTransformChatApp.kt | 10 +- ...eTransformProjectStartupSettingListener.kt | 6 +- .../codemodernizer/client/GumbyClient.kt | 8 +- .../controller/CodeTransformChatController.kt | 10 +- .../ideMaven/MavenRunnerUtils.kt | 8 +- .../model/CodeModernizerArtifact.kt | 8 +- .../model/CodeModernizerSessionContext.kt | 10 +- .../model/CodeTransformFailureBuildLog.kt | 6 +- .../model/CodeTransformHilDownloadArtifact.kt | 6 +- .../CodeModernizerBottomWindowPanelManager.kt | 4 +- .../plan/CodeModernizerPlanEditorProvider.kt | 4 +- .../utils/CodeTransformApiUtils.kt | 14 +- .../utils/CodeTransformFileUtils.kt | 16 +- .../utils/CodeTransformUtils.kt | 18 +- .../feedback/CodeTransformFeedbackDialog.kt | 4 +- .../codemodernizer/CodeTransformChatTest.kt | 2 +- .../CodeTransformHilDownloadArtifactTest.kt | 4 +- .../CodeTransformTelemetryTest.kt | 2 +- ...eWhispererCodeModernizerGumbyClientTest.kt | 16 +- .../CodeWhispererCodeModernizerSessionTest.kt | 6 +- .../CodeWhispererCodeModernizerTest.kt | 2 +- .../CodeWhispererCodeModernizerTestBase.kt | 24 +- .../CodeWhispererCodeModernizerUtilsTest.kt | 4 +- .../codemodernizer/panels/PanelTestBase.kt | 2 +- .../utils/CodeTransformModuleUtilsTest.kt | 2 +- .../jetbrains-community/build.gradle.kts | 6 +- .../CodeWhispererCodeScanIntegrationTest.kt | 2 +- ...odeWhispererCodeScanJavaIntegrationTest.kt | 8 +- .../CodeWhispererCompletionIntegrationTest.kt | 2 +- .../CodeWhispererIntegrationTestBase.kt | 24 +- ...hispererReferenceTrackerIntegrationTest.kt | 2 +- .../META-INF/plugin-codewhisperer.xml | 6 +- .../CodeWhispererExplorerActionManager.kt | 14 +- .../actions/CodeWhispererLearnMoreAction.kt | 2 +- .../actions/CodeWhispererWhatIsAction.kt | 2 +- .../codescan/AmazonQCodeFixSession.kt | 12 +- ...WhispererCodeScanHighlightingFilesPanel.kt | 4 +- .../codescan/CodeWhispererCodeScanManager.kt | 28 +- .../CodeWhispererCodeScanResultsView.kt | 4 +- .../codescan/CodeWhispererCodeScanSession.kt | 12 +- ...spererCodeScanEditorMouseMotionListener.kt | 4 +- .../sessionconfig/CodeScanSessionConfig.kt | 10 +- .../utils/AmazonQCodeReviewGitUtils.kt | 4 +- .../utils/CodeWhispererCodeScanIssueUtils.kt | 14 +- .../sessionconfig/CodeTestSessionConfig.kt | 8 +- .../editor/CodeWhispererEditorManager.kt | 2 +- .../explorer/QStatusBarLoggedInActionGroup.kt | 10 +- .../explorer/actions/ActionFactory.kt | 6 +- .../explorer/actions/Customize.kt | 4 +- .../importadder/CodeWhispererImportAdder.kt | 6 +- .../CodeWhispererImportAdderListener.kt | 4 +- .../inlay/CodeWhispererInlayManager.kt | 2 +- .../inlay/CodeWhispererInlayManagerNew.kt | 2 +- .../learn/LearnCodeWhispererEditorProvider.kt | 6 +- .../codewhisperer/model/CodeWhispererModel.kt | 2 +- .../popup/CodeWhispererPopupComponents.kt | 2 +- .../popup/CodeWhispererPopupManager.kt | 6 +- .../popup/CodeWhispererPopupManagerNew.kt | 6 +- .../popup/QInlineCompletionProvider.kt | 20 +- .../CodeWhispererAutoTriggerHandler.kt | 4 +- .../service/CodeWhispererInvocationStatus.kt | 4 +- .../CodeWhispererInvocationStatusNew.kt | 4 +- .../CodeWhispererLicenseInfoManager.kt | 2 +- .../service/CodeWhispererService.kt | 22 +- .../service/CodeWhispererServiceNew.kt | 20 +- .../service/CodeWhispererUserGroupSettings.kt | 8 +- .../settings/CodeWhispererConfigurable.kt | 12 +- .../CodeWhispererProjectStartupActivity.kt | 8 +- ...WhispererProjectStartupSettingsListener.kt | 10 +- .../status/CodeWhispererStatusBarWidget.kt | 14 +- .../CodeWhispererTelemetryService.kt | 8 +- .../CodeWhispererTelemetryServiceNew.kt | 4 +- .../CodeWhispererCodeReferenceComponents.kt | 4 +- .../util/CodeInsightsSettingsFacade.kt | 4 +- .../util/CodeWhispererEndpointCustomizer.kt | 4 +- .../codewhisperer/util/CodeWhispererUtil.kt | 42 +- .../util/CodeWhispererZipUploadManager.kt | 6 +- .../util/CodeWhispererUtilTest.kt | 14 +- .../util/CodeWhispererUtilTest.kt | 14 +- .../CodeWhispererBasicTestBase.kt | 2 +- .../CodeWhispererClientAdaptorTest.kt | 24 +- .../CodeWhispererConfigurableTest.kt | 2 +- .../CodeWhispererConstantsTest.kt | 4 +- .../CodeWhispererEditorUtilTest.kt | 2 +- .../CodeWhispererEndpointCustomizerTest.kt | 4 +- .../CodeWhispererExplorerActionManagerTest.kt | 28 +- .../CodeWhispererFeatureConfigServiceTest.kt | 14 +- .../CodeWhispererFileContextProviderTest.kt | 2 +- .../CodeWhispererLanguageManagerTest.kt | 2 +- .../CodeWhispererLicenseInfoManagerTest.kt | 2 +- .../CodeWhispererModelConfiguratorTest.kt | 28 +- .../CodeWhispererReferenceManagerTest.kt | 2 +- .../CodeWhispererSettingsTest.kt | 4 +- .../CodeWhispererTelemetryTest.kt | 8 +- .../codewhisperer/CodeWhispererTestBase.kt | 14 +- .../codewhisperer/CodeWhispererTestUtil.kt | 2 +- .../codewhisperer/CodeWhispererUtilTest.kt | 16 +- .../services/codewhisperer/QEndpointsTest.kt | 4 +- .../QRegionProfileManagerTest.kt | 30 +- .../codescan/CodeWhispererCodeFileScanTest.kt | 8 +- .../codescan/CodeWhispererCodeScanTest.kt | 8 +- .../codescan/CodeWhispererCodeScanTestBase.kt | 4 +- .../CodeWhispererProjectCodeScanTest.kt | 6 +- .../codetest/CodeTestSessionConfigTest.kt | 8 +- .../CodeWhispererFallbackImportAdderTest.kt | 2 +- .../CodeWhispererJavaImportAdderTest.kt | 2 +- .../CodeWhispererPythonImportAdderTest.kt | 2 +- .../CodeWhispererImportAdderTestBase.kt | 2 +- .../jetbrains-ultimate/build.gradle.kts | 6 +- .../CodeWhispererJSImportAdderTest.kt | 2 +- .../jetbrains-community/build.gradle.kts | 6 +- .../CodeWhispererFeatureConfigService.kt | 12 +- .../jetbrains/services/amazonq/QUtils.kt | 10 +- .../amazonq/clients/AmazonQStreamingClient.kt | 4 +- .../amazonq/lsp/AmazonQLanguageClientImpl.kt | 20 +- .../services/amazonq/lsp/AmazonQLspService.kt | 14 +- .../services/amazonq/lsp/NodeExePatcher.kt | 12 +- .../services/amazonq/lsp/TrustChainUtil.kt | 4 +- .../amazonq/lsp/artifacts/ArtifactHelper.kt | 16 +- .../amazonq/lsp/artifacts/ArtifactManager.kt | 12 +- .../amazonq/lsp/artifacts/LspUtils.kt | 8 +- .../amazonq/lsp/artifacts/ManifestFetcher.kt | 20 +- .../lsp/auth/DefaultAuthCredentialsService.kt | 22 +- .../lsp/flareChat/ChatCommunicationManager.kt | 12 +- .../lsp/model/ExtendedClientMetadata.kt | 2 +- .../aws/credentials/ConnectionMetadata.kt | 4 +- .../TextDocumentServiceHandler.kt | 6 +- .../amazonq/lsp/util/LspEditorUtil.kt | 4 +- .../services/amazonq/profile/QEndpoints.kt | 4 +- .../amazonq/profile/QProfileResources.kt | 14 +- .../amazonq/profile/QRegionProfile.kt | 2 +- .../amazonq/profile/QRegionProfileDialog.kt | 14 +- .../amazonq/profile/QRegionProfileManager.kt | 30 +- .../project/FeatureDevSessionContext.kt | 10 +- .../services/amazonq/project/Workspace.kt | 2 +- .../utils/CodeTransformSharedUtils.kt | 8 +- .../actions/ManageSubscription.kt | 12 +- .../CodeWhispererCustomizationDialog.kt | 10 +- .../CodeWhispererModelConfigurator.kt | 12 +- .../chat/telemetry/QTelemetryUtils.kt | 6 +- .../settings/CodeWhispererSettings.kt | 4 +- .../jetbrains/settings/LspSettings.kt | 2 +- .../jetbrains/settings/MeetQSettings.kt | 2 +- .../feedback/CodeWhispererFeedbackDialog.kt | 6 +- .../ui/feedback/FeatureDevFeedbackDialog.kt | 3 +- .../ui/feedback/TestGenFeedbackDialog.kt | 3 +- .../auth/DefaultAuthCredentialsServiceTest.kt | 18 +- .../auth/DefaultAuthCredentialsServiceTest.kt | 18 +- .../lsp/AmazonQLanguageClientImplTest.kt | 14 +- .../amazonq/lsp/NodeExePatcherTest.kt | 4 +- .../amazonq/lsp/TrustChainUtilTest.kt | 4 +- .../amazonq/lsp/artifacts/LspUtilsTest.kt | 12 +- .../lsp/artifacts/ManifestFetcherTest.kt | 8 +- .../lsp/model/aws/chat/ChatMessageTest.kt | 2 +- .../TextDocumentServiceHandlerTest.kt | 6 +- .../jetbrains/settings/LspSettingsTest.kt | 2 +- .../jetbrains-ultimate/build.gradle.kts | 6 +- .../src/main/resources/META-INF/plugin.xml | 122 +- .../PluginCoreJvmBinaryCompatabilityTest.kt | 8 +- plugins/core-q/build.gradle.kts | 40 + plugins/core-q/core-q/build.gradle.kts | 34 + .../detekt-baseline-integrationTest.xml | 7 + .../core-q/core-q/detekt-baseline-main.xml | 11 + .../core-q/core-q/detekt-baseline-test.xml | 9 + plugins/core-q/core-q/detekt-baseline.xml | 11 + .../aws/toolkits/core/s3/BucketUtilsTest.kt | 71 + .../amazon/q/core/ToolkitClientManager.kt | 270 ++ .../q/core/clients/SdkClientProvider.kt | 10 + .../q/core/region/ToolkitRegionProvider.kt | 88 + .../amazon/q/core/ConnectionSettings.kt | 41 + .../amazon/q/core/ToolkitClientCustomizer.kt | 34 + .../amazon/q/core/ToolkitClientManager.kt | 8 + .../q/core/clients/ClientBuilderUtils.kt | 25 + .../q/core/clients/SdkClientProvider.kt | 8 + .../q/core/credentials/AwsCredentials.kt | 43 + .../credentials/CredentialProviderFactory.kt | 37 + .../credentials/CredentialsChangeEvent.kt | 20 + .../q/core/credentials/SsoUrlIdentifier.kt | 38 + .../credentials/ToolkitCredentialsProvider.kt | 147 + .../ToolkitCredentialsProviderManager.kt | 23 + .../q/core/lambda/LambdaArchitecture.kt | 32 + .../amazon/q/core/lambda/LambdaRuntime.kt | 49 + .../core/lambda/LambdaSampleEventProvider.kt | 111 + .../amazon/q/core/lambda/LambdaUtils.kt | 10 + .../amazon/q/core/region/AwsPartition.kt | 8 + .../amazon/q/core/region/AwsRegion.kt | 52 + .../amazon/q/core/region/Partitions.kt | 61 + .../q/core/region/ToolkitRegionProvider.kt | 8 + .../software/amazon/q/core/s3/BucketUtils.kt | 31 + .../q/core/telemetry/CachedIdentityStorage.kt | 26 + .../amazon/q/core/telemetry/MetricEvent.kt | 192 + .../q/core/telemetry/TelemetryBatcher.kt | 143 + .../q/core/telemetry/TelemetryPublisher.kt | 12 + .../amazon/q/core/utils/AttributeBag.kt | 32 + .../amazon/q/core/utils/CollectionUtils.kt | 20 + .../software/amazon/q/core/utils/Either.kt | 15 + .../amazon/q/core/utils/ExceptionUtils.kt | 14 + .../software/amazon/q/core/utils/LogUtils.kt | 110 + .../software/amazon/q/core/utils/PathUtils.kt | 217 + .../q/core/utils/RemoteResourceResolver.kt | 168 + .../amazon/q/core/utils/SensitiveField.kt | 50 + .../amazon/q/core/utils/StringUtils.kt | 14 + .../software/amazon/q/core/utils/TextUtils.kt | 72 + .../software/amazon/q/core/utils/Waiter.kt | 133 + .../software/amazon/q/core/utils/ZipUtils.kt | 48 + .../tst-resources/jsonSampleFailure.json | 20 + .../tst-resources/jsonSampleSuccess.json | 63 + .../tst-resources/sampleLambdaEvent.json | 4 + .../core-q/tst-resources/xmlSampleFailure.xml | 13 + .../core-q/tst-resources/xmlSampleSuccess.xml | 13 + .../AwsCredentialsExtensionsTest.kt | 66 + .../aws/toolkits/core/credentials/Mocks.kt | 29 + .../core/credentials/SsoUrlIdentifierTest.kt | 57 + .../lambda/LambdaSampleEventProviderTest.kt | 107 + .../core/parser/EndpointsJsonValidatorTest.kt | 24 + .../parser/LambdaManifestValidatorTest.kt | 25 + .../LambdaSampleEventJsonValidatorTest.kt | 23 + .../aws/toolkits/core/region/AwsRegionTest.kt | 114 + .../core/region/PartitionParserTest.kt | 25 + .../core/rules/ECSTemporaryServiceRule.kt | 53 + .../core/rules/EcrTemporaryRepositoryRule.kt | 48 + .../core/rules/EnvironmentVariableHelper.kt | 66 + .../core/rules/S3TemporaryBucketRule.kt | 50 + .../core/rules/SystemPropertyHelper.kt | 25 + .../core/telemetry/TelemetryBatcherTest.kt | 207 + .../core/utils/CollectionUtilsTest.kt | 28 + .../core/utils/CompletionStageUtils.kt | 17 + .../core/utils/DelegateSdkConsumers.kt | 54 + .../aws/toolkits/core/utils/EitherTest.kt | 41 + .../toolkits/core/utils/ExceptionUtilsTest.kt | 17 + .../core/utils/IntegrationTestCredentials.kt | 39 + .../aws/toolkits/core/utils/LogUtilsTest.kt | 190 + .../core/utils/RemoteResourceResolverTest.kt | 202 + .../aws/toolkits/core/utils/RuleUtils.kt | 21 + .../aws/toolkits/core/utils/RuleUtilsTest.kt | 27 + .../aws/toolkits/core/utils/ZipUtilsTest.kt | 71 + .../core/utils/test/AssertJAsserts.kt | 29 + .../aws/toolkits/core/utils/test/TestUtils.kt | 41 + .../toolkits/core/utils/test/TestUtilsTest.kt | 36 + .../jetbrains/utils/AttributeBagTest.kt | 48 + .../jetbrains/utils/StringUtilsTest.kt | 25 + .../jetbrains-community/build.gradle.kts | 137 + .../detekt-baseline-main.xml | 20 + .../detekt-baseline-test.xml | 15 + .../detekt-baseline-testFixtures.xml | 13 + .../jetbrains-community/detekt-baseline.xml | 22 + .../resources/icons/logos/AWS.svg | 12 + .../resources/icons/logos/AWS_Q.svg | 4 + .../resources/icons/logos/AWS_Q_dark.svg | 4 + .../resources/icons/logos/AWS_dark.svg | 12 + .../resources/icons/logos/AWS_smile.svg | 41 + .../resources/icons/logos/AWS_smile_Large.svg | 41 + .../icons/logos/AWS_smile_Large_dark.svg | 41 + .../resources/icons/logos/AWS_smile_dark.svg | 41 + .../logos/Amazon-Q-Icon_Gradient_Large.svg | 17 + .../logos/Amazon-Q-Icon_Gradient_Medium.svg | 17 + .../logos/Amazon-Q-Icon_Squid-Ink_Medium.svg | 4 + .../logos/Amazon-Q-Icon_White_Medium.svg | 4 + .../logos/Amazon_CodeCatalyst_Medium.svg | 7 + .../logos/Amazon_CodeCatalyst_Medium_dark.svg | 7 + .../icons/logos/Amazon_CodeCatalyst_Small.svg | 7 + .../logos/Amazon_CodeCatalyst_Small_dark.svg | 7 + .../resources/icons/logos/Amazon_Q_grey.svg | 4 + .../icons/logos/CW_InlineSuggestions_dark.svg | 15 + .../logos/CW_InlineSuggestions_light.svg | 15 + .../icons/logos/CloudFormationTool.svg | 15 + .../icons/logos/CloudFormationTool_dark.svg | 15 + .../icons/logos/CodeWhisperer_Large.svg | 12 + .../resources/icons/logos/EventBridge.svg | 11 + .../icons/logos/EventBridge_dark.svg | 11 + .../resources/icons/logos/MynahIcon.svg | 48 + .../resources/icons/logos/MynahIcon_dark.svg | 50 + .../resources/icons/misc/csharp.svg | 10 + .../resources/icons/misc/csharp_dark.svg | 10 + .../resources/icons/misc/frown.svg | 4 + .../resources/icons/misc/frown_dark.svg | 4 + .../resources/icons/misc/java.svg | 6 + .../resources/icons/misc/javaScript.svg | 6 + .../resources/icons/misc/javaScript_dark.svg | 6 + .../resources/icons/misc/java_dark.svg | 6 + .../resources/icons/misc/learn.svg | 5 + .../resources/icons/misc/learn_dark.svg | 5 + .../resources/icons/misc/new.svg | 5 + .../resources/icons/misc/python.svg | 8 + .../resources/icons/misc/smile.svg | 4 + .../resources/icons/misc/smile_dark.svg | 4 + .../resources/icons/misc/smile_grey.svg | 4 + .../resources/icons/misc/smile_grey_dark.svg | 4 + .../resources/icons/misc/typeScript.svg | 5 + .../resources/icons/misc/typeScript_dark.svg | 5 + .../icons/resources/AppRunnerService.svg | 9 + .../icons/resources/AppRunnerService_dark.svg | 9 + .../icons/resources/CloudFormationStack.svg | 11 + .../resources/CloudFormationStack_dark.svg | 11 + .../icons/resources/CodewhispererCustom.svg | 7 + .../icons/resources/ECRRepository.svg | 11 + .../icons/resources/ECRRepository_dark.svg | 11 + .../icons/resources/LambdaFunction.svg | 12 + .../icons/resources/LambdaFunction_dark.svg | 12 + .../resources/icons/resources/Redshift.svg | 13 + .../icons/resources/Redshift_dark.svg | 13 + .../resources/icons/resources/S3Bucket.svg | 11 + .../icons/resources/S3Bucket_dark.svg | 11 + .../resources/icons/resources/Schema.svg | 13 + .../icons/resources/SchemaRegistry.svg | 11 + .../icons/resources/SchemaRegistry_dark.svg | 11 + .../resources/icons/resources/Schema_dark.svg | 13 + .../icons/resources/ServerlessApp.svg | 18 + .../icons/resources/ServerlessApp_dark.svg | 23 + .../cloudwatchlogs/CloudWatchLogs.svg | 9 + .../cloudwatchlogs/CloudWatchLogsGroup.svg | 9 + .../CloudWatchLogsGroup_dark.svg | 9 + .../CloudWatchLogsToolWindow.svg | 9 + .../CloudWatchLogsToolWindow_dark.svg | 9 + .../cloudwatchlogs/CloudWatchLogs_dark.svg | 9 + .../resources/codetransform/checkmark.svg | 3 + .../codetransform/greenCheckmark.svg | 3 + .../codetransform/transform-arrow-dark.svg | 6 + .../codetransform/transform-arrow-light.svg | 6 + .../codetransform/transform-default-dark.svg | 4 + .../codetransform/transform-default-light.svg | 4 + .../transform-dependencies-dark.svg | 7 + .../transform-dependencies-light.svg | 7 + .../codetransform/transform-file-dark.svg | 5 + .../codetransform/transform-file-light.svg | 5 + .../transform-step-into-dark.svg | 9 + .../transform-step-into-light.svg | 9 + .../transform-timeline-step-done-light.svg | 4 + .../transform-timeline-step-done.svg | 4 + .../transform-variables-dark.svg | 7 + .../transform-variables-light.svg | 7 + .../codewhisperer/severity-critical.svg | 1 + .../resources/codewhisperer/severity-high.svg | 1 + .../resources/codewhisperer/severity-info.svg | 1 + .../severity-initial-critical.svg | 4 + .../codewhisperer/severity-initial-high.svg | 4 + .../codewhisperer/severity-initial-info.svg | 4 + .../codewhisperer/severity-initial-low.svg | 4 + .../codewhisperer/severity-initial-medium.svg | 4 + .../resources/codewhisperer/severity-low.svg | 1 + .../codewhisperer/severity-medium.svg | 1 + .../resources/dynamodb/DynamoDbTable.svg | 7 + .../resources/dynamodb/DynamoDbTable_dark.svg | 7 + .../icons/resources/ecs/EcsCluster.svg | 11 + .../icons/resources/ecs/EcsCluster_dark.svg | 11 + .../icons/resources/ecs/EcsService.svg | 11 + .../icons/resources/ecs/EcsService_dark.svg | 11 + .../icons/resources/ecs/EcsTaskDefinition.svg | 11 + .../resources/ecs/EcsTaskDefinition_dark.svg | 11 + .../resources/icons/resources/rds/Mysql.svg | 9 + .../icons/resources/rds/Mysql_dark.svg | 9 + .../icons/resources/rds/Postgres.svg | 9 + .../icons/resources/rds/Postgres_dark.svg | 9 + .../icons/resources/sqs/SqsQueue.svg | 13 + .../icons/resources/sqs/SqsQueue_dark.svg | 13 + .../icons/resources/sqs/SqsToolWindow.svg | 13 + .../resources/sqs/SqsToolWindow_dark.svg | 13 + .../resources/oauthCallback/auth.css | 93 + .../resources/oauthCallback/index.html | 99 + .../resources/telemetryOverride.json | 429 ++ .../services/telemetry/otel/OTelService.kt | 171 + .../services/telemetry/otel/OTelService.kt | 171 + .../impl/jcef/JBCefLocalRequestHandler.kt | 87 + .../impl/jcef/JBCefStreamResourceHandler.kt | 78 + .../jetbrains-community/src/icons/AwsIcons.kt | 191 + .../q/jetbrains/core/AwsResourceCache.kt | 155 + .../core/RemoteResourceResolverProvider.kt | 15 + .../coroutines/PluginCoroutineScopeTracker.kt | 37 + .../core/credentials/CredentialManager.kt | 139 + .../core/credentials/ToolkitAuthManager.kt | 38 + .../pinning/ConnectionPinningManager.kt | 20 + .../credentials/profiles/ProfileWatcher.kt | 15 + .../sso/SsoLoginCallbackProvider.kt | 10 + .../q/jetbrains/settings/AwsSettings.kt | 26 + .../q/jetbrains/telemetry/TelemetryService.kt | 125 + .../software/amazon/q/jetbrains/AwsToolkit.kt | 42 + .../amazon/q/jetbrains/IsDeveloperMode.kt | 8 + .../amazon/q/jetbrains/ToolkitPlaces.kt | 13 + .../q/jetbrains/core/AwsClientManager.kt | 118 + .../q/jetbrains/core/AwsResourceCache.kt | 420 ++ .../amazon/q/jetbrains/core/AwsSdkClient.kt | 66 + .../q/jetbrains/core/AwsTelemetryPrompter.kt | 37 + .../DefaultRemoteResourceResolverProvider.kt | 46 + .../amazon/q/jetbrains/core/HttpUtils.kt | 33 + .../q/jetbrains/core/coroutines/contexts.kt | 37 + .../q/jetbrains/core/coroutines/scopes.kt | 74 + .../core/credentials/AwsConnectionManager.kt | 375 ++ .../AwsConnectionManagerConnection.kt | 15 + .../ChangeConnectionSettingIfValid.kt | 10 + .../core/credentials/ConfigFilesFacade.kt | 291 ++ .../CreateOrUpdateCredentialProfilesAction.kt | 103 + .../core/credentials/CredentialManager.kt | 86 + .../credentials/CredentialTelemetryUtil.kt | 29 + .../credentials/CredentialsRegionHandler.kt | 65 + .../DefaultAwsConnectionManager.kt | 64 + .../credentials/DefaultToolkitAuthManager.kt | 307 ++ .../DefaultToolkitConnectionManager.kt | 166 + .../core/credentials/InteractiveCredential.kt | 22 + .../jetbrains/core/credentials/LoginUtils.kt | 262 ++ .../MfaRequiredInteractiveCredentials.kt | 16 + .../jetbrains/core/credentials/MfaSupport.kt | 17 + .../PostValidateInteractiveCredential.kt | 11 + .../credentials/RefreshConnectionAction.kt | 38 + .../SsoRequiredInteractiveCredentials.kt | 34 + .../jetbrains/core/credentials/SsoSupport.kt | 11 + .../core/credentials/ToolkitAuthManager.kt | 423 ++ .../credentials/ToolkitConnectionImpls.kt | 123 + .../ToolkitConnectionManagerListener.kt | 17 + .../ToolkitCredentialProcessProvider.kt | 112 + .../credentials/actions/SsoLogoutAction.kt | 34 + .../pinning/CodeCatalystConnection.kt | 31 + .../pinning/ConnectionPinningManager.kt | 136 + .../ConnectionPinningManagerListener.kt | 17 + .../core/credentials/pinning/QConnection.kt | 25 + .../profiles/CredentialSourceType.kt | 21 + .../profiles/DEFAULT_PROFILE_ID.kt | 6 + .../profiles/Ec2MetadataConfigProvider.kt | 63 + .../profiles/ProfileAssumeRoleProvider.kt | 84 + .../ProfileCredentialProviderFactory.kt | 522 +++ .../profiles/ProfileLegacySsoProvider.kt | 57 + .../credentials/profiles/ProfileReader.kt | 125 + .../profiles/ProfileSsoSessionIdentifier.kt | 15 + .../profiles/ProfileSsoSessionProvider.kt | 61 + .../core/credentials/profiles/ProfileUtils.kt | 69 + .../credentials/profiles/ProfileWatcher.kt | 93 + .../profiles/SsoSessionConstants.kt | 10 + .../core/credentials/sono/SonoConstants.kt | 38 + .../core/credentials/sso/AccessToken.kt | 94 + .../core/credentials/sso/Authorization.kt | 26 + .../credentials/sso/ClientRegistration.kt | 77 + .../core/credentials/sso/DiskCache.kt | 322 ++ .../credentials/sso/SsoAccessTokenProvider.kt | 636 +++ .../core/credentials/sso/SsoCache.kt | 17 + .../credentials/sso/SsoCredentialProvider.kt | 77 + .../core/credentials/sso/SsoLoginCallback.kt | 24 + .../sso/SsoLoginCallbackProvider.kt | 139 + .../sso/bearer/BearerTokenProvider.kt | 323 ++ .../sso/bearer/BearerTokenProviderListener.kt | 34 + .../sso/bearer/ConfirmUserCodeLoginDialog.kt | 72 + .../sso/pkce/ToolkitOAuthService.kt | 280 ++ .../gettingstarted/GettingStartedAuthUtils.kt | 246 ++ .../core/gettingstarted/IdcRolePopup.kt | 158 + .../SetupAuthenticationDialog.kt | 499 +++ .../editor/GettingStartedPanelUtils.kt | 212 + .../editor/GettingStartedTelemetryUtils.kt | 136 + .../q/jetbrains/core/help/HelpIdTranslator.kt | 25 + .../amazon/q/jetbrains/core/help/HelpIds.kt | 138 + .../notifications/CustomizeNotificationsUi.kt | 129 + .../DisplayToastNotifications.kt | 6 + .../NotificationCustomDeserializers.kt | 127 + .../notifications/NotificationFormatUtils.kt | 200 + .../core/notifications/NotificationPanel.kt | 44 + .../NotificationPollingService.kt | 143 + .../NotificationServiceInitializer.kt | 23 + .../notifications/NotificationStateUtils.kt | 109 + .../notifications/ProcessNotificationsBase.kt | 139 + .../core/notifications/RulesEngine.kt | 209 + .../core/plugin/PluginAutoUpdater.kt | 24 + .../core/plugin/PluginUpdateManager.kt | 232 + .../core/region/AwsRegionProvider.kt | 67 + .../jetbrains/core/webview/BrowserMessage.kt | 78 + .../webview/LocalAssetJBCefRequestHandler.kt | 83 + .../q/jetbrains/core/webview/LoginBrowser.kt | 460 ++ .../core/webview/WebviewTelemetryUtils.kt | 28 + .../jetbrains/services/amazonq/QConstants.kt | 10 + .../explorerActions/QLearnMoreAction.kt | 25 + .../q/jetbrains/services/sts/StsResources.kt | 17 + .../AwsCognitoCredentialsProvider.kt | 87 + .../telemetry/AwsToolkitStartupMetrics.kt | 17 + .../services/telemetry/ClientMetadata.kt | 30 + .../telemetry/DefaultTelemetryPublisher.kt | 134 + .../services/telemetry/OpenTelemetryAction.kt | 112 + .../telemetry/OpenedFileTypesMetrics.kt | 66 + .../services/telemetry/PluginResolver.kt | 52 + .../services/telemetry/TelemetryService.kt | 31 + .../services/telemetry/TelemetryUtils.kt | 282 ++ .../services/telemetry/otel/OtelBase.kt | 262 ++ .../otel/ToolkitTelemetryOTelSpanProcessor.kt | 72 + .../q/jetbrains/settings/AwsSettings.kt | 140 + .../settings/AwsSettingsSharedConfigurable.kt | 51 + .../amazon/q/jetbrains/ui/AsyncComboBox.kt | 187 + .../q/jetbrains/ui/KeyValueTextField.kt | 126 + .../q/jetbrains/ui/feedback/FeedbackDialog.kt | 226 + .../amazon/q/jetbrains/utils/DevFileUtils.kt | 15 + .../amazon/q/jetbrains/utils/FunctionUtils.kt | 60 + .../amazon/q/jetbrains/utils/MRUList.kt | 26 + .../q/jetbrains/utils/NotificationUtils.kt | 185 + .../amazon/q/jetbrains/utils/PsiUtils.kt | 27 + .../q/jetbrains/utils/RemoteEnvUtils.kt | 39 + .../amazon/q/jetbrains/utils/SpinUtils.kt | 42 + .../amazon/q/jetbrains/utils/TextUtils.kt | 44 + .../q/jetbrains/utils/ThreadingUtils.kt | 90 + .../utils/actions/OpenBrowserAction.kt | 21 + .../utils/ui/ResizingColumnRenderer.kt | 52 + .../amazon/q/jetbrains/utils/ui/UiUtils.kt | 285 ++ .../amazon/q/resources/AwsCoreBundle.kt | 19 + .../q/jetbrains/core/BrowserMessageTest.kt | 346 ++ .../q/jetbrains/core/LoginBrowserTest.kt | 161 + .../DefaultToolkitAuthManagerTest.kt | 477 ++ .../core/gettingstarted/IdcRolePopupTest.kt | 112 + .../SetupAuthenticationDialogTest.kt | 349 ++ .../jetbrains/core/BrowserMessageTest.kt | 332 ++ .../jetbrains/core/LoginBrowserTest.kt | 145 + .../DefaultToolkitAuthManagerTest.kt | 455 ++ .../core/gettingstarted/IdcRolePopupTest.kt | 100 + .../SetupAuthenticationDialogTest.kt | 340 ++ .../tst-resources/exampleNotification2.json | 124 + .../tst-resources/olderNotification.json | 115 + .../tst-resources/selfSigned.jks | Bin 0 -> 2295 bytes .../q/jetbrains/core/AwsClientManagerTest.kt | 449 ++ .../q/jetbrains/core/AwsResourceCacheTest.kt | 563 +++ .../q/jetbrains/core/AwsSdkClientTest.kt | 133 + .../core/coroutines/CoroutineUtilsTest.kt | 46 + .../q/jetbrains/core/coroutines/ScopeTest.kt | 201 + ...ateOrUpdateCredentialProfilesActionTest.kt | 209 + .../core/credentials/CredentialManagerTest.kt | 316 ++ .../CredentialsRegionHandlerTest.kt | 188 + .../DefaultAwsConnectionManagerTest.kt | 529 +++ .../DefaultConfigFilesFacadeTest.kt | 608 +++ .../DefaultToolkitConnectionManagerTest.kt | 145 + .../RefreshConnectionActionTest.kt | 89 + .../pinning/ConnectionPinningManagerTest.kt | 201 + .../profiles/Ec2MetadataConfigProviderTest.kt | 102 + .../profiles/ProfileAssumeRoleProviderTest.kt | 192 + .../ProfileCredentialProviderFactoryTest.kt | 1202 +++++ .../ProfileCredentialsIdentifierSsoTest.kt | 60 + .../credentials/profiles/ProfileReaderTest.kt | 335 ++ .../credentials/profiles/ProfileTestUtils.kt | 12 + .../profiles/ProfileWatcherTest.kt | 177 + .../core/credentials/sso/AccessTokenTest.kt | 38 + .../credentials/sso/ClientRegistrationTest.kt | 39 + .../core/credentials/sso/DiskCacheTest.kt | 717 +++ .../sso/SsoAccessTokenProviderTest.kt | 561 +++ .../sso/SsoCredentialProviderTest.kt | 122 + .../sso/bearer/BearerTokenProviderTest.kt | 36 + .../sso/bearer/BearerTokenTestUtil.kt | 17 + .../InteractiveBearerTokenProviderTest.kt | 355 ++ .../ProfileSdkTokenProviderWrapperTest.kt | 115 + .../core/gettingstarted/SourceOfEntryTest.kt | 20 + .../NotificationDismissalStateTest.kt | 84 + .../NotificationFormatUtilsTest.kt | 156 + .../NotificationFormatUtilsTestCases.kt | 388 ++ .../notifications/NotificationManagerTest.kt | 45 + .../NotificationPollingServiceTest.kt | 83 + .../NotificationResourceResolverTest.kt | 78 + .../ProcessNotificationsBaseTest.kt | 161 + .../core/plugin/PluginUpdateManagerTest.kt | 124 + .../AwsCognitoCredentialsProviderTest.kt | 190 + .../DefaultTelemetryPublisherTest.kt | 275 ++ .../telemetry/OpenedFileTypeMetricsTest.kt | 37 + .../services/telemetry/PluginResolverTest.kt | 115 + .../telemetry/TelemetryServiceTest.kt | 289 ++ .../services/telemetry/TelemetryUtilsTest.kt | 67 + .../services/telemetry/otel/OtelBaseTest.kt | 364 ++ .../ToolkitTelemetryOTelSpanProcessorTest.kt | 227 + .../q/jetbrains/settings/AwsSettingsTest.kt | 74 + .../q/jetbrains/ui/AsyncComboBoxTest.kt | 85 + .../amazon/q/jetbrains/utils/MRUListTest.kt | 45 + .../jetbrains/utils/NotificationUtilsTest.kt | 38 + .../amazon/q/jetbrains/utils/TextUtilsTest.kt | 207 + .../q/jetbrains/utils/ThreadingUtilsKtTest.kt | 97 + .../junit5/impl/TestDisposableExtension.kt | 76 + .../amazon/q/jetbrains/core/DummyResource.kt | 21 + .../software/amazon/q/jetbrains/core/Id.kt | 10 + .../q/jetbrains/core/MockClientManager.kt | 134 + .../q/jetbrains/core/MockResourceCache.kt | 207 + .../credentials/MockAwsConnectionManager.kt | 125 + .../credentials/MockCredentialsManager.kt | 183 + .../MockCredentialsRegionHandler.kt | 11 + .../credentials/MockToolkitAuthManagerRule.kt | 31 + .../sso/MockSsoLoginCallbackProvider.kt | 106 + .../core/region/MockRegionProvider.kt | 126 + .../telemetry/MockTelemetryService.kt | 73 + .../q/jetbrains/settings/MockAwsSettings.kt | 37 + .../q/jetbrains/utils/AssertJMatchers.kt | 58 + .../q/jetbrains/utils/SerializationUtils.kt | 29 + .../jetbrains/utils/ServiceExceptionUtils.kt | 47 + .../utils/extensions/SsoLoginExtension.kt | 46 + .../utils/rules/CodeInsightTestFixtureRule.kt | 183 + .../rules/JavaCodeInsightTestFixtureRule.kt | 168 + .../utils/rules/NotificationListenerRule.kt | 50 + .../q/jetbrains/utils/rules/RegistryRule.kt | 36 + .../q/jetbrains/utils/rules/SsoLoginRule.kt | 51 + .../jetbrains-ultimate/build.gradle.kts | 16 + .../lang/javascript/JavascriptLanguage.kt | 8 + .../lang/javascript/JavascriptLanguage.kt | 8 + plugins/core-q/resources/build.gradle.kts | 49 + .../resources/MessagesBundle.properties | 2120 +++++++++ .../toolkits/resources/BundledResources.kt | 10 + .../aws/toolkits/resources/Localization.kt | 26 + .../resources/BundledResourcesTest.kt | 28 + plugins/core-q/sdk-codegen/README.md | 22 + plugins/core-q/sdk-codegen/build.gradle.kts | 17 + .../codewhispererruntime/paginators-1.json | 31 + .../codewhispererruntime/service-2.json | 3933 +++++++++++++++++ .../codewhispererstreaming/service-2.json | 2671 +++++++++++ .../telemetry/customization.config | 6 + .../telemetry/service-2.json | 257 ++ plugins/core-q/webview/.gitignore | 3 + plugins/core-q/webview/build.gradle.kts | 35 + plugins/core-q/webview/package-lock.json | 3931 ++++++++++++++++ plugins/core-q/webview/package.json | 61 + plugins/core-q/webview/src/constants.ts | 6 + plugins/core-q/webview/src/defs.d.ts | 23 + plugins/core-q/webview/src/ideClient.ts | 92 + plugins/core-q/webview/src/model.ts | 149 + .../webview/src/q-ui/assets/common.scss | 105 + .../src/q-ui/components/authenticating.vue | 74 + .../src/q-ui/components/awsProfileForm.vue | 84 + .../webview/src/q-ui/components/login.vue | 135 + .../src/q-ui/components/loginOptions.vue | 45 + .../webview/src/q-ui/components/logo.vue | 120 + .../src/q-ui/components/profileSelection.vue | 227 + .../webview/src/q-ui/components/qOptions.vue | 142 + .../webview/src/q-ui/components/reauth.vue | 108 + .../webview/src/q-ui/components/root.vue | 99 + .../src/q-ui/components/selectableItem.vue | 150 + .../src/q-ui/components/ssoLoginForm.vue | 181 + .../src/q-ui/components/toolkitOptions.vue | 156 + plugins/core-q/webview/src/q-ui/index.ts | 82 + plugins/core-q/webview/src/q-ui/toolkit.ts | 74 + plugins/core-q/webview/src/vue.shims.d.ts | 11 + .../core-q/webview/src/webviewTelemetry.ts | 68 + plugins/core-q/webview/tsconfig.json | 19 + plugins/core-q/webview/webpack.config.js | 134 + plugins/core/build.gradle.kts | 11 - .../resources/META-INF/aws.toolkit.core.xml | 93 - .../resources/META-INF/module-core.xml | 12 - .../notifications/CustomizeNotificationsUi.kt | 2 - .../core/plugin/PluginUpdateManager.kt | 3 - .../resources/MessagesBundle.properties | 3 +- .../src/main/resources/META-INF/plugin.xml | 33 - .../intellij-standalone/build.gradle.kts | 2 +- .../resources/META-INF/plugin-shim.xml | 17 - .../toolkit/jetbrains-core/build.gradle.kts | 2 +- .../resources/META-INF/plugin.xml | 105 +- .../core/startup/QMigrationActivity.kt | 187 - .../META-INF/plugin-shim.xml | 6 - .../jetbrains-ultimate/build.gradle.kts | 2 +- 680 files changed, 53575 insertions(+), 1256 deletions(-) create mode 100644 plugins/core-q/build.gradle.kts create mode 100644 plugins/core-q/core-q/build.gradle.kts create mode 100644 plugins/core-q/core-q/detekt-baseline-integrationTest.xml create mode 100644 plugins/core-q/core-q/detekt-baseline-main.xml create mode 100644 plugins/core-q/core-q/detekt-baseline-test.xml create mode 100644 plugins/core-q/core-q/detekt-baseline.xml create mode 100644 plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt create mode 100644 plugins/core-q/core-q/src/migration/software/amazon/q/core/ToolkitClientManager.kt create mode 100644 plugins/core-q/core-q/src/migration/software/amazon/q/core/clients/SdkClientProvider.kt create mode 100644 plugins/core-q/core-q/src/migration/software/amazon/q/core/region/ToolkitRegionProvider.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/ConnectionSettings.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientCustomizer.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientManager.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/clients/ClientBuilderUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/clients/SdkClientProvider.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/AwsCredentials.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialProviderFactory.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialsChangeEvent.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/SsoUrlIdentifier.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProvider.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProviderManager.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaArchitecture.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaRuntime.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaSampleEventProvider.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/AwsPartition.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/AwsRegion.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/Partitions.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/ToolkitRegionProvider.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/s3/BucketUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/CachedIdentityStorage.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/MetricEvent.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryBatcher.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryPublisher.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/AttributeBag.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/CollectionUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/Either.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/ExceptionUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/LogUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/PathUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/RemoteResourceResolver.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/SensitiveField.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/StringUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/TextUtils.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/Waiter.kt create mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/ZipUtils.kt create mode 100644 plugins/core-q/core-q/tst-resources/jsonSampleFailure.json create mode 100644 plugins/core-q/core-q/tst-resources/jsonSampleSuccess.json create mode 100644 plugins/core-q/core-q/tst-resources/sampleLambdaEvent.json create mode 100644 plugins/core-q/core-q/tst-resources/xmlSampleFailure.xml create mode 100644 plugins/core-q/core-q/tst-resources/xmlSampleSuccess.xml create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt create mode 100644 plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt create mode 100644 plugins/core-q/jetbrains-community/build.gradle.kts create mode 100644 plugins/core-q/jetbrains-community/detekt-baseline-main.xml create mode 100644 plugins/core-q/jetbrains-community/detekt-baseline-test.xml create mode 100644 plugins/core-q/jetbrains-community/detekt-baseline-testFixtures.xml create mode 100644 plugins/core-q/jetbrains-community/detekt-baseline.xml create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Large.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_White_Medium.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_Q_grey.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CodeWhisperer_Large.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/csharp.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/csharp_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/frown.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/frown_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/java.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/javaScript.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/javaScript_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/java_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/learn.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/learn_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/new.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/python.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/typeScript.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/typeScript_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/CodewhispererCustom.svg create mode 100755 plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository.svg create mode 100755 plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Redshift.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Redshift_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Schema.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Schema_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/checkmark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/greenCheckmark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-light.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-critical.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-high.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-info.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-critical.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-high.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-info.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-low.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-medium.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-low.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-medium.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow.svg create mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow_dark.svg create mode 100644 plugins/core-q/jetbrains-community/resources/oauthCallback/auth.css create mode 100644 plugins/core-q/jetbrains-community/resources/oauthCallback/index.html create mode 100644 plugins/core-q/jetbrains-community/resources/telemetryOverride.json create mode 100644 plugins/core-q/jetbrains-community/src-242-252/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt create mode 100644 plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt create mode 100644 plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefLocalRequestHandler.kt create mode 100644 plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefStreamResourceHandler.kt create mode 100644 plugins/core-q/jetbrains-community/src/icons/AwsIcons.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/RemoteResourceResolverProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/coroutines/PluginCoroutineScopeTracker.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/settings/AwsSettings.kt create mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/AwsToolkit.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/IsDeveloperMode.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ToolkitPlaces.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsSdkClient.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsTelemetryPrompter.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/HttpUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/contexts.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/scopes.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManagerConnection.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ChangeConnectionSettingIfValid.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialTelemetryUtil.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/InteractiveCredential.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaRequiredInteractiveCredentials.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaSupport.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/PostValidateInteractiveCredential.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/RefreshConnectionAction.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoRequiredInteractiveCredentials.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoSupport.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionManagerListener.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitCredentialProcessProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/actions/SsoLogoutAction.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerListener.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/QConnection.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/CredentialSourceType.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/DEFAULT_PROFILE_ID.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileLegacySsoProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReader.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionIdentifier.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/SsoSessionConstants.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sono/SonoConstants.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/AccessToken.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/Authorization.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistration.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCache.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallback.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderListener.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIdTranslator.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIds.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/CustomizeNotificationsUi.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/DisplayToastNotifications.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationCustomDeserializers.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPanel.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationServiceInitializer.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationStateUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/RulesEngine.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginAutoUpdater.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/BrowserMessage.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LocalAssetJBCefRequestHandler.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/WebviewTelemetryUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/QConstants.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/explorerActions/QLearnMoreAction.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/sts/StsResources.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProvider.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsToolkitStartupMetrics.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/ClientMetadata.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypesMetrics.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/PluginResolver.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryService.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessor.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettings.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettingsSharedConfigurable.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/AsyncComboBox.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/KeyValueTextField.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/feedback/FeedbackDialog.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/DevFileUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/MRUList.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/PsiUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/SpinUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/TextUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ThreadingUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/actions/OpenBrowserAction.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/ResizingColumnRenderer.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/UiUtils.kt create mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/resources/AwsCoreBundle.kt create mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/BrowserMessageTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/BrowserMessageTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/LoginBrowserTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/IdcRolePopupTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst-resources/exampleNotification2.json create mode 100644 plugins/core-q/jetbrains-community/tst-resources/olderNotification.json create mode 100644 plugins/core-q/jetbrains-community/tst-resources/selfSigned.jks create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/CoroutineUtilsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/ScopeTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesActionTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/AccessTokenTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistrationTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/gettingstarted/SourceOfEntryTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationDismissalStateTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationResourceResolverTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBaseTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/NotificationUtilsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt create mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/ThreadingUtilsKtTest.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/com/intellij/testFramework/junit5/impl/TestDisposableExtension.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/Id.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsRegionHandler.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockToolkitAuthManagerRule.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/sso/MockSsoLoginCallbackProvider.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/services/telemetry/MockTelemetryService.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/AssertJMatchers.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/SerializationUtils.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/ServiceExceptionUtils.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/extensions/SsoLoginExtension.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/CodeInsightTestFixtureRule.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/JavaCodeInsightTestFixtureRule.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/NotificationListenerRule.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/RegistryRule.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/SsoLoginRule.kt create mode 100644 plugins/core-q/jetbrains-ultimate/build.gradle.kts create mode 100644 plugins/core-q/jetbrains-ultimate/src-242-243/compat/com/intellij/lang/javascript/JavascriptLanguage.kt create mode 100644 plugins/core-q/jetbrains-ultimate/src-251+/compat/com/intellij/lang/javascript/JavascriptLanguage.kt create mode 100644 plugins/core-q/resources/build.gradle.kts create mode 100644 plugins/core-q/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties create mode 100644 plugins/core-q/resources/src/software/aws/toolkits/resources/BundledResources.kt create mode 100644 plugins/core-q/resources/src/software/aws/toolkits/resources/Localization.kt create mode 100644 plugins/core-q/resources/tst/software/aws/toolkits/resources/BundledResourcesTest.kt create mode 100644 plugins/core-q/sdk-codegen/README.md create mode 100644 plugins/core-q/sdk-codegen/build.gradle.kts create mode 100644 plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/paginators-1.json create mode 100644 plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/service-2.json create mode 100644 plugins/core-q/sdk-codegen/codegen-resources/codewhispererstreaming/service-2.json create mode 100644 plugins/core-q/sdk-codegen/codegen-resources/telemetry/customization.config create mode 100644 plugins/core-q/sdk-codegen/codegen-resources/telemetry/service-2.json create mode 100644 plugins/core-q/webview/.gitignore create mode 100644 plugins/core-q/webview/build.gradle.kts create mode 100644 plugins/core-q/webview/package-lock.json create mode 100644 plugins/core-q/webview/package.json create mode 100644 plugins/core-q/webview/src/constants.ts create mode 100644 plugins/core-q/webview/src/defs.d.ts create mode 100644 plugins/core-q/webview/src/ideClient.ts create mode 100644 plugins/core-q/webview/src/model.ts create mode 100644 plugins/core-q/webview/src/q-ui/assets/common.scss create mode 100644 plugins/core-q/webview/src/q-ui/components/authenticating.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/awsProfileForm.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/login.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/loginOptions.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/logo.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/profileSelection.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/qOptions.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/reauth.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/root.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/selectableItem.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/ssoLoginForm.vue create mode 100644 plugins/core-q/webview/src/q-ui/components/toolkitOptions.vue create mode 100644 plugins/core-q/webview/src/q-ui/index.ts create mode 100644 plugins/core-q/webview/src/q-ui/toolkit.ts create mode 100644 plugins/core-q/webview/src/vue.shims.d.ts create mode 100644 plugins/core-q/webview/src/webviewTelemetry.ts create mode 100644 plugins/core-q/webview/tsconfig.json create mode 100644 plugins/core-q/webview/webpack.config.js delete mode 100644 plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml delete mode 100644 plugins/core/jetbrains-community/resources/META-INF/module-core.xml delete mode 100644 plugins/core/src/main/resources/META-INF/plugin.xml delete mode 100644 plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml delete mode 100644 plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt delete mode 100644 plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml diff --git a/plugins/amazonq/build.gradle.kts b/plugins/amazonq/build.gradle.kts index 03750601bad..65a5aaa7cd5 100644 --- a/plugins/amazonq/build.gradle.kts +++ b/plugins/amazonq/build.gradle.kts @@ -34,7 +34,7 @@ tasks.jar { } dependencies { - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core-q")) implementation(project(":plugin-amazonq:chat")) implementation(project(":plugin-amazonq:codetransform")) implementation(project(":plugin-amazonq:codewhisperer")) @@ -43,7 +43,7 @@ dependencies { implementation(libs.bundles.jackson) implementation(libs.lsp4j) - testImplementation(project(":plugin-core")) + testImplementation(project(":plugin-core-q")) } tasks.check { diff --git a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts b/plugins/amazonq/chat/jetbrains-community/build.gradle.kts index f8c1617119e..927bbf26216 100644 --- a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/chat/jetbrains-community/build.gradle.kts @@ -12,7 +12,7 @@ intellijToolkit { } dependencies { - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core-q")) implementation(project(":plugin-amazonq:shared:jetbrains-community")) // everything references codewhisperer, which is not ideal @@ -20,9 +20,9 @@ dependencies { implementation(libs.diff.util) implementation(libs.commons.text) - compileOnly(project(":plugin-core:jetbrains-community")) + compileOnly(project(":plugin-core-q:jetbrains-community")) - testImplementation(testFixtures(project(":plugin-core:jetbrains-community"))) + testImplementation(testFixtures(project(":plugin-core-q:jetbrains-community"))) } // hack because our test structure currently doesn't make complete sense diff --git a/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml b/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml index 5a22610e189..d1197e69b67 100644 --- a/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml +++ b/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml @@ -36,7 +36,7 @@ - + + topic="software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener"/> + topic="software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener"/> diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt index ae67e5a42a1..96dc69a9280 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt @@ -19,13 +19,13 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType import software.amazon.awssdk.services.ssooidc.model.SsoOidcException -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.jetbrains.core.coroutines.EDT -import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.core.coroutines.EDT +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_EXPIRED import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient @@ -47,8 +47,8 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHi import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isValidCodeTransformConnection import software.aws.toolkits.jetbrains.services.codemodernizer.utils.openTroubleshootingGuideNotificationAction import software.aws.toolkits.jetbrains.services.codemodernizer.utils.zipToPath -import software.aws.toolkits.jetbrains.utils.notifyStickyInfo -import software.aws.toolkits.jetbrains.utils.notifyStickyWarn +import software.amazon.q.jetbrains.utils.notifyStickyInfo +import software.amazon.q.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformArtifactType import java.io.File diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt index be398707c7a..93c468e026a 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt @@ -24,12 +24,12 @@ import kotlinx.coroutines.launch import software.amazon.awssdk.services.codewhispererruntime.model.TransformationJob import software.amazon.awssdk.services.codewhispererruntime.model.TransformationPlan import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_MVN_FAILURE import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_PROJECT_SIZE import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile @@ -79,8 +79,8 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseXmlDep import software.aws.toolkits.jetbrains.services.codemodernizer.utils.setDependencyVersionInPom import software.aws.toolkits.jetbrains.services.codemodernizer.utils.tryGetJdk import software.aws.toolkits.jetbrains.ui.feedback.CodeTransformFeedbackDialog -import software.aws.toolkits.jetbrains.utils.notifyStickyError -import software.aws.toolkits.jetbrains.utils.notifyStickyInfo +import software.amazon.q.jetbrains.utils.notifyStickyError +import software.amazon.q.jetbrains.utils.notifyStickyInfo import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformBuildSystem import software.aws.toolkits.telemetry.CodeTransformCancelSrcComponents @@ -91,7 +91,7 @@ import java.time.Instant import java.util.concurrent.atomic.AtomicBoolean import kotlin.io.path.pathString -@State(name = "codemodernizerStates", storages = [Storage("aws.xml", roamingType = RoamingType.PER_OS)]) +@State(name = "codemodernizerStates", storages = [Storage("amazonq.xml", roamingType = RoamingType.PER_OS)]) class CodeModernizerManager(private val project: Project) : PersistentStateComponent, Disposable { private val telemetry = CodeTransformTelemetryManager.getInstance(project) private var managerState = CodeModernizerState() diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt index 0e4b7a14780..7a3ddd1b6cf 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt @@ -23,13 +23,13 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType import software.amazon.awssdk.services.ssooidc.model.SsoOidcException -import software.aws.toolkits.core.utils.Waiters.waitUntil -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.core.utils.Waiters.waitUntil +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerException diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt index 3e34ffa1e10..3138c20026e 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt @@ -7,11 +7,11 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch -import software.aws.toolkits.jetbrains.core.coroutines.disposableCoroutineScope -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQApp import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt index d2b333952cb..c05108800e4 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt @@ -6,9 +6,9 @@ package software.aws.toolkits.jetbrains.services.codemodernizer import com.intellij.openapi.application.runInEdt import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ex.ToolWindowManagerListener -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererActivationChangedListener diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt index 37b6c3b2069..14facdaceb6 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt @@ -37,10 +37,10 @@ import software.amazon.awssdk.services.codewhispererstreaming.model.ThrottlingEx import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationExportContext import software.amazon.awssdk.services.codewhispererstreaming.model.ValidationException -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.jetbrains.core.AwsClientManager +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.core.AwsClientManager import software.aws.toolkits.jetbrains.services.amazonq.APPLICATION_ZIP import software.aws.toolkits.jetbrains.services.amazonq.AWS_KMS import software.aws.toolkits.jetbrains.services.amazonq.CONTENT_SHA256 diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt index 9d8e948e6bd..83699a13132 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt @@ -20,11 +20,11 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.jetbrains.yaml.YAMLFileType import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.jetbrains.core.coroutines.EDT +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthFollowUpType diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt index 254dc3b7874..e976317d7d0 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt @@ -15,10 +15,10 @@ import org.jetbrains.idea.maven.execution.MavenRunner import org.jetbrains.idea.maven.execution.MavenRunnerParameters import org.jetbrains.idea.maven.execution.MavenRunnerSettings import org.slf4j.Logger -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.jetbrains.AwsPlugin -import software.aws.toolkits.jetbrains.AwsToolkit +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt index aea9920f61a..f6c28e61a86 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt @@ -12,10 +12,10 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule import com.intellij.openapi.util.io.FileUtil.createTempDirectory import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.services.codemodernizer.TransformationSummary import software.aws.toolkits.jetbrains.services.codemodernizer.utils.unzipFile import java.io.File diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt index ab0e3b5fa2f..5ebacf4d18c 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt @@ -12,11 +12,11 @@ import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.wm.ToolWindowManager -import software.aws.toolkits.core.utils.createTemporaryZipFile -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.putNextEntry +import software.amazon.q.core.utils.createTemporaryZipFile +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.putNextEntry import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_DEPENDENCIES_ROOT_NAME import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_MANIFEST_FILE_NAME import software.aws.toolkits.jetbrains.services.codemodernizer.ideMaven.TransformMavenRunner diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformFailureBuildLog.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformFailureBuildLog.kt index 09edf27f3e1..74922e867fc 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformFailureBuildLog.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformFailureBuildLog.kt @@ -4,9 +4,9 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model import com.intellij.openapi.util.io.FileUtil.createTempDirectory -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codemodernizer.utils.unzipFile import java.io.File import java.nio.file.Path diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilDownloadArtifact.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilDownloadArtifact.kt index 675d27d759c..aba17a06b79 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilDownloadArtifact.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilDownloadArtifact.kt @@ -6,9 +6,9 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codemodernizer.utils.unzipFile import java.io.File import java.nio.file.Path diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt index c846157c95b..2c4f86720d9 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt @@ -19,8 +19,8 @@ import com.intellij.util.ui.JBUI import software.amazon.awssdk.services.codewhispererruntime.model.TransformationJob import software.amazon.awssdk.services.codewhispererruntime.model.TransformationPlan import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditorProvider.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditorProvider.kt index e86fab4b40e..097b215a502 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditorProvider.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditorProvider.kt @@ -13,8 +13,8 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import software.amazon.awssdk.services.codewhispererruntime.model.TransformationPlan -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger class CodeModernizerPlanEditorProvider : FileEditorProvider, DumbAware { override fun accept(project: Project, file: VirtualFile) = file is CodeModernizerPlanVirtualFile diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 4b6b3a85eea..84a3d99299d 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -33,12 +33,12 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.codewhispererruntime.model.ValidationException import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException -import software.aws.toolkits.core.utils.WaiterUnrecoverableException -import software.aws.toolkits.core.utils.Waiters.waitUntil -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.jetbrains.core.coroutines.EDT +import software.amazon.q.core.utils.WaiterUnrecoverableException +import software.amazon.q.core.utils.Waiters.waitUntil +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient @@ -51,7 +51,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId import software.aws.toolkits.jetbrains.services.codemodernizer.model.PlanTable -import software.aws.toolkits.jetbrains.utils.notifyStickyWarn +import software.amazon.q.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.resources.message import java.nio.file.Path import java.nio.file.Paths diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt index 4f925f142e8..2d2a684db7d 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt @@ -11,14 +11,14 @@ import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.readText import kotlinx.coroutines.withContext -import software.aws.toolkits.core.utils.createParentDirectories -import software.aws.toolkits.core.utils.createTemporaryZipFile -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.putNextEntry -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.core.utils.createParentDirectories +import software.amazon.q.core.utils.createTemporaryZipFile +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.putNextEntry +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager.Companion.LOG import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_ARTIFACT_DIR_NAME import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_ARTIFACT_POMFOLDER_DIR_NAME diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformUtils.kt index 6729ce167f1..f44b8fe03ca 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformUtils.kt @@ -8,15 +8,15 @@ import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFileManager import software.amazon.awssdk.services.codewhispererruntime.model.TransformationLanguage import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.ActiveConnection -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.ActiveConnectionType -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity -import software.aws.toolkits.jetbrains.utils.actions.OpenBrowserAction +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection +import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnectionType +import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet +import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity +import software.amazon.q.jetbrains.utils.actions.OpenBrowserAction import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CredentialSourceId diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt index 83f73a9e745..a28d02ad991 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt @@ -5,7 +5,9 @@ package software.aws.toolkits.jetbrains.ui.feedback import com.intellij.openapi.project.Project import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.ui.feedback.FEEDBACK_SOURCE +import software.amazon.q.jetbrains.ui.feedback.FeedbackDialog import software.aws.toolkits.resources.message class CodeTransformFeedbackDialog(project: Project) : FeedbackDialog(project) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt index 91918c92db2..e95caa4e7ff 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt @@ -9,7 +9,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTr import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformButtonId import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId -import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.resources.message class CodeTransformChatTest { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt index 3288afb1993..5704e621b62 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt @@ -9,8 +9,8 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.satisfiesKt import kotlin.io.path.createTempDirectory class CodeTransformHilDownloadArtifactTest : CodeWhispererCodeModernizerTestBase(HeavyJavaCodeInsightTestFixtureRule()) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt index 21057226ede..60814eb8297 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt @@ -11,7 +11,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeTransformTelemetryState -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import kotlin.io.path.Path class CodeTransformTelemetryTest : CodeWhispererCodeModernizerTestBase(HeavyJavaCodeInsightTestFixtureRule()) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt index 6e8da48cce8..fa02cab42ad 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt @@ -41,14 +41,14 @@ import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveRequest import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveResponseHandler import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ManagedSsoProfile -import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule -import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ManagedSsoProfile +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt index 648d359d9e4..0b24ae92a81 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt @@ -48,7 +48,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUr import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.ssooidc.model.SsoOidcException -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.aws.toolkits.jetbrains.services.codemodernizer.model.CLIENT_SIDE_BUILD import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext @@ -63,8 +63,8 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.SELECTIVE_T import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.ZipCreationResult import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.addFileToModule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.addFileToModule import java.io.File import java.io.FileInputStream import java.net.ConnectException diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt index a6f2af3b7c0..fed4fdd3e53 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt @@ -25,7 +25,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType import software.amazon.awssdk.services.ssooidc.model.SsoOidcException -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadArtifactResult diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt index 6ba37aa6b9f..d0039725790 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt @@ -40,14 +40,14 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStep import software.amazon.awssdk.services.codewhispererruntime.model.TransformationType -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.credentials.ToolkitBearerTokenProvider -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerManifest @@ -60,10 +60,10 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.C import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState import software.aws.toolkits.jetbrains.services.codemodernizer.toolwindow.CodeModernizerBottomToolWindowFactory import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.addModule +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.addModule import java.io.File import java.time.Instant import kotlin.io.path.Path diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt index f338b967090..bc43df19c1a 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt @@ -44,8 +44,8 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseBuildF import software.aws.toolkits.jetbrains.services.codemodernizer.utils.pollTransformationStatusAndPlan import software.aws.toolkits.jetbrains.services.codemodernizer.utils.validateCustomVersionsFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.validateSctMetadata -import software.aws.toolkits.jetbrains.utils.notifyStickyWarn -import software.aws.toolkits.jetbrains.utils.rules.addFileToModule +import software.amazon.q.jetbrains.utils.notifyStickyWarn +import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.aws.toolkits.resources.message import java.util.concurrent.atomic.AtomicBoolean import java.util.zip.ZipFile diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/PanelTestBase.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/PanelTestBase.kt index de0c247bb05..f1d61d3b32f 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/PanelTestBase.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/PanelTestBase.kt @@ -6,7 +6,7 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.panels import com.intellij.openapi.project.Project import org.junit.Before import org.junit.Rule -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule open class PanelTestBase( @Rule @JvmField val projectRule: CodeInsightTestFixtureRule = CodeInsightTestFixtureRule(), diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt index 5b9010230ce..247a23368c7 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt @@ -21,7 +21,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import software.aws.toolkits.jetbrains.services.codemodernizer.CodeWhispererCodeModernizerTestBase -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule class CodeTransformModuleUtilsTest : CodeWhispererCodeModernizerTestBase(HeavyJavaCodeInsightTestFixtureRule()) { lateinit var javaSdkMock: JavaSdk diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts b/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts index 73593eaae51..1a51e8c67da 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts @@ -12,16 +12,16 @@ intellijToolkit { } dependencies { - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core-q")) - compileOnly(project(":plugin-core:jetbrains-community")) + compileOnly(project(":plugin-core-q:jetbrains-community")) implementation(project(":plugin-amazonq:shared:jetbrains-community")) implementation(libs.lsp4j) // CodeWhispererTelemetryService uses a CircularFifoQueue, previously transitive from zjsonpatch implementation(libs.commons.collections) - testFixturesApi(testFixtures(project(":plugin-core:jetbrains-community"))) + testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-community"))) testFixturesApi(project(path = ":plugin-toolkit:jetbrains-core", configuration = "testArtifacts")) } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt index 7a0c2f0219d..a25b4e3fe43 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt @@ -8,7 +8,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppTestLeftContext -import software.aws.toolkits.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt index 467a326a7f9..32ebb360492 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt @@ -6,10 +6,10 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.runInEdtAndWait import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaTestContext -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials -import software.aws.toolkits.jetbrains.utils.rules.addClass -import software.aws.toolkits.jetbrains.utils.rules.addModule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials +import software.amazon.q.jetbrains.utils.rules.addClass +import software.amazon.q.jetbrains.utils.rules.addModule import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt index b946f14ce27..1de0210ffda 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt @@ -11,7 +11,7 @@ import org.mockito.kotlin.never import org.mockito.kotlin.verify import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppFileName import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.aws.toolkits.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt index 5ff82bdae58..e6272b827c6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt @@ -28,15 +28,15 @@ import org.mockito.kotlin.timeout import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.credentials.ToolkitBearerTokenProvider -import software.aws.toolkits.jetbrains.core.MockClientManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManager -import software.aws.toolkits.jetbrains.core.credentials.loginSso -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_REGION -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.loginSso +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.codeWhispererRecommendationActionId import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext @@ -58,9 +58,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhisp import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.RunWithRealCredentials +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials open class CodeWhispererIntegrationTestBase(val projectRule: CodeInsightTestFixtureRule = PythonCodeInsightTestFixtureRule()) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt index 67b75619b3d..8a04b517eb1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt @@ -12,7 +12,7 @@ import org.mockito.kotlin.never import org.mockito.kotlin.verify import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.jsFileName import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.aws.toolkits.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml index 88a52fd16bc..4ee20975c33 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml +++ b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml @@ -17,9 +17,9 @@ + topic="software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener"/> + topic="software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener"/> @@ -63,7 +63,7 @@ - + diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt index efb99a7aa8c..35518dad497 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt @@ -8,12 +8,12 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreStateType @@ -22,7 +22,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.telemetry.AwsTelemetry // TODO: refactor this class, now it's managing action and state -@State(name = "codewhispererStates", storages = [Storage("aws.xml")]) +@State(name = "codewhispererStates", storages = [Storage("amazonq.xml")]) class CodeWhispererExplorerActionManager : PersistentStateComponent { private val actionState = CodeWhispererExploreActionState() private val suspendedConnections = mutableSetOf() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererLearnMoreAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererLearnMoreAction.kt index f359a646f54..8899acae264 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererLearnMoreAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererLearnMoreAction.kt @@ -8,7 +8,7 @@ import com.intellij.ide.BrowserUtil import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAware -import software.aws.toolkits.jetbrains.services.amazonq.QConstants.Q_MARKETPLACE_URI +import software.amazon.q.jetbrains.services.amazonq.QConstants.Q_MARKETPLACE_URI import software.aws.toolkits.resources.message import java.net.URI diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererWhatIsAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererWhatIsAction.kt index fa12a1dfd7b..ebc10fc8ff8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererWhatIsAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererWhatIsAction.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAware -import software.aws.toolkits.jetbrains.services.amazonq.QConstants +import software.amazon.q.jetbrains.services.amazonq.QConstants import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled import software.aws.toolkits.resources.message import java.net.URI diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/AmazonQCodeFixSession.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/AmazonQCodeFixSession.kt index 7ed08eb511f..4f07e2c0bd4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/AmazonQCodeFixSession.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/AmazonQCodeFixSession.kt @@ -19,12 +19,12 @@ import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeFixJo import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeFixJobResponse import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.codewhispererruntime.model.UploadIntent -import software.aws.toolkits.core.utils.createTemporaryZipFile -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.putNextEntry +import software.amazon.q.core.utils.createTemporaryZipFile +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.putNextEntry import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanHighlightingFilesPanel.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanHighlightingFilesPanel.kt index 59f6a7ad25f..a0950a1f4b2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanHighlightingFilesPanel.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanHighlightingFilesPanel.kt @@ -15,8 +15,8 @@ import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.TreeSpeedSearch import com.intellij.ui.treeStructure.Tree import com.intellij.util.ui.UIUtil -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.resources.message import java.awt.BorderLayout import java.awt.event.MouseEvent diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt index 67041b24f92..8e6e1432680 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt @@ -56,18 +56,18 @@ import kotlinx.coroutines.withContext import org.jetbrains.annotations.TestOnly import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException -import software.aws.toolkits.core.utils.WaiterTimeoutException -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.WaiterTimeoutException +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.ProblemsViewMutator -import software.aws.toolkits.jetbrains.core.coroutines.EDT -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineUiContext -import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.coroutines.EDT +import software.amazon.q.jetbrains.core.coroutines.getCoroutineUiContext +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanDocumentListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanEditorMouseMotionListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanFileListener @@ -95,9 +95,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.Result import java.time.Duration diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanResultsView.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanResultsView.kt index 3de0cd85152..1a9c8ddbf3d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanResultsView.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanResultsView.kt @@ -20,8 +20,8 @@ import com.intellij.ui.components.ActionLink import com.intellij.ui.treeStructure.Tree import com.intellij.util.ui.JBUI import icons.AwsIcons -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.IssueGroupingStrategy import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.IssueSeverity import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt index 65ef44f18dc..62d8c66f5b8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt @@ -29,11 +29,11 @@ import software.amazon.awssdk.services.codewhispererruntime.model.ListCodeAnalys import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisRequest import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisResponse -import software.aws.toolkits.core.utils.Waiters.waitUntil -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info +import software.amazon.q.core.utils.Waiters.waitUntil +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext @@ -52,7 +52,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererZipUploadManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage -import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread +import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererLanguage import java.nio.file.Path diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanEditorMouseMotionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanEditorMouseMotionListener.kt index e4730aa6af4..afe14e6d371 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanEditorMouseMotionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanEditorMouseMotionListener.kt @@ -18,8 +18,8 @@ import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.ui.awt.RelativePoint import com.intellij.ui.components.JBScrollPane import com.intellij.util.Alarm -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.context.CodeScanIssueDetailsDisplayType diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt index 2ae5ca13dd9..29f64a89c93 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt @@ -14,11 +14,11 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.isFile import kotlinx.coroutines.runBlocking -import software.aws.toolkits.core.utils.createTemporaryZipFile -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.putNextEntry +import software.amazon.q.core.utils.createTemporaryZipFile +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.putNextEntry import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.cannotFindBuildArtifacts import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.cannotFindFile import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.fileTooLarge diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/AmazonQCodeReviewGitUtils.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/AmazonQCodeReviewGitUtils.kt index c4cab3fef32..ec1def6c42c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/AmazonQCodeReviewGitUtils.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/AmazonQCodeReviewGitUtils.kt @@ -8,8 +8,8 @@ import com.intellij.execution.util.ExecUtil import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import java.io.File object AmazonQCodeReviewGitUtils { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt index 31444fec641..e2f4f05c203 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt @@ -25,10 +25,10 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.ui.JBColor import icons.AwsIcons import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException -import software.aws.toolkits.core.utils.convertMarkdownToHTML -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.ToolkitPlaces +import software.amazon.q.core.utils.convertMarkdownToHTML +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.ToolkitPlaces import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReferencePosition import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanHighlightingFilesPanel @@ -45,9 +45,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhi import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_ISSUE_TITLE_MAX_LENGTH import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled -import software.aws.toolkits.jetbrains.utils.applyPatch -import software.aws.toolkits.jetbrains.utils.notifyError -import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.jetbrains.utils.applyPatch +import software.amazon.q.jetbrains.utils.notifyError +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeFixAction import software.aws.toolkits.telemetry.Result diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt index a565da482c1..1981fe2d762 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt @@ -14,10 +14,10 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.isFile import kotlinx.coroutines.runBlocking -import software.aws.toolkits.core.utils.createTemporaryZipFile -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.putNextEntry +import software.amazon.q.core.utils.createTemporaryZipFile +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.putNextEntry import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadMetadata diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt index 5b1e3320f82..90bffafd7ff 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt @@ -22,7 +22,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CaretMovement import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_BRACKETS import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_QUOTES import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.message import java.util.Stack diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt index b79eea1f6aa..c30cc24b825 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt @@ -9,11 +9,11 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.actionSystem.Separator import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.actions.SsoLogoutAction -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.actions.SsoLogoutAction +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererConnectOnGithubAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererLearnMoreAction diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt index 8364bf593b1..cad6a7f3d49 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt @@ -4,9 +4,9 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isUserBuilderId diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt index 1330046dbe1..8209afbca8c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt @@ -7,8 +7,8 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import icons.AwsIcons -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.resources.message diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt index d4657aa06f1..b99885b8d74 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt @@ -9,9 +9,9 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionImports import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt index 3e8721e753b..9056dc3d4fa 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt @@ -4,8 +4,8 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.importadder import com.intellij.openapi.editor.RangeMarker -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt index 11fb8f2d61e..7b0eb0ec303 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt @@ -12,7 +12,7 @@ import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend @Service class CodeWhispererInlayManager { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt index 020b016e999..d11e16ab6f0 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt @@ -10,7 +10,7 @@ import com.intellij.openapi.editor.Inlay import com.intellij.openapi.util.Disposer import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend @Service class CodeWhispererInlayManagerNew { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt index 9b99abf9dad..af9059020ed 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt @@ -12,10 +12,10 @@ import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager -import software.aws.toolkits.jetbrains.utils.isQWebviewsAvailable +import software.amazon.q.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.telemetry.UiTelemetry class LearnCodeWhispererEditorProvider : FileEditorProvider, DumbAware { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt index b13c71652b6..d96c8006b87 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt @@ -15,7 +15,7 @@ import com.intellij.openapi.util.UserDataHolderBase import com.intellij.util.concurrency.annotations.RequiresEdt import kotlinx.coroutines.channels.Channel import software.amazon.awssdk.services.codewhispererruntime.model.IdeDiagnostic -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt index 85aa09e3cd8..61bf54a77df 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt @@ -33,7 +33,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_REF_NOTICE_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_BUTTON_TEXT_SIZE import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.GridBagLayout import java.awt.event.MouseAdapter diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 3e4f52bca6f..712c7e0fabb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -39,8 +39,8 @@ import com.intellij.ui.popup.AbstractPopup import com.intellij.ui.popup.PopupFactoryImpl import com.intellij.util.messages.Topic import com.intellij.util.ui.UIUtil -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionImports import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager @@ -67,7 +67,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_DIM_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.Point import java.awt.Rectangle diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt index 380354db40d..a8816165ac5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt @@ -37,8 +37,8 @@ import com.intellij.ui.popup.AbstractPopup import com.intellij.ui.popup.PopupFactoryImpl import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.ui.UIUtil -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionImports import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManagerNew @@ -66,7 +66,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_DIM_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.Point import java.awt.Rectangle diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt index 7d7c310c220..41c68d6dd50 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt @@ -47,14 +47,14 @@ import kotlinx.coroutines.withContext import migration.software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import org.eclipse.lsp4j.jsonrpc.ResponseErrorException import org.eclipse.lsp4j.jsonrpc.messages.Either -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.importadder.CodeWhispererImportAdder @@ -70,8 +70,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhi import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDocumentDiagnostics -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.awt.Dimension diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt index 73b9f59be6f..1b07e6bda3a 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt @@ -4,8 +4,8 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service import com.intellij.openapi.editor.Editor -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt index 69408ea4c5f..68d1187b6c8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt @@ -8,8 +8,8 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.util.messages.Topic -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import java.time.Duration import java.time.Instant import java.util.concurrent.atomic.AtomicBoolean diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt index 2c465f8be95..0ca05e5cba0 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt @@ -6,8 +6,8 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.components.service -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus.Companion.CODEWHISPERER_INVOCATION_STATE_CHANGED import java.time.Duration import java.time.Instant diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererLicenseInfoManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererLicenseInfoManager.kt index f105b94af38..88454ee6491 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererLicenseInfoManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererLicenseInfoManager.kt @@ -8,7 +8,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.openapi.components.Service import com.intellij.openapi.components.service -import software.aws.toolkits.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded import software.aws.toolkits.resources.message @Service diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt index b3398e170e8..bf97ecd95df 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt @@ -33,15 +33,15 @@ import org.eclipse.lsp4j.jsonrpc.JsonRpcException import org.eclipse.lsp4j.jsonrpc.messages.Either import software.amazon.awssdk.core.exception.SdkServiceException import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.coroutines.EDT -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.coroutines.EDT +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.GetConfigurationFromServerParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerConfigurations @@ -72,8 +72,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider -import software.aws.toolkits.jetbrains.utils.isInjectedText -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isInjectedText +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.net.URI diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt index f8604b89686..67f4e2249d9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt @@ -33,14 +33,14 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either import software.amazon.awssdk.core.exception.SdkServiceException import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext -import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionContext import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences @@ -69,8 +69,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider -import software.aws.toolkits.jetbrains.utils.isInjectedText -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isInjectedText +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.util.concurrent.TimeUnit diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererUserGroupSettings.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererUserGroupSettings.kt index 74e24d9d4e5..f0c455ec2ac 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererUserGroupSettings.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererUserGroupSettings.kt @@ -10,9 +10,9 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.core.utils.tryOrNull -import software.aws.toolkits.jetbrains.AwsPlugin -import software.aws.toolkits.jetbrains.AwsToolkit +import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.KClass @@ -20,7 +20,7 @@ import kotlin.reflect.KClass * Component controlling codewhisperer user group settings */ @Service -@State(name = "codewhispererUserGroupSettings", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)]) +@State(name = "codewhispererUserGroupSettings", storages = [Storage("amazonq.xml", roamingType = RoamingType.DISABLED)]) class CodeWhispererUserGroupSettings : PersistentStateComponent { private var version: String? = null diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt index 5dec4546a26..215939ee65d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt @@ -25,12 +25,12 @@ import com.intellij.util.concurrency.EdtExecutorService import com.intellij.util.execution.ParametersListUtil import kotlinx.coroutines.launch import org.eclipse.lsp4j.DidChangeConfigurationParams -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isInternalUser +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isInternalUser import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt index eb730d12ecf..16cd722e933 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.startup.StartupActivity import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.calculateIfIamIdentityCenterConnection import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager @@ -21,9 +21,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispere import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FEATURE_CONFIG_POLL_INTERVAL_IN_MS import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired -import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread // TODO: add logics to check if we want to remove recommendation suspension date when user open the IDE class CodeWhispererProjectStartupActivity : StartupActivity.DumbAware { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt index 2cd3197ebbc..cd78df9885c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt @@ -10,11 +10,11 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ex.ToolWindowManagerListener import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererActivationChangedListener diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt index 4810f16729e..d094f3655f5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt @@ -16,10 +16,10 @@ import com.intellij.openapi.wm.StatusBarWidget import com.intellij.openapi.wm.impl.status.EditorBasedWidget import com.intellij.ui.AnimatedIcon import com.intellij.util.Consumer -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.profiles.ProfileWatcher -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.QActionGroups.Q_SIGNED_OUT_ACTION_GROUP import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile @@ -32,9 +32,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.reconnectCodeWhisperer -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.event.MouseEvent import javax.swing.Icon diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt index 897df646a1f..b0e0d0da763 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt @@ -10,9 +10,9 @@ import com.intellij.openapi.project.Project import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.core.credentials.sono.isInternalUser +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.core.credentials.sono.isInternalUser import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.InlineCompletionStates import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LogInlineCompletionSessionResultsParams @@ -31,7 +31,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.DiagnosticDif import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDiagnosticDifferences import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDocumentDiagnostics import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl -import software.aws.toolkits.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.AwsSettings import software.aws.toolkits.telemetry.CodeFixAction import software.aws.toolkits.telemetry.CodewhispererCodeScanScope import software.aws.toolkits.telemetry.CodewhispererGettingStartedTask diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt index 2252c26b855..9026c83d3f5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt @@ -8,8 +8,8 @@ import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.InlineCompletionStates import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LogInlineCompletionSessionResultsParams diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceComponents.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceComponents.kt index f80a87053a2..1ec18ff4ff4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceComponents.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceComponents.kt @@ -8,8 +8,8 @@ import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.Project import com.intellij.ui.components.ActionLink -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacade.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacade.kt index 23a213de2b1..69532161752 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacade.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacade.kt @@ -8,8 +8,8 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.SimpleModificationTracker import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger import kotlin.reflect.KMutableProperty class CodeInsightsSettingsFacade : SimpleModificationTracker(), Disposable { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt index 7f3a40781d8..852b38ed7fe 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt @@ -20,8 +20,8 @@ import software.amazon.awssdk.http.nio.netty.ProxyConfiguration import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClientBuilder import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStreamingAsyncClientBuilder -import software.aws.toolkits.core.ToolkitClientCustomizer -import software.aws.toolkits.core.utils.tryOrNull +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.utils.tryOrNull import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt index 956ae670825..d4eedb68493 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt @@ -30,22 +30,22 @@ import software.amazon.awssdk.services.codewhispererruntime.model.IdeDiagnostic import software.amazon.awssdk.services.codewhispererruntime.model.OptOutPreference import software.amazon.awssdk.services.codewhispererruntime.model.Position import software.amazon.awssdk.services.codewhispererruntime.model.Range -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.ReauthSource -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.maybeReauthProviderIfNeeded -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.ActiveConnection -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.ActiveConnectionType -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet -import software.aws.toolkits.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ReauthSource +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.maybeReauthProviderIfNeeded +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection +import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnectionType +import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet +import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.learn.LearnCodeWhispererManager.Companion.taskTypeToFilename @@ -54,11 +54,11 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.isTelemetryEnabled import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CrossFile.NUMBER_OF_CHUNK_TO_FETCH import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CrossFile.NUMBER_OF_LINE_IN_CHUNK -import software.aws.toolkits.jetbrains.settings.AwsSettings -import software.aws.toolkits.jetbrains.utils.isQExpired -import software.aws.toolkits.jetbrains.utils.notifyError -import software.aws.toolkits.jetbrains.utils.notifyInfo -import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.notifyError +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererCompletionType import software.aws.toolkits.telemetry.CodewhispererGettingStartedTask diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererZipUploadManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererZipUploadManager.kt index f964092a8d5..f326a2a7a9b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererZipUploadManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererZipUploadManager.kt @@ -18,9 +18,9 @@ import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingExce import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.codewhispererruntime.model.UploadIntent import software.amazon.awssdk.utils.IoUtils -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.core.AwsClientManager +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.core.AwsClientManager import software.aws.toolkits.jetbrains.services.amazonq.RetryableOperation import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanServerException diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt index e41bf55b11b..0906b2f914b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt @@ -14,13 +14,13 @@ import io.mockk.mockkStatic import io.mockk.verify import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.credentials.ManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.ReauthSource -import software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded -import software.aws.toolkits.jetbrains.core.region.MockRegionProviderExtension +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.ManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ReauthSource +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension class CodeWhispererUtilTest { companion object { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt index 60c5da294d3..70d3ae49dbb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt @@ -10,13 +10,13 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.verify -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.credentials.ManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.ReauthSource -import software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded -import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.ManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ReauthSource +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule class CodeWhispererUtilTest : HeavyPlatformTestCase() { private val mockRegionProviderExtension = MockRegionProviderRule() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt index 2ca5847c2ca..ff0b63bf159 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt @@ -5,7 +5,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.DisposableRule import org.junit.Rule -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule open class CodeWhispererBasicTestBase { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt index d8745123336..56cc30ae7c0 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt @@ -42,24 +42,24 @@ import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnaly import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisResponse import software.amazon.awssdk.services.codewhispererruntime.paginators.GenerateCompletionsIterable import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ManagedSsoProfile -import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule -import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.logoutFromSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ManagedSsoProfile +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.logoutFromSsoConnection +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION import software.aws.toolkits.jetbrains.services.amazonq.FEATURE_EVALUATION_PRODUCT_NAME import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.metadata import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.sdkHttpResponse import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptorImpl import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.settings.AwsSettings -import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule class CodeWhispererClientAdaptorTest { val projectRule = JavaCodeInsightTestFixtureRule() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConfigurableTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConfigurableTest.kt index a26792056cf..eba0fe04349 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConfigurableTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConfigurableTest.kt @@ -9,7 +9,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.mockito.kotlin.doNothing import org.mockito.kotlin.whenever -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable import software.aws.toolkits.resources.message import javax.swing.JCheckBox diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConstantsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConstantsTest.kt index 4a64584af85..7e6a202df31 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConstantsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConstantsTest.kt @@ -7,8 +7,8 @@ import com.intellij.testFramework.ApplicationRule import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test -import software.aws.toolkits.jetbrains.core.help.HelpIdTranslator -import software.aws.toolkits.jetbrains.core.help.HelpIds +import software.amazon.q.jetbrains.core.help.HelpIdTranslator +import software.amazon.q.jetbrains.core.help.HelpIds class CodeWhispererConstantsTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt index 84332655d74..fd64e77fe4b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt @@ -18,7 +18,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestU import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererEditorUtilTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEndpointCustomizerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEndpointCustomizerTest.kt index fc081ca7a5d..77bef630dd8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEndpointCustomizerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEndpointCustomizerTest.kt @@ -28,8 +28,8 @@ import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStreamingAsyncClient import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStreamingAsyncClientBuilder import software.amazon.awssdk.services.codewhispererstreaming.model.GenerateAssistantResponseResponseHandler -import software.aws.toolkits.jetbrains.core.AwsClientManager -import software.aws.toolkits.jetbrains.core.MockClientManager.Companion.useRealImplementations +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.MockClientManager.Companion.useRealImplementations import java.util.concurrent.CountDownLatch class CodeWhispererEndpointCustomizerTest { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt index 8330cd75c1d..d005c5b41e2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt @@ -17,20 +17,20 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.ConnectionPinningManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL -import software.aws.toolkits.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken -import software.aws.toolkits.jetbrains.core.credentials.sso.DeviceGrantAccessTokenCacheKey -import software.aws.toolkits.jetbrains.core.credentials.sso.DiskCache -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.DeviceGrantAccessTokenCacheKey +import software.amazon.q.jetbrains.core.credentials.sso.DiskCache +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFeatureConfigServiceTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFeatureConfigServiceTest.kt index 89de36d53fb..78f1f2e19e6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFeatureConfigServiceTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFeatureConfigServiceTest.kt @@ -27,13 +27,13 @@ import software.amazon.awssdk.services.codewhispererruntime.model.ListAvailableC import software.amazon.awssdk.services.codewhispererruntime.model.ListFeatureEvaluationsRequest import software.amazon.awssdk.services.codewhispererruntime.model.ListFeatureEvaluationsResponse import software.amazon.awssdk.services.codewhispererruntime.paginators.ListAvailableCustomizationsIterable -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.region.AwsRegion -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt index 61ed40d41d8..387721b74c8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt @@ -18,7 +18,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConf import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJava import software.aws.toolkits.jetbrains.services.codewhisperer.util.DefaultCodeWhispererFileContextProvider import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule class CodeWhispererFileContextProviderTest { @JvmField diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt index 3d0f0728692..a3605871643 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt @@ -46,7 +46,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererVue import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererYaml -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import kotlin.reflect.full.createInstance import kotlin.reflect.full.primaryConstructor diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt index 8dd5b4be0ad..4d2429846cf 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt @@ -9,7 +9,7 @@ import com.fasterxml.jackson.module.kotlin.readValue import org.assertj.core.api.Assertions.assertThat import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererLicenseInfoManager -import software.aws.toolkits.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded import software.aws.toolkits.resources.message class CodeWhispererLicenseInfoManagerTest : CodeWhispererTestBase() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt index bb79a6608e1..fa9b0355986 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt @@ -10,7 +10,7 @@ import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.registerServiceInstance import com.intellij.testFramework.replaceService import com.intellij.util.xmlb.XmlSerializer -import migration.software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator +import migration.software.amazon.q.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import org.assertj.core.api.Assertions.assertThat import org.jdom.output.XMLOutputter import org.junit.Before @@ -29,18 +29,18 @@ import software.amazon.awssdk.services.codewhispererruntime.model.FeatureValue import software.amazon.awssdk.services.codewhispererruntime.model.ListAvailableCustomizationsRequest import software.amazon.awssdk.services.codewhispererruntime.model.ListAvailableCustomizationsResponse import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.DefaultToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerState -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_REGION -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.DefaultToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerState +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.FeatureContext import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile @@ -50,7 +50,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomizationState import software.aws.toolkits.jetbrains.services.codewhisperer.customization.DefaultCodeWhispererModelConfigurator -import software.aws.toolkits.jetbrains.utils.xmlElement +import software.amazon.q.jetbrains.utils.xmlElement import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.reflect.full.memberProperties diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt index 8ce8947cce6..df4c28892db 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt @@ -12,7 +12,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererReferenceManagerTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt index 1adcd2a795e..c5afe5892a9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt @@ -23,7 +23,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl +import software.amazon.q.jetbrains.core.ToolWindowHeadlessManagerImpl import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled @@ -31,7 +31,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.status.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceToolWindowFactory import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.xmlElement +import software.amazon.q.jetbrains.utils.xmlElement import kotlin.test.fail class CodeWhispererSettingsTest : CodeWhispererTestBase() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index 63a8bdc653e..d42c6b1a0e2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -15,13 +15,13 @@ import org.mockito.kotlin.doNothing import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever -import software.aws.toolkits.core.telemetry.MetricEvent -import software.aws.toolkits.core.telemetry.TelemetryBatcher -import software.aws.toolkits.core.telemetry.TelemetryPublisher +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Pause import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Resume import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.aws.toolkits.jetbrains.services.telemetry.NoOpPublisher +import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.settings.AwsSettings diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index 80f2bf42a55..705adf33f4d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -41,12 +41,12 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.kotlin.wheneverBlocking import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ManagedSsoProfile -import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule -import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.ManagedSsoProfile +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQServerInstanceFacade @@ -81,7 +81,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.settings.CodeWhispererConfigurationType import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.resources.message import java.util.concurrent.CompletableFuture import java.util.concurrent.Future diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt index fd6b9a0c49f..8817bf9e07f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt @@ -9,7 +9,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either import software.amazon.awssdk.awscore.DefaultAwsResponseMetadata import software.amazon.awssdk.awscore.util.AwsHeader import software.amazon.awssdk.http.SdkHttpResponse -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionImports import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt index f2564f69ad0..d8643cdbcca 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt @@ -19,13 +19,13 @@ import software.amazon.awssdk.services.codewhispererruntime.model.OptOutPreferen import software.amazon.awssdk.services.codewhispererruntime.model.Position import software.amazon.awssdk.services.codewhispererruntime.model.Range import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.core.utils.test.aStringWithLineCount -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_REGION -import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL -import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.core.utils.test.aStringWithLineCount +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getTelemetryOptOutPreference import software.aws.toolkits.jetbrains.services.codewhisperer.util.convertSeverity @@ -36,7 +36,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConne import software.aws.toolkits.jetbrains.services.codewhisperer.util.toCodeChunk import software.aws.toolkits.jetbrains.services.codewhisperer.util.truncateLineByLine import software.aws.toolkits.jetbrains.settings.AwsSettings -import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererCompletionType class CodeWhispererUtilTest { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt index acb685f8551..6dc55668365 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt @@ -11,8 +11,8 @@ import org.junit.jupiter.api.extension.RegisterExtension import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionEndpoint -import software.aws.toolkits.jetbrains.utils.rules.RegistryExtension -import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.rules.RegistryExtension +import software.amazon.q.jetbrains.utils.satisfiesKt @ExtendWith(ApplicationExtension::class) class QEndpointsTest { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt index 4b1f470b67e..b6bb1629ddb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt @@ -26,19 +26,19 @@ import software.amazon.awssdk.services.codewhispererruntime.model.ListAvailableP import software.amazon.awssdk.services.codewhispererruntime.model.Profile import software.amazon.awssdk.services.codewhispererruntime.paginators.ListAvailableProfilesIterable import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.aws.toolkits.core.region.AwsRegion -import software.aws.toolkits.jetbrains.core.MockClientManager -import software.aws.toolkits.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.jetbrains.core.MockResourceCacheRule -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ManagedSsoProfile -import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.logoutFromSsoConnection -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.MockResourceCacheRule +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ManagedSsoProfile +import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.logoutFromSsoConnection +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileResources import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileState @@ -46,8 +46,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIn import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.jetbrains.utils.satisfiesKt -import software.aws.toolkits.jetbrains.utils.xmlElement +import software.amazon.q.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.xmlElement import java.net.URI import java.util.function.Consumer import kotlin.test.fail diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt index af25dca9c3b..b2a8dc52985 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt @@ -26,15 +26,15 @@ import org.mockito.kotlin.whenever import software.amazon.awssdk.awscore.exception.AwsErrorDetails import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlRequest -import software.aws.toolkits.core.utils.WaiterTimeoutException +import software.amazon.q.core.utils.WaiterTimeoutException import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND -import software.aws.toolkits.jetbrains.utils.isInstanceOf -import software.aws.toolkits.jetbrains.utils.isInstanceOfSatisfying -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.isInstanceOf +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.FileInputStream import java.lang.management.ManagementFactory diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt index 2cfd9942235..ce628533791 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt @@ -23,16 +23,16 @@ import org.mockito.kotlin.verify import software.amazon.awssdk.awscore.exception.AwsErrorDetails import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlRequest -import software.aws.toolkits.core.utils.WaiterTimeoutException +import software.amazon.q.core.utils.WaiterTimeoutException import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage -import software.aws.toolkits.jetbrains.utils.isInstanceOf -import software.aws.toolkits.jetbrains.utils.isInstanceOfSatisfying -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.isInstanceOf +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.File import java.io.FileInputStream diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt index c3e72939bdd..cd68ead054e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt @@ -40,7 +40,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.amazon.awssdk.services.codewhispererruntime.model.Span import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisResponse import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeFixJobResponse -import software.aws.toolkits.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.MockClientManagerRule import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor @@ -48,7 +48,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererZipUploadManager -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.nio.file.Path import kotlin.test.assertNotNull diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt index 7fb99b4413b..f600b345d0f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt @@ -15,9 +15,9 @@ import org.mockito.kotlin.stub import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getNormalizedRelativePath -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.addFileToModule -import software.aws.toolkits.jetbrains.utils.rules.addModule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.addFileToModule +import software.amazon.q.jetbrains.utils.rules.addModule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.BufferedInputStream import java.nio.file.Paths diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt index 881b0861a1d..92febd922bd 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt @@ -15,10 +15,10 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.stub import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.sessionconfig.CodeTestSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.rules.addFileToModule -import software.aws.toolkits.jetbrains.utils.rules.addModule +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.addFileToModule +import software.amazon.q.jetbrains.utils.rules.addModule import java.io.BufferedInputStream import java.util.zip.ZipInputStream diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt index 6051a7789f5..30bd57bd6c2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt @@ -11,7 +11,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererFallbackImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererFallbackImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJavaImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJavaImportAdderTest.kt index 19ff820b4b3..1088a7b3df3 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJavaImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJavaImportAdderTest.kt @@ -14,7 +14,7 @@ import com.intellij.testFramework.runInEdtAndGet import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule class CodeWhispererJavaImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererJavaImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt index 7964948b555..7d03bd0a39e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt @@ -15,7 +15,7 @@ import com.jetbrains.python.psi.PyImportStatement import com.jetbrains.python.psi.PyImportStatementBase import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererPythonImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererPythonImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderTestBase.kt index 1393efdbbe2..4dc51ac08e9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderTestBase.kt @@ -12,7 +12,7 @@ import com.intellij.testFramework.runInEdtAndGet import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.junit.Rule -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule import kotlin.test.fail open class CodeWhispererImportAdderTestBase( diff --git a/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts b/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts index 8c88ac4128d..263483a63ee 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts +++ b/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts @@ -12,13 +12,13 @@ intellijToolkit { } dependencies { - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core-q")) compileOnly(project(":plugin-amazonq:codewhisperer:jetbrains-community")) compileOnly(project(":plugin-amazonq:shared:jetbrains-ultimate")) - compileOnly(project(":plugin-core:jetbrains-ultimate")) - testCompileOnly(project(":plugin-core:jetbrains-ultimate")) + compileOnly(project(":plugin-core-q:jetbrains-ultimate")) + testCompileOnly(project(":plugin-core-q:jetbrains-ultimate")) testImplementation(testFixtures(project(":plugin-amazonq:codewhisperer:jetbrains-community"))) testImplementation(project(path = ":plugin-toolkit:jetbrains-ultimate", configuration = "testArtifacts")) diff --git a/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt index a483812930e..be815a3f282 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt @@ -13,7 +13,7 @@ import com.intellij.testFramework.runInEdtAndWait import compat.com.intellij.lang.javascript.JavascriptLanguage import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.aws.toolkits.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule import kotlin.test.fail class CodeWhispererJSImportAdderTest : CodeWhispererImportAdderTestBase( diff --git a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts index e75036713b6..0bed9291951 100644 --- a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts @@ -17,16 +17,16 @@ dependencies { platformDependency(Coordinates(groupId = "com.jetbrains.intellij.rd", artifactId = "rd-platform")) } - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core-q")) - compileOnlyApi(project(":plugin-core:jetbrains-community")) + compileOnlyApi(project(":plugin-core-q:jetbrains-community")) // CodeWhispererTelemetryService uses a CircularFifoQueue implementation(libs.commons.collections) implementation(libs.nimbus.jose.jwt) api(libs.lsp4j) - testFixturesApi(testFixtures(project(":plugin-core:jetbrains-community"))) + testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-community"))) } // hack because our test structure currently doesn't make complete sense diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt index f3d9dafc8e7..e64b0beaffa 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt @@ -11,14 +11,14 @@ import com.intellij.openapi.project.Project import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient import software.amazon.awssdk.services.codewhispererruntime.model.FeatureValue -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQExpired @Service class CodeWhispererFeatureConfigService { private val featureConfigs = mutableMapOf() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt index 516047bf209..d2496ef429b 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt @@ -10,11 +10,11 @@ import com.intellij.openapi.util.SystemInfo import software.amazon.awssdk.services.codewhispererruntime.model.IdeCategory import software.amazon.awssdk.services.codewhispererruntime.model.OperatingSystem import software.amazon.awssdk.services.codewhispererruntime.model.UserContext -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.services.telemetry.ClientMetadata fun calculateIfIamIdentityCenterConnection(project: Project, calculationTask: (connection: ToolkitConnection) -> T): T? = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClient.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClient.kt index f4f31f715c6..751ae1f1310 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClient.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClient.kt @@ -14,8 +14,8 @@ import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveResponseHandler import software.amazon.awssdk.services.codewhispererstreaming.model.ThrottlingException import software.amazon.awssdk.services.codewhispererstreaming.model.ValidationException -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.services.amazonq.RetryableOperation import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import java.time.Instant diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index 981b4152865..b8990c3f153 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -20,7 +20,7 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFileManager -import migration.software.aws.toolkits.jetbrains.settings.AwsSettings +import migration.software.amazon.q.jetbrains.settings.AwsSettings import org.eclipse.lsp4j.ApplyWorkspaceEditParams import org.eclipse.lsp4j.ApplyWorkspaceEditResponse import org.eclipse.lsp4j.ConfigurationParams @@ -38,12 +38,12 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseError import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode import org.slf4j.event.Level import software.amazon.awssdk.utils.UserHomeDirectoryUtils -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LSPAny @@ -67,10 +67,10 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.TelemetryParsingUtil import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.applyExtensionFilter import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.getCleanedContent -import software.aws.toolkits.jetbrains.utils.notify +import software.amazon.q.jetbrains.utils.getCleanedContent +import software.amazon.q.jetbrains.utils.notify import software.aws.toolkits.resources.message import java.io.File import java.net.URLDecoder diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 9d95df96998..95e0e6d496f 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -61,12 +61,12 @@ import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage import org.eclipse.lsp4j.launch.LSPLauncher import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.core.utils.writeText -import software.aws.toolkits.jetbrains.core.coroutines.ioDispatcher +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.core.utils.writeText +import software.amazon.q.jetbrains.core.coroutines.ioDispatcher import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService @@ -79,7 +79,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolder import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints -import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata +import software.amazon.q.jetbrains.services.telemetry.ClientMetadata import software.aws.toolkits.jetbrains.settings.LspSettings import java.io.IOException import java.io.OutputStreamWriter diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt index 938953d2175..f82956122f0 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt @@ -12,14 +12,14 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.SystemInfo import com.intellij.util.system.CpuArch import com.intellij.util.text.nullize -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.aws.toolkits.jetbrains.settings.LspSettings -import software.aws.toolkits.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.Telemetry import java.nio.file.Files diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt index ff82c544307..2d8a32aea12 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt @@ -12,8 +12,8 @@ import org.apache.http.impl.client.HttpClientBuilder import org.apache.http.impl.client.SystemDefaultCredentialsProvider import org.apache.http.impl.conn.SystemDefaultRoutePlanner import org.jetbrains.annotations.TestOnly -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import java.net.URI import java.security.KeyStore import java.security.cert.CertPathBuilder diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt index f8c73ff8d97..d19536cf92a 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt @@ -10,15 +10,15 @@ import com.intellij.util.io.createDirectories import com.intellij.util.text.SemVer import kotlinx.coroutines.CancellationException import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.core.utils.deleteIfExists -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.saveFileFromUrl +import software.amazon.q.core.utils.deleteIfExists +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.saveFileFromUrl import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl -import software.aws.toolkits.resources.AwsCoreBundle +import software.amazon.q.resources.AwsCoreBundle import software.aws.toolkits.telemetry.LanguageServerSetupStage import software.aws.toolkits.telemetry.Telemetry import java.nio.file.Files diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt index d7970612854..b8f27e357c4 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt @@ -13,12 +13,12 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.AwsPlugin -import software.aws.toolkits.jetbrains.AwsToolkit +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.aws.toolkits.telemetry.LanguageServerSetupStage import software.aws.toolkits.telemetry.MetricResult diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtils.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtils.kt index a6846c905a6..a025b7179e3 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtils.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtils.kt @@ -8,10 +8,10 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.util.io.DigestUtil import com.intellij.util.system.CpuArch import org.apache.commons.io.FileUtils -import software.aws.toolkits.core.utils.ZIP_PROPERTY_POSIX -import software.aws.toolkits.core.utils.createParentDirectories -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.hasPosixFilePermissions +import software.amazon.q.core.utils.ZIP_PROPERTY_POSIX +import software.amazon.q.core.utils.createParentDirectories +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.hasPosixFilePermissions import java.io.FileNotFoundException import java.net.URI import java.nio.file.FileSystems diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcher.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcher.kt index b6c3a6cf62c..1b38bbb884d 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcher.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcher.kt @@ -9,16 +9,16 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.openapi.util.registry.Registry import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.core.utils.deleteIfExists -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.exists -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.info -import software.aws.toolkits.core.utils.readText -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.getETagFromUrl -import software.aws.toolkits.jetbrains.core.getTextFromUrl -import software.aws.toolkits.jetbrains.core.saveFileFromUrl +import software.amazon.q.core.utils.deleteIfExists +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.readText +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.getETagFromUrl +import software.amazon.q.jetbrains.core.getTextFromUrl +import software.amazon.q.jetbrains.core.saveFileFromUrl import java.nio.file.Path class ManifestFetcher { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt index bb420a4e034..787659d80f8 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt @@ -12,15 +12,15 @@ import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.UpdateConfigurationParams @@ -31,8 +31,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credential import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import java.util.concurrent.CompletableFuture import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt index 0fbb1553a21..282c3b79110 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt @@ -13,12 +13,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.eclipse.lsp4j.ProgressParams import org.eclipse.lsp4j.jsonrpc.ResponseErrorException -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.tryOrNull -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ProgressNotificationUtils.getObject import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LSPAny diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/ExtendedClientMetadata.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/ExtendedClientMetadata.kt index cabb37492ad..7b7ef59c338 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/ExtendedClientMetadata.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/ExtendedClientMetadata.kt @@ -4,7 +4,7 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata +import software.amazon.q.jetbrains.services.telemetry.ClientMetadata data class ExtendedClientMetadata( val aws: AwsMetadata, diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/ConnectionMetadata.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/ConnectionMetadata.kt index 3f180085694..4651ab969df 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/ConnectionMetadata.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/ConnectionMetadata.kt @@ -3,8 +3,8 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspConstants data class ConnectionMetadata( diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt index 2d6494f3dd6..638abd1e971 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt @@ -33,9 +33,9 @@ import org.eclipse.lsp4j.TextDocumentContentChangeEvent import org.eclipse.lsp4j.TextDocumentIdentifier import org.eclipse.lsp4j.TextDocumentItem import org.eclipse.lsp4j.VersionedTextDocumentIdentifier -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.tryOrNull -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ACTIVE_EDITOR_CHANGED_NOTIFICATION diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt index dd35c32e719..f026b957072 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt @@ -18,8 +18,8 @@ import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range import org.eclipse.lsp4j.TextEdit import org.eclipse.lsp4j.WorkspaceEdit -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CursorPosition import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CursorRange import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CursorState diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt index fb732109eb2..c5947c57af5 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt @@ -5,8 +5,8 @@ package software.aws.toolkits.jetbrains.services.amazonq.profile import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.openapi.util.registry.Registry -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn data class QRegionEndpoint(val region: String, val endpoint: String) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QProfileResources.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QProfileResources.kt index 9f954de1c4e..b054bc29371 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QProfileResources.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QProfileResources.kt @@ -5,13 +5,13 @@ package software.aws.toolkits.jetbrains.services.amazonq.profile import software.amazon.awssdk.awscore.exception.AwsServiceException import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient -import software.aws.toolkits.core.ClientConnectionSettings -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.AwsClientManager -import software.aws.toolkits.jetbrains.core.Resource -import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.Resource +import software.amazon.q.jetbrains.core.region.AwsRegionProvider import java.time.Duration /** diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfile.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfile.kt index e8181d75228..302ca31ec5e 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfile.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfile.kt @@ -3,7 +3,7 @@ package software.aws.toolkits.jetbrains.services.amazonq.profile import software.amazon.awssdk.arns.Arn -import software.aws.toolkits.core.utils.tryOrNull +import software.amazon.q.core.utils.tryOrNull data class QRegionProfile( var profileName: String = "", diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt index c0921f3362f..cccaf9dd3ba 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt @@ -14,14 +14,14 @@ import com.intellij.ui.dsl.builder.BottomGap import com.intellij.ui.dsl.builder.bindItem import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.builder.toNullableProperty -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.help.HelpIds -import software.aws.toolkits.jetbrains.ui.AsyncComboBox -import software.aws.toolkits.jetbrains.utils.ui.selected +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.help.HelpIds +import software.amazon.q.jetbrains.ui.AsyncComboBox +import software.amazon.q.jetbrains.utils.ui.selected import software.aws.toolkits.resources.AmazonQBundle.message -import software.aws.toolkits.resources.AwsCoreBundle +import software.amazon.q.resources.AwsCoreBundle import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry import javax.swing.JComponent diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt index 3729a4e0fc3..6d42b3d370a 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt @@ -17,20 +17,20 @@ import com.intellij.util.xmlb.annotations.MapAnnotation import com.intellij.util.xmlb.annotations.Property import software.amazon.awssdk.core.SdkClient import software.amazon.awssdk.services.codewhispererruntime.model.AccessDeniedException -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.AwsClientManager -import software.aws.toolkits.jetbrains.core.AwsResourceCache -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider -import software.aws.toolkits.jetbrains.utils.notifyInfo +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.AwsResourceCache +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.AmazonQBundle.message import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry @@ -39,7 +39,7 @@ import java.util.Collections import kotlin.reflect.KClass @Service(Service.Level.APP) -@State(name = "qProfileStates", storages = [Storage("aws.xml")]) +@State(name = "qProfileStates", storages = [Storage("amazonq.xml")]) class QRegionProfileManager : PersistentStateComponent, Disposable { // Map to store connectionId to its active profile diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/FeatureDevSessionContext.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/FeatureDevSessionContext.kt index 974ac171b6a..bfdd0a771b0 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/FeatureDevSessionContext.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/FeatureDevSessionContext.kt @@ -16,11 +16,11 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.io.FileUtils -import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext -import software.aws.toolkits.jetbrains.services.amazonq.QConstants.MAX_FILE_SIZE_BYTES -import software.aws.toolkits.jetbrains.utils.getWorkspaceDevFile -import software.aws.toolkits.jetbrains.utils.isWorkspaceDevFile -import software.aws.toolkits.resources.AwsCoreBundle +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.jetbrains.services.amazonq.QConstants.MAX_FILE_SIZE_BYTES +import software.amazon.q.jetbrains.utils.getWorkspaceDevFile +import software.amazon.q.jetbrains.utils.isWorkspaceDevFile +import software.amazon.q.resources.AwsCoreBundle import software.aws.toolkits.telemetry.AmazonqTelemetry import java.io.File import java.io.FileInputStream diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/Workspace.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/Workspace.kt index 2cf503aa374..5f45d4139ec 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/Workspace.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/Workspace.kt @@ -8,7 +8,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.changes.ChangeListManager import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile -import software.aws.toolkits.jetbrains.services.telemetry.ALLOWED_CODE_EXTENSIONS +import software.amazon.q.jetbrains.services.telemetry.ALLOWED_CODE_EXTENSIONS fun findWorkspaceContentRoots(project: Project): Set { val contentRoots = mutableSetOf() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformSharedUtils.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformSharedUtils.kt index c1596603f5b..5472d58dc5c 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformSharedUtils.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformSharedUtils.kt @@ -5,10 +5,10 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.utils import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.lazyIsUnauthedBearerConnection -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.lazyIsUnauthedBearerConnection +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import java.time.Instant fun calculateTotalLatency(startTime: Instant, endTime: Instant) = (endTime.toEpochMilli() - startTime.toEpochMilli()).toInt() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt index 10568619c64..b75b0c9c2a4 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt @@ -11,12 +11,12 @@ import com.intellij.openapi.project.DumbAware import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import org.eclipse.lsp4j.ExecuteCommandParams -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.isSono +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService class ManageSubscription : AnAction(), DumbAware { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt index bf0f27630be..47f24aeaccf 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt @@ -23,12 +23,12 @@ import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.builder.selected import com.intellij.ui.dsl.builder.toNullableProperty import software.amazon.awssdk.arns.Arn -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.tryOrNull +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.ui.AsyncComboBox -import software.aws.toolkits.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.ui.AsyncComboBox +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.message import javax.swing.JComponent import javax.swing.JList diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt index e968aaab481..0d7de51729c 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt @@ -19,17 +19,17 @@ import com.intellij.util.xmlb.annotations.MapAnnotation import com.intellij.util.xmlb.annotations.Property import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.calculateIfIamIdentityCenterConnection import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIntent import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.jetbrains.utils.notifyInfo -import software.aws.toolkits.jetbrains.utils.notifyWarn -import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.notifyWarn +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean @@ -65,7 +65,7 @@ private fun notifyNewCustomization(project: Project) { } @Service(Service.Level.APP) -@State(name = "codewhispererCustomizationStates", storages = [Storage("aws.xml")]) +@State(name = "codewhispererCustomizationStates", storages = [Storage("amazonq.xml")]) class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, PersistentStateComponent, Disposable { // TODO: refactor and clean these states, probably not need all the follwing and it's hard to maintain // Map to store connectionId to its active customization diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/QTelemetryUtils.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/QTelemetryUtils.kt index 0fc05c1f503..57f91706045 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/QTelemetryUtils.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/QTelemetryUtils.kt @@ -4,9 +4,9 @@ package software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection fun getStartUrl(project: Project): String? { val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection? diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/CodeWhispererSettings.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/CodeWhispererSettings.kt index 0fc7e53f5ba..837cc3f4225 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/CodeWhispererSettings.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/CodeWhispererSettings.kt @@ -11,11 +11,11 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.util.xmlb.annotations.Property -import software.aws.toolkits.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.AmazonQBundle @Service -@State(name = "codewhispererSettings", storages = [Storage("aws.xml")]) +@State(name = "codewhispererSettings", storages = [Storage("amazonq.xml")]) class CodeWhispererSettings : PersistentStateComponent { private val state = CodeWhispererConfiguration() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt index bc199de4917..a672597367d 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt @@ -13,7 +13,7 @@ import com.intellij.openapi.components.service import com.intellij.util.text.nullize @Service -@State(name = "lspSettings", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)]) +@State(name = "lspSettings", storages = [Storage("amazonq.xml", roamingType = RoamingType.DISABLED)]) class LspSettings : PersistentStateComponent { private var state = LspConfiguration() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/MeetQSettings.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/MeetQSettings.kt index ef452d10f68..57dd811bf7e 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/MeetQSettings.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/MeetQSettings.kt @@ -11,7 +11,7 @@ import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service @Service -@State(name = "meetQPage", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)]) +@State(name = "meetQPage", storages = [Storage("amazonq.xml", roamingType = RoamingType.DISABLED)]) class MeetQSettings : PersistentStateComponent { private var state = MeetQSettingsConfiguration() override fun getState(): MeetQSettingsConfiguration? = state diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeWhispererFeedbackDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeWhispererFeedbackDialog.kt index 1e0b9a23cc8..2b41cbddb20 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeWhispererFeedbackDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeWhispererFeedbackDialog.kt @@ -4,8 +4,10 @@ package software.aws.toolkits.jetbrains.ui.feedback import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.core.help.HelpIds -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.core.help.HelpIds +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.ui.feedback.FEEDBACK_SOURCE +import software.amazon.q.jetbrains.ui.feedback.FeedbackDialog import software.aws.toolkits.resources.message class CodeWhispererFeedbackDialog(project: Project) : FeedbackDialog(project) { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/FeatureDevFeedbackDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/FeatureDevFeedbackDialog.kt index d625ee64234..5807694d0d1 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/FeatureDevFeedbackDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/FeatureDevFeedbackDialog.kt @@ -4,7 +4,8 @@ package software.aws.toolkits.jetbrains.ui.feedback import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.ui.feedback.FeedbackDialog import software.aws.toolkits.resources.message class FeatureDevFeedbackDialog(project: Project) : FeedbackDialog(project) { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/TestGenFeedbackDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/TestGenFeedbackDialog.kt index f4cd6ab7fdd..308d4801be0 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/TestGenFeedbackDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/TestGenFeedbackDialog.kt @@ -4,7 +4,8 @@ package software.aws.toolkits.jetbrains.ui.feedback import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.ui.feedback.FeedbackDialog import software.aws.toolkits.resources.message class TestGenFeedbackDialog( diff --git a/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt index a5055b4077f..2cb7160654e 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt @@ -24,13 +24,13 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.credentials.ToolkitBearerTokenProvider -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager @@ -38,8 +38,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerC import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import java.time.Instant import java.util.concurrent.CompletableFuture diff --git a/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt index b1946f9e556..e81b4823ff0 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt @@ -21,13 +21,13 @@ import io.mockk.verify import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage -import software.aws.toolkits.core.TokenConnectionSettings -import software.aws.toolkits.core.credentials.ToolkitBearerTokenProvider -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager @@ -35,8 +35,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerC import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload -import software.aws.toolkits.jetbrains.utils.isQConnected -import software.aws.toolkits.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import java.time.Instant import java.util.concurrent.CompletableFuture diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt index 09c804abbf7..a66da4a458c 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt @@ -19,7 +19,7 @@ import io.mockk.mockk import io.mockk.mockkObject import io.mockk.slot import io.mockk.verify -import migration.software.aws.toolkits.jetbrains.settings.AwsSettings +import migration.software.amazon.q.jetbrains.settings.AwsSettings import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.entry import org.eclipse.lsp4j.ConfigurationItem @@ -27,12 +27,12 @@ import org.eclipse.lsp4j.ConfigurationParams import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit -import software.aws.toolkits.core.telemetry.DefaultMetricEvent -import software.aws.toolkits.core.telemetry.MetricEvent -import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.core.telemetry.DefaultMetricEvent +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcherTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcherTest.kt index 9ee22f23d52..c579b44f19d 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcherTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcherTest.kt @@ -19,8 +19,8 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Assume.assumeTrue import org.junit.Rule import org.junit.Test -import software.aws.toolkits.core.rules.EnvironmentVariableHelper -import software.aws.toolkits.core.utils.exists +import software.amazon.q.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.utils.exists import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import java.nio.file.Paths diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtilTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtilTest.kt index ae2c9cd765c..c39f62caaed 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtilTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtilTest.kt @@ -26,8 +26,8 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.extension.ExtendWith -import software.aws.toolkits.core.utils.outputStream -import software.aws.toolkits.core.utils.writeText +import software.amazon.q.core.utils.outputStream +import software.amazon.q.core.utils.writeText import java.math.BigInteger import java.net.URI import java.nio.file.Files diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtilsTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtilsTest.kt index 607b94f34bf..c8a86e443ae 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtilsTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtilsTest.kt @@ -8,12 +8,12 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir -import software.aws.toolkits.core.utils.ZIP_PROPERTY_POSIX -import software.aws.toolkits.core.utils.hasPosixFilePermissions -import software.aws.toolkits.core.utils.putNextEntry -import software.aws.toolkits.core.utils.test.assertPosixPermissions -import software.aws.toolkits.core.utils.writeText -import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.amazon.q.core.utils.ZIP_PROPERTY_POSIX +import software.amazon.q.core.utils.hasPosixFilePermissions +import software.amazon.q.core.utils.putNextEntry +import software.amazon.q.core.utils.test.assertPosixPermissions +import software.amazon.q.core.utils.writeText +import software.amazon.q.jetbrains.utils.satisfiesKt import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcherTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcherTest.kt index d343f69037c..8e13cfc3065 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcherTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcherTest.kt @@ -19,7 +19,7 @@ import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import software.aws.toolkits.jetbrains.core.getTextFromUrl +import software.amazon.q.jetbrains.core.getTextFromUrl import java.nio.file.Path import java.nio.file.Paths @@ -64,7 +64,7 @@ class ManifestFetcherTest { @Test fun `fetchManifestFromRemote should return null due to invalid manifestString`() { - mockkStatic("software.aws.toolkits.jetbrains.core.HttpUtilsKt") + mockkStatic("software.amazon.q.jetbrains.core.HttpUtilsKt") every { getTextFromUrl(any()) } returns "ManifestContent" assertThat(manifestFetcher.fetchManifestFromRemote()).isNull() @@ -73,7 +73,7 @@ class ManifestFetcherTest { @Test fun `fetchManifestFromRemote should return manifest and update manifest`() { val validManifest = Manifest(manifestSchemaVersion = "1.0") - mockkStatic("software.aws.toolkits.jetbrains.core.HttpUtilsKt") + mockkStatic("software.amazon.q.jetbrains.core.HttpUtilsKt") every { getTextFromUrl(any()) } returns "{ \"manifestSchemaVersion\": \"1.0\" }" @@ -83,7 +83,7 @@ class ManifestFetcherTest { @Test fun `fetchManifestFromRemote should return null if manifest is deprecated`() { - mockkStatic("software.aws.toolkits.jetbrains.core.HttpUtilsKt") + mockkStatic("software.amazon.q.jetbrains.core.HttpUtilsKt") every { getTextFromUrl(any()) } returns // language=JSON """ diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt index 8c8c4ddd1d1..0bfcc11447e 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt @@ -11,7 +11,7 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.AutoCloseableSoftAssertions import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestFactory -import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.satisfiesKt import java.util.stream.Stream import kotlin.streams.asStream import kotlin.test.Test diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt index bde4e2e989e..797638382c6 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt @@ -41,12 +41,12 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestName -import software.aws.toolkits.jetbrains.core.coroutines.EDT +import software.amazon.q.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil -import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.satisfiesKt import java.net.URI import java.nio.file.Path import java.util.concurrent.CompletableFuture diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/LspSettingsTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/LspSettingsTest.kt index 6b9d425ba3d..505aabbd771 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/LspSettingsTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/LspSettingsTest.kt @@ -8,7 +8,7 @@ import org.assertj.core.api.Assertions.assertThat import org.jdom.output.XMLOutputter import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import software.aws.toolkits.jetbrains.utils.xmlElement +import software.amazon.q.jetbrains.utils.xmlElement class LspSettingsTest { private lateinit var lspSettings: LspSettings diff --git a/plugins/amazonq/shared/jetbrains-ultimate/build.gradle.kts b/plugins/amazonq/shared/jetbrains-ultimate/build.gradle.kts index ee3df06cae0..2a685b265b6 100644 --- a/plugins/amazonq/shared/jetbrains-ultimate/build.gradle.kts +++ b/plugins/amazonq/shared/jetbrains-ultimate/build.gradle.kts @@ -21,8 +21,8 @@ dependencies { } } compileOnly(project(":plugin-amazonq:shared:jetbrains-community")) - compileOnly(project(":plugin-core:jetbrains-ultimate")) + compileOnly(project(":plugin-core-q:jetbrains-ultimate")) - testFixturesApi(testFixtures(project(":plugin-core:jetbrains-community"))) - testFixturesApi(testFixtures(project(":plugin-core:jetbrains-ultimate"))) + testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-community"))) + testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-ultimate"))) } diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 989e48ab717..7bcd7709264 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -2,9 +2,6 @@ - - - amazon.q Amazon Q com.intellij.jetbrains.client com.intellij.gateway - - - - - - - + + + + + @@ -103,6 +93,14 @@ + + + + + + + + @@ -110,13 +108,95 @@ + + + + + + + + + + + + + + +] + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - + diff --git a/plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt b/plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt index f03543a8a53..518fd36549f 100644 --- a/plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt +++ b/plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt @@ -25,7 +25,7 @@ class PluginCoreJvmBinaryCompatabilityTest { // )Lsoftware/aws/toolkits/jetbrains/core/credentials/AwsBearerTokenConnection; // loginSso(...) - val clazz = Class.forName("software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManagerKt") + val clazz = Class.forName("software.amazon.q.jetbrains.core.credentials.ToolkitAuthManagerKt") val method = clazz.getDeclaredMethod( "loginSso\$default", Class.forName("com.intellij.openapi.project.Project"), @@ -35,13 +35,13 @@ class PluginCoreJvmBinaryCompatabilityTest { Class.forName("kotlin.jvm.functions.Function1"), Class.forName("kotlin.jvm.functions.Function1"), Class.forName("kotlin.jvm.functions.Function0"), - Class.forName("software.aws.toolkits.jetbrains.core.credentials.ConnectionMetadata"), + Class.forName("software.amazon.q.jetbrains.core.credentials.ConnectionMetadata"), // can't request primitive type using reflection Integer.TYPE, Class.forName("java.lang.Object"), ) - assertThat(method.returnType).isEqualTo(Class.forName("software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection")) + assertThat(method.returnType).isEqualTo(Class.forName("software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection")) } @Test @@ -53,7 +53,7 @@ class PluginCoreJvmBinaryCompatabilityTest { // 21: invokestatic #96 // Method software/aws/toolkits/jetbrains/core/credentials/sono/SonoConstantsKt.getQ_SCOPES:()Ljava/util/List; // not sure why CODEWHISPERER_SCOPES is being used when Q_SCOPES is a superset - val clazz = Class.forName("software.aws.toolkits.jetbrains.core.credentials.sono.SonoConstantsKt") + val clazz = Class.forName("software.amazon.q.jetbrains.core.credentials.sono.SonoConstantsKt") // type erasure :/ assertThat(clazz.getMethod("getCODEWHISPERER_SCOPES").invoke(null)).isInstanceOf(List::class.java) diff --git a/plugins/core-q/build.gradle.kts b/plugins/core-q/build.gradle.kts new file mode 100644 index 00000000000..8fce752a655 --- /dev/null +++ b/plugins/core-q/build.gradle.kts @@ -0,0 +1,40 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id("toolkit-jvm-conventions") + id("toolkit-testing") + alias(libs.plugins.gradleup.shadow) +} + +dependencies { + implementation(project(":plugin-core-q:core-q")) + implementation(project(":plugin-core-q:jetbrains-community")) + implementation(project(":plugin-core-q:jetbrains-ultimate")) + implementation(project(":plugin-core-q:resources")) + implementation(project(":plugin-core-q:sdk-codegen")) + implementation(project(":plugin-core-q:webview")) + implementation(libs.slf4j.api) + implementation(libs.slf4j.jdk14) +} + +configurations { + configureEach { + // IDE provides netty + exclude("io.netty") + } + + // Make sure we exclude stuff we either A) ships with IDE, B) we don't use to cut down on size + runtimeClasspath { + exclude(group = "com.google.code.gson") + exclude(group = "org.jetbrains.kotlin") + exclude(group = "org.jetbrains.kotlinx") + } +} + +tasks.check { + val coreProject = project(":plugin-core-q").subprojects + coreProject.forEach { + dependsOn(":plugin-core-q:${it.name}:check") + } +} diff --git a/plugins/core-q/core-q/build.gradle.kts b/plugins/core-q/core-q/build.gradle.kts new file mode 100644 index 00000000000..e624c53504d --- /dev/null +++ b/plugins/core-q/core-q/build.gradle.kts @@ -0,0 +1,34 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id("java-library") + id("toolkit-kotlin-conventions") + id("toolkit-testing") + id("toolkit-integration-testing") +} + +dependencies { + compileOnlyApi(project(":plugin-core-q:resources")) + compileOnlyApi(project(":plugin-core-q:sdk-codegen")) + compileOnly(libs.kotlin.coroutines) + + api(libs.aws.cognitoidentity) + api(libs.aws.ecr) + api(libs.aws.ecs) + api(libs.aws.lambda) + api(libs.aws.s3) + api(libs.aws.sso) + api(libs.aws.ssooidc) + api(libs.aws.sts) + api(libs.bundles.jackson) + implementation(libs.commonmark) + testImplementation(libs.junit4) + + testRuntimeOnly(project(":plugin-core-q:resources")) + testRuntimeOnly(project(":plugin-core-q:sdk-codegen")) +} + +tasks.test { + useJUnitPlatform() +} diff --git a/plugins/core-q/core-q/detekt-baseline-integrationTest.xml b/plugins/core-q/core-q/detekt-baseline-integrationTest.xml new file mode 100644 index 00000000000..b9d892d26ee --- /dev/null +++ b/plugins/core-q/core-q/detekt-baseline-integrationTest.xml @@ -0,0 +1,7 @@ + + + + + NoNameShadowing:BucketUtilsTest.kt$BucketUtilsTest${ it.status(BucketVersioningStatus.ENABLED) } + + diff --git a/plugins/core-q/core-q/detekt-baseline-main.xml b/plugins/core-q/core-q/detekt-baseline-main.xml new file mode 100644 index 00000000000..8e5f932438b --- /dev/null +++ b/plugins/core-q/core-q/detekt-baseline-main.xml @@ -0,0 +1,11 @@ + + + + + UseCheckOrError:LambdaRuntime.kt$LambdaRuntime$throw IllegalStateException("LambdaRuntime has no runtime or override string") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("$serviceId in ${region.partitionId} lacks a partitionEndpoint") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("$serviceId is not global in ${region.partitionId}") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("Partition data is missing for ${region.partitionId}") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("Unknown service $serviceId in ${region.partitionId}") + + diff --git a/plugins/core-q/core-q/detekt-baseline-test.xml b/plugins/core-q/core-q/detekt-baseline-test.xml new file mode 100644 index 00000000000..538e8f4c56a --- /dev/null +++ b/plugins/core-q/core-q/detekt-baseline-test.xml @@ -0,0 +1,9 @@ + + + + + UnsafeCallOnNullableType:EnvironmentVariableHelper.kt$EnvironmentVariableHelper$getField(System.getenv().javaClass, System.getenv(), "m")!! + UnsafeCallOnNullableType:PartitionParserTest.kt$PartitionParserTest$PartitionParser.parse(BundledResources.ENDPOINTS_FILE)!! + UnsafeCallOnNullableType:ZipUtilsTest.kt$ZipUtilsTest$zipFile!! + + diff --git a/plugins/core-q/core-q/detekt-baseline.xml b/plugins/core-q/core-q/detekt-baseline.xml new file mode 100644 index 00000000000..8e5f932438b --- /dev/null +++ b/plugins/core-q/core-q/detekt-baseline.xml @@ -0,0 +1,11 @@ + + + + + UseCheckOrError:LambdaRuntime.kt$LambdaRuntime$throw IllegalStateException("LambdaRuntime has no runtime or override string") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("$serviceId in ${region.partitionId} lacks a partitionEndpoint") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("$serviceId is not global in ${region.partitionId}") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("Partition data is missing for ${region.partitionId}") + UseCheckOrError:ToolkitRegionProvider.kt$ToolkitRegionProvider$throw IllegalStateException("Unknown service $serviceId in ${region.partitionId}") + + diff --git a/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt b/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt new file mode 100644 index 00000000000..5ad1ac43143 --- /dev/null +++ b/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt @@ -0,0 +1,71 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.s3 + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import software.amazon.awssdk.core.sync.RequestBody +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.BucketVersioningStatus +import software.amazon.awssdk.services.s3.model.PutObjectRequest +import software.amazon.q.core.s3.deleteBucketAndContents +import software.amazon.q.core.s3.regionForBucket +import software.aws.toolkits.core.rules.S3TemporaryBucketRule + +class BucketUtilsTest { + private val usEast1Client = S3Client.builder().region(Region.US_EAST_1).build() + private val euWest2Client = S3Client.builder().region(Region.EU_WEST_2).build() + + @Rule + @JvmField + val usEast1TempBucket = S3TemporaryBucketRule(usEast1Client) + + @Rule + @JvmField + val euWest2TempBucket = S3TemporaryBucketRule(euWest2Client) + + @Test + fun deleteAnEmptyBucket() { + createAndDeleteBucket {} + } + + @Test + fun deleteABucketWithObjects() { + createAndDeleteBucket { bucket -> + usEast1Client.putObject(PutObjectRequest.builder().bucket(bucket).key("hello").build(), RequestBody.fromString("")) + } + } + + @Test + fun deleteABucketWithVersionedObjects() { + createAndDeleteBucket { bucket -> + usEast1Client.putBucketVersioning { it.bucket(bucket).versioningConfiguration { it.status(BucketVersioningStatus.ENABLED) } } + usEast1Client.putObject(PutObjectRequest.builder().bucket(bucket).key("hello").build(), RequestBody.fromString("")) + usEast1Client.putObject(PutObjectRequest.builder().bucket(bucket).key("hello").build(), RequestBody.fromString("")) + } + } + + @Test + fun canGetRegionBucketWithRegionNotSameAsClient() { + val bucket = euWest2TempBucket.createBucket() + + assertThat(usEast1Client.regionForBucket(bucket)).isEqualTo("eu-west-2") + } + + @Test + fun canGetRegionInSameRegionAsClient() { + val bucket = usEast1TempBucket.createBucket() + + assertThat(usEast1Client.regionForBucket(bucket)).isEqualTo("us-east-1") + } + + private fun createAndDeleteBucket(populateBucket: (String) -> Unit) { + val bucket = usEast1TempBucket.createBucket() + populateBucket(bucket) + usEast1Client.deleteBucketAndContents(bucket) + assertThat(usEast1Client.listBuckets().buckets().map { it.name() }).doesNotContain(bucket) + } +} diff --git a/plugins/core-q/core-q/src/migration/software/amazon/q/core/ToolkitClientManager.kt b/plugins/core-q/core-q/src/migration/software/amazon/q/core/ToolkitClientManager.kt new file mode 100644 index 00000000000..707dddd9da0 --- /dev/null +++ b/plugins/core-q/core-q/src/migration/software/amazon/q/core/ToolkitClientManager.kt @@ -0,0 +1,270 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.core + +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner +import software.amazon.awssdk.awscore.AwsExecutionAttribute +import software.amazon.awssdk.awscore.AwsRequest +import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder +import software.amazon.awssdk.core.SdkClient +import software.amazon.awssdk.core.SdkRequest +import software.amazon.awssdk.core.client.builder.SdkSyncClientBuilder +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption +import software.amazon.awssdk.core.interceptor.Context +import software.amazon.awssdk.core.interceptor.ExecutionAttributes +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute +import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage +import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage.HEADER_USER_AGENT +import software.amazon.awssdk.core.retry.RetryMode +import software.amazon.awssdk.http.SdkHttpClient +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.clients.nullDefaultProfileFile +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.region.ToolkitRegionProvider +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import java.lang.reflect.Modifier +import java.net.URI +import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KClass + +/** + * An SPI for caching of AWS clients inside of a toolkit + */ +abstract class ToolkitClientManager { + data class AwsClientKey( + val providerId: String, + val region: AwsRegion, + val serviceClass: KClass, + ) + + private val cachedClients = ConcurrentHashMap() + + protected abstract fun userAgent(): String + + protected abstract fun sdkHttpClient(): SdkHttpClient + + inline fun getClient(credProvider: ToolkitCredentialsProvider, region: AwsRegion): T = + this.getClient(T::class, ConnectionSettings(credProvider, region)) + + inline fun getClient(connection: ClientConnectionSettings<*>): T = this.getClient(T::class, connection) + + fun getClient(sdkClass: KClass, connection: ClientConnectionSettings<*>): T { + val key = AwsClientKey( + providerId = connection.providerId, + region = connection.region, + serviceClass = sdkClass + ) + + val serviceId = key.serviceClass.java.getField("SERVICE_METADATA_ID").get(null) as String + if (serviceId !in GLOBAL_SERVICE_DENY_LIST && getRegionProvider().isServiceGlobal(connection.region, serviceId)) { + val globalRegion = getRegionProvider().getGlobalRegionForService(connection.region, serviceId) + @Suppress("UNCHECKED_CAST") + return cachedClients.computeIfAbsent(key.copy(region = globalRegion)) { + createNewClient(sdkClass, connection.withRegion(region = globalRegion)) + } as T + } + + @Suppress("UNCHECKED_CAST") + return cachedClients.computeIfAbsent(key) { createNewClient(sdkClass, connection) } as T + } + + private fun createNewClient(sdkClass: KClass, connection: ClientConnectionSettings<*>): T = when (connection) { + is ConnectionSettings -> constructAwsClient( + sdkClass = sdkClass, + credProvider = connection.credentials, + region = Region.of(connection.region.id), + ) + + is TokenConnectionSettings -> constructAwsClient( + sdkClass = sdkClass, + tokenProvider = connection.tokenProvider, + region = Region.of(connection.region.id), + ) + } + + /** + * Constructs a new low-level AWS client whose lifecycle is **NOT** managed centrally. Caller is responsible for shutting down the client + */ + inline fun createUnmanagedClient( + credProvider: AwsCredentialsProvider, + region: Region, + endpointOverride: String? = null, + clientCustomizer: ToolkitClientCustomizer? = null, + ): T = createUnmanagedClient(T::class, credProvider, region, endpointOverride, clientCustomizer) + + /** + * Constructs a new low-level AWS client whose lifecycle is **NOT** managed centrally. Caller is responsible for shutting down the client + */ + fun createUnmanagedClient( + sdkClass: KClass, + credProvider: AwsCredentialsProvider, + region: Region, + endpointOverride: String?, + clientCustomizer: ToolkitClientCustomizer? = null, + ): T = constructAwsClient(sdkClass, credProvider = credProvider, region = region, endpointOverride = endpointOverride, clientCustomizer = clientCustomizer) + + protected abstract fun getRegionProvider(): ToolkitRegionProvider + + /** + * Allow implementations to apply customizations to clients before they are built + */ + protected open fun globalClientCustomizer( + credentialProvider: AwsCredentialsProvider?, + tokenProvider: SdkTokenProvider?, + regionId: String, + builder: AwsClientBuilder<*, *>, + clientOverrideConfiguration: ClientOverrideConfiguration.Builder, + ) {} + + /** + * Calls [SdkAutoCloseable.close] on all managed clients and clears the cache + */ + protected fun shutdown() { + cachedClients.values.forEach { it.close() } + cachedClients.clear() + } + + protected fun invalidateSdks(providerId: String) { + val invalidClients = cachedClients.entries.filter { it.key.providerId == providerId }.toSet() + cachedClients.entries.removeAll(invalidClients) + invalidClients.forEach { it.value.close() } + } + + protected open fun constructAwsClient( + sdkClass: KClass, + credProvider: AwsCredentialsProvider? = null, + tokenProvider: SdkTokenProvider? = null, + region: Region, + endpointOverride: String? = null, + clientCustomizer: ToolkitClientCustomizer? = null, + ): T { + checkNotNull(credProvider ?: tokenProvider) { "Either a credential provider or a bearer token provider must be provided" } + + val builderMethod = sdkClass.java.methods.find { + it.name == "builder" && Modifier.isStatic(it.modifiers) && Modifier.isPublic(it.modifiers) + } ?: throw IllegalArgumentException("Expected service interface to have a public static `builder()` method.") + + val builder = builderMethod.invoke(null) as AwsDefaultClientBuilder<*, *> + + @Suppress("UNCHECKED_CAST") + return builder + .region(region) + .apply { + if (this is SdkSyncClientBuilder<*, *>) { + // async clients use CRT, and keeps trying to shut down our apache client even though it doesn't respect our client settings + // so only set this for sync clients + httpClient(sdkHttpClient()) + } + + val clientOverrideConfig = ClientOverrideConfiguration.builder() + + if (credProvider != null) { + credentialsProvider(credProvider) + } + + if (tokenProvider != null) { + val tokenMethod = builderMethod.returnType.methods.find { + it.name == "tokenProvider" && + it.parameterCount == 1 && + it.parameters[0].type.name == "software.amazon.awssdk.auth.token.credentials.SdkTokenProvider" + } + + if (tokenMethod == null) { + LOG.warn { "Ignoring bearer provider parameter for ${sdkClass.qualifiedName} since it's not a supported client attribute" } + } else { + tokenMethod.invoke(this, tokenProvider) + clientOverrideConfig.nullDefaultProfileFile() + // TODO: why do we need this? + clientOverrideConfig.putAdvancedOption(SdkAdvancedClientOption.SIGNER, BearerTokenSigner()) + } + } + + clientOverrideConfig.addExecutionInterceptor(object : ExecutionInterceptor { + override fun modifyRequest( + context: Context.ModifyRequest, + executionAttributes: ExecutionAttributes, + ): SdkRequest { + val request = context.request() + if (request !is AwsRequest) { + return request + } + + val clientType = executionAttributes.getAttribute(AwsExecutionAttribute.CLIENT_TYPE) + val sdkClient = executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_CLIENT) + val serviceClientConfiguration = sdkClient.serviceClientConfiguration() + val retryMode = serviceClientConfiguration.overrideConfiguration().retryMode().orElse(RetryMode.defaultRetryMode()) + val toolkitUserAgent = userAgent() + + val requestUserAgent = ApplyUserAgentStage.resolveClientUserAgent( + toolkitUserAgent, + null, + clientType, + null, + null, + retryMode.toString().lowercase() + ) + + val overrideConfiguration = request.overrideConfiguration() + .map { config -> + config.toBuilder() + .putHeader(HEADER_USER_AGENT, requestUserAgent) + .build() + } + .orElseGet { + AwsRequestOverrideConfiguration.builder() + .putHeader(HEADER_USER_AGENT, requestUserAgent) + .build() + } + + return request.toBuilder() + .overrideConfiguration(overrideConfiguration) + .build() + } + }) + + clientOverrideConfig.let { configuration -> + configuration.retryStrategy(RetryMode.STANDARD) + } + + endpointOverride?.let { + endpointOverride(URI.create(it)) + } + + globalClientCustomizer(credProvider, tokenProvider, region.id(), this, clientOverrideConfig) + + clientCustomizer?.let { + it.customize(credProvider, tokenProvider, region.id(), this, clientOverrideConfig) + } + + // TODO: ban overrideConfiguration outside of here + overrideConfiguration(clientOverrideConfig.build()) + } + .build() as T + } + + @TestOnly + fun cachedClients() = cachedClients + + companion object { + private val LOG = getLogger() + private val GLOBAL_SERVICE_DENY_LIST = setOf( + // sts is regionalized but does not identify as such in metadata + "sts" + ) + } +} diff --git a/plugins/core-q/core-q/src/migration/software/amazon/q/core/clients/SdkClientProvider.kt b/plugins/core-q/core-q/src/migration/software/amazon/q/core/clients/SdkClientProvider.kt new file mode 100644 index 00000000000..ed7f088cbde --- /dev/null +++ b/plugins/core-q/core-q/src/migration/software/amazon/q/core/clients/SdkClientProvider.kt @@ -0,0 +1,10 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.core.clients + +import software.amazon.awssdk.http.SdkHttpClient + +interface SdkClientProvider { + fun sharedSdkClient(): SdkHttpClient +} diff --git a/plugins/core-q/core-q/src/migration/software/amazon/q/core/region/ToolkitRegionProvider.kt b/plugins/core-q/core-q/src/migration/software/amazon/q/core/region/ToolkitRegionProvider.kt new file mode 100644 index 00000000000..24fd136aa93 --- /dev/null +++ b/plugins/core-q/core-q/src/migration/software/amazon/q/core/region/ToolkitRegionProvider.kt @@ -0,0 +1,88 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.core.region + +import software.amazon.q.core.region.AwsPartition +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.region.Service +import java.util.concurrent.ConcurrentHashMap + +/** + * An SPI to provide regions supported by this toolkit + */ +abstract class ToolkitRegionProvider { + protected data class PartitionData( + val description: String, + val services: Map, + val regions: Map, + ) + + protected abstract fun partitionData(): Map + + private val globalCache = ConcurrentHashMap, AwsRegion>() + + /** + * Returns a map of region ID([AwsRegion.id]) to [AwsRegion] + */ + fun allRegions(): Map = partitionData().flatMap { it.value.regions.asIterable() }.associate { it.key to it.value } + + /** + * Returns a map of region ID([AwsRegion.id]) to [AwsRegion], filtering by if the service is supported + */ + fun allRegionsForService(serviceId: String): Map = allRegions().filter { isServiceSupported(it.value, serviceId) } + + /** + * Returns a map of region ID([AwsRegion.id]) to [AwsRegion] for the specified partition + */ + fun regions(partitionId: String): Map = partitionData()[partitionId]?.regions + ?: throw IllegalArgumentException("Unknown partition $partitionId") + + /** + * Returns a map of partition ID([AwsPartition.id]) to [AwsPartition] + */ + fun partitions(): Map = partitionData().asSequence() + .associate { it.key to AwsPartition(it.key, it.value.description, it.value.regions.values) } + + /** + * Returns the default region to use based on the environment + */ + abstract fun defaultRegion(): AwsRegion + + /** + * Returns the default partition to use based on the environment + */ + abstract fun defaultPartition(): AwsPartition + + operator fun get(regionId: String): AwsRegion? = allRegions()[regionId] + + open fun isServiceGlobal(region: AwsRegion, serviceId: String): Boolean { + val partition = partitionData()[region.partitionId] ?: throw IllegalStateException("Partition data is missing for ${region.partitionId}") + return partition.services[serviceId]?.isGlobal == true + } + + fun getGlobalRegionForService(region: AwsRegion, serviceId: String): AwsRegion { + val cacheKey = region to serviceId + globalCache[cacheKey]?.let { + return it + } + + val partition = partitionData()[region.partitionId] ?: throw IllegalStateException("Partition data is missing for ${region.partitionId}") + val service = partition.services[serviceId] ?: throw IllegalStateException("Unknown service $serviceId in ${region.partitionId}") + if (!service.isGlobal) { + throw IllegalStateException("$serviceId is not global in ${region.partitionId}") + } + + // TODO: A few services lack partition endpoint like Shield and MTurk, how should that be handled? + val partitionEndpoint = service.partitionEndpoint ?: throw IllegalStateException("$serviceId in ${region.partitionId} lacks a partitionEndpoint") + return globalCache.computeIfAbsent(cacheKey) { + AwsRegion(partitionEndpoint, partitionEndpoint, region.partitionId) + } + } + + open fun isServiceSupported(region: AwsRegion, serviceName: String): Boolean { + val currentPartition = partitionData()[region.partitionId] ?: return false + val service = currentPartition.services[serviceName] ?: return false + return service.isGlobal || service.endpoints.containsKey(region.id) + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/ConnectionSettings.kt b/plugins/core-q/core-q/src/software/amazon/q/core/ConnectionSettings.kt new file mode 100644 index 00000000000..10fdf053340 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/ConnectionSettings.kt @@ -0,0 +1,41 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core + +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.credentials.toEnvironmentVariables +import software.amazon.q.core.region.AwsRegion + +sealed interface ClientConnectionSettings { + val region: AwsRegion + val providerId: String + + /** + * Copies bean with the region replaced + */ + fun withRegion(region: AwsRegion): ClientConnectionSettings +} + +data class ConnectionSettings(val credentials: ToolkitCredentialsProvider, override val region: AwsRegion) : ClientConnectionSettings { + override val providerId: String + get() = credentials.id + + override fun withRegion(region: AwsRegion) = copy(region = region) +} + +data class TokenConnectionSettings( + val tokenProvider: ToolkitBearerTokenProvider, + override val region: AwsRegion, +) : ClientConnectionSettings { + override val providerId: String + get() = tokenProvider.id + + override fun withRegion(region: AwsRegion) = copy(region = region) +} + +val ConnectionSettings.shortName get() = "${credentials.shortName}@${region.id}" + +fun ConnectionSettings.toEnvironmentVariables(): Map = region.toEnvironmentVariables() + + credentials.resolveCredentials().toEnvironmentVariables() diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientCustomizer.kt b/plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientCustomizer.kt new file mode 100644 index 00000000000..c462cb2f365 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientCustomizer.kt @@ -0,0 +1,34 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration + +/** + * Used to override/add behavior during AWS SDK Client creation. + * + * Example usage to add a local development endpoint for a particular service: + * + * ``` + * class MyDevEndpointCustomizer : AwsClientCustomizer { + * override fun customize(credentialProvider: AwsCredentialsProvider, regionId: String, builder: AwsClientBuilder<*, *>) { + * if (builder is LambdaClientBuilder && connection.region.id == "us-west-2") { + * builder.endpointOverride(URI.create("http://localhost:8888")) + * } + * } + * } + * ``` + */ +fun interface ToolkitClientCustomizer { + fun customize( + credentialProvider: AwsCredentialsProvider?, + tokenProvider: SdkTokenProvider?, + regionId: String, + builder: AwsClientBuilder<*, *>, + clientOverrideConfiguration: ClientOverrideConfiguration.Builder, + ) +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientManager.kt b/plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientManager.kt new file mode 100644 index 00000000000..563aa4ad9d2 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientManager.kt @@ -0,0 +1,8 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core + +import migration.software.amazon.q.core.ToolkitClientManager + +typealias ToolkitClientManager = ToolkitClientManager diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/clients/ClientBuilderUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/clients/ClientBuilderUtils.kt new file mode 100644 index 00000000000..6f9c3958091 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/clients/ClientBuilderUtils.kt @@ -0,0 +1,25 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.clients + +import software.amazon.awssdk.core.client.builder.SdkClientBuilder +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.profiles.ProfileFile +import java.io.InputStream + +fun ClientOverrideConfiguration.Builder.nullDefaultProfileFile() = defaultProfileFile( + ProfileFile.builder() + .content(InputStream.nullInputStream()) + .type(ProfileFile.Type.CONFIGURATION) + .build() +) + +/** + * Only use if this is the only [overrideConfiguration] block used by the [SdkClientBuilder] + */ +fun SdkClientBuilder<*, C>.nullDefaultProfileFile() = apply { + overrideConfiguration { + it.nullDefaultProfileFile() + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/clients/SdkClientProvider.kt b/plugins/core-q/core-q/src/software/amazon/q/core/clients/SdkClientProvider.kt new file mode 100644 index 00000000000..6106db767a9 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/clients/SdkClientProvider.kt @@ -0,0 +1,8 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.clients + +import migration.software.amazon.q.core.clients.SdkClientProvider + +typealias SdkClientProvider = SdkClientProvider diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/credentials/AwsCredentials.kt b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/AwsCredentials.kt new file mode 100644 index 00000000000..b6d5a671e66 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/AwsCredentials.kt @@ -0,0 +1,43 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.credentials + +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.core.SdkSystemSetting + +private val CREDENTIAL_ENVIRONMENT_VARIABLES = setOf( + SdkSystemSetting.AWS_ACCESS_KEY_ID.environmentVariable(), + SdkSystemSetting.AWS_SECRET_ACCESS_KEY.environmentVariable(), + SdkSystemSetting.AWS_SESSION_TOKEN.environmentVariable() +) + +fun AwsCredentials.toEnvironmentVariables(): Map { + val map = mutableMapOf() + map[SdkSystemSetting.AWS_ACCESS_KEY_ID.environmentVariable()] = this.accessKeyId() + map[SdkSystemSetting.AWS_SECRET_ACCESS_KEY.environmentVariable()] = this.secretAccessKey() + + if (this is AwsSessionCredentials) { + map[SdkSystemSetting.AWS_SESSION_TOKEN.environmentVariable()] = this.sessionToken() + } + + return map +} + +fun AwsCredentials.mergeWithExistingEnvironmentVariables(existing: MutableMap, replace: Boolean = false) { + mergeWithExistingEnvironmentVariables(existing.keys, existing::remove, existing::putAll, replace) +} + +fun AwsCredentials.mergeWithExistingEnvironmentVariables( + existingKeys: Collection, + removeKey: (String) -> Unit, + putValues: (Map) -> Unit, + replace: Boolean = false, +) { + val envVars = toEnvironmentVariables() + if (replace || existingKeys.none { it in CREDENTIAL_ENVIRONMENT_VARIABLES }) { + CREDENTIAL_ENVIRONMENT_VARIABLES.forEach { removeKey(it) } + putValues(envVars) + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialProviderFactory.kt b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialProviderFactory.kt new file mode 100644 index 00000000000..26fa638a53e --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialProviderFactory.kt @@ -0,0 +1,37 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.credentials + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.q.core.region.AwsRegion + +/** + * Factory for adding new credential providers to the central credential management system + */ +interface CredentialProviderFactory { + /** + * ID used to uniquely identify this factory + */ + val id: String + + /** + * ID used to indicate where credentials are stored or retrieved from + */ + val credentialSourceId: CredentialSourceId + + /** + * Invoked on creation of the factory to update the credential system with what [CredentialIdentifier] this factory + * is capable of creating. The provided [credentialLoadCallback] is capable of being invoked multiple times in the case that + * the credentials this factory creates is modified in some way. + */ + fun setUp(credentialLoadCallback: CredentialsChangeListener) + + /** + * Creates an [AwsCredentialsProvider] for the specified [CredentialIdentifier] scoped to the specified [region] + */ + fun createAwsCredentialProvider( + providerId: CredentialIdentifier, + region: AwsRegion, + ): AwsCredentialsProvider +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialsChangeEvent.kt b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialsChangeEvent.kt new file mode 100644 index 00000000000..e99999fa9e9 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialsChangeEvent.kt @@ -0,0 +1,20 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.credentials + +/** + * Event that indicates that credentials were manipulated in a way that the toolkit needs to be notified so state can be updated + * to give an accurate representation of the state of the credentials system + */ +data class CredentialsChangeEvent( + val added: List = emptyList(), + val modified: List = emptyList(), + val removed: List = emptyList(), + + val ssoAdded: List = emptyList(), + val ssoModified: List = emptyList(), + val ssoRemoved: List = emptyList(), +) + +typealias CredentialsChangeListener = (changeEvent: CredentialsChangeEvent) -> Unit diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/credentials/SsoUrlIdentifier.kt b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/SsoUrlIdentifier.kt new file mode 100644 index 00000000000..bf930f62bc5 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/SsoUrlIdentifier.kt @@ -0,0 +1,38 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.credentials + +import software.amazon.q.core.utils.tryOrNull +import java.net.URI + +private const val CLASSIC_ISSUER_URL = "identitycenter.amazonaws.com/" +private const val GOVCLOUD_ISSUER_URL = "identitycenter.us-gov.amazonaws.com/" +private const val CN_ISSUER_URL = "identitycenter.amazonaws.com.cn/" +private const val CLASSIC_START_URL = ".awsapps.com/start" +private const val GOVCLOUD_START_URL = "us-gov-home.awsapps.com/directory/" +private const val CN_START_URL = "awsapps.cn/directory/" + +fun ssoIdentifierFromUrl(url: String): String { + val base = url.removePrefix("https://") + + return when { + base.contains(CLASSIC_START_URL) -> base.substringBefore(CLASSIC_START_URL) + base.contains(CLASSIC_ISSUER_URL) -> base.substringAfter(CLASSIC_ISSUER_URL) + + base.contains(GOVCLOUD_START_URL) -> base.substringAfter(GOVCLOUD_START_URL) + base.startsWith(GOVCLOUD_ISSUER_URL) -> base.substringAfter(GOVCLOUD_ISSUER_URL) + + base.contains(CN_START_URL) -> base.substringAfter(CN_START_URL) + base.startsWith(CN_ISSUER_URL) -> base.substringAfter(CN_ISSUER_URL) + else -> base + } +} + +fun validatedSsoIdentifierFromUrl(url: String): String { + tryOrNull { + URI(url) + } ?: error("Invalid SSO URL") + + return ssoIdentifierFromUrl(url) +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProvider.kt b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProvider.kt new file mode 100644 index 00000000000..733ea671469 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProvider.kt @@ -0,0 +1,147 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.credentials + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.aws.toolkits.resources.message + +enum class CredentialType { + StaticProfile, + StaticSessionProfile, + CredentialProcessProfile, + AssumeRoleProfile, + AssumeMfaRoleProfile, + SsoProfile, + Ec2Metadata, + EcsMetadata, +} + +enum class CredentialSourceId { + SharedCredentials, + SdkStore, + Ecs, + Ec2, + EnvVars, +} + +/** + * Represents a possible credential provider that can be used within the toolkit. + * + * Implementers should extend [CredentialIdentifierBase] instead of directly implementing this interface. + */ +interface CredentialIdentifier { + /** + * The ID must be unique across all [CredentialIdentifier] instances. + * It is recommended to concatenate the factory ID into this field to help enforce this requirement. + */ + val id: String + + /** + * A user friendly display name shown in the UI. + */ + val displayName: String + + /** + * An optional shortened version of the name to display in the UI where space is at a premium + */ + val shortName: String get() = displayName + + /** + * The ID of the corresponding [CredentialProviderFactory] so that the credential manager knows which factory to invoke in order + * to resolve this into a [ToolkitCredentialsProvider] + */ + val factoryId: String + + /** + * The type of credential + */ + val credentialType: CredentialType? + + /** + * Some ID types (e.g. Profile) have a concept of a default region, this is optional. + */ + val defaultRegionId: String? get() = null +} + +interface SsoSessionBackedCredentialIdentifier { + val sessionIdentifier: String +} + +interface SsoSessionIdentifier { + val id: String + val startUrl: String + val ssoRegion: String + val scopes: Set +} + +abstract class CredentialIdentifierBase(override val credentialType: CredentialType?) : CredentialIdentifier { + final override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CredentialIdentifierBase + + if (id != other.id) return false + + return true + } + + final override fun hashCode(): Int = id.hashCode() + + final override fun toString(): String = "${this::class.simpleName}(id='$id')" +} + +interface ToolkitAuthenticationProvider { + val id: String + val displayName: String +} + +class ToolkitCredentialsProvider( + val identifier: CredentialIdentifier, + val delegate: AwsCredentialsProvider, +) : ToolkitAuthenticationProvider, AwsCredentialsProvider by delegate { + override val id: String = identifier.id + override val displayName = identifier.displayName + val shortName = identifier.shortName + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ToolkitCredentialsProvider + + if (identifier != other.identifier) return false + + return true + } + + override fun hashCode(): Int = identifier.hashCode() + + override fun toString(): String = "${this::class.simpleName}(identifier='$identifier')" +} + +// TODO: try to get rid of this because it's really annoying casting the delegate everywhere +interface ToolkitBearerTokenProviderDelegate : SdkTokenProvider, ToolkitAuthenticationProvider + +class ToolkitBearerTokenProvider(val delegate: ToolkitBearerTokenProviderDelegate) : SdkTokenProvider by delegate, ToolkitAuthenticationProvider by delegate { + companion object { + // TODO: is there a better place for this + fun ssoIdentifier(startUrl: String, region: String = DEFAULT_SSO_REGION) = "sso;$region;$startUrl" + + // TODO: For AWS Builder ID, we only have startUrl for now instead of each users' metadata data i.e. Email address + fun ssoDisplayName(startUrl: String) = if (startUrl == SONO_URL) { + message("aws_builder_id.service_name") + } else { + message("iam_identity_center.service_name", ssoIdentifierFromUrl(startUrl)) + } + + fun diskSessionIdentifier(profileName: String) = "sso-session:$profileName" + fun diskSessionDisplayName(profileName: String) = "IAM Identity Center Session ($profileName)" + } +} + +private const val SONO_URL = "https://view.awsapps.com/start" + +const val DEFAULT_SSO_REGION = "us-east-1" diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProviderManager.kt b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProviderManager.kt new file mode 100644 index 00000000000..8b6aa3b0d8a --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProviderManager.kt @@ -0,0 +1,23 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.credentials + +/** + * TODO: Deprecate and remove this since it is less efficient than [CredentialsChangeEvent] + */ +interface ToolkitCredentialsChangeListener { + fun providerAdded(identifier: CredentialIdentifier) {} + fun providerModified(identifier: CredentialIdentifier) {} + fun providerRemoved(identifier: CredentialIdentifier) {} + fun providerRemoved(providerId: String) {} + + fun ssoSessionAdded(identifier: SsoSessionIdentifier) {} + fun ssoSessionModified(identifier: SsoSessionIdentifier) {} + fun ssoSessionRemoved(identifier: SsoSessionIdentifier) {} +} + +class CredentialProviderNotFoundException : RuntimeException { + constructor(msg: String) : super(msg) + constructor(msg: String, exception: Exception) : super(msg, exception) +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaArchitecture.kt b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaArchitecture.kt new file mode 100644 index 00000000000..d964b0c4dda --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaArchitecture.kt @@ -0,0 +1,32 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.lambda + +import software.amazon.awssdk.services.lambda.model.Architecture + +enum class LambdaArchitecture( + private val architecture: Architecture, + val minSam: String? = null, +) { + X86_64(Architecture.X86_64), + ARM64(Architecture.ARM64, minSam = "1.33.0"), + ; + + override fun toString() = architecture.toString() + + fun toSdkArchitecture() = architecture.validOrNull + + companion object { + fun fromValue(value: String?): LambdaArchitecture? = if (value == null) { + null + } else { + values().find { it.toString() == value } + } + + fun fromValue(value: Architecture): LambdaArchitecture? = values().find { it.architecture == value } + + val DEFAULT = X86_64 + val ARM_COMPATIBLE = listOf(X86_64, ARM64) + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaRuntime.kt b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaRuntime.kt new file mode 100644 index 00000000000..e64c49fff8d --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaRuntime.kt @@ -0,0 +1,49 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.lambda + +import software.amazon.awssdk.services.lambda.model.Runtime + +enum class LambdaRuntime( + private val runtime: Runtime?, + val minSamInit: String? = null, + val minSamDebugging: String? = null, + val architectures: List? = listOf(LambdaArchitecture.DEFAULT), + private val runtimeOverride: String? = null, +) { + GO1_X( + Runtime.GO1_X, + // Although go sam debugging was supported before 1.18.1, it does not work on 1.13.0-1.16.0 + // and 1.17.0 broke the arguments + minSamDebugging = "1.18.1" + ), + NODEJS16_X(Runtime.NODEJS16_X, minSamDebugging = "1.49.0", minSamInit = "1.49.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + NODEJS18_X(Runtime.NODEJS18_X, minSamDebugging = "1.65.0", minSamInit = "1.65.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + NODEJS20_X(Runtime.NODEJS20_X, minSamDebugging = "1.102.0", minSamInit = "1.102.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + JAVA8_AL2(Runtime.JAVA8_AL2, minSamDebugging = "1.2.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + JAVA11(Runtime.JAVA11, architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + JAVA17(Runtime.JAVA17, minSamDebugging = "1.81.0", minSamInit = "1.81.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + JAVA21(Runtime.JAVA21, minSamDebugging = "1.103.0", minSamInit = "1.103.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + PYTHON3_8(Runtime.PYTHON3_8, architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + PYTHON3_9(Runtime.PYTHON3_9, minSamDebugging = "1.28.0", minSamInit = "1.28.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + PYTHON3_10(Runtime.PYTHON3_10, minSamDebugging = "1.78.0", minSamInit = "1.78.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + PYTHON3_11(Runtime.PYTHON3_11, minSamDebugging = "1.87.0", minSamInit = "1.87.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + PYTHON3_12(Runtime.PYTHON3_12, minSamDebugging = "1.103.0", minSamInit = "1.103.0", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + DOTNET6_0(Runtime.DOTNET6, minSamDebugging = "1.40.1", minSamInit = "1.40.1", architectures = LambdaArchitecture.Companion.ARM_COMPATIBLE), + ; + + override fun toString() = runtime?.toString() ?: runtimeOverride ?: throw IllegalStateException("LambdaRuntime has no runtime or override string") + + fun toSdkRuntime() = runtime.validOrNull + + companion object { + fun fromValue(value: String?): LambdaRuntime? = if (value == null) { + null + } else { + values().find { it.toString() == value } + } + + fun fromValue(value: Runtime): LambdaRuntime? = values().find { it.runtime == value } + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaSampleEventProvider.kt b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaSampleEventProvider.kt new file mode 100644 index 00000000000..c5b5a4d0316 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaSampleEventProvider.kt @@ -0,0 +1,111 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.lambda + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import org.slf4j.LoggerFactory +import software.amazon.q.core.utils.RemoteResolveParser +import software.amazon.q.core.utils.RemoteResource +import software.amazon.q.core.utils.RemoteResourceResolver +import software.amazon.q.core.utils.inputStream +import software.amazon.q.core.utils.readText +import software.amazon.q.core.utils.tryOrNull +import java.io.InputStream +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage +import java.util.concurrent.atomic.AtomicReference + +class LambdaSampleEventProvider(private val resourceResolver: RemoteResourceResolver) { + private val mapper = XmlMapper().registerKotlinModule().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + + private val manifest = AtomicReference?>(null) + + fun get(): CompletionStage> { + val value = manifest.get() + if (value != null) { + return CompletableFuture.completedFuture(value) + } else { + return resourceResolver.resolve(LambdaSampleEventManifestResource).thenApply { resource -> + val resolved = mapper.readValue(resource.inputStream()).requests.map { request -> + LambdaSampleEvent(request.name) { + resourceResolver.resolve(LambdaSampleEventResource(request.filename)) + .thenApply { it?.readText() } + } + } + manifest.set(resolved) + resolved + } + } + } +} + +open class LambdaSampleEvent(val name: String, private val contentProvider: () -> CompletionStage) { + val content: CompletionStage by lazy { contentProvider() } + override fun toString() = name +} + +data class LambdaSampleEventManifest( + @JsonProperty(value = "request") + @JacksonXmlElementWrapper(useWrapping = false) + val requests: List, +) + +data class LambdaSampleEventRequest( + val filename: String, + val name: String, +) + +internal val LambdaSampleEventManifestResource = LambdaSampleEventResource("manifest.xml") +object LambdaManifestValidator : RemoteResolveParser { + + private val LOG = LoggerFactory.getLogger(LambdaManifestValidator::class.java) + override fun canBeParsed(data: InputStream): Boolean { + val mapper = XmlMapper().registerKotlinModule().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + val result = LOG.tryOrNull("Failed to parse Requests") { + mapper.readValue(data) + } + + return result?.requests?.isNotEmpty() ?: false + } +} +internal data class LambdaSampleEventResource(val filename: String) : RemoteResource { + override val urls: List = listOf( + "https://aws-vs-toolkit.s3.amazonaws.com/LambdaSampleFunctions/SampleRequests/$filename" + ) + + override val name: String = "lambda-sample-event-$filename" + override val ttl: Duration = Duration.ofDays(7) + override val remoteResolveParser: RemoteResolveParser? = resolveParserForGivenFile(filename.substringAfterLast('.', "")) +} +object LambdaSampleEventJsonValidator : RemoteResolveParser { + private val LOG = LoggerFactory.getLogger(LambdaSampleEventJsonValidator::class.java) + + private val mapper = jacksonObjectMapper() + .disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(JsonParser.Feature.ALLOW_COMMENTS) + + override fun canBeParsed(data: InputStream): Boolean { + val jsonMapper: Map = HashMap() + val result = LOG.tryOrNull("Failed to parse Lambda Sample Event request") { + this.mapper.readValue(data, jsonMapper.javaClass) + } + return result?.isNotEmpty() ?: false + } +} +fun resolveParserForGivenFile(extension: String): RemoteResolveParser? = + when (extension) { + "xml" -> LambdaManifestValidator + "json" -> LambdaSampleEventJsonValidator + else -> null + } diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaUtils.kt new file mode 100644 index 00000000000..014146c903b --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaUtils.kt @@ -0,0 +1,10 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.lambda + +import software.amazon.awssdk.services.lambda.model.Architecture +import software.amazon.awssdk.services.lambda.model.Runtime + +val Runtime?.validOrNull: Runtime? get() = this?.takeUnless { it == Runtime.UNKNOWN_TO_SDK_VERSION } +val Architecture?.validOrNull: Architecture? get() = this?.takeUnless { it == Architecture.UNKNOWN_TO_SDK_VERSION } diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/region/AwsPartition.kt b/plugins/core-q/core-q/src/software/amazon/q/core/region/AwsPartition.kt new file mode 100644 index 00000000000..b2021be2f60 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/region/AwsPartition.kt @@ -0,0 +1,8 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.region + +data class AwsPartition(val id: String, val description: String, val regions: Collection) { + val displayName = "$description ($id)" +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/region/AwsRegion.kt b/plugins/core-q/core-q/src/software/amazon/q/core/region/AwsRegion.kt new file mode 100644 index 00000000000..86b70123398 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/region/AwsRegion.kt @@ -0,0 +1,52 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.region + +import software.amazon.awssdk.regions.Region + +data class AwsRegion(val id: String, val name: String, val partitionId: String) { + val category: String? = when { + id.startsWith("af") -> "Africa" + id.startsWith("us") -> "North America" + id.startsWith("ca") -> "North America" + id.startsWith("eu") -> "Europe" + id.startsWith("ap") -> "Asia Pacific" + id.startsWith("sa") -> "South America" + id.startsWith("cn") -> "China" + id.startsWith("me") -> "Middle East" + else -> null + } + + val displayName: String = when { + category == "Europe" -> "${name.removePrefix("Europe").trimPrefixAndRemoveBrackets("EU")} ($id)" + category == "North America" -> "${name.removePrefix("US West").trimPrefixAndRemoveBrackets("US East")} ($id)" + category != null && name.startsWith(category) -> "${name.trimPrefixAndRemoveBrackets(category)} ($id)" + else -> name + } + + fun toEnvironmentVariables() = mapOf( + "AWS_REGION" to id, + "AWS_DEFAULT_REGION" to id + ) + + companion object { + val GLOBAL = AwsRegion(Region.AWS_GLOBAL.id(), "Global", "Global") + private fun String.trimPrefixAndRemoveBrackets(prefix: String) = this.removePrefix(prefix).replace("(", "").replace(")", "").trim() + } +} + +fun AwsRegion.mergeWithExistingEnvironmentVariables(existing: MutableMap, replace: Boolean = false) { + mergeWithExistingEnvironmentVariables(existing.keys, existing::putAll, replace) +} + +fun AwsRegion.mergeWithExistingEnvironmentVariables( + existingKeys: Collection, + putValues: (Map) -> Unit, + replace: Boolean = false, +) { + val regionEnvs = this.toEnvironmentVariables() + if (replace || regionEnvs.keys.none { it in existingKeys }) { + putValues(regionEnvs) + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/region/Partitions.kt b/plugins/core-q/core-q/src/software/amazon/q/core/region/Partitions.kt new file mode 100644 index 00000000000..4744931a66e --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/region/Partitions.kt @@ -0,0 +1,61 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.region + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import org.slf4j.LoggerFactory +import software.amazon.q.core.utils.RemoteResolveParser +import software.amazon.q.core.utils.RemoteResource +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.resources.BundledResources +import java.io.InputStream +import java.time.Duration + +data class Partitions(val partitions: List) { + fun getPartition(name: String): Partition = + partitions.find { it.partition == name } ?: throw RuntimeException("Partition named '$name' not found") +} + +data class Partition( + val partition: String, + val partitionName: String, + val regions: Map, + val services: Map, +) + +data class PartitionRegion(val description: String) + +data class Service(val endpoints: Map, val isRegionalized: Boolean?, val partitionEndpoint: String?) { + val isGlobal = isRegionalized != true && partitionEndpoint != null +} + +class Endpoint + +object PartitionParser { + private val LOG = LoggerFactory.getLogger(PartitionParser::class.java) + + private val mapper = jacksonObjectMapper() + .disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(JsonParser.Feature.ALLOW_COMMENTS) + + fun parse(inputStream: InputStream): Partitions? = LOG.tryOrNull("Failed to parse Partitions") { + mapper.readValue(inputStream, Partitions::class.java) + } +} +object EndpointsJsonValidator : RemoteResolveParser { + override fun canBeParsed(data: InputStream): Boolean { + return PartitionParser.parse(data)?.partitions?.isNotEmpty() ?: return false + } +} +object ServiceEndpointResource : RemoteResource { + override val urls: List = listOf("https://idetoolkits.amazonwebservices.com/endpoints.json") + override val name: String = "service-endpoints.json" + override val ttl: Duration? = Duration.ofHours(24) + override val initialValue: (() -> InputStream)? = { BundledResources.ENDPOINTS_FILE } + override val remoteResolveParser: EndpointsJsonValidator = EndpointsJsonValidator +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/region/ToolkitRegionProvider.kt b/plugins/core-q/core-q/src/software/amazon/q/core/region/ToolkitRegionProvider.kt new file mode 100644 index 00000000000..796ce96e90a --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/region/ToolkitRegionProvider.kt @@ -0,0 +1,8 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.region + +import migration.software.amazon.q.core.region.ToolkitRegionProvider + +typealias ToolkitRegionProvider = ToolkitRegionProvider diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/s3/BucketUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/s3/BucketUtils.kt new file mode 100644 index 00000000000..245b58e3126 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/s3/BucketUtils.kt @@ -0,0 +1,31 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.s3 + +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest +import software.amazon.awssdk.services.s3.model.ObjectIdentifier + +fun S3Client.deleteBucketAndContents(bucket: String) { + this.listObjectVersionsPaginator(ListObjectVersionsRequest.builder().bucket(bucket).build()).forEach { resp -> + val versions = resp.versions().map { + ObjectIdentifier.builder() + .key(it.key()) + .versionId(it.versionId()).build() + } + if (versions.isEmpty()) { + return@forEach + } + this.deleteObjects { it.bucket(bucket).delete { obj -> obj.objects(versions) } } + } + + this.deleteBucket { it.bucket(bucket) } +} + +fun S3Client.regionForBucket(bucketName: String): String = this.getBucketLocation { + it.bucket(bucketName) +}.locationConstraintAsString() + .takeIf { it.isNotEmpty() } // getBucketLocation returns an explicit empty string location contraint for us-east-1 + ?: Region.US_EAST_1.id() diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/CachedIdentityStorage.kt b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/CachedIdentityStorage.kt new file mode 100644 index 00000000000..19542447018 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/CachedIdentityStorage.kt @@ -0,0 +1,26 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.telemetry + +/** + * Used to provide a way to cache an identity ID in order to prevent creating additional unneeded identities. + */ +interface CachedIdentityStorage { + /** + * Saves the identity ID to the backing storage. + * + * @param identityPoolId The pool the identity belongs to + * @param identityId The generated ID + */ + fun storeIdentity(identityPoolId: String, identityId: String) + + /** + * Attempts to retrieve the identity ID from the backing storage. If no ID exists for the specified pool, + * `null` should be returned in order to generate a new ID. + * + * @param identityPoolId The ID of the pool we are requested the ID for + * @return The ID for the specified pool, else null + */ + fun loadIdentity(identityPoolId: String): String? +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/MetricEvent.kt b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/MetricEvent.kt new file mode 100644 index 00000000000..23e50046788 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/MetricEvent.kt @@ -0,0 +1,192 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.telemetry + +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import java.time.Instant + +interface MetricEvent { + val createTime: Instant + val awsAccount: String + val awsRegion: String + val awsProduct: AWSProduct + val awsVersion: String + val data: Iterable + + interface Builder { + fun createTime(createTime: Instant): Builder + + fun awsAccount(awsAccount: String): Builder + + fun awsRegion(awsRegion: String): Builder + + fun awsProduct(product: AWSProduct): Builder + + fun awsVersion(version: String): Builder + + fun datum(name: String, buildDatum: Datum.Builder.() -> Unit = {}): Builder + + fun build(): MetricEvent + } + + interface Datum { + val name: String + val value: Double + val unit: MetricUnit + val passive: Boolean + val metadata: Map + + interface Builder { + fun name(name: String): Builder + + fun value(value: Double): Builder + + fun unit(unit: MetricUnit): Builder + + fun passive(value: Boolean): Builder + + fun metadata(key: String, value: String): Builder + + fun metadata(key: String, value: Boolean): Builder = metadata(key, value.toString()) + + fun count(value: Double = 1.0): Builder { + value(value) + unit(MetricUnit.COUNT) + return this + } + + fun build(): Datum + } + } + + companion object { + val illegalCharsRegex = "[^\\w+-.:]".toRegex() + } +} + +fun String.replaceIllegal(replacement: String = "") = this.replace(MetricEvent.Companion.illegalCharsRegex, replacement) + +data class DefaultMetricEvent internal constructor( + override val createTime: Instant, + override val awsAccount: String, + override val awsRegion: String, + override val awsProduct: AWSProduct, + override val awsVersion: String, + override val data: Iterable, +) : MetricEvent { + + class BuilderImpl : MetricEvent.Builder { + private var createTime: Instant = Instant.now() + private var awsAccount: String = METADATA_NA + private var awsRegion: String = METADATA_NA + private var awsProduct: AWSProduct = AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS + private var awsVersion: String = METADATA_NA + + private val data: MutableCollection = mutableListOf() + + override fun createTime(createTime: Instant): MetricEvent.Builder { + this.createTime = createTime + return this + } + + override fun awsAccount(awsAccount: String): MetricEvent.Builder { + this.awsAccount = awsAccount + return this + } + + override fun awsRegion(awsRegion: String): MetricEvent.Builder { + this.awsRegion = awsRegion + return this + } + + override fun awsProduct(awsProduct: AWSProduct): MetricEvent.Builder { + this.awsProduct = awsProduct + return this + } + + override fun awsVersion(awsVersion: String): MetricEvent.Builder { + this.awsVersion = awsVersion + return this + } + + override fun datum(name: String, buildDatum: MetricEvent.Datum.Builder.() -> Unit): MetricEvent.Builder { + val builder = DefaultDatum.builder(name) + buildDatum(builder) + data.add(builder.build()) + return this + } + + override fun build(): MetricEvent = DefaultMetricEvent(createTime, awsAccount, awsRegion, awsProduct, awsVersion, data) + } + + companion object { + fun builder() = BuilderImpl() + + const val METADATA_NA = "n/a" + const val METADATA_NOT_SET = "not-set" + const val METADATA_INVALID = "invalid" + } + + data class DefaultDatum( + override val name: String, + override val value: Double, + override val unit: MetricUnit, + override val passive: Boolean, + override val metadata: Map, + ) : MetricEvent.Datum { + class BuilderImpl(private var name: String) : MetricEvent.Datum.Builder { + private var value: Double = 0.0 + private var unit: MetricUnit = MetricUnit.NONE + private var passive: Boolean = false + private val metadata: MutableMap = HashMap() + + override fun name(name: String): MetricEvent.Datum.Builder { + this.name = name + return this + } + + override fun value(value: Double): MetricEvent.Datum.Builder { + this.value = value + return this + } + + override fun unit(unit: MetricUnit): MetricEvent.Datum.Builder { + this.unit = unit + return this + } + + override fun passive(value: Boolean): MetricEvent.Datum.Builder { + this.passive = value + return this + } + + override fun metadata(key: String, value: String): MetricEvent.Datum.Builder { + if (metadata.containsKey(key)) { + LOG.warn { "Attempted to add multiple pieces of metadata with the same key" } + return this + } + + metadata[key] = value + return this + } + + override fun build(): MetricEvent.Datum = DefaultDatum( + name.replaceIllegal(), + this.value, + this.unit, + this.passive, + this.metadata + ) + } + + companion object { + private val LOG = getLogger() + + fun builder(name: String): MetricEvent.Datum.Builder = BuilderImpl(name) + } + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryBatcher.kt b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryBatcher.kt new file mode 100644 index 00000000000..b96a83a4929 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryBatcher.kt @@ -0,0 +1,143 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.telemetry + +import kotlinx.coroutines.runBlocking +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.core.exception.SdkServiceException +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import java.util.concurrent.Executors +import java.util.concurrent.LinkedBlockingDeque +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +interface TelemetryBatcher { + fun enqueue(event: MetricEvent) + + fun flush(retry: Boolean) + + fun onTelemetryEnabledChanged(isEnabled: Boolean, onChangeEvent: (Boolean) -> Unit = {}) + + fun shutdown() +} + +class DefaultTelemetryBatcher( + private val publisher: TelemetryPublisher, + private val maxBatchSize: Int = DEFAULT_MAX_BATCH_SIZE, + maxQueueSize: Int = DEFAULT_MAX_QUEUE_SIZE, + private val executor: ScheduledExecutorService = createDefaultExecutor(), +) : TelemetryBatcher { + private val isTelemetryEnabled: AtomicBoolean = AtomicBoolean(false) + private val isShuttingDown: AtomicBoolean = AtomicBoolean(false) + + val eventQueue: LinkedBlockingDeque = LinkedBlockingDeque(maxQueueSize) + @TestOnly get + + init { + executor.scheduleWithFixedDelay( + { + if (!isShuttingDown.get()) { + try { + flush(true) + } catch (e: Exception) { + LOG.warn(e) { "Unexpected exception while publishing telemetry" } + } + } + }, + DEFAULT_PUBLISH_INTERVAL, + DEFAULT_PUBLISH_INTERVAL, + DEFAULT_PUBLISH_INTERVAL_UNIT + ) + } + + override fun shutdown() { + if (!isShuttingDown.compareAndSet(false, true)) { + return + } + + executor.shutdown() + flush(false) + } + + override fun enqueue(event: MetricEvent) { + if (!isTelemetryEnabled.get()) { + return + } + + try { + eventQueue.add(event) + } catch (e: Exception) { + LOG.warn(e) { "Failed to add metric to queue" } + } + } + + @Synchronized + override fun flush(retry: Boolean) { + if (!isTelemetryEnabled.get()) { + return + } + + while (!eventQueue.isEmpty()) { + val batch: ArrayList = arrayListOf() + + while (!eventQueue.isEmpty() && batch.size < maxBatchSize) { + batch.add(eventQueue.pop()) + } + + val stop = runBlocking { + try { + publisher.publish(batch) + } catch (e: Exception) { + LOG.warn(e) { "Failed to publish metrics" } + val shouldRetry = retry && when (e) { + is SdkServiceException -> e.statusCode() !in 400..499 + else -> true + } + if (shouldRetry) { + LOG.warn { "Telemetry metrics failed to publish, retrying later..." } + eventQueue.addAll(batch) + // don't want an infinite loop... + return@runBlocking true + } + } + return@runBlocking false + } + if (stop) { + return + } + } + } + + override fun onTelemetryEnabledChanged(isEnabled: Boolean, onChangeEvent: (Boolean) -> Unit) { + if (isEnabled) { + isTelemetryEnabled.set(true) + } else { + eventQueue.clear() + } + + onChangeEvent(isEnabled) + flush(isEnabled) + + if (!isEnabled) { + isTelemetryEnabled.set(false) + } + } + + companion object { + private val LOG = getLogger() + private const val DEFAULT_MAX_BATCH_SIZE = 20 + private const val DEFAULT_MAX_QUEUE_SIZE = 10000 + private const val DEFAULT_PUBLISH_INTERVAL = 5L + private val DEFAULT_PUBLISH_INTERVAL_UNIT = TimeUnit.MINUTES + + private fun createDefaultExecutor() = Executors.newSingleThreadScheduledExecutor { + val daemonThread = Thread(it) + daemonThread.isDaemon = true + daemonThread.name = "AWS-Toolkit-Metrics-Publisher" + daemonThread + } + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryPublisher.kt b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryPublisher.kt new file mode 100644 index 00000000000..ce68444a8b3 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryPublisher.kt @@ -0,0 +1,12 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.telemetry + +import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment + +interface TelemetryPublisher : AutoCloseable { + suspend fun publish(metricEvents: Collection) + + suspend fun sendFeedback(sentiment: Sentiment, comment: String, metadata: Map) +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/AttributeBag.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/AttributeBag.kt new file mode 100644 index 00000000000..10b7f6fe2e7 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/AttributeBag.kt @@ -0,0 +1,32 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +data class AttributeBagKey private constructor(val key: String) { + companion object { + private val map: ConcurrentMap> = ConcurrentHashMap() + + @Suppress("UNCHECKED_CAST") + fun create(name: String): AttributeBagKey = map.computeIfAbsent(name) { + AttributeBagKey(name) + } as AttributeBagKey + } +} + +class AttributeBag { + private val data = mutableMapOf() + + fun putData(key: AttributeBagKey, value: T) { + data.put(key.key, value) + } + + @Suppress("UNCHECKED_CAST") + fun get(key: AttributeBagKey): T? = data[key.key]?.let { it as T } + + fun getOrThrow(key: AttributeBagKey): T = get(key) + ?: throw NoSuchElementException("Required element $key not found in AttributeBag") +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/CollectionUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/CollectionUtils.kt new file mode 100644 index 00000000000..f2a595b644f --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/CollectionUtils.kt @@ -0,0 +1,20 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +/** + * Removes all items in this collection and replaces them with the items in the [other] collection + */ +fun MutableCollection.replace(other: Collection) { + clear() + addAll(other) +} + +/** + * Removes all items in this map and replaces them with the items in the [other] map + */ +fun MutableMap.replace(other: Map) { + clear() + putAll(other) +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/Either.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/Either.kt new file mode 100644 index 00000000000..f568e4cafe0 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/Either.kt @@ -0,0 +1,15 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +sealed class Either { + data class Left(val value: L) : Either() + data class Right(val value: R) : Either() +} + +infix fun L?.xor(other: R?): Either = when { + this != null && other == null -> Either.Left(this) + this == null && other != null -> Either.Right(other) + else -> throw IllegalArgumentException("Exactly one of $this or $other must be null") +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/ExceptionUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/ExceptionUtils.kt new file mode 100644 index 00000000000..b78bd4cab09 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/ExceptionUtils.kt @@ -0,0 +1,14 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +@file:JvmName("ExceptionUtils") + +package software.amazon.q.core.utils + +/** + * Convert exceptions raised from [block] to null + */ +fun tryOrNull(block: () -> T): T? = try { + block() +} catch (_: Exception) { + null +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/LogUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/LogUtils.kt new file mode 100644 index 00000000000..d82d32ca8c2 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/LogUtils.kt @@ -0,0 +1,110 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +@file:Suppress("LazyLog") + +package software.amazon.q.core.utils + +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.slf4j.event.Level +import kotlin.reflect.KClass + +inline fun getLogger(): Logger = getLogger(T::class) +fun getLogger(clazz: KClass<*>): Logger = LoggerFactory.getLogger(clazz.java) + +/** + * Execute the given [block] and log any exception that occurs at the [level] with the provided [message]. + * + * Defaults to [Level.ERROR] if none specified. + */ +fun Logger.tryOrNull(message: String, level: Level = Level.ERROR, block: () -> T?): T? = try { + block() +} catch (e: Exception) { + log(level, e) { message } + null +} + +/** + * Execute the given block, log at the given [level] and then bubble any exception that occurs. + * + * A [block] that returns null bubbles an exception + */ +fun Logger.tryOrThrow(message: String, level: Level = Level.ERROR, block: () -> T?): T = try { + block() ?: throw NullPointerException() +} catch (e: Exception) { + log(level, e) { message } + throw e +} + +/** + * Execute the given block, log an error and then bubble any exception that occurs. + * + * A [block] that returns null is legal + */ +fun Logger.tryOrThrowNullable(message: String, level: Level = Level.ERROR, block: () -> T?) = try { + block() +} catch (e: Exception) { + log(level, e) { message } + throw e +} + +/** + * Execute the given block and return the result. Log a warning when the result is null + */ +fun Logger.logWhenNull(message: String, level: Level = Level.WARN, block: () -> T?): T? { + val value = block() + if (value == null) { + log(level) { message } + } + return value +} + +fun Logger.log(level: Level, exception: Throwable? = null, block: () -> String) { + when (level) { + Level.ERROR -> error(exception, block) + Level.WARN -> warn(exception, block) + Level.INFO -> info(exception, block) + Level.DEBUG -> debug(exception, block) + Level.TRACE -> trace(exception, block) + } +} + +fun Logger.debug(exception: Throwable? = null, block: () -> String) { + if (isDebugEnabled) { + debug(block(), exception) + } +} + +fun Logger.info(exception: Throwable? = null, block: () -> String) { + if (isInfoEnabled) { + info(block(), exception) + } +} + +fun Logger.error(exception: Throwable? = null, block: () -> String) { + if (isErrorEnabled) { + error(block(), exception) + } +} + +fun Logger.warn(exception: Throwable? = null, block: () -> String) { + if (isWarnEnabled) { + warn(block(), exception) + } +} + +fun Logger.trace(exception: Throwable? = null, block: () -> String) { + if (isTraceEnabled) { + trace(block(), exception) + } +} + +fun Logger.assertTrue(value: Boolean, block: () -> String): Boolean { + if (!value) { + val resultMessage = "Assertion failed: ${block.invoke()}" + error(resultMessage, Throwable(resultMessage)) + } + + return value +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/PathUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/PathUtils.kt new file mode 100644 index 00000000000..77b6f35c1f7 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/PathUtils.kt @@ -0,0 +1,217 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import org.slf4j.Logger +import java.io.InputStream +import java.io.OutputStream +import java.nio.charset.Charset +import java.nio.file.AccessMode +import java.nio.file.FileAlreadyExistsException +import java.nio.file.Files +import java.nio.file.NoSuchFileException +import java.nio.file.Path +import java.nio.file.attribute.AclEntry +import java.nio.file.attribute.AclFileAttributeView +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission +import java.nio.file.attribute.PosixFilePermissions +import java.nio.file.attribute.UserPrincipal +import kotlin.io.path.getPosixFilePermissions +import kotlin.io.path.isRegularFile + +val POSIX_OWNER_ONLY_FILE = setOf(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE) +val POSIX_OWNER_ONLY_DIR = setOf(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE) + +fun Path.inputStream(): InputStream = Files.newInputStream(this) +fun Path.inputStreamIfExists(): InputStream? = try { + inputStream() +} catch (e: NoSuchFileException) { + null +} + +fun Path.touch(restrictToOwner: Boolean = false) { + try { + if (!restrictToOwner || !hasPosixFilePermissions()) { + Files.createFile(this) + } else { + Files.createFile(this, PosixFilePermissions.asFileAttribute(POSIX_OWNER_ONLY_FILE)) + } + } catch (_: FileAlreadyExistsException) {} +} + +fun Path.outputStream(): OutputStream { + this.createParentDirectories() + return Files.newOutputStream(this) +} + +fun Path.createParentDirectories(restrictToOwner: Boolean = false) = if (!restrictToOwner || !hasPosixFilePermissions()) { + Files.createDirectories(this.parent) +} else { + Files.createDirectories(this.parent, PosixFilePermissions.asFileAttribute(POSIX_OWNER_ONLY_DIR)) +} + +fun Path.exists() = Files.exists(this) +fun Path.deleteIfExists() = Files.deleteIfExists(this) +fun Path.lastModified(): FileTime = Files.getLastModifiedTime(this) +fun Path.readText(charset: Charset = Charsets.UTF_8) = toFile().readText(charset) +fun Path.writeText(text: String, charset: Charset = Charsets.UTF_8) = toFile().writeText(text, charset) +fun Path.appendText(text: String, charset: Charset = Charsets.UTF_8) = toFile().appendText(text, charset) + +// Comes from PosixFileAttributeView#name() +fun Path.hasPosixFilePermissions() = "posix" in this.fileSystem.supportedFileAttributeViews() +fun Path.filePermissions(permissions: Set) { + if (hasPosixFilePermissions()) { + Files.setPosixFilePermissions(this, permissions) + } +} + +fun Path.tryDirOp(log: Logger, block: Path.() -> Unit) { + try { + log.debug { "dir op on $this" } + block(this) + } catch (e: Exception) { + if (e !is java.nio.file.AccessDeniedException && e !is AccessDeniedException) { + throw e + } + + if (!hasPosixFilePermissions()) { + throw tryAugmentExceptionMessage(e, this) + } + + log.info(e) { "Attempting to handle ADE for directory operation" } + try { + var parent = if (this.isRegularFile()) { parent } else { this } + + while (parent != null) { + if (!parent.exists()) { + log.info { "${parent.toAbsolutePath()}: does not exist yet" } + } else { + if (tryOrNull { parent.fileSystem.provider().checkAccess(parent, AccessMode.READ, AccessMode.WRITE, AccessMode.EXECUTE) } != null) { + log.debug { "$parent has rwx, exiting" } + // can assume parent permissions are correct + break + } + + log.debug { "fixing perms for $parent" } + parent.tryFixPerms(log, POSIX_OWNER_ONLY_DIR) + } + + parent = parent.parent + } + } catch (e2: Exception) { + log.warn(e2) { "Encountered error while handling ADE for ${e.message}" } + + throw tryAugmentExceptionMessage(e, this) + } + + log.info { "Done attempting to handle ADE for directory operation" } + block(this) + } +} + +fun Path.tryFileOp(log: Logger, block: Path.() -> T) = + try { + log.debug { "file op on $this" } + block(this) + } catch (e: Exception) { + if (e !is java.nio.file.AccessDeniedException && e !is AccessDeniedException) { + throw e + } + + if (!hasPosixFilePermissions()) { + throw tryAugmentExceptionMessage(e, this) + } + + log.info(e) { "Attempting to handle ADE for file operation" } + try { + log.debug { "fixing perms for $this" } + tryFixPerms(log, POSIX_OWNER_ONLY_FILE) + } catch (e2: Exception) { + log.warn(e2) { "Encountered error while handling ADE for ${e.message}" } + + throw tryAugmentExceptionMessage(e, this) + } + + log.info { "Done attempting to handle ADE for file operation" } + block(this) + } + +private fun Path.tryFixPerms(log: Logger, desiredPermissions: Set) { + // TODO: consider handling linux ACLs + // only try ops if we own the file + // (ab)use invariant that chmod only works if you are root or the file owner + val perms = tryOrLogShortException(log) { Files.getPosixFilePermissions(this) } + val ownership = tryOrLogShortException(log) { Files.getOwner(this) } + + log.info { "Permissions for ${toAbsolutePath()}: $ownership, $perms" } + if (perms != null && ownership != null) { + if (ownership.name != "root" && tryOrNull { filePermissions(perms) } != null) { + val permissions = perms + desiredPermissions + log.info { "Setting perms for ${toAbsolutePath()}: $permissions" } + filePermissions(permissions) + } + } +} + +private fun tryAugmentExceptionMessage(e: Exception, path: Path): Exception { + if (e !is java.nio.file.AccessDeniedException && e !is AccessDeniedException) { + return e + } + + var potentialProblem = if (path.exists()) { path } else { path.parent } + var acls: List? = null + var ownership: UserPrincipal? = null + while (potentialProblem != null) { + acls = tryOrNull { Files.getFileAttributeView(potentialProblem, AclFileAttributeView::class.java).acl } + ownership = tryOrNull { Files.getOwner(potentialProblem) } + + if (acls != null || ownership != null) { + break + } + + potentialProblem = potentialProblem.parent + } + + val message = buildString { + // $path is automatically added to the front of the exception message + appendLine("Exception trying to perform operation") + + if (potentialProblem != null) { + append("Potential issue is with $potentialProblem") + + if (ownership != null) { + append(", which has owner: $ownership") + } + + if (acls != null) { + append(", and ACL entries for: ${acls.map { it.principal() }}") + } + + val posixPermissions = tryOrNull { PosixFilePermissions.toString(potentialProblem.getPosixFilePermissions()) } + if (posixPermissions != null) { + append(", and POSIX permissions: $posixPermissions") + } + } + } + + return when (e) { + is AccessDeniedException -> AccessDeniedException(e.file, e.other, message) + is java.nio.file.AccessDeniedException -> java.nio.file.AccessDeniedException(e.file, e.otherFile, message) + // should never happen + else -> e + }.also { + it.stackTrace = e.stackTrace + } +} + +private fun tryOrLogShortException(log: Logger, block: () -> T) = try { + block() +} catch (e: Exception) { + log.warn { "${e::class.simpleName}: ${e.message}" } + null +} + +// https://github.com/corretto/corretto-21/blob/364eb35886643e504344136075f4a2442d6c0cb0/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java#L90C33-L90C78 +const val ZIP_PROPERTY_POSIX = "enablePosixFileAttributes" diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/RemoteResourceResolver.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/RemoteResourceResolver.kt new file mode 100644 index 00000000000..0c6f16fa594 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/RemoteResourceResolver.kt @@ -0,0 +1,168 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import java.io.FileInputStream +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.time.Duration +import java.time.Instant +import java.util.UUID +import java.util.concurrent.Callable +import java.util.concurrent.CompletionStage +import java.util.concurrent.atomic.AtomicBoolean + +interface RemoteResourceResolver { + fun resolve(resource: RemoteResource): CompletionStage + fun checkForUpdates(endpoint: String, eTagProvider: ETagProvider): UpdateCheckResult = UpdateCheckResult.NoUpdates + fun getLocalResourcePath(filename: String): Path? = null +} +interface RemoteResolveParser { + fun canBeParsed(data: InputStream): Boolean +} + +interface ETagProvider { + var etag: String? + fun updateETag(newETag: String?) +} + +sealed class UpdateCheckResult { + data object HasUpdates : UpdateCheckResult() + data object NoUpdates : UpdateCheckResult() + data object FirstPollCheck : UpdateCheckResult() +} + +class DefaultRemoteResourceResolver( + private val urlFetcher: UrlFetcher, + private val cacheBasePath: Path, + private val executor: (Callable) -> CompletionStage, +) : RemoteResourceResolver { + private val isFirstPoll = AtomicBoolean(true) + + override fun resolve(resource: RemoteResource): CompletionStage = executor(Callable { internalResolve(resource) }) + + override fun getLocalResourcePath(filename: String): Path? { + val expectedLocation = cacheBasePath.resolve(filename) + return expectedLocation.existsOrNull() + } + + override fun checkForUpdates(endpoint: String, eTagProvider: ETagProvider): UpdateCheckResult { + val hasETagUpdate = updateETags(eTagProvider, endpoint) + // for when we need to notify on first poll even when there's no new ETag + if (isFirstPoll.compareAndSet(true, false) && !hasETagUpdate) { + return UpdateCheckResult.FirstPollCheck + } + + return if (hasETagUpdate) { + UpdateCheckResult.HasUpdates + } else { + UpdateCheckResult.NoUpdates + } + } + + private fun internalResolve(resource: RemoteResource): Path { + val expectedLocation = cacheBasePath.resolve(resource.name) + val current = expectedLocation.existsOrNull() + if (current != null && !isExpired(current, resource)) { + LOG.debug { "Existing file ($current) for ${resource.name} is present and not expired - using it." } + return current + } + + LOG.debug { "Current file for ${resource.name} does not exist or is expired. Attempting to fetch from ${resource.urls}" } + var lastException: Exception? = null + val downloaded = resource.urls.asSequence().mapNotNull { url -> + val tmpFile = try { + Files.createTempFile("${resource.name}-${UUID.randomUUID()}", ".tmp") + } catch (e: Exception) { + LOG.warn(e) { "Error creating temporary file" } + lastException = e + return@mapNotNull null + } + LOG.debug { "Beginning file download from $url to $tmpFile" } + + try { + urlFetcher.fetch(url, tmpFile) + } catch (e: Exception) { + LOG.warn(e) { "Exception occurred downloading" } + lastException = e + tmpFile.deleteIfExists() + return@mapNotNull null + } + tmpFile + }.firstOrNull() + + val initialValue = resource.initialValue + + when { + downloaded != null -> { + LOG.debug { "Downloaded new file $downloaded, replacing old file $expectedLocation" } + val isParsingSuccess = FileInputStream(downloaded.toFile()).use { + resource.remoteResolveParser?.canBeParsed(it) ?: true + } + if (isParsingSuccess) { + Files.move(downloaded, expectedLocation, StandardCopyOption.REPLACE_EXISTING) + } + } + current != null -> LOG.debug { "No new file available - re-using current file $current" } + initialValue != null -> { + Files.copy(initialValue(), expectedLocation, StandardCopyOption.REPLACE_EXISTING) + } + else -> throw RuntimeException("Unable to resolve file $resource", lastException) + } + + return expectedLocation + } + + private fun updateETags(eTagProvider: ETagProvider, endpoint: String): Boolean { + val currentEtag = eTagProvider.etag + val remoteEtag = getEndpointETag(endpoint) + eTagProvider.etag = remoteEtag + return currentEtag != remoteEtag + } + + private fun getEndpointETag(endpoint: String): String = + try { + urlFetcher.getETag(endpoint) + } catch (e: Exception) { + LOG.warn { "Failed to fetch ETag: $e.message" } + throw e + } + + private companion object { + val LOG = getLogger() + fun Path.existsOrNull() = if (this.exists()) { + this + } else { + null + } + + fun isExpired(file: Path, resource: RemoteResource): Boolean { + val ttl = resource.ttl ?: return false + return (Duration.between(file.lastModified().toInstant(), Instant.now()) > ttl).also { + if (it) { + LOG.debug { "TTL for file $file has expired." } + } + } + } + } +} + +interface UrlFetcher { + fun fetch(url: String, file: Path) + fun getETag(url: String): String +} + +/** + * A resource that can be resolved remotely from a list of [urls], falling back to an [initialValue] if specified. + * The resource is cached at [name] until the [ttl]. + */ +interface RemoteResource { + val urls: List + val name: String + val ttl: Duration? get() = null + val initialValue: (() -> InputStream)? get() = null + val remoteResolveParser: RemoteResolveParser? get() = null +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/SensitiveField.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/SensitiveField.kt new file mode 100644 index 00000000000..5ec2705c0f2 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/SensitiveField.kt @@ -0,0 +1,50 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.superclasses + +@Target(AnnotationTarget.PROPERTY) +annotation class SensitiveField + +fun redactedString(o: Any): String { + val clazz = o::class + if (!clazz.isData) { + error("Only supports redacting data classes") + } + + return buildString { + append(clazz.simpleName) + append("(") + + val properties = o::class.memberProperties + properties.forEachIndexed { i, prop -> + append(prop.name) + append("=") + + // @Inherited does not work in Kotlin + // https://youtrack.jetbrains.com/issue/KT-22265/Support-for-inherited-annotations + if ( + prop.hasAnnotation() || + clazz.superclasses.flatMap { superClazz -> superClazz.members.filter { it.name == prop.name } }.any { it.hasAnnotation() } + ) { + if (prop.getter.call(o) == null) { + append("null") + } else { + append("") + } + } else { + append(prop.getter.call(o)) + } + + if (i != properties.size - 1) { + append(", ") + } + } + + append(")") + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/StringUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/StringUtils.kt new file mode 100644 index 00000000000..ec6ae4b39aa --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/StringUtils.kt @@ -0,0 +1,14 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +/** + * Like String.split(), but discards blank (whitespace-only) results. + */ +fun String.splitNoBlank(vararg delimiters: Char, ignoreCase: Boolean = false, limit: Int = 0): List = + split(*delimiters, ignoreCase = ignoreCase, limit = limit).filter { it.isNotBlank() } + +fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } + +fun String.htmlWrap() = """${this.replace("\n", "
")}""" diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/TextUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/TextUtils.kt new file mode 100644 index 00000000000..3d4a96a184a --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/TextUtils.kt @@ -0,0 +1,72 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import org.commonmark.node.FencedCodeBlock +import org.commonmark.node.Node +import org.commonmark.parser.Parser +import org.commonmark.renderer.NodeRenderer +import org.commonmark.renderer.html.HtmlRenderer +import org.commonmark.renderer.html.HtmlWriter + +fun convertMarkdownToHTML(markdown: String): String { + val parser: Parser = Parser.builder().build() + val document: Node = parser.parse(markdown) + val htmlRenderer: HtmlRenderer = HtmlRenderer.builder().nodeRendererFactory { CodeBlockRenderer(it.writer) }.build() + return htmlRenderer.render(document) +} + +fun extractCodeBlockLanguage(message: String): String { + // This fulfills both the cases of unit test generation(java, python) and general use case(Non java and Non python) languages. + val defaultTestGenResponseLanguage = "plaintext" + val indexStart = 3 + val codeBlockStart = message.indexOf("```") + if (codeBlockStart == -1) { + return defaultTestGenResponseLanguage + } + + val languageStart = codeBlockStart + indexStart + val languageEnd = message.indexOf('\n', languageStart) + + if (languageEnd == -1) { + return defaultTestGenResponseLanguage + } + + return message.substring(languageStart, languageEnd).trim().ifEmpty { defaultTestGenResponseLanguage } +} + +class CodeBlockRenderer(private val html: HtmlWriter) : NodeRenderer { + override fun getNodeTypes(): Set> = setOf(FencedCodeBlock::class.java) + override fun render(node: Node?) { + val codeBlock = node as FencedCodeBlock + val language = codeBlock.info + + html.line() + html.tag("div", mapOf("class" to "code-block")) + + if (language == "diff") { + codeBlock.literal.lines().forEach { + when { + it.startsWith("-") -> html.tag("div", mapOf("class" to "deletion")) + it.startsWith("+") -> html.tag("div", mapOf("class" to "addition")) + it.startsWith("@@") -> html.tag("div", mapOf("class" to "meta")) + else -> html.tag("div") + } + html.tag("pre") + html.text(it) + html.tag("/pre") + html.tag("/div") + } + } else { + html.tag("pre") + html.tag("code", mapOf("class" to "language-$language")) + html.text(codeBlock.literal) + html.tag("/code") + html.tag("/pre") + } + + html.tag("/div") + html.line() + } +} diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/Waiter.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/Waiter.kt new file mode 100644 index 00000000000..fa247fa31c3 --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/Waiter.kt @@ -0,0 +1,133 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import kotlinx.coroutines.delay +import software.amazon.awssdk.awscore.exception.AwsServiceException +import java.time.Duration +import java.time.Instant +import java.util.concurrent.CompletionException +import kotlin.math.min +import kotlin.reflect.KClass + +object Waiters { + private val LOG = getLogger() + + /** + * Creates a waiter that attempts executing the provided [call] until the specified conditions are met. + * + * @param T The response type of the [call] + * @param succeedOn The condition on the response under which the thing we are trying is complete. Defaults to if the call succeeds, we stop waiting + * @param failOn The condition on the response under which the thing we are trying has already failed and further attempts are pointless. Defaults to always try again + * @param exceptionsToStopOn The exception types that should be considered a success and stop waiting. Default to never stop on any exception + * @param exceptionsToIgnore The exception types that should be ignored if the thing we are trying throws them. Default to not ignoring any exceptions and let it bubble out + * @param maxDuration The max amount of time we want to wait for + * @param call The function we want to keep retrying + */ + suspend fun waitUntil( + succeedOn: (T) -> Boolean = { true }, + failOn: (T) -> Boolean = { false }, + exceptionsToStopOn: Set> = emptySet(), + exceptionsToIgnore: Set> = emptySet(), + maxDuration: Duration = Duration.ofMinutes(1), + // The status pulling method to get the latest resource + call: suspend () -> T, + ): T? { + val start = Instant.now() + var attempt = 0 + + while (Duration.between(start, Instant.now()) < maxDuration) { + attempt++ + + try { + val result = call() + if (succeedOn.invoke(result)) { + LOG.info { "Got expected response: $result" } + return result + } + + if (failOn.invoke(result)) { + throw WaiterUnrecoverableException("Received a response that matched the failOn predicate: $result") + } + + LOG.info { "Attempt $attempt failed predicate." } + } catch (e: WaiterUnrecoverableException) { + throw e + } catch (e: Exception) { + val cause = if (e is CompletionException) { + e.cause ?: e + } else { + e + } + + if (cause::class in exceptionsToStopOn) { + LOG.info { "Got expected exception: ${cause::class}" } + return null + } + + if (cause::class in exceptionsToIgnore) { + LOG.info { "Attempt $attempt failed with an expected exception (${cause::class})" } + } else { + throw e + } + } + + delay(calculateBackOffMs(attempt)) + } + + throw WaiterTimeoutException("Condition was not met after $attempt attempts (${Duration.between(start, Instant.now()).seconds} seconds)") + } + + private fun calculateBackOffMs(attempt: Int) = 250L shl min(attempt - 1, 4) // Exponential backoff: Max = 250 * 2^4 = 4_000 +} + +/** + * Generic waiter method to wait for a certain AWS resource to be in a steady status until it reaches in a failed status + * or time out for pulling. + * @param T The AWS resource type for querying the status from it + */ +// TODO: Migrate off of, this should not expose strings on errors +@Deprecated("Exposes localized strings, use waitUntil") +fun wait( + // The status pulling method to get the latest resource + call: () -> T, + // The success predicate based on the returned resource + success: (T) -> Boolean, + // The fail predicate based on the returned resource. Return an error message if it fails, null otherwise + fail: (T) -> String?, + // The success predicate based on the exception thrown + successByException: (AwsServiceException) -> Boolean = { false }, + // The fail predicate based on the exception thrown. Return an error message if it fails, null otherwise. + failByException: (AwsServiceException) -> String? = { null }, + // The error message for timeout this pulling process. + timeoutErrorMessage: String = "Timeout for transitioning the resource to be in the desired state.", + // The maximum attempt for pulling the resource + attempts: Int, + // A fixed time interval between the pulling + delay: Duration, +) { + repeat(attempts) { + try { + val result = call() + val errorMessage = fail(result) + when { + success(result) -> return + errorMessage != null -> throw WaiterUnrecoverableException(errorMessage) + else -> Thread.sleep(delay.toMillis()) + } + } catch (e: AwsServiceException) { + val errorMessage = failByException(e) + when { + successByException(e) -> return + errorMessage != null -> throw WaiterUnrecoverableException(errorMessage) + else -> throw e + } + } + } + throw WaiterTimeoutException(timeoutErrorMessage) +} + +class WaiterTimeoutException(message: String) : Exception(message) + +class WaiterUnrecoverableException(message: String) : Exception(message) diff --git a/plugins/core-q/core-q/src/software/amazon/q/core/utils/ZipUtils.kt b/plugins/core-q/core-q/src/software/amazon/q/core/utils/ZipUtils.kt new file mode 100644 index 00000000000..e339a707aaa --- /dev/null +++ b/plugins/core-q/core-q/src/software/amazon/q/core/utils/ZipUtils.kt @@ -0,0 +1,48 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.core.utils + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +/** + * Adds a new [ZipEntry] with the contents of [file] to the [ZipOutputStream]. + */ +fun ZipOutputStream.putNextEntry(entryName: String, file: Path) { + try { + Files.newInputStream(file).buffered().use { inputStream -> + putNextEntry(entryName, inputStream) + } + } catch (e: IOException) { + val bytes = Files.readAllBytes(file) + putNextEntry(entryName, ByteArrayInputStream(bytes).buffered()) + } +} + +/** + * Adds a new [ZipEntry] with the contents of [inputStream] to the [ZipOutputStream]. + */ +fun ZipOutputStream.putNextEntry(entryName: String, inputStream: InputStream) { + this.putNextEntry(ZipEntry(entryName)) + inputStream.copyTo(this) + this.closeEntry() +} + +/** + * Create a zip file in a temporary location. + * + * Statements included in [block] populate the zip file with entries. + * + * @return the [Path] of the temporary file + */ +fun createTemporaryZipFile(block: (ZipOutputStream) -> Unit): Path { + val file = Files.createTempFile(null, ".zip") + ZipOutputStream(Files.newOutputStream(file)).use(block) + return file +} diff --git a/plugins/core-q/core-q/tst-resources/jsonSampleFailure.json b/plugins/core-q/core-q/tst-resources/jsonSampleFailure.json new file mode 100644 index 00000000000..2bbf3fd3c18 --- /dev/null +++ b/plugins/core-q/core-q/tst-resources/jsonSampleFailure.json @@ -0,0 +1,20 @@ +{ + "partitions" : [ { + "defaults" : { + "hostname" : "{service}.{region}.{dnsSuffix}", + "protocols" : [ "https" ], + "signatureVersions" : [ "v4" ], + "variants" : [ { + "dnsSuffix" : "amazonaws.com", + "hostname" : "{service}-fips.{region}.{dnsSuffix}", + "tags" : [ "fips" ] + }, { + "dnsSuffix" : "api.aws", + "hostname" : "{service}-fips.{region}.{dnsSuffix}", + "tags" : [ "dualstack", "fips" ] + }, { + "dnsSuffix" : "api.aws", + "hostname" : "{service}.{region}.{dnsSuffix}", + "tags" : [ "dualstack" ] + } ] + }, diff --git a/plugins/core-q/core-q/tst-resources/jsonSampleSuccess.json b/plugins/core-q/core-q/tst-resources/jsonSampleSuccess.json new file mode 100644 index 00000000000..2287c5a7782 --- /dev/null +++ b/plugins/core-q/core-q/tst-resources/jsonSampleSuccess.json @@ -0,0 +1,63 @@ +{ + "partitions" : [ { + "defaults" : { + "hostname" : "{service}.{region}.{dnsSuffix}", + "protocols" : [ "https" ], + "signatureVersions" : [ "v4" ], + "variants" : [ { + "dnsSuffix" : "amazonaws.com", + "hostname" : "{service}-fips.{region}.{dnsSuffix}", + "tags" : [ "fips" ] + }, { + "dnsSuffix" : "api.aws", + "hostname" : "{service}-fips.{region}.{dnsSuffix}", + "tags" : [ "dualstack", "fips" ] + }, { + "dnsSuffix" : "api.aws", + "hostname" : "{service}.{region}.{dnsSuffix}", + "tags" : [ "dualstack" ] + } ] + }, + "dnsSuffix" : "amazonaws.com", + "partition" : "aws", + "partitionName" : "AWS Standard", + "regionRegex" : "^(us|eu|ap|sa|ca|me|af)\\-\\w+\\-\\d+$", + "regions" : { + "af-south-1" : { + "description" : "Africa (Cape Town)" + }, + "ap-east-1" : { + "description" : "Asia Pacific (Hong Kong)" + } + }, + "services" : { + "a4b" : { + "endpoints" : { + "us-east-1" : { } + } + }, + "access-analyzer" : { + "endpoints" : { + "af-south-1" : { }, + "ap-east-1" : { }, + "ca-central-1" : { + "variants" : [ { + "hostname" : "access-analyzer-fips.ca-central-1.amazonaws.com", + "tags" : [ "fips" ] + } ] + }, + "eu-west-2" : { }, + "eu-west-3" : { }, + "fips-ca-central-1" : { + "credentialScope" : { + "region" : "ca-central-1" + }, + "deprecated" : true, + "hostname" : "access-analyzer-fips.ca-central-1.amazonaws.com" + } + } + } + } + } ], + "version" : 3 +} diff --git a/plugins/core-q/core-q/tst-resources/sampleLambdaEvent.json b/plugins/core-q/core-q/tst-resources/sampleLambdaEvent.json new file mode 100644 index 00000000000..13121b6d8c3 --- /dev/null +++ b/plugins/core-q/core-q/tst-resources/sampleLambdaEvent.json @@ -0,0 +1,4 @@ +{ + "key1": "value1", + "key2": "value2" +} diff --git a/plugins/core-q/core-q/tst-resources/xmlSampleFailure.xml b/plugins/core-q/core-q/tst-resources/xmlSampleFailure.xml new file mode 100644 index 00000000000..285ca3e5776 --- /dev/null +++ b/plugins/core-q/core-q/tst-resources/xmlSampleFailure.xml @@ -0,0 +1,13 @@ + + + + + + Alexa End Session + alexa-end-session.json + + + Alexa Intent - Answer + alexa-intent-answer.json + + diff --git a/plugins/core-q/core-q/tst-resources/xmlSampleSuccess.xml b/plugins/core-q/core-q/tst-resources/xmlSampleSuccess.xml new file mode 100644 index 00000000000..7d3c143d949 --- /dev/null +++ b/plugins/core-q/core-q/tst-resources/xmlSampleSuccess.xml @@ -0,0 +1,13 @@ + + + + + + Alexa End Session + alexa-end-session.json + + + Alexa Intent - Answer + alexa-intent-answer.json + + diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt new file mode 100644 index 00000000000..1114ed00160 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt @@ -0,0 +1,66 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.credentials + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.q.core.credentials.mergeWithExistingEnvironmentVariables +import software.amazon.q.core.credentials.toEnvironmentVariables +import software.aws.toolkits.core.utils.test.aString + +class AwsCredentialsExtensionsTest { + + @Test + fun `can convert basic credentials to environment variables`() { + val credentials = AwsBasicCredentials.create(aString(), aString()) + assertThat(credentials.toEnvironmentVariables()).hasSize(2) + .containsEntry("AWS_ACCESS_KEY_ID", credentials.accessKeyId()) + .containsEntry("AWS_SECRET_ACCESS_KEY", credentials.secretAccessKey()) + } + + @Test + fun `can convert session credentials to environment variables`() { + val credentials = AwsSessionCredentials.create(aString(), aString(), aString()) + assertThat(credentials.toEnvironmentVariables()).hasSize(3) + .containsEntry("AWS_ACCESS_KEY_ID", credentials.accessKeyId()) + .containsEntry("AWS_SECRET_ACCESS_KEY", credentials.secretAccessKey()) + .containsEntry("AWS_SESSION_TOKEN", credentials.sessionToken()) + } + + @Test + fun `can add environment variables to an existing env map`() { + val credentials = AwsSessionCredentials.create(aString(), aString(), aString()) + val env = mutableMapOf() + + credentials.mergeWithExistingEnvironmentVariables(env) + + assertThat(env).containsOnlyKeys("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN") + } + + @Test + fun `existing credentials are not replaced by default`() { + val credentials = AwsBasicCredentials.create(aString(), aString()) + val existingToken = aString() + val env = mutableMapOf("AWS_SESSION_TOKEN" to existingToken) + + credentials.mergeWithExistingEnvironmentVariables(env) + + assertThat(env).hasSize(1).containsEntry("AWS_SESSION_TOKEN", existingToken) + } + + @Test + fun `existing credentials can be replaced`() { + val credentials = AwsBasicCredentials.create(aString(), aString()) + val existingToken = aString() + val env = mutableMapOf("AWS_SESSION_TOKEN" to existingToken) + + credentials.mergeWithExistingEnvironmentVariables(env, replace = true) + + assertThat(env).hasSize(2) + .containsEntry("AWS_ACCESS_KEY_ID", credentials.accessKeyId()) + .containsEntry("AWS_SECRET_ACCESS_KEY", credentials.secretAccessKey()) + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt new file mode 100644 index 00000000000..621acb14514 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt @@ -0,0 +1,29 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.credentials + +import org.mockito.kotlin.mock +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialIdentifierBase +import software.amazon.q.core.credentials.CredentialType +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.aws.toolkits.core.utils.test.aString + +fun aToolkitCredentialsProvider( + identifier: CredentialIdentifier = aCredentialsIdentifier(), + delegate: AwsCredentialsProvider = mock(), +) = ToolkitCredentialsProvider(identifier, delegate) + +fun aCredentialsIdentifier( + id: String = aString(), + displayName: String = aString(), + factoryId: String = aString(), + defaultRegionId: String? = null, +) = object : CredentialIdentifierBase(CredentialType.StaticProfile) { + override val id: String = id + override val displayName: String = displayName + override val factoryId: String = factoryId + override val defaultRegionId: String? = defaultRegionId +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt new file mode 100644 index 00000000000..c316d5cc870 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt @@ -0,0 +1,57 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.credentials + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import software.amazon.q.core.credentials.validatedSsoIdentifierFromUrl +import java.util.stream.Stream + +class SsoUrlIdentifierTest { + companion object { + class ValidArgs : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?) = Stream.of( + Arguments.of("https://test.awsapps.com/start", "test"), + Arguments.of("https://identitycenter.amazonaws.com/ssoins-something", "ssoins-something"), + Arguments.of("https://identitycenter.amazonaws.com.cn/ssoins-something", "ssoins-something"), + Arguments.of("https://identitycenter.us-gov.amazonaws.com/ssoins-something", "ssoins-something"), + Arguments.of("https://something.identitycenter.amazonaws.com/ssoins-something", "ssoins-something"), + Arguments.of("https://test.awsapps.com/start-something", "test"), + Arguments.of("https://start.us-gov-home.awsapps.com/directory/test", "test"), + Arguments.of("https://start.us-gov-east-1.us-gov-home.awsapps.com/directory/test", "test"), + Arguments.of("https://start.home.awsapps.cn/directory/test", "test"), + Arguments.of("https://start.cn-north-1.home.awsapps.cn/directory/test", "test"), + ) + } + + class InvalidArgs : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?) = Stream.of( + Arguments.of("https://identitycenter.amazonaws.com/ssoins-something;;\\];\\;\\;12\\;3''1'\\'31\\23'\\\"\"\"|\\\\_{}}}]]"), + Arguments.of("https://identitycenter.amazonaws.com/ssoins-something, scopes=[injecthere:completions]"), + Arguments.of("!@#\$%^&*()!@"), + ) + } + } + + @ParameterizedTest + @ArgumentsSource(ValidArgs::class) + fun ssoIdentifierFromUrl(input: String, expectedIdentifier: String) { + assertThat(validatedSsoIdentifierFromUrl(input)).isEqualTo(expectedIdentifier) + } + + @ParameterizedTest + @ArgumentsSource(InvalidArgs::class) + fun `throws on malformed url`(input: String) { + val exception = assertThrows { + validatedSsoIdentifierFromUrl(input) + } + + assertThat(exception).isNotNull() + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt new file mode 100644 index 00000000000..321728344c6 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt @@ -0,0 +1,107 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.lambda + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.q.core.lambda.LambdaSampleEventManifestResource +import software.amazon.q.core.lambda.LambdaSampleEventProvider +import software.amazon.q.core.lambda.LambdaSampleEventResource +import software.amazon.q.core.utils.RemoteResourceResolver +import java.util.concurrent.CompletableFuture + +class LambdaSampleEventProviderTest { + + @Rule + @JvmField + val tempPath = TemporaryFolder() + + @Test + fun canParseManifestFileAndLoadContent() { + val manifestFile = tempPath.newFile() + val firstFile = tempPath.newFile() + val secondFile = tempPath.newFile() + + manifestFile.writeText( + """ + + + First Sample + first.json + + + Second Sample + second.json + + + """.trimIndent() + ) + + val firstContent = + """ + { + "hello": "world" + } + """.trimIndent() + + firstFile.writeText(firstContent) + + val secondContent = + """ + ["hello"] + """.trimIndent() + + secondFile.writeText(secondContent) + + val resourceResolver = mock { + on { resolve(LambdaSampleEventManifestResource) }.thenReturn(CompletableFuture.completedFuture(manifestFile.toPath())) + on { resolve(LambdaSampleEventResource("first.json")) }.thenReturn(CompletableFuture.completedFuture(firstFile.toPath())) + on { resolve(LambdaSampleEventResource("second.json")) }.thenReturn(CompletableFuture.completedFuture(secondFile.toPath())) + } + val sut = LambdaSampleEventProvider(resourceResolver) + + val samples = sut.get().toCompletableFuture().get() + + assertThat(samples).hasSize(2) + + val first = samples[0] + assertThat(first.name).isEqualTo("First Sample") + assertThat(first.content.toCompletableFuture().get()).isEqualTo(firstContent) + + val second = samples[1] + assertThat(second.name).isEqualTo("Second Sample") + assertThat(second.content.toCompletableFuture().get()).isEqualTo(secondContent) + } + + @Test + fun manifestResultsAreCached() { + val manifestFile = tempPath.newFile() + + manifestFile.writeText( + """ + + + First Sample + first.json + + + """.trimIndent() + ) + + val resourceResolver = mock { + on { resolve(LambdaSampleEventManifestResource) }.thenReturn(CompletableFuture.completedFuture(manifestFile.toPath())) + } + val sut = LambdaSampleEventProvider(resourceResolver) + + sut.get().toCompletableFuture().get() + sut.get().toCompletableFuture().get() + + verify(resourceResolver, times(1)).resolve(LambdaSampleEventManifestResource) + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt new file mode 100644 index 00000000000..3559bd9a9e4 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt @@ -0,0 +1,24 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.parser + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.region.EndpointsJsonValidator + +class EndpointsJsonValidatorTest { + @Test + fun `endpoints json file parsing succeeds`() { + EndpointsJsonValidatorTest::class.java.getResourceAsStream("/jsonSampleSuccess.json").use { + assertThat(EndpointsJsonValidator.canBeParsed(it)).isTrue + } + } + + @Test + fun `endpoints json file parsing fails`() { + EndpointsJsonValidatorTest::class.java.getResourceAsStream("/jsonSampleFailure.json").use { + assertThat(EndpointsJsonValidator.canBeParsed(it)).isFalse + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt new file mode 100644 index 00000000000..76a88a42a2d --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt @@ -0,0 +1,25 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.parser + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.lambda.LambdaManifestValidator + +class LambdaManifestValidatorTest { + + @Test + fun `manifest xml file parsing succeeds`() { + LambdaManifestValidatorTest::class.java.getResourceAsStream("/xmlSampleSuccess.xml").use { + assertThat(LambdaManifestValidator.canBeParsed(it)).isTrue + } + } + + @Test + fun `manifest xml file parsing fails`() { + LambdaManifestValidatorTest::class.java.getResourceAsStream("/xmlSampleFailure.xml").use { + assertThat(LambdaManifestValidator.canBeParsed(it)).isFalse + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt new file mode 100644 index 00000000000..8234ef2b774 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt @@ -0,0 +1,23 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.parser + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.lambda.LambdaSampleEventJsonValidator +class LambdaSampleEventJsonValidatorTest { + @Test + fun `lambda sample event json file parsing succeeds`() { + LambdaSampleEventJsonValidatorTest::class.java.getResourceAsStream("/sampleLambdaEvent.json").use { + assertThat(LambdaSampleEventJsonValidator.canBeParsed(it)).isTrue + } + } + + @Test + fun `lambda sample event json file parsing fails`() { + LambdaSampleEventJsonValidatorTest::class.java.getResourceAsStream("/jsonSampleFailure.json").use { + assertThat(LambdaSampleEventJsonValidator.canBeParsed(it)).isFalse + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt new file mode 100644 index 00000000000..62c0a399802 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt @@ -0,0 +1,114 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.region + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.experimental.runners.Enclosed +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.region.mergeWithExistingEnvironmentVariables +import software.aws.toolkits.core.utils.test.aString +import kotlin.random.Random + +@RunWith(Enclosed::class) +class AwsRegionTest { + + @RunWith(Parameterized::class) + class NameAndCategorizationTest(private val region: AwsRegion, private val expectedCategory: String, private val expectedDisplayName: String) { + @Test + fun `display name should be correct`() { + assertThat(region.displayName).isEqualTo(expectedDisplayName) + } + + @Test + fun `category should match`() { + assertThat(region.category).isEqualTo(expectedCategory) + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{2}") + fun data(): Collection> = listOf( + arrayOf(AwsRegion("ap-northeast-1", "Asia Pacific (Tokyo)", "aws"), "Asia Pacific", "Tokyo (ap-northeast-1)"), + arrayOf(AwsRegion("ca-central-1", "Canada (Central)", "aws"), "North America", "Canada Central (ca-central-1)"), + arrayOf(AwsRegion("eu-central-1", "EU (Frankfurt)", "aws"), "Europe", "Frankfurt (eu-central-1)"), + arrayOf(AwsRegion("eu-south-1", "Europe (Milan)", "aws"), "Europe", "Milan (eu-south-1)"), + arrayOf(AwsRegion("sa-east-1", "South America (Sao Paulo)", "aws"), "South America", "Sao Paulo (sa-east-1)"), + arrayOf(AwsRegion("us-east-1", "US East (N. Virginia)", "aws"), "North America", "N. Virginia (us-east-1)"), + arrayOf(AwsRegion("us-west-1", "US West (N. California)", "aws"), "North America", "N. California (us-west-1)"), + arrayOf(AwsRegion("cn-north-1", "China (Beijing)", "aws"), "China", "Beijing (cn-north-1)"), + arrayOf(AwsRegion("us-gov-west-1", "AWS GovCloud (US)", "aws"), "North America", "AWS GovCloud US (us-gov-west-1)"), + arrayOf(AwsRegion("me-south-1", "Middle East (Bahrain)", "aws"), "Middle East", "Bahrain (me-south-1)"), + arrayOf(AwsRegion("af-south-1", "Africa (Cape Town)", "aws"), "Africa", "Cape Town (af-south-1)") + ) + } + } + + class ExtensionFunctionTests { + + private val region = anAwsRegion() + + @Test + fun `mergeWithExistingEnvironmentVariables puts basic settings in the map`() { + val env = mutableMapOf() + + region.mergeWithExistingEnvironmentVariables(env) + + assertThat(env).hasSize(2) + .containsEntry("AWS_REGION", region.id) + .containsEntry("AWS_DEFAULT_REGION", region.id) + } + + @Test + fun `mergeWithExistingEnvironmentVariables does not replace existing AWS_REGION`() { + val existing = aString() + val env = mutableMapOf( + "AWS_REGION" to existing + ) + + region.mergeWithExistingEnvironmentVariables(env) + + assertThat(env).hasSize(1).containsEntry("AWS_REGION", existing) + } + + @Test + fun `mergeWithExistingEnvironmentVariables does not replace existing AWS_DEFAULT_REGION`() { + val existing = aString() + val env = mutableMapOf( + "AWS_DEFAULT_REGION" to existing + ) + + region.mergeWithExistingEnvironmentVariables(env) + + assertThat(env).hasSize(1).containsEntry("AWS_DEFAULT_REGION", existing) + } + + @Test + fun `mergeWithExistingEnvironmentVariables can force replace existing`() { + val existing = aString() + val env = mutableMapOf( + "AWS_REGION" to existing, + "AWS_DEFAULT_REGION" to existing + ) + + region.mergeWithExistingEnvironmentVariables(env, replace = true) + + assertThat(env).hasSize(2) + .containsEntry("AWS_REGION", region.id) + .containsEntry("AWS_DEFAULT_REGION", region.id) + } + } +} + +fun anAwsRegion(id: String = aRegionId(), name: String = aString(), partitionId: String = aString()) = + AwsRegion(id, name, partitionId) + +fun aRegionId(): String { + val prefix = arrayOf("af", "us", "ca", "eu", "ap", "me", "cn").random() + val compass = arrayOf("north", "south", "east", "west", "central").random() + val count = Random.nextInt(1, 100) + return "$prefix-$compass-$count" +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt new file mode 100644 index 00000000000..d71edd7894d --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt @@ -0,0 +1,25 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.region + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.region.PartitionParser +import software.aws.toolkits.resources.BundledResources + +class PartitionParserTest { + @Test + fun canLoadPartitionsFromEndpointsFile() { + val partitions = PartitionParser.parse(BundledResources.ENDPOINTS_FILE)!! + val awsPartition = partitions.getPartition("aws") + + val iam = awsPartition.services.getValue("iam") + val s3 = awsPartition.services.getValue("s3") + val lambda = awsPartition.services.getValue("lambda") + + assertThat(iam.isGlobal).isTrue() + assertThat(s3.isGlobal).isFalse() + assertThat(lambda.isGlobal).isFalse() + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt new file mode 100644 index 00000000000..44494c5d85e --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt @@ -0,0 +1,53 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.rules + +import org.junit.rules.ExternalResource +import software.amazon.awssdk.services.ecs.EcsClient +import software.amazon.awssdk.services.ecs.model.CreateServiceRequest +import software.amazon.awssdk.services.ecs.model.Service +import software.amazon.awssdk.services.ecs.model.ServiceNotFoundException +import software.aws.toolkits.core.utils.RuleUtils + +class ECSTemporaryServiceRule(val ecsClient: EcsClient) : ExternalResource() { + private val services = mutableListOf() + + /** + * Creates a temporary service. A random name with the generated prefix will be generated if a name is not provided in the request. + */ + fun createService(prefix: String = RuleUtils.prefixFromCallingClass(), serviceBuilder: (CreateServiceRequest.Builder) -> Unit): Service { + val service = ecsClient.createService { + it.serviceName(RuleUtils.randomName(prefix)) + serviceBuilder(it) + }.service() + + services.add(service) + return service + } + + override fun after() { + val exceptions = services.mapNotNull { service -> + val clusterArn = service.clusterArn() + val serviceArn = service.serviceArn() + deleteService(clusterArn, serviceArn) + } + if (exceptions.isNotEmpty()) { + throw RuntimeException("Failed to delete all services. \n\t- ${exceptions.map { it.message }.joinToString("\n\t- ")}") + } + } + + private fun deleteService(cluster: String, service: String): Exception? = try { + ecsClient.deleteService { + it.cluster(cluster) + it.service(service) + it.force(true) + } + null + } catch (e: Exception) { + when (e) { + is ServiceNotFoundException -> null + else -> RuntimeException("Failed to delete service: $service - ${e.message}", e) + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt new file mode 100644 index 00000000000..4db7f61370f --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt @@ -0,0 +1,48 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.rules + +import org.junit.rules.ExternalResource +import software.amazon.awssdk.services.ecr.EcrClient +import software.amazon.awssdk.services.ecr.model.Repository +import software.amazon.awssdk.services.ecr.model.RepositoryNotFoundException +import software.aws.toolkits.core.utils.RuleUtils + +class EcrTemporaryRepositoryRule(private val ecrClientSupplier: () -> EcrClient) : ExternalResource() { + constructor(ecrClient: EcrClient) : this({ ecrClient }) + + private val repositories = mutableListOf() + + /** + * Creates a temporary repository with the optional prefix (or calling class if prefix is omitted) + */ + fun createRepository(prefix: String = RuleUtils.prefixFromCallingClass()): Repository { + val repositoryName: String = RuleUtils.randomName(prefix).lowercase() + val client = ecrClientSupplier() + + // note there is no waiter for this + val repo = client.createRepository { it.repositoryName(repositoryName) } + + repositories.add(repositoryName) + + return repo.repository() + } + + override fun after() { + val exceptions = repositories.mapNotNull { deleteRepository(it) } + if (exceptions.isNotEmpty()) { + throw RuntimeException("Failed to delete all repositories. \n\t- ${exceptions.map { it.message }.joinToString("\n\t- ")}") + } + } + + private fun deleteRepository(repository: String): Exception? = try { + ecrClientSupplier().deleteRepository { it.repositoryName(repository).force(true) } + null + } catch (e: Exception) { + when (e) { + is RepositoryNotFoundException -> null + else -> RuntimeException("Failed to delete repository: $repository - ${e.message}", e) + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt new file mode 100644 index 00000000000..166de73b8f3 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt @@ -0,0 +1,66 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.rules + +import org.junit.rules.ExternalResource +import java.security.AccessController +import java.security.PrivilegedAction + +/** + * A utility that can temporarily forcibly set environment variables and + * then allows resetting them to the original values. + */ +class EnvironmentVariableHelper : ExternalResource() { + private val originalEnvironmentVariables = System.getenv().toMap() + private val modifiableMap = getProcessEnvMap() ?: getEnvMap() + + @Volatile + private var mutated = false + + fun remove(vararg keys: String) { + mutated = true + keys.forEach { modifiableMap.remove(it) } + } + + operator fun set(key: String, value: String) { + mutated = true + modifiableMap[key] = value + } + + private fun getEnvMap(): MutableMap = getField(System.getenv().javaClass, System.getenv(), "m")!! + + private fun getProcessEnvMap(): MutableMap? { + val processEnvironment = Class.forName("java.lang.ProcessEnvironment") + return getField(processEnvironment, null, "theCaseInsensitiveEnvironment") + } + + private fun getField(processEnvironment: Class<*>, obj: Any?, fieldName: String): MutableMap? = try { + val declaredField = processEnvironment.getDeclaredField(fieldName) + AccessController.doPrivileged( + PrivilegedAction { + declaredField.isAccessible = true + } + ) + @Suppress("UNCHECKED_CAST") + declaredField.get(obj) as MutableMap + } catch (_: NoSuchFieldException) { + null + } + + private fun reset() { + if (mutated) { + synchronized(this) { + if (mutated) { + modifiableMap.clear() + modifiableMap.putAll(originalEnvironmentVariables) + mutated = false + } + } + } + } + + override fun after() { + reset() + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt new file mode 100644 index 00000000000..dc120535de6 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt @@ -0,0 +1,50 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.rules + +import org.junit.rules.ExternalResource +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.NoSuchBucketException +import software.amazon.q.core.s3.deleteBucketAndContents +import software.aws.toolkits.core.utils.RuleUtils + +class S3TemporaryBucketRule(private val s3ClientSupplier: () -> S3Client) : ExternalResource() { + constructor(s3Client: S3Client) : this({ s3Client }) + + private val buckets = mutableListOf() + + /** + * Creates a temporary bucket with the optional prefix (or calling class if prefix is omitted) + */ + fun createBucket(prefix: String = RuleUtils.prefixFromCallingClass()): String { + val bucketName: String = RuleUtils.randomName(prefix).lowercase() + val client = s3ClientSupplier() + + client.createBucket { it.bucket(bucketName) } + + // Wait for bucket to be ready + client.waiter().waitUntilBucketExists { it.bucket(bucketName) } + + buckets.add(bucketName) + + return bucketName + } + + override fun after() { + val exceptions = buckets.mapNotNull { deleteBucketAndContents(it) } + if (exceptions.isNotEmpty()) { + throw RuntimeException("Failed to delete all buckets. \n\t- ${exceptions.map { it.message }.joinToString("\n\t- ")}") + } + } + + private fun deleteBucketAndContents(bucket: String): Exception? = try { + s3ClientSupplier().deleteBucketAndContents(bucket) + null + } catch (e: Exception) { + when (e) { + is NoSuchBucketException -> null + else -> RuntimeException("Failed to delete bucket: $bucket - ${e.message}", e) + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt new file mode 100644 index 00000000000..cfeca9ccb65 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt @@ -0,0 +1,25 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.rules + +import org.junit.rules.ExternalResource +import java.util.Properties + +/** + * A utility that can temporarily forcibly set system properties and + * then allows resetting them to the original values. + */ +class SystemPropertyHelper : ExternalResource() { + private lateinit var originalProperties: Properties + + override fun before() { + originalProperties = Properties().apply { + this.putAll(System.getProperties()) + } + } + + override fun after() { + System.setProperties(originalProperties) + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt new file mode 100644 index 00000000000..edac2ef845b --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt @@ -0,0 +1,207 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.telemetry + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.anyCollection +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyBlocking +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.stubbing.Answer +import software.amazon.awssdk.core.exception.SdkServiceException +import software.amazon.q.core.telemetry.DefaultMetricEvent +import software.amazon.q.core.telemetry.DefaultTelemetryBatcher +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryPublisher +import java.time.Instant +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class TelemetryBatcherTest { + private var publisher: TelemetryPublisher = mock() + private var batcher = DefaultTelemetryBatcher(publisher, MAX_BATCH_SIZE, MAX_QUEUE_SIZE) + + @Before + fun setUp() { + batcher.onTelemetryEnabledChanged(true) + } + + @Test + fun testSingleBatch() { + val publishCountDown = CountDownLatch(1) + val publishCaptor = argumentCaptor>() + + publisher.stub { + onBlocking { publisher.publish(publishCaptor.capture()) } + .doAnswer(createPublishAnswer(publishCountDown)) + } + + batcher.enqueue(DefaultMetricEvent.builder().build()) + batcher.flush(false) + + waitForPublish(publishCountDown) + + verifyBlocking(publisher) { publish(anyCollection()) } + + assertThat(publishCaptor.firstValue).hasSize(1) + } + + @Test + fun testSplitBatch() { + val publishCaptor = argumentCaptor>() + + val totalEvents = MAX_BATCH_SIZE + 1 + repeat(totalEvents) { + batcher.enqueue(createEmptyMetricEvent()) + } + batcher.flush(false) + + verifyBlocking(publisher, times(2)) { publish(publishCaptor.capture()) } + + assertThat(publishCaptor.allValues).hasSize(2) + assertThat(publishCaptor.firstValue).hasSize(MAX_BATCH_SIZE) + assertThat(publishCaptor.secondValue).hasSize(1) + } + + @Test + fun testRetryException() { + val publishCaptor = argumentCaptor>() + + publisher.stub { + onBlocking { publisher.publish(anyCollection()) } + .doThrow(RuntimeException("Mock exception")) + } + + batcher.enqueue(createEmptyMetricEvent()) + batcher.flush(true) + + verifyBlocking(publisher, times(1)) { publish(publishCaptor.capture()) } + + assertThat(publishCaptor.allValues).hasSize(1) + assertThat(batcher.eventQueue).hasSize(1) + } + + @Test + fun testDontRetry400Exception() { + val publishCaptor = argumentCaptor>() + + publisher.stub { + onBlocking { publisher.publish(anyCollection()) } + .doThrow(SdkServiceException.builder().statusCode(400).build()) + .doAnswer(Answer {}) + } + + batcher.enqueue(createEmptyMetricEvent()) + batcher.flush(true) + + verifyBlocking(publisher, times(1)) { publish(publishCaptor.capture()) } + + assertThat(publishCaptor.allValues).hasSize(1) + assertThat(batcher.eventQueue).hasSize(0) + } + + @Test + fun testDispose() { + val publishCaptor = argumentCaptor>() + + batcher.enqueue(createEmptyMetricEvent()) + batcher.shutdown() + batcher.enqueue(createEmptyMetricEvent()) + batcher.shutdown() + + verifyBlocking(publisher) { publish(publishCaptor.capture()) } + + assertThat(publishCaptor.allValues).hasSize(1) + assertThat(publishCaptor.firstValue.toList()).hasSize(1) + } + + @Test + fun testNotSendingWhileDisabled() { + batcher.onTelemetryEnabledChanged(false) + + batcher.enqueue(createEmptyMetricEvent()) + batcher.flush(false) + + verifyNoMoreInteractions(publisher) + + assertThat(batcher.eventQueue).isEmpty() + } + + @Test + fun verifyOptOut() { + batcher.enqueue(createEmptyMetricEvent()) + assertThat(batcher.eventQueue).isNotEmpty + + batcher.onTelemetryEnabledChanged(false) + + batcher.flush(false) + + verifyNoMoreInteractions(publisher) + + assertThat(batcher.eventQueue).isEmpty() + } + + @Test + fun verifyEnablementCallbackTrue() { + val mockCallback: (Boolean) -> Unit = mock() + + batcher.onTelemetryEnabledChanged(true, mockCallback) + verify(mockCallback, times(1)).invoke(true) + } + + @Test + fun verifyEnablementCallbackFalse() { + val mockCallback: (Boolean) -> Unit = mock() + + batcher.onTelemetryEnabledChanged(false, mockCallback) + verify(mockCallback, times(1)).invoke(false) + } + + @Test + fun verifyCallbackMetricPublished() { + batcher.enqueue(createEmptyMetricEvent()) + val publishCountDown = CountDownLatch(1) + val publishCaptor = argumentCaptor>() + + publisher.stub { + onBlocking { publisher.publish(publishCaptor.capture()) } + .doAnswer(createPublishAnswer(publishCountDown)) + } + + val fooMetricEvent = DefaultMetricEvent.builder() + .createTime(Instant.now()) + .build() + batcher.onTelemetryEnabledChanged(false) { batcher.enqueue(createEmptyMetricEvent()) } + waitForPublish(publishCountDown) + + verifyBlocking(publisher) { publish(anyCollection()) } + + assertThat(publishCaptor.firstValue).hasSize(1) + assertThat(publishCaptor.firstValue.contains(fooMetricEvent)) + } + + private fun createEmptyMetricEvent(): MetricEvent = DefaultMetricEvent.builder().build() + + private fun waitForPublish(publishCountDown: CountDownLatch) { + // Wait for maximum of 5 secs before thread continues, may not reach final count though + publishCountDown.await(5, TimeUnit.SECONDS) + } + + private fun createPublishAnswer(publishCountDown: CountDownLatch): Answer = Answer { + publishCountDown.countDown() + } + + companion object { + private const val MAX_BATCH_SIZE = 5 + private const val MAX_QUEUE_SIZE = 10 + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt new file mode 100644 index 00000000000..6021056643d --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt @@ -0,0 +1,28 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.utils.replace + +class CollectionUtilsTest { + + @Test + fun `collection items are replaced`() { + val source = mutableListOf("hello") + + source.replace(listOf("world")) + + assertThat(source).containsOnly("world") + } + + @Test + fun `map entries are replaced`() { + val source = mutableMapOf("foo" to "bar") + source.replace(mapOf("hello" to "world")) + + assertThat(source).containsOnlyKeys("hello") + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt new file mode 100644 index 00000000000..18aa9841259 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt @@ -0,0 +1,17 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.jetbrains.annotations.TestOnly +import java.util.concurrent.CompletionStage +import java.util.concurrent.ExecutionException + +// Wait for a completion stage to end, and throw the exception that caused it to fail +// if it fails. +@TestOnly +fun CompletionStage.unwrap(): T = try { + this.toCompletableFuture().get() +} catch (e: ExecutionException) { + throw e.cause ?: e +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt new file mode 100644 index 00000000000..b59d463bef2 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt @@ -0,0 +1,54 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.mockito.Mockito +import org.mockito.invocation.InvocationOnMock +import org.mockito.kotlin.KStubbing +import org.mockito.kotlin.withSettings +import org.mockito.stubbing.Answer +import software.amazon.awssdk.core.SdkClient +import software.amazon.awssdk.core.SdkRequest +import kotlin.reflect.full.isSubclassOf + +/** + * Answer that inspects the target invocation and determines if it should call the real method or respond with a mock answer. + * This is tied to the implementation of the Java SDK V2's generated client interfaces where lambda based calls use a default implementation to delegate + * eventually down to a method that takes a built SdkRequest type. The final concrete method is coded to always throw an exception so that is the method that we + * will mock. + * + * This will handle "simple" methods that they generate as well such as ListBuckets that takes 0 arguments. + */ +class DelegateSdkConsumers(private val sdkClass: Class<*>) : Answer { + override fun answer(invocation: InvocationOnMock): Any? { + val method = invocation.method + return if (method.name == "waiter") { + createWaiter(invocation.method.returnType, invocation) + } else if (method.isDefault && method?.parameters?.getOrNull(0)?.type?.kotlin?.isSubclassOf(SdkRequest::class) != true) { + invocation.callRealMethod() + } else { + Mockito.RETURNS_DEFAULTS.answer(invocation) + } + } + + private fun createWaiter(waiterType: Class<*>, invocation: InvocationOnMock): Any? { + val builder = waiterType.getDeclaredMethod("builder").invoke(null) + with(builder::class.java) { + getDeclaredMethod("client", sdkClass).invoke(builder, invocation.mock) + return getDeclaredMethod("build").invoke(builder) + } + } +} + +inline fun delegateMock(verboseLogging: Boolean = false): T = Mockito.mock( + T::class.java, + withSettings( + verboseLogging = verboseLogging, + defaultAnswer = DelegateSdkConsumers(T::class.java) + ) +) + +inline fun delegateMock(stubbing: KStubbing.(T) -> Unit): T = delegateMock().apply { + KStubbing(this).stubbing(this) +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt new file mode 100644 index 00000000000..48b3a625357 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt @@ -0,0 +1,41 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test +import software.amazon.q.core.utils.Either +import software.amazon.q.core.utils.xor + +class EitherTest { + @Test + fun basicLeftFunctionality() { + val result: Either = maybeString(false) xor maybeInt(true) + assertThat(result).isInstanceOfSatisfying(Either.Left::class.java) { + assertThat(it.value).isEqualTo("foo") + } + } + + @Test + fun basicRightFunctionality() { + val result: Either = maybeString(true) xor maybeInt(false) + assertThat(result).isInstanceOfSatisfying(Either.Right::class.java) { + assertThat(it.value).isEqualTo(50) + } + } + + @Test + fun cantBothBeNonNull() { + assertThatThrownBy { maybeString(false) xor maybeInt(false) }.isInstanceOf(IllegalArgumentException::class.java) + } + + @Test + fun cantBothBeNull() { + assertThatThrownBy { maybeString(true) xor maybeInt(true) }.isInstanceOf(IllegalArgumentException::class.java) + } + + private fun maybeString(isNull: Boolean): String? = if (isNull) null else "foo" + private fun maybeInt(isNull: Boolean): Int? = if (isNull) null else 50 +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt new file mode 100644 index 00000000000..cbdce661ee3 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt @@ -0,0 +1,17 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.utils.tryOrNull + +class ExceptionUtilsTest { + @Test + fun exceptionsAreNotBubbled() { + @Suppress("DIVISION_BY_ZERO") + val result = tryOrNull { 1 / 0 } + assertThat(result).isNull() + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt new file mode 100644 index 00000000000..db5648b40ea --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt @@ -0,0 +1,39 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider +import software.amazon.awssdk.services.sts.model.AssumeRoleRequest +import software.amazon.awssdk.utils.SdkAutoCloseable +import java.util.UUID + +/** + * Creates an [AwsCredentialsProvider] meant to be used in integration tests + * + * If the environment variable `ASSUME_ROLE_ARN` is set, it will be assumed using the default credential chain as the source credentials. + * If it is not set, we will just use the default credential provider chain + */ +fun createIntegrationTestCredentialProvider(): AwsCredentialsProvider { + // TODO: Finish https://github.com/aws/aws-toolkit-jetbrains/pull/2193 and revert back to Default Chain + val defaultCredentials = ContainerCredentialsProvider.builder().build() + + return System.getenv("ASSUME_ROLE_ARN")?.takeIf { it.isNotEmpty() }?.let { role -> + val sessionName = UUID.randomUUID().toString() + val stsClient = StsClient.builder().credentialsProvider(defaultCredentials).build() + val credentialsProvider = StsAssumeRoleCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(AssumeRoleRequest.builder().roleArn(role).roleSessionName(sessionName).build()) + .build() + + // Wrap this in SdkAutoClosable so we have a hook to close this STS client else IntelliJ will say we thread leak + return object : AwsCredentialsProvider by credentialsProvider, SdkAutoCloseable { + override fun close() { + stsClient.close() + } + } + } ?: defaultCredentials +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt new file mode 100644 index 00000000000..867f35bce8b --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt @@ -0,0 +1,190 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +@file:Suppress("LazyLog") +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import org.slf4j.Logger +import org.slf4j.event.Level +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.log +import software.amazon.q.core.utils.logWhenNull +import software.amazon.q.core.utils.trace +import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.core.utils.tryOrThrow +import software.amazon.q.core.utils.tryOrThrowNullable +import software.amazon.q.core.utils.warn + +class LogUtilsTest { + + private val log = mock() + + @Test + fun exceptionIsLoggedAndSuppressedInTryOrNull() { + val expectedException = RuntimeException("Boom") + log.tryOrNull("message", level = Level.WARN) { throw expectedException } + verify(log).warn(any(), eq(expectedException)) + } + + @Test + fun exceptionIsLoggedAndBubbledInTryOrThrowNullable() { + val expectedException = RuntimeException("Boom") + val exception = catch { log.tryOrThrowNullable("message") { throw expectedException } } + verify(log).error(any(), eq(expectedException)) + assertThat(exception).isEqualTo(expectedException) + } + + @Test + fun exceptionIsLoggedAndBubbledInTryOrThrow() { + val expectedException = RuntimeException("Boom") + val exception = catch { log.tryOrThrow("message") { throw expectedException } } + verify(log).error(any(), eq(expectedException)) + assertThat(exception).isEqualTo(expectedException) + } + + @Test + fun nullableIsNotOkInTryOrThrow() { + val exception = catch { log.tryOrThrow("message") { mightBeNull(shouldBeNull = true) } } + verify(log).error(any(), eq(exception)) + } + + @Test + fun smartCastToNonNullOnTryOrThrow() { + val nullableValue: String = log.tryOrThrow("message") { mightBeNull(shouldBeNull = false) } + val nonNullableValue: String = log.tryOrThrow("message") { willNeverBeNull() } + assertThat(nullableValue).isEqualTo(nonNullableValue) + } + + @Test + fun nullableIsOkInTryOrThrowNullable() { + log.tryOrThrowNullable("message") { null } + verifyNoMoreInteractions(log) + } + + @Test + fun nullIsOkInTryOrNull() { + log.tryOrNull("message") { null } + verifyNoMoreInteractions(log) + } + + @Test + fun conditionalLazyLoggingInfo() { + val exception = RuntimeException("Boom") + + log.info(exception) { "message" } + + whenever(log.isInfoEnabled).thenReturn(false) + log.info(exception) { "message" } + + verify(log).info("message", exception) + } + + @Test + fun conditionalLazyLoggingDebug() { + val exception = RuntimeException("Boom") + + log.debug(exception) { "message" } + + whenever(log.isDebugEnabled).thenReturn(false) + log.debug(exception) { "message" } + + verify(log).debug("message", exception) + } + + @Test + fun conditionalLazyLoggingWarn() { + val exception = RuntimeException("Boom") + + log.warn(exception) { "message" } + + whenever(log.isWarnEnabled).thenReturn(false) + log.warn(exception) { "message" } + + verify(log).warn("message", exception) + } + + @Test + fun conditionalLazyLoggingError() { + val exception = RuntimeException("Boom") + + log.error(exception) { "message" } + + whenever(log.isErrorEnabled).thenReturn(false) + log.error(exception) { "message" } + + verify(log).error("message", exception) + } + + @Test + fun conditionalLazyLoggingTrace() { + val exception = RuntimeException("Boom") + + log.trace(exception) { "message" } + + whenever(log.isTraceEnabled).thenReturn(false) + log.trace(exception) { "message" } + + verify(log).trace("message", exception) + } + + @Test + fun canLogAtDifferentLevels() { + val exception = RuntimeException("Boom") + + log.log(Level.TRACE) { "trace" } + log.log(Level.INFO) { "info" } + log.log(Level.ERROR, exception = exception) { "error" } + log.log(Level.WARN) { "warn" } + log.log(Level.DEBUG) { "debug" } + + verify(log).trace("trace", null) + verify(log).info("info", null) + verify(log).error("error", exception) + verify(log).warn("warn", null) + verify(log).debug("debug", null) + } + + @Test + fun logWhenNull() { + log.logWhenNull("message", level = Level.WARN) { null } + verify(log).warn("message", null) + } + + @Before + fun setup() { + reset(log) + whenever(log.isInfoEnabled).thenReturn(true) + whenever(log.isWarnEnabled).thenReturn(true) + whenever(log.isTraceEnabled).thenReturn(true) + whenever(log.isErrorEnabled).thenReturn(true) + whenever(log.isDebugEnabled).thenReturn(true) + } + + private fun catch(block: () -> Unit): Exception = try { + block() + throw AssertionError("Expected exception") + } catch (e: Exception) { + e + } + + private fun mightBeNull(shouldBeNull: Boolean): String? = if (shouldBeNull) { + null + } else { + "hello" + } + + @Suppress("FunctionOnlyReturningConstant") + private fun willNeverBeNull(): String = "hello" +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt new file mode 100644 index 00000000000..21f37d5d316 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt @@ -0,0 +1,202 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.mockito.invocation.InvocationOnMock +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.q.core.lambda.LambdaManifestValidator +import software.amazon.q.core.utils.DefaultRemoteResourceResolver +import software.amazon.q.core.utils.RemoteResolveParser +import software.amazon.q.core.utils.RemoteResource +import software.amazon.q.core.utils.UrlFetcher +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.writeText +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import java.time.Duration +import java.util.concurrent.Callable +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage + +class RemoteResourceResolverTest { + + @Rule + @JvmField + val tempPath = TemporaryFolder() + + @Test + fun canDownloadAFileOnce() { + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.doAnswer(writeDataToFile("data")) + } + + val cachePath = tempPath.newFolder().toPath() + val sut = DefaultRemoteResourceResolver(urlFetcher, cachePath, immediateExecutor) + + val resource = resource() + + val firstCall = sut.resolve(resource).unwrap() + val secondCall = sut.resolve(resource).unwrap() + + assertThat(firstCall).isEqualTo(secondCall) + assertThat(firstCall).hasContent("data") + verify(urlFetcher).fetch(eq(PRIMARY_URL), any()) + assertThat(Files.list(cachePath).use { it.toList() }).hasSize(1) + } + + @Test + fun expiredFileIsDownloadedAgain() { + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.doAnswer(writeDataToFile("data1")).doAnswer(writeDataToFile("data2")) + } + + val sut = DefaultRemoteResourceResolver(urlFetcher, tempPath.newFolder().toPath(), immediateExecutor) + + val resource = resource(ttl = Duration.ofMillis(1)) + + val firstCall = sut.resolve(resource).toCompletableFuture().get() + Thread.sleep(100) + val secondCall = sut.resolve(resource).toCompletableFuture().get() + + assertThat(firstCall).isEqualTo(secondCall) + + assertThat(secondCall).hasContent("data2") + verify(urlFetcher, times(2)).fetch(eq(PRIMARY_URL), any()) + } + + @Test + fun downloadedParseFailedSkippedMove() { + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.doAnswer(writeDataToFile(FAIL)) + } + + val cachePath = tempPath.newFolder().toPath() + val sut = DefaultRemoteResourceResolver(urlFetcher, cachePath, immediateExecutor) + + val resource = xmlResource() + + val firstCall = sut.resolve(resource).unwrap() + val secondCall = sut.resolve(resource).unwrap() + + assertThat(firstCall).isEqualTo(secondCall) + assertThat(firstCall.exists()).isFalse() + } + + @Test + fun failureToHitUrlFallsBackToCurrentCopy() { + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.doAnswer(writeDataToFile("data")).thenThrow(RuntimeException("BOOM!")) + } + + val sut = DefaultRemoteResourceResolver(urlFetcher, tempPath.newFolder().toPath(), immediateExecutor) + + val resource = resource(ttl = Duration.ofMillis(1)) + + val firstCall = sut.resolve(resource).toCompletableFuture().get() + val secondCall = sut.resolve(resource).toCompletableFuture().get() + + assertThat(firstCall).isEqualTo(secondCall) + assertThat(firstCall).hasContent("data") + } + + @Test + fun usesInitialValueIfNoOtherValueAvailable() { + val initialValue = "initialValue".byteInputStream() + + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.thenThrow(RuntimeException("BOOM!")) + } + + val sut = DefaultRemoteResourceResolver(urlFetcher, tempPath.newFolder().toPath(), immediateExecutor) + + val resource = resource(initialValue = initialValue) + + val result = sut.resolve(resource).toCompletableFuture().get() + assertThat(result).hasContent("initialValue") + } + + @Test(expected = RuntimeException::class) + fun noCurrentOrInitialValueAndExceptionHittingUrlBubbles() { + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.thenThrow(RuntimeException("BOOM!")) + } + + val sut = DefaultRemoteResourceResolver(urlFetcher, tempPath.newFolder().toPath(), immediateExecutor) + + sut.resolve(resource()).toCompletableFuture().get() + } + + @Test + fun canFallbackDownListOfUrls() { + val urlFetcher = mock { + on { fetch(eq(PRIMARY_URL), any()) }.thenThrow(RuntimeException("BOOM!")) + on { fetch(eq(SECONDARY_URL), any()) }.doAnswer(writeDataToFile("data")) + } + + val sut = DefaultRemoteResourceResolver(urlFetcher, tempPath.newFolder().toPath(), immediateExecutor) + + assertThat(sut.resolve(resource(urls = listOf(PRIMARY_URL, SECONDARY_URL))).toCompletableFuture().get()).hasContent("data") + } + + private companion object { + val LOG = getLogger() + + fun resource( + name: String = "resource", + urls: List = listOf(PRIMARY_URL), + ttl: Duration? = Duration.ofMillis(1000), + initialValue: InputStream? = null, + ) = object : RemoteResource { + override val urls: List = urls + override val name: String = name + override val ttl: Duration? = ttl + override val initialValue = initialValue?.let { { it } } + } + + fun xmlResource( + name: String = "resource", + urls: List = listOf(PRIMARY_URL), + ttl: Duration? = Duration.ofMillis(1000), + initialValue: InputStream? = null, + remoteResolveParser: RemoteResolveParser? = LambdaManifestValidator, + ) = object : RemoteResource { + override val urls: List = urls + override val name: String = name + override val ttl: Duration? = ttl + override val initialValue = initialValue?.let { { it } } + override val remoteResolveParser: RemoteResolveParser = remoteResolveParser as LambdaManifestValidator + } + + fun writeDataToFile(data: String): (InvocationOnMock) -> Unit = { invocation -> + val path = invocation.arguments[1] as Path + path.writeText(data) + // It's possible for it to be done writing but path.exists to not work yet which + // makes the canDownloadAFileOnce test fail (on CodeBuild). + while (!path.exists()) { + LOG.debug { "writeDataToFile path does not exist yet: $path" } + Thread.sleep(10) + } + } + + val immediateExecutor: (Callable) -> CompletionStage = { CompletableFuture.completedFuture(it.call()) } + + const val PRIMARY_URL = "http://example.com" + const val SECONDARY_URL = "http://example2.com" + const val FAIL = "" + + "data" + + "<>" + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt new file mode 100644 index 00000000000..5556fe2ba52 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt @@ -0,0 +1,21 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import java.util.Random + +object RuleUtils { + fun randomName(prefix: String = "a", length: Int = 63): String { + val characters = ('0'..'9') + ('A'..'Z') + ('a'..'Z') + val userName = System.getProperty("user.name", "unknown") + return "${prefix.lowercase()}-${userName.lowercase()}-${List(length) { characters.random() }.joinToString("")}".take(length) + } + + fun prefixFromCallingClass(): String { + val callingClass = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).callerClass + return callingClass.simpleName + } + + fun randomNumber(min: Int = 0, max: Int = 65535): Int = Random().nextInt(max - min + 1) + min +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt new file mode 100644 index 00000000000..c1402150fd6 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt @@ -0,0 +1,27 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test + +class RuleUtilsTest { + private lateinit var callingClass: String + + @Before + fun setUp() { + callingClass = RuleUtils.prefixFromCallingClass() + } + + @Test + fun `late init before works`() { + assertThat(callingClass).isEqualTo("RuleUtilsTest") + } + + @Test + fun `inline works`() { + assertThat(RuleUtils.prefixFromCallingClass()).isEqualTo("RuleUtilsTest") + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt new file mode 100644 index 00000000000..03daa790ee2 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt @@ -0,0 +1,71 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.fail +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import software.amazon.q.core.utils.createTemporaryZipFile +import software.amazon.q.core.utils.putNextEntry +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +class ZipUtilsTest { + + @Rule + @JvmField + val tmpFolder = TemporaryFolder() + var zipFile: Path? = null + + @After fun cleanup() { + if (zipFile != null) { + Files.delete(zipFile) + } + } + + @Test fun fileCanBeAddedToAZip() { + val fileToAdd = tmpFolder.newFile() + fileToAdd.writeText("hello world", StandardCharsets.UTF_8) + val zipFile = tmpFolder.newFile("blah.zip")?.toPath() ?: return fail("Couldn't create new file") + + ZipOutputStream(Files.newOutputStream(zipFile)).use { + it.putNextEntry("file.txt", fileToAdd.toPath()) + } + + assertZipContainsHelloWorldFile(zipFile) + } + + @Test fun inputStreamCanBeAddedToAZip() { + val zipFile = tmpFolder.newFile("blah.zip")?.toPath() ?: return fail("Couldn't create new file") + ZipOutputStream(Files.newOutputStream(zipFile)).use { + it.putNextEntry("file.txt", "hello world".byteInputStream(StandardCharsets.UTF_8)) + } + + assertZipContainsHelloWorldFile(zipFile) + } + + @Test fun shortcutToCreateATemporaryZip() { + zipFile = createTemporaryZipFile { + it.putNextEntry("file.txt", "hello world".byteInputStream(StandardCharsets.UTF_8)) + } + + assertZipContainsHelloWorldFile(zipFile!!) + } + + private fun assertZipContainsHelloWorldFile(zipFile: Path) { + ZipFile(zipFile.toFile()).use { actualZip -> + val actualEntry = actualZip.entries().toList().find { it.name == "file.txt" } + + assertThat(actualEntry).isNotNull + val contents = actualZip.getInputStream(actualEntry).bufferedReader(StandardCharsets.UTF_8).use { it.readText() } + assertThat(contents).isEqualTo("hello world") + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt new file mode 100644 index 00000000000..ee005af612a --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt @@ -0,0 +1,29 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils.test + +import org.assertj.core.api.AbstractIterableAssert +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.ListAssert +import org.assertj.core.api.ObjectAssert +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.PosixFilePermissions + +@Suppress("UNCHECKED_CAST") +val ObjectAssert.notNull: ObjectAssert + get() = this.isNotNull as ObjectAssert + +@Suppress("UNCHECKED_CAST") +inline fun AbstractIterableAssert<*, *, *, *>.hasOnlyElementsOfTypeKt() = + hasOnlyElementsOfType(SubType::class.java) as AbstractIterableAssert<*, Iterable, SubType, *> + +@Suppress("UNCHECKED_CAST") +inline fun ListAssert<*>.hasOnlyOneElementOfType(): ObjectAssert = + (hasOnlyElementsOfType(SubType::class.java) as ListAssert).singleElement() + +fun assertPosixPermissions(path: Path, expected: String) { + val perms = PosixFilePermissions.toString(Files.getPosixFilePermissions(path)) + assertThat(perms).isEqualTo(expected) +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt new file mode 100644 index 00000000000..26fbdd924bc --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt @@ -0,0 +1,41 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils.test + +import java.time.Duration +import java.time.Instant +import java.util.UUID +import java.util.concurrent.atomic.AtomicInteger +import kotlin.random.Random + +fun aString(length: Int = Random.nextInt(5, 30)): String = UUID.randomUUID().toString().substring(length) + +fun aStringWithLineCount(lineCount: Int, start: Int = 0): String = buildString { + for (i in start until start + lineCount) { + append("line$i\n") + } +}.trimEnd() + +fun retryableAssert( + timeout: Duration? = null, + maxAttempts: Int? = null, + interval: Duration = Duration.ofMillis(100), + assertion: () -> Unit, +) { + val calculatedTimeout = timeout ?: maxAttempts?.let { Duration.ofMillis(it * (interval.toMillis() * 2)) } ?: Duration.ofSeconds(1) + val expiry = Instant.now().plus(calculatedTimeout) + val attempts = AtomicInteger(0) + while (true) { + try { + assertion() + return + } catch (e: AssertionError) { // deliberately narrowed to an AssertionError - this is intended to be used in a test assertion + when { + Instant.now().isAfter(expiry) -> throw e + maxAttempts != null && attempts.incrementAndGet() >= maxAttempts -> throw e + else -> Thread.sleep(interval.toMillis()) + } + } + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt new file mode 100644 index 00000000000..83b3a5b5de7 --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt @@ -0,0 +1,36 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.core.utils.test + +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import java.time.Duration + +class TestUtilsTest { + + @Test + fun retryableAssertionErrorsBubbleAfterMaxDuration() { + assertThatThrownBy { + retryableAssert(timeout = Duration.ofMillis(50), interval = Duration.ofMillis(5)) { + throw AssertionError("Boom") + } + }.isInstanceOf(AssertionError::class.java) + } + + @Test + fun nonAssertionErrorsBubbleImmediately() { + val mock = mock { + on { run() }.thenThrow(RuntimeException("Boom")) + } + assertThatThrownBy { + retryableAssert(maxAttempts = 3, interval = Duration.ofMillis(5)) { + mock.run() + } + }.isInstanceOf(RuntimeException::class.java) + verify(mock, times(1)).run() + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt new file mode 100644 index 00000000000..3fa8c33fb1c --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt @@ -0,0 +1,48 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.utils.AttributeBag +import software.amazon.q.core.utils.AttributeBagKey + +@Suppress("UNCHECKED_CAST") +class AttributeBagTest { + @Test + fun createAndRetrieveValuesWorks() { + val bag = AttributeBag() + val key = AttributeBagKey.create("1234567890") + bag.putData(key, "abc") + assertThat(bag.get(key)).isEqualTo("abc") + } + + @Test + fun getNonexistentValueIsNull() { + val bag = AttributeBag() + val key = AttributeBagKey.create("hjkl") + assertThat(bag.get(key)).isNull() + } + + @Test + fun replacingValuesWorks() { + val bag = AttributeBag() + val key = AttributeBagKey.create("23456754") + bag.putData(key, "abc") + bag.putData(key, "cdf") + assertThat(bag.get(key)).isEqualTo("cdf") + } + + @Test + fun creatingTheSameValueIsIdempotent() { + assertThat(AttributeBagKey.create("fail")).isSameAs(AttributeBagKey.create("fail")) + } + + @Test(expected = NoSuchElementException::class) + fun getOrThrowThrowsOnFailure() { + val bag = AttributeBag() + val key = AttributeBagKey.create("asdf") + bag.getOrThrow(key) + } +} diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt b/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt new file mode 100644 index 00000000000..528b5e8ecab --- /dev/null +++ b/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt @@ -0,0 +1,25 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.core.utils.htmlWrap +import software.amazon.q.core.utils.splitNoBlank + +class StringUtilsTest { + @Test + fun splitNoBlank() { + assertThat("a\nb\nc\n".split('\n')).isEqualTo(listOf("a", "b", "c", "")) + assertThat("a\nb\nc\n".splitNoBlank('\n')).isEqualTo(listOf("a", "b", "c")) + assertThat("a\nb\nc".splitNoBlank('\n')).isEqualTo(listOf("a", "b", "c")) + assertThat("a\nb\nc\n ".splitNoBlank('\n')).isEqualTo(listOf("a", "b", "c")) + assertThat("a\nb\nc\n \n".splitNoBlank('\n')).isEqualTo(listOf("a", "b", "c")) + } + + @Test + fun htmlWrap() { + assertThat("thing".htmlWrap()).isEqualTo("thing") + } +} diff --git a/plugins/core-q/jetbrains-community/build.gradle.kts b/plugins/core-q/jetbrains-community/build.gradle.kts new file mode 100644 index 00000000000..b217b48e048 --- /dev/null +++ b/plugins/core-q/jetbrains-community/build.gradle.kts @@ -0,0 +1,137 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import io.gitlab.arturbosch.detekt.Detekt +import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask +import software.aws.toolkits.gradle.intellij.IdeFlavor +import software.aws.toolkits.telemetry.generator.gradle.GenerateTelemetry + +plugins { + id("java-library") + id("toolkit-testing") + id("toolkit-intellij-subplugin") +} + +buildscript { + dependencies { + classpath(libs.telemetryGenerator) + } +} + +private val generatedSrcDir = project.layout.buildDirectory.dir("generated-src") +sourceSets { + main { + java.srcDir(generatedSrcDir) + } +} + +idea { + module { + generatedSourceDirs = generatedSourceDirs.toMutableSet() + generatedSrcDir.get().asFile + } +} + +val generateTelemetry = tasks.register("generateTelemetry") { + inputFiles.setFrom(file("${project.projectDir}/resources/telemetryOverride.json")) + outputDirectory.set(generatedSrcDir) + + doFirst { + outputDirectory.get().asFile.deleteRecursively() + } +} + +val replaceInGeneratedSources = tasks.register("replaceInGeneratedSources") { + dependsOn(generateTelemetry) + + doLast { + // Define your string replacements as pairs (old -> new) + val replacements = mapOf( + "software.aws.toolkits.jetbrains.services.telemetry" to "software.amazon.q.jetbrains.services.telemetry", + "software.aws.toolkits.core" to "software.amazon.q.core" + ) + + // Walk through all generated source files + generatedSrcDir.get().asFile.walkTopDown() + .filter { it.isFile && (it.extension == "kt" || it.extension == "java") } + .forEach { file -> + var content = file.readText() + var modified = false + + // Apply each replacement + replacements.forEach { (oldString, newString) -> + val newContent = content.replace(oldString, newString) + if (newContent != content) { + modified = true + content = newContent + } + } + + // Write back only if changes were made + if (modified) { + file.writeText(content) + println("Replaced strings in: ${file.name}") + } + } + } +} + + +tasks.compileKotlin { + dependsOn(replaceInGeneratedSources) +} + +intellijToolkit { + ideFlavor.set(IdeFlavor.IC) +} + +// expose intellij test framework to fixture consumers +configurations.testFixturesCompileOnlyApi { + extendsFrom( + configurations.intellijPlatformTestDependencies.get() + ) +} + +// intellij java-test-framework pollutes test classpath with extracted java plugins +configurations.testFixturesApi { + exclude("com.jetbrains.intellij.java", "java") + exclude("com.jetbrains.intellij.java", "java-impl") +} + +dependencies { + compileOnlyApi(project(":plugin-core-q:core-q")) + compileOnlyApi(libs.aws.apacheClient) + compileOnlyApi(libs.aws.nettyClient) + + api(libs.aws.iam) + + testFixturesApi(project(path = ":plugin-core-q:core-q", configuration = "testArtifacts")) + testFixturesApi(project(":plugin-core-q:resources")) + testFixturesApi(libs.wiremock) { + // conflicts with transitive inclusion from docker plugin + exclude(group = "org.apache.httpcomponents.client5") + // provided by IDE + exclude(group = "commons-io") + } + + testImplementation(project(":plugin-core-q:core-q")) + testRuntimeOnly(project(":plugin-core-q:sdk-codegen")) +} + +// fix implicit dependency on generated source +tasks.withType().configureEach { + dependsOn(generateTelemetry) +} + +tasks.withType().configureEach { + dependsOn(generateTelemetry) +} + +// hack because our test structure currently doesn't make complete sense +tasks.prepareTestSandbox { + val pluginXmlJar = project(":plugin-core-q").tasks.jar + + dependsOn(pluginXmlJar) + from(pluginXmlJar) { + into(intellijPlatform.projectName.map { "$it/lib" }) + } +} diff --git a/plugins/core-q/jetbrains-community/detekt-baseline-main.xml b/plugins/core-q/jetbrains-community/detekt-baseline-main.xml new file mode 100644 index 00000000000..49162e068c7 --- /dev/null +++ b/plugins/core-q/jetbrains-community/detekt-baseline-main.xml @@ -0,0 +1,20 @@ + + + + + Filename:DEFAULT_PROFILE_ID.kt$software.amazon.q.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID.kt + Filename:contexts.kt$software.amazon.q.jetbrains.core.coroutines.contexts.kt + Filename:scopes.kt$software.amazon.q.jetbrains.core.coroutines.scopes.kt + UseCheckOrError:AwsConnectionManager.kt$throw IllegalStateException("Bug: Attempting to retrieve connection settings with invalid connection state") + UseCheckOrError:AwsConnectionManager.kt$throw IllegalStateException("Connection settings are not configured") + UseCheckOrError:AwsRegionProvider.kt$AwsRegionProvider$throw IllegalStateException("Region provider data is missing default data") + UseCheckOrError:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$throw IllegalStateException("Profile $sourceProfileName looks to have been removed") + UseCheckOrError:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$throw IllegalStateException("Profile ${profileProviderId.profileName} looks to have been removed") + UseCheckOrError:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$throw IllegalStateException("ProfileCredentialProviderFactory can only handle ProfileCredentialsIdentifier, but got ${providerId::class}") + UseOrEmpty:NotificationUtils.kt$<no name provided>$title ?: "" + UseOrEmpty:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$e.message?.let { ": $it" } ?: "" + UseOrEmpty:ToolkitCredentialProcessProvider.kt$ToolkitCredentialProcessProvider$errorOutput?.let { ": $it" } ?: "" + UseRequire:ProfileUtils.kt$throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.assume_role.duplicate_source", currentProfileName)) + UseRequire:ProfileUtils.kt$throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.assume_role.missing_source", currentProfileName)) + + diff --git a/plugins/core-q/jetbrains-community/detekt-baseline-test.xml b/plugins/core-q/jetbrains-community/detekt-baseline-test.xml new file mode 100644 index 00000000000..ed423c9072e --- /dev/null +++ b/plugins/core-q/jetbrains-community/detekt-baseline-test.xml @@ -0,0 +1,15 @@ + + + + + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun bothFilesOpened_bothFilesExists() + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun configFileOpened_onlyConfigExists() + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun confirmConfigFileCreated_bothFilesDoNotExist() + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun credentialFileOpened_onlyCredentialsExists() + FunctionNaming:DefaultTelemetryPublisherTest.kt$DefaultTelemetryPublisherTest$@Test fun testPublish_withNamespace() + FunctionNaming:DefaultTelemetryPublisherTest.kt$DefaultTelemetryPublisherTest$@Test fun testPublish_withoutNamespace() + UnsafeCallOnNullableType:CredentialManagerTest.kt$CredentialManagerTest.TestCredentialProviderFactory$credentialsMapping.remove(providerId)!! + UnsafeCallOnNullableType:ScopeTest.kt$ScopeTest$ProjectManagerEx.getInstanceEx().openProject(projectFile, options)!! + UnsafeCallOnNullableType:TelemetryServiceTest.kt$TelemetryServiceTest$ProjectManagerEx.getInstanceEx().openProject(projectFile, options)!! + + diff --git a/plugins/core-q/jetbrains-community/detekt-baseline-testFixtures.xml b/plugins/core-q/jetbrains-community/detekt-baseline-testFixtures.xml new file mode 100644 index 00000000000..d0322e4e64a --- /dev/null +++ b/plugins/core-q/jetbrains-community/detekt-baseline-testFixtures.xml @@ -0,0 +1,13 @@ + + + + + NoNameShadowing:MockAwsConnectionManager.kt${ manager.changeCredentialProviderAndWait(it) } + UnsafeCallOnNullableType:CodeInsightTestFixtureRule.kt$ClearableLazy$_value!! + UnsafeCallOnNullableType:CodeInsightTestFixtureRule.kt$PsiManager.getInstance(project).findFile(file)!! + UnsafeCallOnNullableType:CodeInsightTestFixtureRule.kt$ref!! + UnsafeCallOnNullableType:MockResourceCache.kt$MockResourceCacheInterface$connectionManager.selectedCredentialIdentifier!! + UnsafeCallOnNullableType:MockResourceCache.kt$MockResourceCacheInterface$connectionManager.selectedRegion!! + UseCheckOrError:MockClientManager.kt$MockClientManager$throw IllegalStateException("No mock registered for $sdkClass") + + diff --git a/plugins/core-q/jetbrains-community/detekt-baseline.xml b/plugins/core-q/jetbrains-community/detekt-baseline.xml new file mode 100644 index 00000000000..447eda8c802 --- /dev/null +++ b/plugins/core-q/jetbrains-community/detekt-baseline.xml @@ -0,0 +1,22 @@ + + + + + Filename:DEFAULT_PROFILE_ID.kt$software.amazon.q.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID.kt + Filename:contexts.kt$software.amazon.q.jetbrains.core.coroutines.contexts.kt + Filename:scopes.kt$software.amazon.q.jetbrains.core.coroutines.scopes.kt + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun bothFilesOpened_bothFilesExists() + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun configFileOpened_onlyConfigExists() + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun confirmConfigFileCreated_bothFilesDoNotExist() + FunctionNaming:CreateOrUpdateCredentialProfilesActionTest.kt$CreateOrUpdateCredentialProfilesActionTest$@Test fun credentialFileOpened_onlyCredentialsExists() + FunctionNaming:DefaultTelemetryPublisherTest.kt$DefaultTelemetryPublisherTest$@Test fun testPublish_withNamespace() + FunctionNaming:DefaultTelemetryPublisherTest.kt$DefaultTelemetryPublisherTest$@Test fun testPublish_withoutNamespace() + UseCheckOrError:AwsConnectionManager.kt$throw IllegalStateException("Bug: Attempting to retrieve connection settings with invalid connection state") + UseCheckOrError:AwsConnectionManager.kt$throw IllegalStateException("Connection settings are not configured") + UseCheckOrError:AwsRegionProvider.kt$AwsRegionProvider$throw IllegalStateException("Region provider data is missing default data") + UseCheckOrError:MockClientManager.kt$MockClientManager$throw IllegalStateException("No mock registered for $sdkClass") + UseCheckOrError:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$throw IllegalStateException("Profile $sourceProfileName looks to have been removed") + UseCheckOrError:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$throw IllegalStateException("Profile ${profileProviderId.profileName} looks to have been removed") + UseCheckOrError:ProfileCredentialProviderFactory.kt$ProfileCredentialProviderFactory$throw IllegalStateException("ProfileCredentialProviderFactory can only handle ProfileCredentialsIdentifier, but got ${providerId::class}") + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS.svg new file mode 100644 index 00000000000..30b172fd8ea --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS.svg @@ -0,0 +1,12 @@ + + + + toolkit-icon-jetbrains-13x13 + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q.svg new file mode 100644 index 00000000000..7058d77f108 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q_dark.svg new file mode 100644 index 00000000000..f04d331fd89 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_dark.svg new file mode 100644 index 00000000000..802d70acfcb --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_dark.svg @@ -0,0 +1,12 @@ + + + + toolkit-icon-jetbrains-13x13-dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile.svg new file mode 100644 index 00000000000..4726c4a3d0a --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large.svg new file mode 100644 index 00000000000..82b623c5b4c --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large_dark.svg new file mode 100644 index 00000000000..241a3c8e03b --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large_dark.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_dark.svg new file mode 100644 index 00000000000..0186dda9639 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_dark.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Large.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Large.svg new file mode 100644 index 00000000000..ff0771eb1fc --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Large.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg new file mode 100644 index 00000000000..12aa875939b --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg new file mode 100644 index 00000000000..39e10674b2c --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_White_Medium.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_White_Medium.svg new file mode 100644 index 00000000000..b33a27a021e --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_White_Medium.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium.svg new file mode 100644 index 00000000000..4b5f1571b92 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium.svg @@ -0,0 +1,7 @@ + + + Icon-Service/32/Amazon-CodeCatalyst_32 + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium_dark.svg new file mode 100644 index 00000000000..e57b01d888d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium_dark.svg @@ -0,0 +1,7 @@ + + + Icon-Service/32/Amazon-CodeCatalyst_32_White + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small.svg new file mode 100644 index 00000000000..022c0e18d0d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small.svg @@ -0,0 +1,7 @@ + + + Icon-Service/16/Amazon-CodeCatalyst_16 + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small_dark.svg new file mode 100644 index 00000000000..505028b98f7 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small_dark.svg @@ -0,0 +1,7 @@ + + + Icon-Service/16/Amazon-CodeCatalyst_16_White + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_Q_grey.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_Q_grey.svg new file mode 100644 index 00000000000..f6a94b98b5f --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_Q_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_dark.svg new file mode 100644 index 00000000000..3e5fa00548d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_light.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_light.svg new file mode 100644 index 00000000000..c1b0447902f --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_light.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool.svg new file mode 100644 index 00000000000..cbf56ffa60f --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool.svg @@ -0,0 +1,15 @@ + + + + service-cloudformation-vscode + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool_dark.svg new file mode 100644 index 00000000000..b9e53a0d394 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool_dark.svg @@ -0,0 +1,15 @@ + + + + service-cloudformation-vscode-dark + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/CodeWhisperer_Large.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/CodeWhisperer_Large.svg new file mode 100644 index 00000000000..b1e571ac158 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/CodeWhisperer_Large.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge.svg new file mode 100644 index 00000000000..ed83db136e1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge.svg @@ -0,0 +1,11 @@ + + + +Amazon-EventBridge_Icon_16_Squid +Created with Sketch. + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge_dark.svg new file mode 100644 index 00000000000..88196a80eb3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge_dark.svg @@ -0,0 +1,11 @@ + + + + Amazon-EventBridge_Icon_16_White + Created with Sketch. + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon.svg new file mode 100644 index 00000000000..cbef4eff9d2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon_dark.svg new file mode 100644 index 00000000000..1e14d6f3677 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon_dark.svg @@ -0,0 +1,50 @@ + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/csharp.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/csharp.svg new file mode 100644 index 00000000000..b93b39244ea --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/csharp.svg @@ -0,0 +1,10 @@ + + Csharp(Gray) + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/csharp_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/csharp_dark.svg new file mode 100644 index 00000000000..0249b5a6d46 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/csharp_dark.svg @@ -0,0 +1,10 @@ + + Csharp(GrayDark) + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/frown.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/frown.svg new file mode 100644 index 00000000000..c858a9d8687 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/frown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/frown_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/frown_dark.svg new file mode 100644 index 00000000000..7217c52e7cb --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/frown_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/java.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/java.svg new file mode 100644 index 00000000000..00e1a4c96fb --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/java.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/javaScript.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/javaScript.svg new file mode 100644 index 00000000000..d8bff9651dc --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/javaScript.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/javaScript_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/javaScript_dark.svg new file mode 100644 index 00000000000..043bb73c89e --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/javaScript_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/java_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/java_dark.svg new file mode 100644 index 00000000000..155295f592c --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/java_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/learn.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/learn.svg new file mode 100644 index 00000000000..1dac00e1807 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/learn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/learn_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/learn_dark.svg new file mode 100644 index 00000000000..76fd1c02983 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/learn_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/new.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/new.svg new file mode 100644 index 00000000000..6a1b10a0592 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/new.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/python.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/python.svg new file mode 100644 index 00000000000..b81b75e9d1f --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/python.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/smile.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/smile.svg new file mode 100644 index 00000000000..4ddb3bb6e33 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/smile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/smile_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/smile_dark.svg new file mode 100644 index 00000000000..5f3265a82c6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/smile_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey.svg new file mode 100644 index 00000000000..dc3161ee7f5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey_dark.svg new file mode 100644 index 00000000000..1048c10286b --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/typeScript.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/typeScript.svg new file mode 100644 index 00000000000..957790f3ec5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/typeScript.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/misc/typeScript_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/misc/typeScript_dark.svg new file mode 100644 index 00000000000..bcac440d468 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/misc/typeScript_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService.svg new file mode 100644 index 00000000000..8c7b070aeb9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService.svg @@ -0,0 +1,9 @@ + + + AWS-AppRunner_13 + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService_dark.svg new file mode 100644 index 00000000000..26990e1f28d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService_dark.svg @@ -0,0 +1,9 @@ + + + AWS-AppRunner_13 + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack.svg new file mode 100644 index 00000000000..1ca23ec4e67 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack.svg @@ -0,0 +1,11 @@ + + + + AWS-CloudFormation + Created with Sketch. + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack_dark.svg new file mode 100644 index 00000000000..5b84d526f66 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack_dark.svg @@ -0,0 +1,11 @@ + + + + AWS-CloudFormation + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/CodewhispererCustom.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/CodewhispererCustom.svg new file mode 100644 index 00000000000..bcb11c35c48 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/CodewhispererCustom.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository.svg new file mode 100755 index 00000000000..6271afb19e4 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository.svg @@ -0,0 +1,11 @@ + + + + Icon-Service/16/Amazon-Elastic-Container-Registry + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository_dark.svg new file mode 100755 index 00000000000..f2f1bfb152d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository_dark.svg @@ -0,0 +1,11 @@ + + + + Icon-Service/16/Amazon-Elastic-Container-Registry + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction.svg new file mode 100644 index 00000000000..d57ca9b927b --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction.svg @@ -0,0 +1,12 @@ + + + + lambda - dark + Created with Sketch. + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction_dark.svg new file mode 100644 index 00000000000..74bc358ea11 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction_dark.svg @@ -0,0 +1,12 @@ + + + + lambda - dark + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/Redshift.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/Redshift.svg new file mode 100644 index 00000000000..5f940e06bf4 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/Redshift.svg @@ -0,0 +1,13 @@ + + + redshift dark + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/Redshift_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/Redshift_dark.svg new file mode 100644 index 00000000000..91b52bc36dc --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/Redshift_dark.svg @@ -0,0 +1,13 @@ + + + redshift light + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket.svg new file mode 100644 index 00000000000..64747942e33 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket.svg @@ -0,0 +1,11 @@ + + + + Amazon-S3-Standard-Icon_16_Squid + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket_dark.svg new file mode 100644 index 00000000000..290ddf83604 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket_dark.svg @@ -0,0 +1,11 @@ + + + + Amazon-S3-Standard-Icon_16_Squid + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/Schema.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/Schema.svg new file mode 100644 index 00000000000..b3c55b2fe9f --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/Schema.svg @@ -0,0 +1,13 @@ + + + + icon-schema + Created with Sketch. + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry.svg new file mode 100644 index 00000000000..b543c23c6bb --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry.svg @@ -0,0 +1,11 @@ + + + + icon-folder + Created with Sketch. + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry_dark.svg new file mode 100644 index 00000000000..9ff51b8404b --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry_dark.svg @@ -0,0 +1,11 @@ + + + + icon-folder + Created with Sketch. + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/Schema_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/Schema_dark.svg new file mode 100644 index 00000000000..02b5af007f9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/Schema_dark.svg @@ -0,0 +1,13 @@ + + + + icon-schema + Created with Sketch. + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp.svg new file mode 100644 index 00000000000..e0e1829e2c9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp.svg @@ -0,0 +1,18 @@ + + + + + AWS-CS-2687_IDE_icons + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp_dark.svg new file mode 100644 index 00000000000..82321af0204 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp_dark.svg @@ -0,0 +1,23 @@ + + + + icon / app / 13x13 v1 dark + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs.svg new file mode 100644 index 00000000000..2d0eb289ece --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs.svg @@ -0,0 +1,9 @@ + + + + Cloudwatch logs - dark + Created with Sketch. + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup.svg new file mode 100644 index 00000000000..e95d08f4b44 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup.svg @@ -0,0 +1,9 @@ + + + + Cloudwatch log group - dark + Created with Sketch. + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup_dark.svg new file mode 100644 index 00000000000..e68a0ffbd4e --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup_dark.svg @@ -0,0 +1,9 @@ + + + + Cloudwatch log group - light + Created with Sketch. + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow.svg new file mode 100644 index 00000000000..ced3ead8566 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow.svg @@ -0,0 +1,9 @@ + + + + Cloudwatch logs - dark + Created with Sketch. + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow_dark.svg new file mode 100644 index 00000000000..34aeb51670d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow_dark.svg @@ -0,0 +1,9 @@ + + + + Cloudwatch logs - light + Created with Sketch. + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs_dark.svg new file mode 100644 index 00000000000..b42aa1cd822 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs_dark.svg @@ -0,0 +1,9 @@ + + + + Cloudwatch logs - light + Created with Sketch. + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/checkmark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/checkmark.svg new file mode 100644 index 00000000000..4ef6c7c7ce1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/greenCheckmark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/greenCheckmark.svg new file mode 100644 index 00000000000..ae9c8b04f81 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/greenCheckmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-dark.svg new file mode 100644 index 00000000000..2bd8779b4f0 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-light.svg new file mode 100644 index 00000000000..2b186c9b1e5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-dark.svg new file mode 100644 index 00000000000..341d7f0d635 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-light.svg new file mode 100644 index 00000000000..1f867ecb86d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-dark.svg new file mode 100644 index 00000000000..11f360c3f05 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-light.svg new file mode 100644 index 00000000000..d607ca3640d --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-light.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-dark.svg new file mode 100644 index 00000000000..eee0e6b30de --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-light.svg new file mode 100644 index 00000000000..6ac5ba0df81 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-dark.svg new file mode 100644 index 00000000000..3b534ca1762 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-light.svg new file mode 100644 index 00000000000..def977e698f --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done-light.svg new file mode 100644 index 00000000000..5e70a9c1249 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done.svg new file mode 100644 index 00000000000..f2c0fcec9f8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-dark.svg new file mode 100644 index 00000000000..27f5ed19836 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-light.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-light.svg new file mode 100644 index 00000000000..f36d366d2db --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-light.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-critical.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-critical.svg new file mode 100644 index 00000000000..cb70a16dd27 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-critical.svg @@ -0,0 +1 @@ +CriticalCritical \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-high.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-high.svg new file mode 100644 index 00000000000..9bcd00e66cf --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-high.svg @@ -0,0 +1 @@ +HighHigh diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-info.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-info.svg new file mode 100644 index 00000000000..2b43ff0d06e --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-info.svg @@ -0,0 +1 @@ +InfoInfo diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-critical.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-critical.svg new file mode 100644 index 00000000000..7733994d24e --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-critical.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-high.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-high.svg new file mode 100644 index 00000000000..ff92aebc817 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-high.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-info.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-info.svg new file mode 100644 index 00000000000..dbf78609170 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-info.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-low.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-low.svg new file mode 100644 index 00000000000..4ca6d96961e --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-medium.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-medium.svg new file mode 100644 index 00000000000..a906d9b4873 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-low.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-low.svg new file mode 100644 index 00000000000..d7195532662 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-low.svg @@ -0,0 +1 @@ +LowLow diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-medium.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-medium.svg new file mode 100644 index 00000000000..7377b683ed3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-medium.svg @@ -0,0 +1 @@ +MediumMedium diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable.svg new file mode 100644 index 00000000000..dc56c4eb170 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable.svg @@ -0,0 +1,7 @@ + + + Icon-Resource/Database/Res_Amazon-DynamoDB_Table_48_Dark + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable_dark.svg new file mode 100644 index 00000000000..761baab6609 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable_dark.svg @@ -0,0 +1,7 @@ + + + Icon-Resource/Database/Res_Amazon-DynamoDB_Table_48_Dark + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster.svg new file mode 100644 index 00000000000..9920618f696 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster.svg @@ -0,0 +1,11 @@ + + + + Amazon-Cluster_Icon_16_Light + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster_dark.svg new file mode 100644 index 00000000000..bb2e32603f2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster_dark.svg @@ -0,0 +1,11 @@ + + + + Amazon-Cluster_Icon_16_Dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService.svg new file mode 100644 index 00000000000..97b2cc02bfb --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService.svg @@ -0,0 +1,11 @@ + + + + Amazon-Service_Icon_16_Light + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService_dark.svg new file mode 100644 index 00000000000..ab2b8d908eb --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService_dark.svg @@ -0,0 +1,11 @@ + + + + Amazon-Service_Icon_16_Dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition.svg new file mode 100644 index 00000000000..6cad16a0efa --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition.svg @@ -0,0 +1,11 @@ + + + + Amazon-Task-Definition_Icon_16_Light + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition_dark.svg new file mode 100644 index 00000000000..45ca78b09c5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition_dark.svg @@ -0,0 +1,11 @@ + + + + Amazon-Task-Definition_Icon_16_Dark + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql.svg new file mode 100644 index 00000000000..2a0fe0226be --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql.svg @@ -0,0 +1,9 @@ + + + mysql - dark + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql_dark.svg new file mode 100644 index 00000000000..6e94dee16e8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql_dark.svg @@ -0,0 +1,9 @@ + + + mysql - light + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres.svg new file mode 100644 index 00000000000..780df6400ff --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres.svg @@ -0,0 +1,9 @@ + + + PostGreSQL - dark option 1 + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres_dark.svg new file mode 100644 index 00000000000..ef21866f472 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres_dark.svg @@ -0,0 +1,9 @@ + + + PostGreSQL - light option 1 + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue.svg new file mode 100644 index 00000000000..01a0a802022 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue.svg @@ -0,0 +1,13 @@ + + + Amazon simple queue service (SQS) - dark + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue_dark.svg new file mode 100644 index 00000000000..8c89760c8ff --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue_dark.svg @@ -0,0 +1,13 @@ + + + Amazon simple queue service (SQS) - light + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow.svg new file mode 100644 index 00000000000..9e5df0cc263 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow.svg @@ -0,0 +1,13 @@ + + + Amazon simple queue service (SQS) - dark + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow_dark.svg b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow_dark.svg new file mode 100644 index 00000000000..ecb6efcf703 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow_dark.svg @@ -0,0 +1,13 @@ + + + Amazon simple queue service (SQS) - light + + + + + + + + + + diff --git a/plugins/core-q/jetbrains-community/resources/oauthCallback/auth.css b/plugins/core-q/jetbrains-community/resources/oauthCallback/auth.css new file mode 100644 index 00000000000..092b7eb8bf6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/oauthCallback/auth.css @@ -0,0 +1,93 @@ +/* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +/* SPDX-License-Identifier: Apache-2.0 */ + +html { + height: 100%; +} + +body { + box-sizing: border-box; + min-height: 100%; + margin: 0; + padding: 15px 30px; + display: flex; + flex-direction: column; + font-family: Verdana, Geneva, Tahoma, sans-serif; + font-size: 0.9rem; + background-color: #f2f3f3; + justify-content: center; + min-width: 400px; +} + +.flex-container { + background-color: #ffffff; + width: 30%; + margin: 0 auto; + padding: 2% 2% 1% 2%; + max-width: 400px; + box-shadow: 0px 1px 1px 1px #8a969a; +} + +.request { + border-block: 1px solid; + border-inline: 1px solid; + border-end-end-radius: 3px; + border-end-start-radius: 3px; + border-start-end-radius: 3px; + border-start-start-radius: 3px; + margin-top: 5%; + padding: 5%; + display: flex; + flex-direction: row; +} + +.request h4 { + margin: 1% 1% 1% 0%; +} + +.request p { + margin-bottom: 0; + margin-top: 2%; +} + +.approval { + background-color: #f2f8f0; + border-color: #1d8102; +} + +.denial { + background-color: #fff7f7; + border-color: #d91515; +} + +.request-container { + width: 100%; +} + +.success-icon { + color: #1d8102; + padding-right: 5px; +} + +.denial-icon { + color: #d91515; + padding-right: 5px; +} + +.center-icon { + width: 100%; + text-align: center; +} + +.hidden { + display: none; + visibility: none; +} + +.hint { + color: #545b64; +} + +.aws-icon { + margin-bottom: 50px; +} diff --git a/plugins/core-q/jetbrains-community/resources/oauthCallback/index.html b/plugins/core-q/jetbrains-community/resources/oauthCallback/index.html new file mode 100644 index 00000000000..4969eb9d06b --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/oauthCallback/index.html @@ -0,0 +1,99 @@ + + + + + + + + AWS Authentication + + + + + + +
+
+ + + + + +
+
+ +
+
+ +
+

Request approved

+

+
+
+

+
+ + + +
+
+ + + diff --git a/plugins/core-q/jetbrains-community/resources/telemetryOverride.json b/plugins/core-q/jetbrains-community/resources/telemetryOverride.json new file mode 100644 index 00000000000..d0f27341496 --- /dev/null +++ b/plugins/core-q/jetbrains-community/resources/telemetryOverride.json @@ -0,0 +1,429 @@ +{ + "types": [ + { + "name": "amazonQProfileRegion", + "type": "string", + "description": "Region of the Q Profile associated with a metric\n- \"n/a\" if metric is not associated with a profile or region.\n- \"not-set\" if metric is associated with a profile, but profile is unknown." + }, + { + "name": "ssoRegion", + "type": "string", + "description": "Region of the current SSO connection. Typically associated with credentialStartUrl\n- \"n/a\" if metric is not associated with a region.\n- \"not-set\" if metric is associated with a region, but region is unknown." + }, + { + "name": "profileCount", + "type": "int", + "description": "The number of profiles that were available to choose from" + }, + { + "name": "amazonqIndexFileSizeInMB", + "type": "int", + "description": "Index size in MB" + }, + { + "name": "amazonqIndexFileCount", + "type": "int", + "description": "Files indexed" + }, + { + "name": "amazonqIndexMemoryUsageInMB", + "type": "int", + "description": "Memory usage of LSP server" + }, + { + "name": "amazonqIndexCpuUsagePercentage", + "type": "int", + "description": "Cpu usage of LSP server" + }, + { + "name": "authRefreshSource", + "type": "string", + "description": "Source triggering token refresh" + }, + { + "name": "component", + "allowedValues": [ + "editor", + "viewer", + "filesystem", + "explorer", + "infobar", + "hover", + "webview", + "quickfix", + "Manage Extensions", + "Got It", + "Read More", + "Install", + "Dismiss" + ], + "description": "The IDE or OS component used for the action. (Examples: S3 download to filesystem, S3 upload from editor, ...)" + }, + { + "name": "connectionState", + "type": "string", + "description": "A detailed state of a specific auth connection. Use `authStatus` for a higher level view of an extension's general connection." + }, + { + "name": "cwsprChatCommandName", + "type": "string", + "description": "Type of chat command name executed" + }, + { + "name": "cwsprChatCommandType", + "type": "string", + "allowedValues": [ + "clear", + "help", + "transform", + "auth" + ], + "description": "Type of chat command (/command) executed" + }, + { + "name": "cwsprChatModificationPercentage", + "type": "double", + "description": "Percentage of characters edited by user after copying/inserting code from a message" + }, + { + "name": "executedCount", + "type": "int", + "description": "The number of executed operations" + }, + { + "name": "reAuth", + "type": "boolean", + "description": "Connection requires re-authentication." + }, + { + "name": "startUpState", + "allowedValues": [ + "firstStartUp", + "reloaded" + ], + "description": "Toolkit run state." + }, + { + "name": "filePath", + "type": "string", + "description": "The path of the file" + }, + { + "name": "workspaceRoot", + "type": "string", + "description": "The path of the project root" + }, + { + "name": "relativePath", + "type": "string", + "description": "The relative path of the file" + } + ], + "metrics": [ + { + "name": "amazonq_didSelectProfile", + "description": "Emitted after the user's Q Profile has been set, whether the user was prompted with a dialog, or a profile was automatically assigned after signing in.", + "metadata": [ + { "type": "source" }, + { "type": "amazonQProfileRegion" }, + { "type": "result" }, + { "type": "ssoRegion", "required": false }, + { "type": "credentialStartUrl", "required": false }, + { "type": "profileCount", "required": false } + ], + "passive": true + }, + { + "name": "amazonq_profileState", + "description": "Indicates a change in the user's Q Profile state", + "metadata": [ + { "type": "source" }, + { "type": "amazonQProfileRegion" }, + { "type": "result" }, + { "type": "ssoRegion", "required": false }, + { "type": "credentialStartUrl", "required": false } + ], + "passive": true + }, + { + "name": "amazonq_indexWorkspace", + "description": "Indexing of local workspace", + "metadata": [ + { + "type": "duration" + }, + { + "type": "result" + }, + { + "type": "amazonqIndexFileSizeInMB" + }, + { + "type": "amazonqIndexFileCount" + }, + { + "type": "amazonqIndexMemoryUsageInMB", + "required": false + }, + { + "type": "amazonqIndexCpuUsagePercentage", + "required": false + }, + { + "type": "credentialStartUrl", + "required": false + } + ] + }, + { + "name": "amazonq_modifyCode", + "description": "Percentage of code modified by the user after copying/inserting code from a message", + "metadata": [ + { + "type": "cwsprChatConversationId" + }, + { + "type": "cwsprChatMessageId" + }, + { + "type": "cwsprChatModificationPercentage" + }, + { + "type": "credentialStartUrl", + "required": false + } + ] + }, + { + "name": "amazonq_closeChat", + "description": "When chat panel is closed" + }, + { + "name": "amazonq_enterFocusChat", + "description": "When chat panel comes into focus" + }, + { + "name": "amazonq_enterFocusConversation", + "description": "When a conversation comes into focus", + "metadata": [ + { + "type": "cwsprChatConversationId" + } + ] + }, + { + "name": "amazonq_exitFocusChat", + "description": "When chat panel goes out of focus" + }, + { + "name": "amazonq_exitFocusConversation", + "description": "When a conversation goes out of focus", + "metadata": [ + { + "type": "cwsprChatConversationId" + } + ] + }, + { + "name": "amazonq_messageResponseError", + "description": "When an error has occured in response to a prompt", + "metadata": [ + { + "type": "cwsprChatConversationId", + "required": false + }, + { + "type": "cwsprChatTriggerInteraction" + }, + { + "type": "cwsprChatUserIntent", + "required": false + }, + { + "type": "cwsprChatHasCodeSnippet", + "required": false + }, + { + "type": "cwsprChatProgrammingLanguage", + "required": false + }, + { + "type": "cwsprChatActiveEditorTotalCharacters", + "required": false + }, + { + "type": "cwsprChatActiveEditorImportCount", + "required": false + }, + { + "type": "cwsprChatResponseCode" + }, + { + "type": "cwsprChatRequestLength" + }, + { + "type": "cwsprChatConversationType" + }, + { + "type": "credentialStartUrl", + "required": false + } + ] + }, + { + "name": "amazonq_openChat", + "description": "When user opens CWSPR chat panel" + }, + { + "name": "amazonq_runCommand", + "description": "When a chat command is executed", + "metadata": [ + { + "type": "cwsprChatCommandType" + }, + { + "type": "cwsprChatCommandName", + "required": false + }, + { + "type": "credentialStartUrl", + "required": false + } + ] + }, + { + "name": "amazonq_startConversation", + "description": "When user starts a new conversation", + "metadata": [ + { + "type": "cwsprChatConversationId" + }, + { + "type": "cwsprChatTriggerInteraction" + }, + { + "type": "cwsprChatUserIntent", + "required": false + }, + { + "type": "cwsprChatHasCodeSnippet", + "required": false + }, + { + "type": "cwsprChatProgrammingLanguage", + "required": false + }, + { + "type": "cwsprChatConversationType" + }, + { + "type": "credentialStartUrl", + "required": false + }, + { + "type": "cwsprChatHasProjectContext", + "required": false + }, + { + "type": "cwsprChatProjectContextQueryMs", + "required": false + } + ] + }, + { + "name": "auth_modifyConnection", + "description": "An auth connection was modified in some way, e.g. deleted, updated", + "metadata": [ + { + "type": "action", + "required": true + }, + { + "type": "authScopes", + "required": false + }, + { + "type": "authStatus", + "required": false + }, + { + "type": "connectionState", + "required": false + }, + { + "type": "credentialStartUrl", + "required": false + }, + { + "type": "sessionDuration", + "required": false + }, + { + "type": "source", + "required": true + }, + { + "type": "ssoRegistrationClientId", + "required": false + }, + { + "type": "ssoRegistrationExpiresAt", + "required": false + }, + { + "type": "id", + "required": false + } + ], + "passive": true + }, + { + "name": "auth_sourceOfRefresh", + "description": "Source of user triggered refresh", + "metadata": [ + { + "type": "authRefreshSource", + "required": false + } + ] + }, + { + "name": "aws_openLocalTerminal", + "description": "Open local terminal with aws connection injected", + "metadata": [ + { + "type": "result" + } + ] + }, + { + "name": "webview_amazonqSignInOpened", + "description": "Called when a Amazon Q sign in webview is opened.", + "metadata": [ + { + "type": "reAuth", + "required": true + } + ], + "passive": true + }, + { + "name": "codewhisperer_invalidZip", + "description": "Invalid zip file", + "metadata": [ + { + "type": "filePath", + "required": true + }, + { + "type": "workspaceRoot", + "required": true + }, + { + "type": "relativePath", + "required": true + } + ] + } + ] +} diff --git a/plugins/core-q/jetbrains-community/src-242-252/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt b/plugins/core-q/jetbrains-community/src-242-252/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt new file mode 100644 index 00000000000..71eb0069bca --- /dev/null +++ b/plugins/core-q/jetbrains-community/src-242-252/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt @@ -0,0 +1,171 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +@file:Suppress("UnusedPrivateClass") + +package software.amazon.q.jetbrains.services.telemetry.otel + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.util.SystemInfoRt +import com.intellij.platform.util.http.ContentType +import com.intellij.platform.util.http.httpPost +import com.intellij.serviceContainer.NonInjectable +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.context.Context +import io.opentelemetry.context.propagation.ContextPropagators +import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.sdk.trace.ReadWriteSpan +import io.opentelemetry.sdk.trace.ReadableSpan +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.SpanProcessor +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.http.ContentStreamProvider +import software.amazon.awssdk.http.HttpExecuteRequest +import software.amazon.awssdk.http.SdkHttpMethod +import software.amazon.awssdk.http.SdkHttpRequest +import software.amazon.awssdk.http.apache.ApacheHttpClient +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner +import java.io.ByteArrayOutputStream +import java.net.ConnectException + +private class BasicOtlpSpanProcessor( + private val coroutineScope: CoroutineScope, + private val traceUrl: String = "http://127.0.0.1:4318/v1/traces", +) : SpanProcessor { + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + override fun isStartRequired() = false + override fun isEndRequired() = true + + override fun onEnd(span: ReadableSpan) { + val data = span.toSpanData() + coroutineScope.launch { + try { + val item = TraceRequestMarshaler.create(listOf(data)) + + httpPost(traceUrl, contentLength = item.binarySerializedSize.toLong(), contentType = ContentType.XProtobuf) { + item.writeBinaryTo(this) + } + } catch (e: CancellationException) { + throw e + } catch (e: ConnectException) { + thisLogger().warn("Cannot export (url=$traceUrl): ${e.message}") + } catch (e: Throwable) { + thisLogger().error("Cannot export (url=$traceUrl)", e) + } + } + } +} + +private class SigV4OtlpSpanProcessor( + private val coroutineScope: CoroutineScope, + private val traceUrl: String, + private val creds: AwsCredentialsProvider, +) : SpanProcessor { + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + override fun isStartRequired() = false + override fun isEndRequired() = true + + private val client = ApacheHttpClient.create() + + override fun onEnd(span: ReadableSpan) { + coroutineScope.launch { + val data = span.toSpanData() + try { + val item = TraceRequestMarshaler.create(listOf(data)) + // calculate the sigv4 header + val signer = AwsV4HttpSigner.create() + val httpRequest = + SdkHttpRequest.builder() + .uri(traceUrl) + .method(SdkHttpMethod.POST) + .putHeader("Content-Type", "application/x-protobuf") + .build() + + val baos = ByteArrayOutputStream() + item.writeBinaryTo(baos) + val payload = ContentStreamProvider.fromByteArray(baos.toByteArray()) + val signedRequest = signer.sign { + it.identity(creds.resolveIdentity().get()) + it.request(httpRequest) + it.payload(payload) + it.putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "osis") + it.putProperty(AwsV4HttpSigner.REGION_NAME, "us-west-2") + } + + // Create and HTTP client and send the request. ApacheHttpClient requires the 'apache-client' module. + client.prepareRequest( + HttpExecuteRequest.builder() + .request(signedRequest.request()) + .contentStreamProvider(signedRequest.payload().orElse(null)) + .build() + ).call() + } catch (e: CancellationException) { + throw e + } catch (e: ConnectException) { + thisLogger().warn("Cannot export (url=$traceUrl): ${e.message}") + } catch (e: Throwable) { + thisLogger().error("Cannot export (url=$traceUrl)", e) + } + } + } +} + +private object StdoutSpanProcessor : SpanProcessor { + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + override fun isStartRequired() = false + override fun isEndRequired() = true + + override fun onEnd(span: ReadableSpan) { + println(span.toSpanData()) + } +} + +@Service +class OTelService @NonInjectable internal constructor(spanProcessors: List) : Disposable { + @Suppress("unused") + constructor() : this(listOf(ToolkitTelemetryOTelSpanProcessor())) + + private val sdkDelegate = lazy { + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .apply { + spanProcessors.forEach { + addSpanProcessor(it) + } + } + .setResource( + Resource.create( + Attributes.builder() + .put(AttributeKey.stringKey("os.type"), SystemInfoRt.OS_NAME) + .put(AttributeKey.stringKey("os.version"), SystemInfoRt.OS_VERSION) + .put(AttributeKey.stringKey("host.arch"), System.getProperty("os.arch")) + .build() + ) + ) + .build() + ) + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .build() + } + internal val sdk: OpenTelemetrySdk by sdkDelegate + + override fun dispose() { + if (sdkDelegate.isInitialized()) { + sdk.close() + } + } + + companion object { + fun getSdk() = service().sdk + } +} diff --git a/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt b/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt new file mode 100644 index 00000000000..698e6e43b95 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt @@ -0,0 +1,171 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +@file:Suppress("UnusedPrivateClass") + +package software.aws.toolkits.jetbrains.services.telemetry.otel + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.util.SystemInfoRt +import com.intellij.platform.util.http.ContentType +import com.intellij.platform.util.http.httpPost +import com.intellij.serviceContainer.NonInjectable +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.context.Context +import io.opentelemetry.context.propagation.ContextPropagators +import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.sdk.trace.ReadWriteSpan +import io.opentelemetry.sdk.trace.ReadableSpan +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.SpanProcessor +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.http.ContentStreamProvider +import software.amazon.awssdk.http.HttpExecuteRequest +import software.amazon.awssdk.http.SdkHttpMethod +import software.amazon.awssdk.http.SdkHttpRequest +import software.amazon.awssdk.http.apache.ApacheHttpClient +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner +import java.io.ByteArrayOutputStream +import java.net.ConnectException + +private class BasicOtlpSpanProcessor( + private val coroutineScope: CoroutineScope, + private val traceUrl: String = "http://127.0.0.1:4318/v1/traces", +) : SpanProcessor { + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + override fun isStartRequired() = false + override fun isEndRequired() = true + + override fun onEnd(span: ReadableSpan) { + val data = span.toSpanData() + coroutineScope.launch { + try { + val item = TraceRequestMarshaler.create(listOf(data)) + val output = ByteArrayOutputStream() + item.writeBinaryTo(output) + + httpPost(traceUrl, contentType = ContentType.XProtobuf, body = output.toByteArray()) + } catch (e: CancellationException) { + throw e + } catch (e: ConnectException) { + thisLogger().warn("Cannot export (url=$traceUrl): ${e.message}") + } catch (e: Throwable) { + thisLogger().error("Cannot export (url=$traceUrl)", e) + } + } + } +} + +private class SigV4OtlpSpanProcessor( + private val coroutineScope: CoroutineScope, + private val traceUrl: String, + private val creds: AwsCredentialsProvider, +) : SpanProcessor { + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + override fun isStartRequired() = false + override fun isEndRequired() = true + + private val client = ApacheHttpClient.create() + + override fun onEnd(span: ReadableSpan) { + coroutineScope.launch { + val data = span.toSpanData() + try { + val item = TraceRequestMarshaler.create(listOf(data)) + // calculate the sigv4 header + val signer = AwsV4HttpSigner.create() + val httpRequest = + SdkHttpRequest.builder() + .uri(traceUrl) + .method(SdkHttpMethod.POST) + .putHeader("Content-Type", "application/x-protobuf") + .build() + + val baos = ByteArrayOutputStream() + item.writeBinaryTo(baos) + val payload = ContentStreamProvider.fromByteArray(baos.toByteArray()) + val signedRequest = signer.sign { + it.identity(creds.resolveIdentity().get()) + it.request(httpRequest) + it.payload(payload) + it.putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "osis") + it.putProperty(AwsV4HttpSigner.REGION_NAME, "us-west-2") + } + + // Create and HTTP client and send the request. ApacheHttpClient requires the 'apache-client' module. + client.prepareRequest( + HttpExecuteRequest.builder() + .request(signedRequest.request()) + .contentStreamProvider(signedRequest.payload().orElse(null)) + .build() + ).call() + } catch (e: CancellationException) { + throw e + } catch (e: ConnectException) { + thisLogger().warn("Cannot export (url=$traceUrl): ${e.message}") + } catch (e: Throwable) { + thisLogger().error("Cannot export (url=$traceUrl)", e) + } + } + } +} + +private object StdoutSpanProcessor : SpanProcessor { + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + override fun isStartRequired() = false + override fun isEndRequired() = true + + override fun onEnd(span: ReadableSpan) { + println(span.toSpanData()) + } +} + +@Service +class OTelService @NonInjectable internal constructor(spanProcessors: List) : Disposable { + @Suppress("unused") + constructor() : this(listOf(ToolkitTelemetryOTelSpanProcessor())) + + private val sdkDelegate = lazy { + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .apply { + spanProcessors.forEach { + addSpanProcessor(it) + } + } + .setResource( + Resource.create( + Attributes.builder() + .put(AttributeKey.stringKey("os.type"), SystemInfoRt.OS_NAME) + .put(AttributeKey.stringKey("os.version"), SystemInfoRt.OS_VERSION) + .put(AttributeKey.stringKey("host.arch"), System.getProperty("os.arch")) + .build() + ) + ) + .build() + ) + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .build() + } + internal val sdk: OpenTelemetrySdk by sdkDelegate + + override fun dispose() { + if (sdkDelegate.isInitialized()) { + sdk.close() + } + } + + companion object { + fun getSdk() = service().sdk + } +} diff --git a/plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefLocalRequestHandler.kt b/plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefLocalRequestHandler.kt new file mode 100644 index 00000000000..dcbac7c531f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefLocalRequestHandler.kt @@ -0,0 +1,87 @@ +@file:Suppress("all") + +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// adapted from https://github.com/JetBrains/intellij-community/blob/54429f3ba00c695c22d09e164135b0713f2cfc0f/platform/ui.jcef/jcef/utils/JBCefLocalRequestHandler.kt + +package contrib.org.intellij.images.editor.impl.jcef + +import org.cef.browser.CefBrowser +import org.cef.browser.CefFrame +import org.cef.callback.CefCallback +import org.cef.handler.CefRequestHandlerAdapter +import org.cef.handler.CefResourceHandler +import org.cef.handler.CefResourceHandlerAdapter +import org.cef.handler.CefResourceRequestHandler +import org.cef.handler.CefResourceRequestHandlerAdapter +import org.cef.misc.BoolRef +import org.cef.network.CefRequest +import java.net.URL + +/** + * Handles local protocol-specific CEF resource requests for a defined `protocol` and `authority`. + * + * This class implements a mechanism to serve protocol-specific resources based on mappings provided + * through the `addResource` function. Only requests matching the configured protocol and authority are processed, + * while others are rejected. + * + * @param myProtocol The protocol to handle (e.g., "http", "file"). + * @param myAuthority The authority of the requests (e.g., "localhost", "mydomain"). + */ +open class JBCefLocalRequestHandler( + private val myProtocol: String, + private val myAuthority: String, +) : CefRequestHandlerAdapter() { + private val myResources: MutableMap CefResourceHandler?> = HashMap() + + private val REJECTING_RESOURCE_HANDLER: CefResourceHandler = object : CefResourceHandlerAdapter() { + override fun processRequest(request: CefRequest, callback: CefCallback): Boolean { + callback.cancel() + return false + } + } + + private val RESOURCE_REQUEST_HANDLER = resourceHandlerWrapper { path -> + myResources[path]?.let { it() } + } + + protected fun resourceHandlerWrapper(handler: (String) -> CefResourceHandler?): CefResourceRequestHandler = + object : CefResourceRequestHandlerAdapter() { + override fun getResourceHandler(browser: CefBrowser?, frame: CefFrame?, request: CefRequest): CefResourceHandler { + val url = URL(request.url) + url.protocol + if (!url.protocol.equals(myProtocol) || !url.authority.equals(myAuthority)) { + return REJECTING_RESOURCE_HANDLER + } + return try { + val path = url.path.trim('/') + handler(path) ?: REJECTING_RESOURCE_HANDLER + } catch (e: RuntimeException) { + println(e.message) + REJECTING_RESOURCE_HANDLER + } + } + } + + fun addResource(resourcePath: String, resourceProvider: () -> CefResourceHandler?) { + val normalisedPath = resourcePath.trim('/') + myResources[normalisedPath] = resourceProvider + } + + fun createResource(resourcePath: String, resourceProvider: () -> CefResourceHandler?): String { + val normalisedPath = resourcePath.trim('/') + myResources[normalisedPath] = resourceProvider + return "$myProtocol://$myAuthority/$normalisedPath" + } + + override fun getResourceRequestHandler( + browser: CefBrowser?, + frame: CefFrame?, + request: CefRequest?, + isNavigation: Boolean, + isDownload: Boolean, + requestInitiator: String?, + disableDefaultHandling: BoolRef?, + ): CefResourceRequestHandler { + return RESOURCE_REQUEST_HANDLER + } +} diff --git a/plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefStreamResourceHandler.kt b/plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefStreamResourceHandler.kt new file mode 100644 index 00000000000..d2b5da24269 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefStreamResourceHandler.kt @@ -0,0 +1,78 @@ +@file:Suppress("all") + +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// https://github.com/JetBrains/intellij-community/blob/54429f3ba00c695c22d09e164135b0713f2cfc0f/platform/ui.jcef/jcef/utils/JBCefStreamResourceHandler.kt +package contrib.org.intellij.images.editor.impl.jcef + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.Disposer +import org.cef.callback.CefCallback +import org.cef.handler.CefResourceHandler +import org.cef.misc.IntRef +import org.cef.misc.StringRef +import org.cef.network.CefRequest +import org.cef.network.CefResponse +import java.io.IOException +import java.io.InputStream + +/** + * A handler for managing custom resource requests in JCEF. + * This class implements the `CefResourceHandler` interface, enabling the customization of resource + * loading from a provided input stream. It supports setting MIME types and headers for responses + * and ensures proper disposal of resources. + * + * @param myStream The input stream from which the resource will be read. + * @param myMimeType The MIME type of the response to be sent to the client. + * @param parent A `Disposable` object to which this handler is registered for automatic disposal. + * @param headers Optional headers that will be included in the response. + */ +open class JBCefStreamResourceHandler( + private val myStream: InputStream, + private val myMimeType: String, + parent: Disposable, + private val headers: Map = mapOf(), +) : CefResourceHandler, Disposable { + init { + Disposer.register(parent, this) + } + + override fun processRequest(request: CefRequest, callback: CefCallback): Boolean { + callback.Continue() + return true + } + + override fun getResponseHeaders(response: CefResponse, responseLength: IntRef, redirectUrl: StringRef) { + response.mimeType = myMimeType + response.status = 200 + for (header in headers) { + response.setHeaderByName(header.key, header.value, true /* overwrite */) + } + } + + override fun readResponse(dataOut: ByteArray, bytesToRead: Int, bytesRead: IntRef, callback: CefCallback): Boolean { + try { + bytesRead.set(myStream.read(dataOut, 0, bytesToRead)) + if (bytesRead.get() != -1) { + return true + } + } catch (_: IOException) { + callback.cancel() + } + bytesRead.set(0) + Disposer.dispose(this) + return false + } + + override fun cancel() { + Disposer.dispose(this) + } + + override fun dispose() { + try { + myStream.close() + } catch (e: IOException) { + Logger.getInstance(JBCefStreamResourceHandler::class.java).warn("Failed to close the stream", e) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/icons/AwsIcons.kt b/plugins/core-q/jetbrains-community/src/icons/AwsIcons.kt new file mode 100644 index 00000000000..3ed96be9f50 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/icons/AwsIcons.kt @@ -0,0 +1,191 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package icons + +import com.intellij.icons.AllIcons +import com.intellij.openapi.util.IconLoader +import com.intellij.ui.LayeredIcon +import javax.swing.Icon + +/** + * Lives in `icons` package due to that is how [com.intellij.openapi.util.IconLoader.getReflectiveIcon] works + */ +@Deprecated("Plugin-specific icons should not be declared in shared icons") +object AwsIcons { + object Logos { + @JvmField val AWS = load("/icons/logos/AWS.svg") // 13x13 + + @JvmField val AWS_SMILE_SMALL = load("/icons/logos/AWS_smile.svg") // 16x16 + + @JvmField val AWS_SMILE_LARGE = load("/icons/logos/AWS_smile_Large.svg") // 64x64 + + @JvmField val CLOUD_FORMATION_TOOL = load("/icons/logos/CloudFormationTool.svg") // 13x13 + + @JvmField val CODE_CATALYST_MEDIUM = load("/icons/logos/Amazon_CodeCatalyst_Medium.svg") // 32x32 + + @JvmField val CODE_CATALYST_SMALL = load("/icons/logos/Amazon_CodeCatalyst_Small.svg") // 16x16 + + @JvmField val EVENT_BRIDGE = load("/icons/logos/EventBridge.svg") // 13x13 + + @JvmField val CODEWHISPERER_LARGE = load("/icons/logos/CodeWhisperer_Large.svg") // 54x54 + + @JvmField val AWS_Q = load("/icons/logos/AWS_Q.svg") // 13x13 + + @JvmField val AWS_Q_GREY = load("/icons/logos/Amazon_Q_grey.svg") // 16x16 + + @JvmField val AWS_Q_GRADIENT = load("/icons/logos/Amazon-Q-Icon_Gradient_Large.svg") // 54x54 + + @JvmField val AWS_Q_GRADIENT_SMALL = load("/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg") // 54x54 + } + + object Misc { + @JvmField val SMILE = load("/icons/misc/smile.svg") // 16x16 + + @JvmField val SMILE_GREY = load("/icons/misc/smile_grey.svg") // 16x16 + + @JvmField val FROWN = load("/icons/misc/frown.svg") // 16x16 + + @JvmField val LEARN = load("/icons/misc/learn.svg") // 16x16 + + @JvmField val JAVA = load("/icons/misc/java.svg") // 16x16 + + @JvmField val PYTHON = load("/icons/misc/python.svg") // 16x16 + + @JvmField val JAVASCRIPT = load("/icons/misc/javaScript.svg") // 16x16 + + @JvmField val TYPESCRIPT = load("/icons/misc/typeScript.svg") // 16x16 + + @JvmField val CSHARP = load("/icons/misc/csharp.svg") // 16x16 + + @JvmField val NEW = load("/icons/misc/new.svg") // 16x16 + } + + object Resources { + @JvmField val APPRUNNER_SERVICE = load("/icons/resources/AppRunnerService.svg") // 16x16 + + @JvmField val CLOUDFORMATION_STACK = load("/icons/resources/CloudFormationStack.svg") // 16x16 + + object CloudWatch { + @JvmField val LOGS = load("/icons/resources/cloudwatchlogs/CloudWatchLogs.svg") // 16x16 + + @JvmField val LOGS_TOOL_WINDOW = load("/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow.svg") // 13x13 + + @JvmField val LOG_GROUP = load("/icons/resources/cloudwatchlogs/CloudWatchLogsGroup.svg") // 16x16 + } + + @JvmField val ECR_REPOSITORY = load("/icons/resources/ECRRepository.svg") // 16x16 + + @JvmField val LAMBDA_FUNCTION = load("/icons/resources/LambdaFunction.svg") // 16x16 + + @JvmField val SCHEMA_REGISTRY = load("/icons/resources/SchemaRegistry.svg") // 16x16 + + @JvmField val SCHEMA = load("/icons/resources/Schema.svg") // 16x16 + + @JvmField val SERVERLESS_APP = load("/icons/resources/ServerlessApp.svg") // 16x16 + + @JvmField val S3_BUCKET = load("/icons/resources/S3Bucket.svg") // 16x16 + + @JvmField val REDSHIFT = load("/icons/resources/Redshift.svg") // 16x16 + + object DynamoDb { + @JvmField val TABLE = load("/icons/resources/dynamodb/DynamoDbTable.svg") + } + + object Ecs { + @JvmField val ECS_CLUSTER = load("/icons/resources/ecs/EcsCluster.svg") + + @JvmField val ECS_SERVICE = load("/icons/resources/ecs/EcsService.svg") + + @JvmField val ECS_TASK_DEFINITION = load("/icons/resources/ecs/EcsTaskDefinition.svg") + } + + object Rds { + @JvmField val MYSQL = load("/icons/resources/rds/Mysql.svg") // 16x16 + + @JvmField val POSTGRES = load("/icons/resources/rds/Postgres.svg") // 16x16 + } + + object Sqs { + @JvmField val SQS_QUEUE = load("/icons/resources/sqs/SqsQueue.svg") // 16x16 + + @JvmField val SQS_TOOL_WINDOW = load("/icons/resources/sqs/SqsToolWindow.svg") // 13x13 + } + + object CodeWhisperer { + @JvmField val CUSTOM = load("icons/resources/CodewhispererCustom.svg") // 16 * 16 + + // Icons with full severity string + + @JvmField val SEVERITY_INFO = load("/icons/resources/codewhisperer/severity-info.svg") + + @JvmField val SEVERITY_LOW = load("/icons/resources/codewhisperer/severity-low.svg") + + @JvmField val SEVERITY_MEDIUM = load("/icons/resources/codewhisperer/severity-medium.svg") + + @JvmField val SEVERITY_HIGH = load("/icons/resources/codewhisperer/severity-high.svg") + + @JvmField val SEVERITY_CRITICAL = load("/icons/resources/codewhisperer/severity-critical.svg") + + // Icons with severity initials + + @JvmField val SEVERITY_INITIAL_INFO = load("/icons/resources/codewhisperer/severity-initial-info.svg") + + @JvmField val SEVERITY_INITIAL_LOW = load("/icons/resources/codewhisperer/severity-initial-low.svg") + + @JvmField val SEVERITY_INITIAL_MEDIUM = load("/icons/resources/codewhisperer/severity-initial-medium.svg") + + @JvmField val SEVERITY_INITIAL_HIGH = load("/icons/resources/codewhisperer/severity-initial-high.svg") + + @JvmField val SEVERITY_INITIAL_CRITICAL = load("/icons/resources/codewhisperer/severity-initial-critical.svg") + } + } + + object Actions { + @JvmField val LAMBDA_FUNCTION_NEW: Icon = LayeredIcon.create(Resources.LAMBDA_FUNCTION, AllIcons.Actions.New) + + @JvmField val SCHEMA_VIEW: Icon = AllIcons.Actions.Preview + + @JvmField val SCHEMA_CODE_GEN: Icon = AllIcons.Actions.Download + + @JvmField val SCHEMA_SEARCH: Icon = AllIcons.Actions.Search + } + + object CodeTransform { + @JvmField val TIMELINE_STEP_DARK = load("/icons/resources/codetransform/transform-timeline-step-done.svg") // 16 * 16 + + @JvmField val TIMELINE_STEP_LIGHT = load("/icons/resources/codetransform/transform-timeline-step-done-light.svg") // 16 * 16 + + @JvmField val CHECKMARK_GREEN = load("/icons/resources/codetransform/greenCheckmark.svg") + + @JvmField val CHECKMARK_GRAY = load("/icons/resources/codetransform/checkmark.svg") + + @JvmField val TIMELINE_STEP = load("/icons/resources/codetransform/transform-timeline-step-done.svg") // 16 * 16 + + @JvmField val PLAN_VARIABLES_LIGHT = load("/icons/resources/codetransform/transform-variables-light.svg") + + @JvmField val PLAN_VARIABLES_DARK = load("/icons/resources/codetransform/transform-variables-dark.svg") + + @JvmField val PLAN_STEP_INTO_LIGHT = load("/icons/resources/codetransform/transform-step-into-light.svg") + + @JvmField val PLAN_STEP_INTO_DARK = load("/icons/resources/codetransform/transform-step-into-dark.svg") + + @JvmField val PLAN_DEPENDENCIES_LIGHT = load("/icons/resources/codetransform/transform-dependencies-light.svg") + + @JvmField val PLAN_DEPENDENCIES_DARK = load("/icons/resources/codetransform/transform-dependencies-dark.svg") + + @JvmField val PLAN_FILE_LIGHT = load("/icons/resources/codetransform/transform-file-light.svg") + + @JvmField val PLAN_FILE_DARK = load("/icons/resources/codetransform/transform-file-dark.svg") + + @JvmField val PLAN_ARROW_LIGHT = load("/icons/resources/codetransform/transform-arrow-light.svg") + + @JvmField val PLAN_ARROW_DARK = load("/icons/resources/codetransform/transform-arrow-dark.svg") + + @JvmField val PLAN_DEFAULT_LIGHT = load("/icons/resources/codetransform/transform-default-light.svg") + + @JvmField val PLAN_DEFAULT_DARK = load("/icons/resources/codetransform/transform-default-dark.svg") + } + + private fun load(path: String): Icon = IconLoader.getIcon(path, AwsIcons::class.java) +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt new file mode 100644 index 00000000000..6704f34867e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt @@ -0,0 +1,155 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core + +import com.intellij.openapi.components.service +import software.amazon.q.jetbrains.core.Resource +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import java.time.Duration +import java.util.concurrent.CompletionStage +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit + +// Getting resources can take a long time on a slow connection or if there are a lot of resources. This call should +// always be done in an async context so it should be OK to take multiple seconds. +private val DEFAULT_TIMEOUT = Duration.ofSeconds(30) + +/** + * Intended to prevent repeated unnecessary calls to AWS to understand resource state. + * + * Will cache responses from AWS by [AwsRegion]/[ToolkitCredentialsProvider] - generically applicable to any AWS call. + */ +interface AwsResourceCache { + /** + * Get a [resource] either by making a call or returning it from the cache if present and unexpired. + * + * @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true + * @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false + */ + fun getResource( + resource: Resource, + region: AwsRegion, + credentialProvider: ToolkitCredentialsProvider, + useStale: Boolean = true, + forceFetch: Boolean = false, + ): CompletionStage + + /** + * @see [getResource] + */ + fun getResource( + resource: Resource, + region: AwsRegion, + tokenProvider: ToolkitBearerTokenProvider, + useStale: Boolean = true, + forceFetch: Boolean = false, + ): CompletionStage + + /** + * @see [getResource] + */ + fun getResource( + resource: Resource, + connectionSettings: ClientConnectionSettings<*>, + useStale: Boolean = true, + forceFetch: Boolean = false, + ): CompletionStage = when (connectionSettings) { + is ConnectionSettings -> getResource(resource, connectionSettings.region, connectionSettings.credentials, useStale, forceFetch) + is TokenConnectionSettings -> getResource(resource, connectionSettings.region, connectionSettings.tokenProvider, useStale, forceFetch) + } + + /** + * Blocking version of [getResource] + * + * @param[region] the specific [AwsRegion] to use for this resource + * @param[credentialProvider] the specific [ToolkitCredentialsProvider] to use for this resource + */ + fun getResourceNow( + resource: Resource, + region: AwsRegion, + credentialProvider: ToolkitCredentialsProvider, + timeout: Duration = DEFAULT_TIMEOUT, + useStale: Boolean = true, + forceFetch: Boolean = false, + ): T = wait(timeout) { getResource(resource, region, credentialProvider, useStale, forceFetch) } + + /** + * Blocking version of [getResource] + */ + fun getResourceNow( + resource: Resource, + region: AwsRegion, + tokenProvider: ToolkitBearerTokenProvider, + timeout: Duration = DEFAULT_TIMEOUT, + useStale: Boolean = true, + forceFetch: Boolean = false, + ): T = wait(timeout) { getResource(resource, region, tokenProvider, useStale, forceFetch) } + + /** + * Blocking version of [getResource] + */ + fun getResourceNow( + resource: Resource, + connectionSettings: ClientConnectionSettings<*>, + timeout: Duration = DEFAULT_TIMEOUT, + useStale: Boolean = true, + forceFetch: Boolean = false, + ): T = when (connectionSettings) { + is ConnectionSettings -> getResourceNow(resource, connectionSettings.region, connectionSettings.credentials, timeout, useStale, forceFetch) + is TokenConnectionSettings -> getResourceNow(resource, connectionSettings.region, connectionSettings.tokenProvider, timeout, useStale, forceFetch) + } + + /** + * Gets the [resource] if it exists in the cache. + * + * @param[region] the specific [AwsRegion] to use for this resource + * @param[credentialProvider] the specific [ToolkitCredentialsProvider] to use for this resource + */ + fun getResourceIfPresent(resource: Resource, region: AwsRegion, credentialProvider: ToolkitCredentialsProvider, useStale: Boolean = true): T? + + /** + * Gets the [resource] if it exists in the cache. + */ + fun getResourceIfPresent(resource: Resource, region: AwsRegion, tokenProvider: ToolkitBearerTokenProvider, useStale: Boolean = true): T? + + /** + * Gets the [resource] if it exists in the cache. + */ + fun getResourceIfPresent(resource: Resource, connectionSettings: ClientConnectionSettings<*>, useStale: Boolean = true): T? = + when (connectionSettings) { + is ConnectionSettings -> getResourceIfPresent(resource, connectionSettings.region, connectionSettings.credentials, useStale) + is TokenConnectionSettings -> getResourceIfPresent(resource, connectionSettings.region, connectionSettings.tokenProvider, useStale) + } + + /** + * Clears the contents of the cache across all regions, credentials and resource types. + */ + suspend fun clear() // TODO: ultimately all of these calls need to be made suspend - start with this one to resolve UI lock + + /** + * Clears the contents of the cache for the specific [ClientConnectionSettings] + */ + fun clear(connectionSettings: ClientConnectionSettings<*>) + + /** + * Clears the contents of the cache for the specific [resource] type] & [ClientConnectionSettings] + */ + fun clear(resource: Resource<*>, connectionSettings: ClientConnectionSettings<*>) + + companion object { + @JvmStatic + fun getInstance(): AwsResourceCache = service() + + private fun wait(timeout: Duration, call: () -> CompletionStage) = try { + call().toCompletableFuture().get(timeout.toMillis(), TimeUnit.MILLISECONDS) + } catch (e: ExecutionException) { + throw e.cause ?: e + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/RemoteResourceResolverProvider.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/RemoteResourceResolverProvider.kt new file mode 100644 index 00000000000..5352e91af25 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/RemoteResourceResolverProvider.kt @@ -0,0 +1,15 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core + +import com.intellij.openapi.components.service +import software.amazon.q.core.utils.RemoteResourceResolver + +interface RemoteResourceResolverProvider { + fun get(): RemoteResourceResolver + + companion object { + fun getInstance(): RemoteResourceResolverProvider = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/coroutines/PluginCoroutineScopeTracker.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/coroutines/PluginCoroutineScopeTracker.kt new file mode 100644 index 00000000000..e5376f0a84d --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/coroutines/PluginCoroutineScopeTracker.kt @@ -0,0 +1,37 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core.coroutines + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import java.util.concurrent.CancellationException + +class PluginCoroutineScopeTracker : Disposable { + @PublishedApi + internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = BackgroundThreadPoolScope(coroutineName, this) + + override fun dispose() {} + + companion object { + fun getInstance() = service() + fun getInstance(project: Project) = project.service() + } +} + +private class BackgroundThreadPoolScope(coroutineName: String, disposable: Disposable) : CoroutineScope { + override val coroutineContext = SupervisorJob() + CoroutineName(coroutineName) + getCoroutineBgContext() + + init { + Disposer.register(disposable) { + coroutineContext.cancel(CancellationException("Parent disposable was disposed")) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt new file mode 100644 index 00000000000..0fdc4c0adca --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt @@ -0,0 +1,139 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service +import com.intellij.openapi.util.SimpleModificationTracker +import com.intellij.util.messages.Topic +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialProviderFactory +import software.amazon.q.core.credentials.CredentialProviderNotFoundException +import software.amazon.q.core.credentials.SsoSessionBackedCredentialIdentifier +import software.amazon.q.core.credentials.SsoSessionIdentifier +import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import java.util.concurrent.ConcurrentHashMap + +abstract class CredentialManager : SimpleModificationTracker() { + private val providerIds = ConcurrentHashMap() + private val ssoSessionIds = ConcurrentHashMap() + private val awsCredentialProviderCache = ConcurrentHashMap>() + + protected abstract fun factoryMapping(): Map + + @Throws(CredentialProviderNotFoundException::class) + fun getAwsCredentialProvider(providerId: CredentialIdentifier, region: AwsRegion): ToolkitCredentialsProvider = + ToolkitCredentialsProvider(providerId, AwsCredentialProviderProxy(providerId.id, region)) + + fun getCredentialIdentifiers(): List = providerIds.values + .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.displayName }) + + fun getSsoSessionIdentifiers(): List = ssoSessionIds.values + .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.id }) + + fun getCredentialIdentifierById(id: String): CredentialIdentifier? = providerIds[id] + + // TODO: Convert these to bulk listeners so we only send N messages where N is # of extensions vs # of providers + protected fun addProvider(identifier: CredentialIdentifier) { + providerIds[identifier.id] = identifier + + incModificationCount() + ApplicationManager.getApplication().messageBus.syncPublisher(CREDENTIALS_CHANGED).providerAdded(identifier) + } + + protected fun modifyProvider(identifier: CredentialIdentifier) { + awsCredentialProviderCache.remove(identifier.id) + providerIds[identifier.id] = identifier + + incModificationCount() + ApplicationManager.getApplication().messageBus.syncPublisher(CREDENTIALS_CHANGED).providerModified(identifier) + } + + protected fun modifyDependentProviders(providerId: String) { + providerIds.values.forEach { + if (it is SsoSessionBackedCredentialIdentifier && it.sessionIdentifier == providerId) { + modifyProvider(it) + } + } + } + + protected fun removeProvider(identifier: CredentialIdentifier) { + providerIds.remove(identifier.id) + awsCredentialProviderCache.remove(identifier.id) + + incModificationCount() + ApplicationManager.getApplication().messageBus.syncPublisher(CREDENTIALS_CHANGED).providerRemoved(identifier) + } + + protected fun addSsoSession(identifier: SsoSessionIdentifier) { + ssoSessionIds[identifier.id] = identifier + + incModificationCount() + ApplicationManager.getApplication().messageBus.syncPublisher(CREDENTIALS_CHANGED).ssoSessionAdded(identifier) + } + + protected fun modifySsoSession(identifier: SsoSessionIdentifier) { + ssoSessionIds[identifier.id] = identifier + + incModificationCount() + ApplicationManager.getApplication().messageBus.syncPublisher(CREDENTIALS_CHANGED).ssoSessionModified(identifier) + } + + protected fun removeSsoSession(identifier: SsoSessionIdentifier) { + ssoSessionIds.remove(identifier.id) + + incModificationCount() + ApplicationManager.getApplication().messageBus.syncPublisher(CREDENTIALS_CHANGED).ssoSessionRemoved(identifier) + } + + /** + * Inner class that lazy-ily requests the true AwsCredentialsProvider from the factory when needed. This acts as a middle man that allows for existing + * ToolkitCredentialsProvider (like ones passed to existing SDK clients), to keep operating even if the credentials they represent have been updated such + * as loading from disk when new values. + */ + private inner class AwsCredentialProviderProxy(private val providerId: String, private val region: AwsRegion) : AwsCredentialsProvider { + override fun resolveCredentials(): AwsCredentials = runUnderProgressIfNeeded(null, AwsCoreBundle.message("credentials.retrieving"), cancelable = true) { + getOrCreateAwsCredentialsProvider(providerId, region).resolveCredentials() + } + + private fun getOrCreateAwsCredentialsProvider(providerId: String, region: AwsRegion): AwsCredentialsProvider { + // Validate that the provider ID is still valid and get the latest copy + val identifier = providerIds[providerId] + ?: throw CredentialProviderNotFoundException("Provider ID $providerId was removed, can't resolve credentials") + + val partitionCache = awsCredentialProviderCache.computeIfAbsent(providerId) { ConcurrentHashMap() } + + // If we already resolved creds for this partition and provider ID, just return it, else compute new one + return partitionCache.computeIfAbsent(region.partitionId) { + val providerFactory = factoryMapping()[identifier.factoryId] + ?: throw CredentialProviderNotFoundException("No provider factory found with ID ${identifier.factoryId}") + + try { + providerFactory.createAwsCredentialProvider(identifier, region) + } catch (e: Exception) { + throw CredentialProviderNotFoundException("Failed to create underlying AwsCredentialProvider", e) + } + } + } + } + + companion object { + @JvmStatic + fun getInstance(): CredentialManager = service() + + /*** + * [MessageBus] topic for when credential providers get added/changed/deleted + */ + val CREDENTIALS_CHANGED: Topic = Topic.create( + "AWS toolkit credential providers changed", + ToolkitCredentialsChangeListener::class.java + ) + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt new file mode 100644 index 00000000000..558cac09c6a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt @@ -0,0 +1,38 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.components.service +import software.amazon.q.jetbrains.core.credentials.AuthProfile +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.LastLoginIdcInfo +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile + +interface ToolkitAuthManager { + fun listConnections(): List + + fun createConnection(profile: AuthProfile): ToolkitConnection + + /** + * Creates a connection that is not visible to the rest of the toolkit unless authentication succeeds + * @return [AwsBearerTokenConnection] on success + */ + fun tryCreateTransientSsoConnection(profile: AuthProfile, callback: (AwsBearerTokenConnection) -> Unit): AwsBearerTokenConnection + fun getOrCreateSsoConnection(profile: UserConfigSsoSessionProfile): AwsBearerTokenConnection + + fun deleteConnection(connection: ToolkitConnection) + fun deleteConnection(connectionId: String) + + fun getConnection(connectionId: String): ToolkitConnection? + + /* + * The info user used last time to log in to IdC for prefilling the form. + */ + fun getLastLoginIdcInfo(): LastLoginIdcInfo + + companion object { + fun getInstance() = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt new file mode 100644 index 00000000000..d1094be381a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt @@ -0,0 +1,20 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core.credentials.pinning + +import com.intellij.openapi.components.service +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection + +interface ConnectionPinningManager { + fun isFeaturePinned(feature: FeatureWithPinnedConnection): Boolean + fun getPinnedConnection(feature: FeatureWithPinnedConnection): ToolkitConnection? + fun setPinnedConnection(feature: FeatureWithPinnedConnection, newConnection: ToolkitConnection?) + + fun pinFeatures(oldConnection: ToolkitConnection?, newConnection: ToolkitConnection, features: List) + + companion object { + fun getInstance(): ConnectionPinningManager = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt new file mode 100644 index 00000000000..937d8fc6523 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt @@ -0,0 +1,15 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.openapi.components.service + +interface ProfileWatcher { + fun addListener(listener: () -> Unit) + fun forceRefresh() {} + + companion object { + fun getInstance() = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt new file mode 100644 index 00000000000..4675fa410e2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt @@ -0,0 +1,10 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.core.credentials.sso + +import software.amazon.q.jetbrains.core.credentials.sso.SsoLoginCallback + +interface SsoLoginCallbackProvider { + fun getProvider(isAlwaysShowDeviceCode: Boolean, ssoUrl: String): SsoLoginCallback +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/settings/AwsSettings.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/settings/AwsSettings.kt new file mode 100644 index 00000000000..aef21b8516e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/settings/AwsSettings.kt @@ -0,0 +1,26 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.settings + +import com.intellij.openapi.components.service +import software.amazon.q.jetbrains.settings.ProfilesNotification +import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion +import java.util.UUID + +interface AwsSettings { + var isTelemetryEnabled: Boolean + var promptedForTelemetry: Boolean + var useDefaultCredentialRegion: UseAwsCredentialRegion + var profilesNotification: ProfilesNotification + var isAutoUpdateEnabled: Boolean + var isAutoUpdateNotificationEnabled: Boolean + var isAutoUpdateFeatureNotificationShownOnce: Boolean + var isQMigrationNotificationShownOnce: Boolean + val clientId: UUID + + companion object { + @JvmStatic + fun getInstance(): AwsSettings = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt new file mode 100644 index 00000000000..e00ce64860b --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt @@ -0,0 +1,125 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package migration.software.amazon.q.jetbrains.telemetry + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment +import software.amazon.q.jetbrains.core.credentials.getConnectionSettings +import software.amazon.q.jetbrains.core.getResourceIfPresent +import software.amazon.q.jetbrains.services.sts.StsResources +import software.amazon.q.jetbrains.services.telemetry.MetricEventMetadata +import software.amazon.q.jetbrains.services.telemetry.PluginResolver +import software.amazon.q.jetbrains.services.telemetry.TelemetryListener +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.telemetry.DefaultMetricEvent +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.core.utils.tryOrNull +import java.util.concurrent.atomic.AtomicBoolean + +abstract class TelemetryService(private val publisher: TelemetryPublisher, protected val batcher: TelemetryBatcher) : Disposable { + private val isDisposing = AtomicBoolean(false) + private val listeners = mutableSetOf() + + init { + setTelemetryEnabled(AwsSettings.getInstance().isTelemetryEnabled) + } + + fun record(connectionSettings: ConnectionSettings?, buildEvent: MetricEvent.Builder.() -> Unit) { + val metricEventMetadata = when (connectionSettings) { + is ConnectionSettings -> MetricEventMetadata( + awsAccount = connectionSettings.activeAwsAccountIfKnown() ?: DefaultMetricEvent.METADATA_NOT_SET, + awsRegion = connectionSettings.region.id + ) + else -> MetricEventMetadata() + } + val pluginResolver = PluginResolver.fromCurrentThread() + metricEventMetadata.awsProduct = pluginResolver.product + metricEventMetadata.awsVersion = pluginResolver.version + record(metricEventMetadata, buildEvent) + } + + fun record(project: Project?, buildEvent: MetricEvent.Builder.() -> Unit) { + // It is possible that a race can happen if we record telemetry but project has been closed, i.e. async actions + val metricEventMetadata = if (project != null) { + if (project.isDisposed) { + MetricEventMetadata( + awsAccount = DefaultMetricEvent.METADATA_INVALID, + awsRegion = DefaultMetricEvent.METADATA_INVALID + ) + } else { + val connectionSettings = project.getConnectionSettings() + MetricEventMetadata( + awsAccount = connectionSettings?.activeAwsAccountIfKnown() ?: DefaultMetricEvent.METADATA_NOT_SET, + awsRegion = connectionSettings?.region?.id ?: DefaultMetricEvent.METADATA_NOT_SET + ) + } + } else { + MetricEventMetadata() + } + val pluginResolver = PluginResolver.fromCurrentThread() + metricEventMetadata.awsProduct = pluginResolver.product + metricEventMetadata.awsVersion = pluginResolver.version + record(metricEventMetadata, buildEvent) + } + + private fun ConnectionSettings.activeAwsAccountIfKnown(): String? = tryOrNull { this.getResourceIfPresent(StsResources.ACCOUNT) } + + @Synchronized + fun setTelemetryEnabled(isEnabled: Boolean, onChangeEvent: (Boolean) -> Unit = {}) { + batcher.onTelemetryEnabledChanged(isEnabled and TELEMETRY_ENABLED, onChangeEvent) + } + + fun addListener(listener: TelemetryListener) { + listeners.add(listener) + } + + fun removeListener(listener: TelemetryListener) { + listeners.remove(listener) + } + + override fun dispose() { + if (!isDisposing.compareAndSet(false, true)) { + return + } + + listeners.clear() + + batcher.shutdown() + publisher.close() + } + + fun record(metricEventMetadata: MetricEventMetadata, buildEvent: MetricEvent.Builder.() -> Unit) { + val builder = DefaultMetricEvent.builder() + builder.awsAccount(metricEventMetadata.awsAccount) + builder.awsRegion(metricEventMetadata.awsRegion) + builder.awsProduct(metricEventMetadata.awsProduct) + builder.awsVersion(metricEventMetadata.awsVersion) + + buildEvent(builder) + + val event = builder.build() + + runCatching { + listeners.forEach { it.onTelemetryEvent(event) } + } + + batcher.enqueue(event) + } + + suspend fun sendFeedback(sentiment: Sentiment, comment: String, metadata: Map = emptyMap()) { + publisher.sendFeedback(sentiment, comment, metadata) + } + + companion object { + private const val TELEMETRY_KEY = "aws.toolkits.enableTelemetry" + private val TELEMETRY_ENABLED = System.getProperty(TELEMETRY_KEY)?.toBoolean() ?: true + + fun getInstance(): TelemetryService = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/AwsToolkit.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/AwsToolkit.kt new file mode 100644 index 00000000000..e5d41e7fbcb --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/AwsToolkit.kt @@ -0,0 +1,42 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginDescriptor +import com.intellij.openapi.extensions.PluginId +import java.nio.file.Path +import java.util.EnumMap + +object AwsToolkit { + const val TOOLKIT_PLUGIN_ID = "aws.toolkit" + const val Q_PLUGIN_ID = "amazon.q" + + private val TOOLKIT_PLUGIN_INFO = PluginInfo(TOOLKIT_PLUGIN_ID, "AWS Toolkit") + private val Q_PLUGIN_INFO = PluginInfo(Q_PLUGIN_ID, "Amazon Q") + + val PLUGINS_INFO = EnumMap(AwsPlugin::class.java).apply { + put(AwsPlugin.TOOLKIT, TOOLKIT_PLUGIN_INFO) + put(AwsPlugin.Q, Q_PLUGIN_INFO) + } + + const val GITHUB_URL = "https://github.com/aws/aws-toolkit-jetbrains" + const val AWS_DOCS_URL = "https://docs.aws.amazon.com/console/toolkit-for-jetbrains" + const val GITHUB_CHANGELOG = "https://github.com/aws/aws-toolkit-jetbrains/blob/main/CHANGELOG.md" +} + +data class PluginInfo(val id: String, val name: String) { + val descriptor: PluginDescriptor? + get() = PluginManagerCore.getPlugin(PluginId.getId(id)) + val version: String? + get() = descriptor?.version + val path: Path? + get() = descriptor?.pluginPath +} + +enum class AwsPlugin { + TOOLKIT, + Q, + CORE, +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/IsDeveloperMode.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/IsDeveloperMode.kt new file mode 100644 index 00000000000..ec1006507a3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/IsDeveloperMode.kt @@ -0,0 +1,8 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains + +import com.intellij.openapi.util.registry.Registry + +fun isDeveloperMode() = Registry.`is`("aws.toolkit.developerMode", false) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ToolkitPlaces.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ToolkitPlaces.kt new file mode 100644 index 00000000000..9f2e3835781 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ToolkitPlaces.kt @@ -0,0 +1,13 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains + +object ToolkitPlaces { + const val DEVTOOLS_TOOL_WINDOW = "DevToolsToolWindow" + const val EXPLORER_TOOL_WINDOW = "ExplorerToolWindow" + const val EDITOR_PSI_REFERENCE = "Editor" + const val DEV_TOOL_WINDOW = "DeveloperToolsWindow" + const val CWQ_TOOL_WINDOW = "CodeWhispererQWindow" + const val ADD_CONNECTION_DIALOG = "ToolkitAddConnectionDialog" +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt new file mode 100644 index 00000000000..912257e0926 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt @@ -0,0 +1,118 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ApplicationNamesInfo +import com.intellij.openapi.application.ex.ApplicationInfoEx +import com.intellij.openapi.components.service +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder +import software.amazon.awssdk.core.SdkClient +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.http.SdkHttpClient +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.services.telemetry.PluginResolver +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.ToolkitClientManager +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener +import software.amazon.q.core.region.ToolkitRegionProvider +import software.amazon.q.core.utils.tryOrNull + +open class AwsClientManager : ToolkitClientManager(), Disposable { + init { + val busConnection = ApplicationManager.getApplication().messageBus.connect(this) + busConnection.subscribe( + CredentialManager.CREDENTIALS_CHANGED, + object : ToolkitCredentialsChangeListener { + override fun providerRemoved(identifier: CredentialIdentifier) { + invalidateSdks(identifier.id) + } + + override fun providerRemoved(providerId: String) { + invalidateSdks(providerId) + } + } + ) + + busConnection.subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun onProviderChange(providerId: String, newScopes: List?) { + invalidateSdks(providerId) + } + + override fun invalidate(providerId: String) { + invalidateSdks(providerId) + } + } + ) + } + + override fun userAgent() = getUserAgent() + + override fun dispose() { + shutdown() + } + + override fun sdkHttpClient(): SdkHttpClient = AwsSdkClient.getInstance().sharedSdkClient() + + override fun getRegionProvider(): ToolkitRegionProvider = AwsRegionProvider.getInstance() + + override fun globalClientCustomizer( + credentialProvider: AwsCredentialsProvider?, + tokenProvider: SdkTokenProvider?, + regionId: String, + builder: AwsClientBuilder<*, *>, + clientOverrideConfiguration: ClientOverrideConfiguration.Builder, + ) { + CUSTOMIZER_EP.extensionList.forEach { it.customize(credentialProvider, tokenProvider, regionId, builder, clientOverrideConfiguration) } + } + + companion object { + @JvmStatic + fun getInstance(): ToolkitClientManager = service() + + fun getUserAgent(): String { + val pluginResolver = PluginResolver.fromCurrentThread() + val pluginName = pluginResolver.product.toString().replace(" ", "-") + val pluginVersion = pluginResolver.version + return "$pluginName/$pluginVersion $platformName/$platformVersion ClientId/${AwsSettings.getInstance().clientId}" + } + + private val platformName = tryOrNull { ApplicationNamesInfo.getInstance().fullProductNameWithEdition.replace(' ', '-') } + private val platformVersion = tryOrNull { ApplicationInfoEx.getInstanceEx().fullVersion.replace(' ', '-') } + + val CUSTOMIZER_EP = ExtensionPointName("amazon.q.sdk.clientCustomizer") + } +} + +inline fun Project.awsClient(): T { + val accountSettingsManager = AwsConnectionManager.getInstance(this) + + return AwsClientManager + .getInstance() + .getClient(accountSettingsManager.activeCredentialProvider, accountSettingsManager.activeRegion) +} + +inline fun ConnectionSettings.awsClient(): T = AwsClientManager.getInstance().getClient(credentials, region) + +inline fun TokenConnectionSettings.awsClient(): T = AwsClientManager.getInstance().getClient(this) + +inline fun ClientConnectionSettings<*>.awsClient(): T = when (this) { + is ConnectionSettings -> awsClient() + is TokenConnectionSettings -> awsClient() +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt new file mode 100644 index 00000000000..5d55b348a2f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt @@ -0,0 +1,420 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.util.Alarm +import com.intellij.util.AlarmFactory +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.jetbrains.annotations.VisibleForTesting +import software.amazon.awssdk.core.SdkClient +import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.getConnectionSettingsOrThrow +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap +import kotlin.reflect.KClass + +typealias AwsResourceCache = migration.software.amazon.q.jetbrains.core.AwsResourceCache + +/** + * Get a [resource] either by making a call or returning it from the cache if present and unexpired. Uses the currently [AwsRegion] + * & [ToolkitCredentialsProvider] active in [AwsConnectionManager]. + * + * @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true + * @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false + */ +fun Project.getResource(resource: Resource, useStale: Boolean = true, forceFetch: Boolean = false): CompletionStage = + AwsResourceCache.getInstance().getResource(resource, this.getConnectionSettingsOrThrow(), useStale, forceFetch) + +/** + * Blocking version of [getResource] + * + * @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true + * @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false + */ +fun Project.getResourceNow(resource: Resource, timeout: Duration = Duration.ofSeconds(30), useStale: Boolean = true, forceFetch: Boolean = false): T = + AwsResourceCache.getInstance().getResourceNow(resource, this.getConnectionSettingsOrThrow(), timeout, useStale, forceFetch) + +/** + * Gets the [resource] if it exists in the cache. + * + * @param[useStale] return a cached version if it exists (even if it's expired). Default: true + */ +fun ConnectionSettings.getResourceIfPresent(resource: Resource, useStale: Boolean = true): T? = + AwsResourceCache.getInstance().getResourceIfPresent(resource, this, useStale) + +/** + * Gets the [resource] if it exists in the cache. + * + * @see [ConnectionSettings.getResourceIfPresent] + */ +fun Project.getResourceIfPresent(resource: Resource, useStale: Boolean = true): T? = + getConnectionSettingsOrThrow().getResourceIfPresent(resource, useStale) + +/** + * Clears the contents of the cache for the specific [resource] type, in the currently active [ConnectionSettings] + */ +fun Project.clearResourceForCurrentConnection(resource: Resource<*>) = + AwsResourceCache.getInstance().clear(resource, this.getConnectionSettingsOrThrow()) + +/** + * Clears the contents of the cache of all resource types for the currently active [ConnectionSettings] + */ +fun Project.clearResourceForCurrentConnection() = + AwsResourceCache.getInstance().clear(this.getConnectionSettingsOrThrow()) + +sealed class Resource { + + /** + * A [Cached] resource is one whose fetch is potentially expensive, the result of which should be memoized for a period of time ([expiry]). + */ + abstract class Cached : Resource() { + abstract fun fetch(connectionSettings: ClientConnectionSettings<*>): T + open fun expiry(): Duration = DEFAULT_EXPIRY + abstract val id: String + + companion object { + private val DEFAULT_EXPIRY = Duration.ofMinutes(10) + } + } + + /** + * A [View] resource depends on some other underlying [Resource] and then performs some [transform] of the [underlying]'s result + * in order to return the desired type [Output]. The [transform] result is not cached, [transform]s are re-applied on each fetch - thus should + * should be relatively cheap. + */ + class View(val underlying: Resource, private val transform: (Input, AwsRegion) -> Output) : Resource() { + @Suppress("UNCHECKED_CAST") + fun doMap(input: Any, region: AwsRegion) = transform(input as Input, region) + } + + companion object { + fun view(underlying: Resource, transform: Input.() -> Output): Resource = + View(underlying) { input, _ -> transform(input) } + } +} + +fun Resource>.map(transform: (Input) -> Output): Resource> = Resource.view(this) { map(transform) } + +fun Resource>.filter(predicate: (T) -> Boolean): Resource> = Resource.view(this) { filter(predicate) } + +fun Resource>.find(predicate: (T) -> Boolean): Resource = Resource.view(this) { find(predicate) } + +class ClientBackedCachedResource( + private val sdkClientClass: KClass, + override val id: String, + private val expiry: Duration?, + private val fetchCall: ClientType.() -> ReturnType, +) : Resource.Cached() { + + constructor(sdkClientClass: KClass, id: String, fetchCall: ClientType.() -> ReturnType) : this(sdkClientClass, id, null, fetchCall) + + override fun fetch(connectionSettings: ClientConnectionSettings<*>): ReturnType { + val client = AwsClientManager.getInstance().getClient(sdkClientClass, connectionSettings) + return fetchCall(client) + } + + override fun expiry(): Duration = expiry ?: super.expiry() + override fun toString(): String = "ClientBackedCachedResource(id='$id')" +} + +@ExperimentalCoroutinesApi +class DefaultAwsResourceCache( + private val clock: Clock, + private val maximumCacheEntries: Int, + private val maintenanceInterval: Duration, +) : AwsResourceCache, Disposable, ToolkitCredentialsChangeListener { + private val coroutineScope = disposableCoroutineScope(this) + + @Suppress("unused") + constructor() : this(Clock.systemDefaultZone(), MAXIMUM_CACHE_ENTRIES, DEFAULT_MAINTENANCE_INTERVAL) + + private val cache = ConcurrentHashMap>() + private val alarm = AlarmFactory.getInstance().create(Alarm.ThreadToUse.POOLED_THREAD, this) + + init { + ApplicationManager.getApplication().messageBus.connect(this).apply { + subscribe(CredentialManager.CREDENTIALS_CHANGED, this@DefaultAwsResourceCache) + + subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun onProviderChange(providerId: String, newScopes: List?) { + clearByCredential(providerId) + } + } + ) + } + scheduleCacheMaintenance() + } + + override fun getResource( + resource: Resource, + region: AwsRegion, + credentialProvider: ToolkitCredentialsProvider, + useStale: Boolean, + forceFetch: Boolean, + ): CompletionStage = when (resource) { + is Resource.View<*, T> -> getResource( + resource.underlying, + region, + credentialProvider, + useStale, + forceFetch + ).thenApply { resource.doMap(it as Any, region) } + is Resource.Cached -> Context(resource, region, ConnectionSettings(credentialProvider, region), useStale, forceFetch) + .also { getCachedResource(it) } + .future + } + + override fun getResource( + resource: Resource, + region: AwsRegion, + tokenProvider: ToolkitBearerTokenProvider, + useStale: Boolean, + forceFetch: Boolean, + ): CompletionStage = when (resource) { + is Resource.View<*, T> -> getResource( + resource.underlying, + region, + tokenProvider, + useStale, + forceFetch + ).thenApply { resource.doMap(it as Any, region) } + is Resource.Cached -> Context(resource, region, TokenConnectionSettings(tokenProvider, region), useStale, forceFetch) + .also { getCachedResource(it) } + .future + } + + private fun getCachedResource(context: Context) { + pluginAwareExecuteOnPooledThread { + var currentValue: Entry? = null + try { + @Suppress("UNCHECKED_CAST") + val result = cache.compute(context.cacheKey) { _, value -> + currentValue = value as Entry? + fetchIfNeeded(context, currentValue) + } as Entry + + coroutineScope.launch { + try { + context.future.complete(result.value.await()) + } catch (e: Throwable) { + val previousValue = currentValue + if (context.useStale && previousValue != null && previousValue.value.isCompleted && !previousValue.value.isCompletedExceptionally) { + context.future.complete(previousValue.value.getCompleted()) + } else { + context.future.completeExceptionally(e) + } + } + } + } catch (e: Throwable) { + context.future.completeExceptionally(e) + } + } + } + + private fun runCacheMaintenance() { + try { + doRunCacheMaintenance() + } finally { + scheduleCacheMaintenance() + } + } + + @VisibleForTesting + internal fun doRunCacheMaintenance() { + var totalWeight = 0 + cache.entries.removeIf { it.value.value.isCompletedExceptionally } + val entries = cache.entries.asSequence().filter { it.value.value.isCompleted }.onEach { totalWeight += it.value.weight }.toList() + var exceededWeight = totalWeight - maximumCacheEntries + if (exceededWeight <= 0) return + entries.sortedBy { it.value.expiry }.forEach { (key, value) -> + if (exceededWeight <= 0) return@doRunCacheMaintenance + if (cache.computeRemoveIf(key) { it === value }) { + exceededWeight -= value.weight + } + } + } + + private fun scheduleCacheMaintenance() { + if (!alarm.isDisposed) { + alarm.addRequest(this::runCacheMaintenance, maintenanceInterval.toMillis()) + } + } + + override fun getResourceIfPresent(resource: Resource, region: AwsRegion, credentialProvider: ToolkitCredentialsProvider, useStale: Boolean): T? = + when (resource) { + is Resource.Cached -> { + val key = CacheKey(resource.id, region.id, credentialProvider.id) + val entry = cache.getTyped(key) + when { + entry != null && (useStale || entry.notExpired) && + entry.value.isCompleted && entry.value.getCompletionExceptionOrNull() == null -> entry.value.getCompleted() + else -> null + } + } + is Resource.View<*, T> -> getResourceIfPresent(resource.underlying, region, credentialProvider, useStale)?.let { + resource.doMap( + it, + region + ) + } + } + + override fun getResourceIfPresent(resource: Resource, region: AwsRegion, tokenProvider: ToolkitBearerTokenProvider, useStale: Boolean): T? = + when (resource) { + is Resource.Cached -> { + val key = CacheKey(resource.id, region.id, tokenProvider.id) + val entry = cache.getTyped(key) + when { + entry != null && (useStale || entry.notExpired) && + entry.value.isCompleted && entry.value.getCompletionExceptionOrNull() == null -> entry.value.getCompleted() + else -> null + } + } + is Resource.View<*, T> -> getResourceIfPresent(resource.underlying, region, tokenProvider, useStale)?.let { + resource.doMap( + it, + region + ) + } + } + + override fun clear(resource: Resource<*>, connectionSettings: ClientConnectionSettings<*>) { + when (resource) { + is Resource.Cached<*> -> cache.remove(CacheKey(resource.id, connectionSettings.region.id, connectionSettings.providerId)) + is Resource.View<*, *> -> clear(resource.underlying, connectionSettings) + } + } + + override suspend fun clear() { + coroutineScope { launch { cache.clear() } } + } + + override fun clear(connectionSettings: ClientConnectionSettings<*>) { + cache.keys.removeIf { it.providerId == connectionSettings.providerId && it.regionId == connectionSettings.region.id } + } + + override fun dispose() { + coroutineScope.launch { clear() } + } + + override fun providerRemoved(identifier: CredentialIdentifier) = clearByCredential(identifier.id) + + override fun providerModified(identifier: CredentialIdentifier) = clearByCredential(identifier.id) + + private fun clearByCredential(providerId: String) { + cache.keys.removeIf { it.providerId == providerId } + } + + private fun fetchIfNeeded(context: Context, currentEntry: Entry?) = when { + currentEntry == null -> fetch(context) + currentEntry.value.isCompletedExceptionally -> fetch(context) + currentEntry.notExpired && !context.forceFetch -> currentEntry + context.useStale -> fetchWithFallback(context, currentEntry) + else -> fetch(context) + } + + private val Deferred<*>.isCompletedExceptionally get() = isCompleted && getCompletionExceptionOrNull() != null + + private fun fetchWithFallback(context: Context, currentEntry: Entry) = try { + fetch(context) + } catch (e: Exception) { + LOG.warn(e) { "Failed to fetch resource using ${context.cacheKey}, falling back to expired entry" } + currentEntry + } + + private fun fetch(context: Context): Entry { + val value = coroutineScope.async { + context.resource.fetch(context.connectionSettings) + } + + return Entry(clock.instant().plus(context.resource.expiry()), value) + } + + private val Entry<*>.notExpired get() = value.isActive || clock.instant().isBefore(expiry) + + @VisibleForTesting + internal fun hasCacheEntry(resourceId: String): Boolean = cache.filterKeys { it.resourceId == resourceId }.isNotEmpty() + + companion object { + private val LOG = getLogger() + private const val MAXIMUM_CACHE_ENTRIES = 1000 + private val DEFAULT_MAINTENANCE_INTERVAL: Duration = Duration.ofMinutes(5) + + private data class CacheKey(val resourceId: String, val regionId: String, val providerId: String) + + private class Context( + val resource: Resource.Cached, + val region: AwsRegion, + val connectionSettings: ClientConnectionSettings<*>, + val useStale: Boolean, + val forceFetch: Boolean, + ) { + val cacheKey = CacheKey(resource.id, region.id, connectionSettings.providerId) + val future = CompletableFuture() + } + + private class Entry(val expiry: Instant, val value: Deferred) { + val weight: Int + get() = if (value.isCompleted && value.getCompletionExceptionOrNull() == null) { + when (val underlying = value.getCompleted()) { + is Collection<*> -> underlying.size + else -> 1 + } + } else { + 1 + } + } + + private fun ConcurrentMap>.getTyped(key: CacheKey) = this[key]?.let { + @Suppress("UNCHECKED_CAST") + it as Entry + } + + /** + * Atomically apply a [predicate] to the value at [key] (if it exists) and remove if matched. + * + * @return - true if removal occurred else false + */ + private fun ConcurrentHashMap.computeRemoveIf(key: K, predicate: (V) -> Boolean): Boolean { + var removed = false + computeIfPresent(key) { _, v -> + if (predicate(v)) { + removed = true + null + } else { + v + } + } + return removed + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsSdkClient.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsSdkClient.kt new file mode 100644 index 00000000000..3f80c2dcfb1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsSdkClient.kt @@ -0,0 +1,66 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service +import com.intellij.util.net.ssl.CertificateManager +import com.intellij.util.proxy.CommonProxy +import org.apache.http.impl.client.SystemDefaultCredentialsProvider +import org.apache.http.impl.conn.SystemDefaultRoutePlanner +import software.amazon.awssdk.http.ExecutableHttpRequest +import software.amazon.awssdk.http.HttpExecuteRequest +import software.amazon.awssdk.http.SdkHttpClient +import software.amazon.awssdk.http.apache.ApacheHttpClient +import software.amazon.awssdk.http.apache.ProxyConfiguration +import software.amazon.q.core.clients.SdkClientProvider +import software.amazon.q.core.utils.assertTrue +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info + +class AwsSdkClient : SdkClientProvider, Disposable { + private val sdkHttpClient: SdkHttpClient by lazy { + LOG.info { "Create new Apache client" } + val httpClientBuilder = ApacheHttpClient.builder() + .proxyConfiguration( + ProxyConfiguration.builder() + .useSystemPropertyValues(false) + .useEnvironmentVariableValues(false) + .build() + ) + .httpRoutePlanner(SystemDefaultRoutePlanner(CommonProxy.getInstance())) + .credentialsProvider(SystemDefaultCredentialsProvider()) + .tlsTrustManagersProvider { arrayOf(CertificateManager.getInstance().trustManager) } + + ValidateCorrectThreadClient(httpClientBuilder.build()) + } + + override fun sharedSdkClient(): SdkHttpClient = sdkHttpClient + + override fun dispose() { + sdkHttpClient.close() + } + + private class ValidateCorrectThreadClient(private val base: SdkHttpClient) : SdkHttpClient by base { + override fun prepareRequest(request: HttpExecuteRequest?): ExecutableHttpRequest { + val application = ApplicationManager.getApplication() + + val isValidThread = !application.isWriteAccessAllowed && !application.isReadAccessAllowed + LOG.assertTrue(isValidThread) { WRONG_THREAD } + if (!isValidThread && application.isUnitTestMode) { + throw AssertionError(WRONG_THREAD) + } + + return base.prepareRequest(request) + } + } + + companion object { + private val LOG = getLogger() + private const val WRONG_THREAD = "Network calls can't be made inside read/write action" + + fun getInstance(): SdkClientProvider = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsTelemetryPrompter.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsTelemetryPrompter.kt new file mode 100644 index 00000000000..4cc9e731b0e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsTelemetryPrompter.kt @@ -0,0 +1,37 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.openapi.options.ShowSettingsUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.AwsSettingsSharedConfigurable +import software.amazon.q.resources.AwsCoreBundle + +class AwsTelemetryPrompter : ProjectActivity { + override suspend fun execute(project: Project) { + if (AwsSettings.getInstance().promptedForTelemetry || System.getProperty("aws.telemetry.skip_prompt", null)?.toBoolean() == true) { + return + } + + val notification = Notification( + "aws.toolkit_telemetry", + AwsCoreBundle.message("aws.settings.telemetry.prompt.title"), + AwsCoreBundle.message("aws.settings.telemetry.prompt.message"), + NotificationType.INFORMATION + ).also { + it.setListener { notification, _ -> + ShowSettingsUtil.getInstance().showSettingsDialog(project, AwsSettingsSharedConfigurable::class.java) + notification.expire() + } + } + + notification.notify(project) + + AwsSettings.getInstance().promptedForTelemetry = true + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt new file mode 100644 index 00000000000..0342ee07cca --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt @@ -0,0 +1,46 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.application.PathManager +import com.intellij.util.io.createDirectories +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.core.utils.DefaultRemoteResourceResolver +import software.amazon.q.core.utils.UrlFetcher +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.CompletableFuture + +typealias RemoteResourceResolverProvider = migration.software.amazon.q.jetbrains.core.RemoteResourceResolverProvider + +class DefaultRemoteResourceResolverProvider : RemoteResourceResolverProvider { + override fun get() = RESOLVER_INSTANCE + + companion object { + private val RESOLVER_INSTANCE by lazy { + val cachePath = Paths.get(PathManager.getSystemPath(), "aws-static-resources").createDirectories() + + DefaultRemoteResourceResolver(HttpRequestUrlFetcher, cachePath) { + val future = CompletableFuture() + pluginAwareExecuteOnPooledThread { + try { + future.complete(it.call()) + } catch (e: Exception) { + future.completeExceptionally(e) + } + } + future + } + } + + object HttpRequestUrlFetcher : UrlFetcher { + override fun fetch(url: String, file: Path) { + saveFileFromUrl(url, file) + } + + override fun getETag(url: String): String = + getETagFromUrl(url) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/HttpUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/HttpUtils.kt new file mode 100644 index 00000000000..9e775ea4df5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/HttpUtils.kt @@ -0,0 +1,33 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.util.io.HttpRequests +import org.apache.http.entity.ContentType +import java.nio.file.Path + +fun saveFileFromUrl(url: String, path: Path, indicator: ProgressIndicator? = null) = + HttpRequests.request(url).userAgent(AwsClientManager.getUserAgent()).saveToFile(path.toFile(), indicator) + +fun readBytesFromUrl(url: String, indicator: ProgressIndicator? = null) = + HttpRequests.request(url).userAgent(AwsClientManager.getUserAgent()).readBytes(indicator) + +fun getTextFromUrl(url: String): String = + HttpRequests.request(url).userAgent(AwsClientManager.getUserAgent()).readString() + +fun writeJsonToUrl(url: String, jsonString: String, indicator: ProgressIndicator? = null): String = + HttpRequests.post(url, ContentType.APPLICATION_JSON.toString()) + .userAgent(AwsClientManager.getUserAgent()) + .connect { request -> + request.write(jsonString) + request.readString(indicator) + } + +fun getETagFromUrl(url: String): String = + HttpRequests.head(url) + .userAgent(AwsClientManager.getUserAgent()) + .connect { request -> + request.connection.headerFields["ETag"]?.firstOrNull().orEmpty() + } diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/contexts.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/contexts.kt new file mode 100644 index 00000000000..f184ac1c7de --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/contexts.kt @@ -0,0 +1,37 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// kotlinx.coroutines.Dispatchers is banned +@file:Suppress("BannedImports") + +package software.amazon.q.jetbrains.core.coroutines + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ModalityState +import com.intellij.util.concurrency.AppExecutorUtil +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +private class ModalityStateElement(val modalityState: ModalityState) : AbstractCoroutineContextElement(ModalityStateElementKey) + +private object ModalityStateElementKey : CoroutineContext.Key + +private object EdtCoroutineDispatcher : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) { + val state = context[ModalityStateElementKey]?.modalityState ?: ModalityState.any() + ApplicationManager.getApplication().invokeLater(block, state) + } +} + +@Deprecated("Always uses ModalityState.any() by default", ReplaceWith("EDT", "software.amazon.q.jetbrains.core.coroutines.EDT")) +fun getCoroutineUiContext(): CoroutineContext = EdtCoroutineDispatcher + +fun getCoroutineBgContext(): CoroutineContext = AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher() + +val EDT = Dispatchers.EDT + +// parallelism should be defined https://youtrack.jetbrains.com/issue/KTOR-6462 +fun ioDispatcher(limitedParallelism: Int) = Dispatchers.IO.limitedParallelism(limitedParallelism) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/scopes.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/scopes.kt new file mode 100644 index 00000000000..e354f84046f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/scopes.kt @@ -0,0 +1,74 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.coroutines + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.Application +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import migration.software.amazon.q.jetbrains.core.coroutines.PluginCoroutineScopeTracker +import java.util.concurrent.CancellationException + +/** + * Coroutine scope that is tied to the full Application closing, or plugin unloading. Default to dispatching to background thread pool. + * + * Use this if the coroutine needs to live past a project being closed or across projects such as an Application Service + */ +@Deprecated("Application x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") +fun applicationCoroutineScope(coroutineName: String): CoroutineScope = + PluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName) + +/** + * Coroutine scope that is tied to a project closing, or plugin unloading. Default to dispatching to background thread pool. + * + * Use this if the coroutine needs to live past a UI being closed, or tied to a project's life cycle such as a Project Service. + */ +@Deprecated("Project x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") +fun projectCoroutineScope(project: Project, coroutineName: String): CoroutineScope = + PluginCoroutineScopeTracker.getInstance(project).applicationThreadPoolScope(coroutineName) + +/** + * Coroutine scope that is tied to a disposable or a plugin unloading. Default to dispatching to background thread pool. + * + * Use this if the coroutine is tied to a UI such as a tool window, or a run configuration + * + * **Note: If a call lives past the closing of a UI such as kicking off a resource creation, use [projectCoroutineScope]. + * Otherwise, the coroutine will be canceled when the UI is closed!** + */ +@Deprecated( + "Coroutine scope should not be shared across entire plugin lifecycle https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes" +) +fun disposableCoroutineScope(disposable: Disposable, coroutineName: String): CoroutineScope { + check(disposable !is Project && disposable !is Application) { "disposable should not be a project or application" } + return PluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName).also { + Disposer.register(disposable) { + it.cancel(CancellationException("Parent disposable was disposed")) + } + } +} + +/** + * Version of [applicationCoroutineScope] the class name as the coroutine name. + */ +@Deprecated("Application x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") +inline fun T.applicationCoroutineScope(): CoroutineScope = + applicationCoroutineScope(T::class.java.name) + +/** + * Version of [projectCoroutineScope] the class name as the coroutine name. + */ +@Deprecated("Project x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") +inline fun T.projectCoroutineScope(project: Project): CoroutineScope = + projectCoroutineScope(project, T::class.java.name) + +/** + * Version of [disposableCoroutineScope] the class name as the coroutine name. + */ +@Deprecated( + "Coroutine scope should not be shared across entire plugin lifecycle https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes" +) +inline fun T.disposableCoroutineScope(disposable: Disposable): CoroutineScope = + disposableCoroutineScope(disposable, T::class.java.name) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt new file mode 100644 index 00000000000..0e1629f7e87 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt @@ -0,0 +1,375 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.application.AppUIExecutor +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SimpleModificationTracker +import com.intellij.util.ExceptionUtil +import com.intellij.util.messages.Topic +import org.jetbrains.concurrency.AsyncPromise +import software.amazon.q.jetbrains.core.AwsResourceCache +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.services.sts.StsResources +import software.amazon.q.jetbrains.utils.MRUList +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialProviderNotFoundException +import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.AwsTelemetry +import java.util.concurrent.atomic.AtomicReference + +abstract class AwsConnectionManager(private val project: Project) : SimpleModificationTracker(), Disposable { + private val resourceCache = AwsResourceCache.getInstance() + private val regionProvider = AwsRegionProvider.getInstance() + private val credentialsRegionHandler = CredentialsRegionHandler.getInstance(project) + + private val validationJob = AtomicReference>() + + @Volatile + var connectionState: ConnectionState = ConnectionState.InitializingToolkit + internal set(value) { + field = value + incModificationCount() + + AppUIExecutor.onWriteThread(ModalityState.any()).expireWith(this).execute { + project.messageBus.syncPublisher(CONNECTION_SETTINGS_STATE_CHANGED).settingsStateChanged(value) + } + } + + protected val recentlyUsedProfiles = MRUList(MAX_HISTORY) + protected val recentlyUsedRegions = MRUList(MAX_HISTORY) + + // Internal state is visible for AwsSettingsPanel and ChangeAccountSettingsActionGroup + var selectedCredentialIdentifier: CredentialIdentifier? = null + internal set + var selectedRegion: AwsRegion? = null + internal set + + init { + @Suppress("LeakingThis") + ApplicationManager.getApplication().messageBus.connect(this) + .subscribe( + CredentialManager.CREDENTIALS_CHANGED, + object : ToolkitCredentialsChangeListener { + override fun providerRemoved(identifier: CredentialIdentifier) { + if (selectedCredentialIdentifier == identifier) { + changeConnectionSettings(null, selectedRegion) + } + } + + override fun providerModified(identifier: CredentialIdentifier) { + if (selectedCredentialIdentifier == identifier) { + refreshConnectionState() + } + } + } + ) + } + + fun isValidConnectionSettings(): Boolean = connectionState is ConnectionState.ValidConnection + + fun connectionSettings(): ConnectionSettings? = when (val state = connectionState) { + is ConnectionState.ValidConnection -> ConnectionSettings(state.credentials, state.region) + else -> null + } + + /** + * Re-trigger validation of the current connection + */ + fun refreshConnectionState() { + changeFieldsAndNotify { } + } + + /** + * Internal setter that allows for null values and is intended to set the internal state and still notify + */ + protected fun changeConnectionSettings(identifier: CredentialIdentifier?, region: AwsRegion?) { + changeFieldsAndNotify { + identifier?.let { + recentlyUsedProfiles.add(it.id) + } + + region?.let { + recentlyUsedRegions.add(it.id) + } + + selectedCredentialIdentifier = identifier + selectedRegion = region + } + } + + /** + * Changes the credentials and then validates them. Notifies listeners of results + */ + fun changeCredentialProvider(identifier: CredentialIdentifier, passive: Boolean = false) { + changeFieldsAndNotify { + recentlyUsedProfiles.add(identifier.id) + + selectedCredentialIdentifier = identifier + + selectedRegion = credentialsRegionHandler.determineSelectedRegion(identifier, selectedRegion) + } + + AwsTelemetry.setCredentials(project = project, credentialType = identifier.credentialType.toTelemetryType(), passive = passive) + } + + /** + * Changes the region and then validates them. Notifies listeners of results + */ + fun changeRegion(region: AwsRegion, passive: Boolean = false) { + val oldRegion = selectedRegion + changeFieldsAndNotify { + recentlyUsedRegions.add(region.id) + selectedRegion = region + } + + if (oldRegion?.partitionId != region.partitionId) { + AwsTelemetry.setPartition(project = project, partitionId = region.partitionId, passive = passive) + } + + AwsTelemetry.setRegion(project = project, passive = passive) + } + + @Synchronized + private fun changeFieldsAndNotify(fieldUpdateBlock: () -> Unit) { + val isInitial = connectionState is ConnectionState.InitializingToolkit + connectionState = ConnectionState.ValidatingConnection + + // Grab the current state stamp + val modificationStamp = this.modificationCount + + fieldUpdateBlock() + + val validateCredentialsResult = validateCredentials(selectedCredentialIdentifier, selectedRegion, isInitial) + validationJob.getAndSet(validateCredentialsResult)?.cancel() + + validateCredentialsResult.onSuccess { + // Validate we are still operating in the latest view of the world + if (modificationStamp == this.modificationCount) { + connectionState = it + } else { + LOGGER.warn { "validateCredentials returned but the account manager state has been manipulated before results were back, ignoring" } + } + } + } + + private fun validateCredentials(credentialsIdentifier: CredentialIdentifier?, region: AwsRegion?, isInitial: Boolean): AsyncPromise { + val promise = AsyncPromise() + pluginAwareExecuteOnPooledThread { + if (credentialsIdentifier == null || region == null) { + promise.setResult(ConnectionState.IncompleteConfiguration(credentialsIdentifier, region)) + return@pluginAwareExecuteOnPooledThread + } + + if (isInitial && credentialsIdentifier is InteractiveCredential && credentialsIdentifier.userActionRequired()) { + promise.setResult(ConnectionState.RequiresUserAction(credentialsIdentifier)) + return@pluginAwareExecuteOnPooledThread + } + + var success = true + try { + val credentialsProvider = CredentialManager.getInstance().getAwsCredentialProvider(credentialsIdentifier, region) + + validate(credentialsProvider, region) + + promise.setResult(ConnectionState.ValidConnection(credentialsProvider, region)) + } catch (e: Exception) { + LOGGER.warn(e) { AwsCoreBundle.message("credentials.profile.validation_error", credentialsIdentifier.displayName) } + val result = if (credentialsIdentifier is PostValidateInteractiveCredential) { + try { + credentialsIdentifier.handleValidationException(e) + } catch (nested: Exception) { + LOGGER.warn(nested) { "$credentialsIdentifier threw while attempting to handle initial validation exception" } + null + } + } else { + null + } + + if (result == null) { + success = false + } + promise.setResult(result ?: ConnectionState.InvalidConnection(e)) + } finally { + AwsTelemetry.validateCredentials( + project, + success = success, + credentialType = credentialsIdentifier.credentialType.toTelemetryType() + ) + } + } + + return promise + } + + /** + * Legacy method, should be considered deprecated and avoided since it loads defaults out of band + */ + val activeRegion: AwsRegion + get() = selectedRegion ?: AwsRegionProvider.getInstance().defaultRegion().also { + LOGGER.warn(IllegalStateException()) { "Using activeRegion when region is null, calling code needs to be migrated to handle null" } + } + + /** + * Legacy method, should be considered deprecated and avoided since it loads defaults out of band + */ + val activeCredentialProvider: ToolkitCredentialsProvider + @Throws(CredentialProviderNotFoundException::class) + get() { + val state = connectionState + return if (state is ConnectionState.ValidConnection) { + state.credentials + } else { + if (selectedCredentialIdentifier == null) { + LOGGER.warn(IllegalStateException()) { + "Using activeCredentialProvider when credentials is null, calling code needs to be migrated to handle null" + } + } + + throw CredentialProviderNotFoundException(AwsCoreBundle.message("credentials.profile.not_configured")) + } + } + + /** + * Returns the list of recently used [AwsRegion] + */ + fun recentlyUsedRegions(): List = recentlyUsedRegions.elements().mapNotNull { regionProvider.allRegions()[it] } + + /** + * Returns the list of recently used [CredentialIdentifier] + */ + fun recentlyUsedCredentials(): List { + val credentialManager = CredentialManager.getInstance() + return recentlyUsedProfiles.elements().mapNotNull { credentialManager.getCredentialIdentifierById(it) } + } + + /** + * Internal method that executes the actual validation of credentials + */ + protected open fun validate(credentialsProvider: ToolkitCredentialsProvider, region: AwsRegion) { + resourceCache.getResource( + StsResources.ACCOUNT, + region = region, + credentialProvider = credentialsProvider, + useStale = false, + forceFetch = true + ).toCompletableFuture().get() + } + + override fun dispose() { + } + + companion object { + /*** + * MessageBus topic for when the active credential profile or region is changed + */ + val CONNECTION_SETTINGS_STATE_CHANGED: Topic = Topic.create( + "AWS Account setting changed", + ConnectionSettingsStateChangeNotifier::class.java + ) + + @JvmStatic + fun getInstance(project: Project): AwsConnectionManager = project.service() + + private val LOGGER = getLogger() + private const val MAX_HISTORY = 5 + internal val AwsConnectionManager.selectedPartition get() = selectedRegion?.let { AwsRegionProvider.getInstance().partitions()[it.partitionId] } + } +} + +fun Project.getConnectionSettingsOrThrow(): ConnectionSettings = getConnectionSettings() + ?: throw IllegalStateException("Bug: Attempting to retrieve connection settings with invalid connection state") + +fun Project.getConnectionSettings(): ConnectionSettings? = AwsConnectionManager.getInstance(this).connectionSettings() + +fun Project.withAwsConnection(block: (ConnectionSettings) -> T): T { + val connectionSettings = AwsConnectionManager.getInstance(this).connectionSettings() + ?: throw IllegalStateException("Connection settings are not configured") + return block(connectionSettings) +} + +/** + * A state machine around the connection validation steps the toolkit goes through. Attempts to encapsulate both state, data available at each state and + * a consistent place to determine how to display state information (e.g. [displayMessage]). Exposes an [isTerminal] property that indicates if this + * state is temporary in the 'connection validation' workflow or if this is a terminal state. + */ +sealed class ConnectionState(val displayMessage: String, val isTerminal: Boolean) { + protected val gettingStartedAction by lazy { ActionManager.getInstance().getAction("aws.toolkit.toolwindow.newConnection") } + protected val editCredsAction by lazy { ActionManager.getInstance().getAction("aws.settings.upsertCredentials") } + + /** + * An optional short message to display in places where space is at a premium + */ + open val shortMessage: String = displayMessage + + open val actions: List = emptyList() + + object InitializingToolkit : ConnectionState(AwsCoreBundle.message("settings.states.initializing"), isTerminal = false) + + object ValidatingConnection : ConnectionState(AwsCoreBundle.message("settings.states.validating"), isTerminal = false) { + override val shortMessage: String = AwsCoreBundle.message("settings.states.validating.short") + } + + class ValidConnection(val credentials: ToolkitCredentialsProvider, val region: AwsRegion) : + ConnectionState("${credentials.displayName}@${region.displayName}", isTerminal = true) { + override val shortMessage: String = "${credentials.shortName}@${region.id}" + val connection by lazy { ConnectionSettings(credentials, region) } + } + + class IncompleteConfiguration(credentials: CredentialIdentifier?, region: AwsRegion?) : ConnectionState( + when { + region == null && credentials == null -> AwsCoreBundle.message("settings.none_selected") + region == null -> AwsCoreBundle.message("settings.regions.none_selected") + credentials == null -> AwsCoreBundle.message("settings.credentials.none_selected") + else -> throw IllegalArgumentException("At least one of regionId ($region) or toolkitCredentialsIdentifier ($credentials) must be null") + }, + isTerminal = true + ) { + override val actions: List = listOf(gettingStartedAction, editCredsAction) + } + + class InvalidConnection(private val cause: Exception) : + ConnectionState( + AwsCoreBundle.message("settings.states.invalid", ExceptionUtil.getMessage(cause) ?: ExceptionUtil.getThrowableText(cause)), + isTerminal = true + ) { + override val shortMessage = AwsCoreBundle.message("settings.states.invalid.short") + + override val actions: List = listOf(RefreshConnectionAction(AwsCoreBundle.message("settings.retry")), gettingStartedAction, editCredsAction) + } + + class RequiresUserAction(interactiveCredentials: InteractiveCredential) : + ConnectionState(interactiveCredentials.userActionDisplayMessage, isTerminal = true) { + override val shortMessage = interactiveCredentials.userActionShortDisplayMessage + + override val actions = listOf(interactiveCredentials.userAction) + } +} + +interface ConnectionSettingsStateChangeNotifier { + fun settingsStateChanged(newState: ConnectionState) +} + +/** + * Legacy method, should be considered deprecated and avoided since it loads defaults out of band + */ +fun Project.activeRegion(): AwsRegion = AwsConnectionManager.getInstance(this).activeRegion + +/** + * Legacy method, should be considered deprecated and avoided since it loads defaults out of band + */ +fun Project.activeCredentialProvider(): ToolkitCredentialsProvider = AwsConnectionManager.getInstance(this).activeCredentialProvider diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManagerConnection.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManagerConnection.kt new file mode 100644 index 00000000000..8a56de713e2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManagerConnection.kt @@ -0,0 +1,15 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.project.Project +import software.amazon.q.core.ConnectionSettings + +class AwsConnectionManagerConnection(private val project: Project) : AwsCredentialConnection { + override val id: String = "AwsConnectionManagerConnection" + override val label: String + get() = AwsConnectionManager.getInstance(project).connectionState.displayMessage + + override fun getConnectionSettings(): ConnectionSettings = error("Use AwsConnectionManager for connection") +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ChangeConnectionSettingIfValid.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ChangeConnectionSettingIfValid.kt new file mode 100644 index 00000000000..46c42dee137 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ChangeConnectionSettingIfValid.kt @@ -0,0 +1,10 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifier + +interface ChangeConnectionSettingIfValid { + fun changeConnection(profile: ProfileCredentialsIdentifier) {} +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt new file mode 100644 index 00000000000..a8afa4358fe --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt @@ -0,0 +1,291 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileDocumentManager +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileFile +import software.amazon.awssdk.profiles.ProfileFileLocation +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants +import software.amazon.q.jetbrains.core.credentials.profiles.ssoSessions +import software.amazon.q.core.utils.appendText +import software.amazon.q.core.utils.createParentDirectories +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.inputStreamIfExists +import software.amazon.q.core.utils.touch +import software.amazon.q.core.utils.tryDirOp +import software.amazon.q.core.utils.tryFileOp +import software.amazon.q.core.utils.writeText +import java.nio.file.Path + +interface ConfigFilesFacade { + val configPath: Path + val credentialsPath: Path + + /** + * Returns all valid "profile" sections defined in config/credentials. + * This should follow the same semantics of [software.amazon.awssdk.profiles.ProfileFile.profiles] + */ + fun readAllProfiles(): Map + fun readSsoSessions(): Map + + fun createConfigFile() + + fun appendProfileToConfig(profile: Profile) + fun appendProfileToCredentials(profile: Profile) + fun appendSectionToConfig(sectionName: String, profile: Profile) + fun updateSectionInConfig(sectionName: String, profile: Profile) + + fun deleteSsoConnectionFromConfig(sessionName: String) +} + +class DefaultConfigFilesFacade( + override val configPath: Path = ProfileFileLocation.configurationFilePath(), + override val credentialsPath: Path = ProfileFileLocation.credentialsFilePath(), +) : ConfigFilesFacade { + companion object { + private val LOG = getLogger() + + val TEMPLATE = + """ + # Amazon Web Services Config File used by AWS CLI, SDKs, and tools + # This file was created by the AWS Toolkit for JetBrains plugin. + # + # Your AWS credentials are represented by access keys associated with IAM users. + # For information about how to create and manage AWS access keys for a user, see: + # https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html + # + # This config file can store multiple access keys by placing each one in a + # named "profile". For information about how to change the access keys in a + # profile or to add a new profile with a different access key, see: + # https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html + # + # If both a credential and config file exists, the values in the credential file + # take precedence + + [default] + # The access key and secret key pair identify your account and grant access to AWS. + aws_access_key_id = [accessKey] + # Treat your secret key like a password. Never share your secret key with anyone. Do + # not post it in online forums, or store it in a source control system. If your secret + # key is ever disclosed, immediately use IAM to delete the access key and secret key + # and create a new key pair. Then, update this file with the replacement key details. + aws_secret_access_key = [secretKey] + + # [profile user1] + # aws_access_key_id = [accessKey1] + # aws_secret_access_key = [secretKey1] + + + # AWS IAM Identity Center (successor to AWS Single Sign-On) helps you stay logged into AWS tools + # without needing to enter your info all the time + # For information about how to create and manage AWS IAM Identity Center see: + # https://docs.aws.amazon.com/singlesignon/latest/userguide/get-started-enable-identity-center.html + # For more information on how to configure this file to use AWS IAM Identity Center, see: + # https://docs.aws.amazon.com/cli/latest/userguide/sso-configure-profile-token.html + + # [sso-session my-sso] + # sso_region = us-east-1 + # sso_start_url = https://my-sso-portal.awsapps.com/start + # sso_registration_scopes = sso:account:access + + # [profile dev] + # sso_session = my-sso + # sso_account_id = 111122223333 + # sso_role_name = SampleRole + """.trimIndent() + } + + private fun aggregateProfiles() = ProfileFile.aggregator() + .applyMutation { + if (credentialsPath.exists()) { + it.addFile( + ProfileFile.builder() + .content(credentialsPath) + .type(ProfileFile.Type.CREDENTIALS) + .build() + ) + } + } + .applyMutation { + if (configPath.exists()) { + it.addFile( + ProfileFile.builder() + .content(configPath) + .type(ProfileFile.Type.CONFIGURATION) + .build() + ) + } + } + .build() + + override fun readAllProfiles(): Map = aggregateProfiles().profiles() + + override fun readSsoSessions(): Map = aggregateProfiles().ssoSessions() + + override fun createConfigFile() { + configPath.tryDirOp(LOG) { createParentDirectories() } + + configPath.tryFileOp(LOG) { + touch(restrictToOwner = true) + writeText(TEMPLATE) + } + } + + override fun appendProfileToConfig(profile: Profile) = + appendSection(configPath, "profile", profile) + + override fun appendProfileToCredentials(profile: Profile) = + appendSection(credentialsPath, "profile", profile) + + override fun appendSectionToConfig(sectionName: String, profile: Profile) = + appendSection(configPath, sectionName, profile) + + override fun updateSectionInConfig(sectionName: String, profile: Profile) { + assert(sectionName == "sso-session") { "Method only supports updating sso-session" } + configPath.tryFileOp(LOG) { + touch(restrictToOwner = true) + val lines = inputStreamIfExists()?.reader()?.readLines().orEmpty() + val profileHeaderLine = lines.indexOfFirst { it.startsWith("[$sectionName ${profile.name()}]") } + if (profileHeaderLine == -1) { + // does not have profile, just write directly to end + appendSectionToConfig(sectionName, profile) + } else { + // has profile + val nextHeaderLine = lines.subList(profileHeaderLine + 1, lines.size).indexOfFirst { it.startsWith("[") } + val endIndex = if (nextHeaderLine == -1) { + // is last profile in file + lines.size + } else { + nextHeaderLine + profileHeaderLine + 1 + } + + // update contents between profileHeaderLine and nextHeaderLine + val profileLines = lines.subList(profileHeaderLine, endIndex).toMutableList() + profile.properties().forEach { key, value -> + val line = profileLines.indexOfLast { it.startsWith("$key=") } + if (line == -1) { + profileLines.add("$key=$value") + } else { + profileLines[line] = "$key=$value" + } + } + writeText((lines.subList(0, profileHeaderLine) + profileLines + lines.subList(endIndex, lines.size)).joinToString("\n")) + } + } + } + + override fun deleteSsoConnectionFromConfig(sessionName: String) { + val filePath = configPath + val lines = filePath.inputStreamIfExists()?.reader()?.readLines().orEmpty() + val ssoHeaderLine = lines.indexOfFirst { it.startsWith("[${SsoSessionConstants.SSO_SESSION_SECTION_NAME} $sessionName]") } + if (ssoHeaderLine == -1) return + val nextHeaderLine = lines.subList(ssoHeaderLine + 1, lines.size).indexOfFirst { it.startsWith("[") } + val endIndex = if (nextHeaderLine == -1) lines.size else ssoHeaderLine + nextHeaderLine + 1 + val updatedArray = lines.subList(0, ssoHeaderLine) + lines.subList(endIndex, lines.size) + val profileHeaderLine = getCorrespondingSsoSessionProfilePosition(updatedArray, sessionName) + filePath.writeText(profileHeaderLine.joinToString("\n")) + + val applicationManager = ApplicationManager.getApplication() + if (applicationManager != null && !applicationManager.isUnitTestMode) { + FileDocumentManager.getInstance().saveAllDocuments() + ProfileWatcher.getInstance().forceRefresh() + } + } + + private fun getCorrespondingSsoSessionProfilePosition(updatedArray: List, sessionName: String): List { + var content = updatedArray + val finalContent = mutableListOf() + while (content.size > 0) { + val sessionProfile = checkIfProfileIsPartOfSession(content, sessionName) + if (sessionProfile != null) { // There is atleast one profile with the prefix matching the session name + if (sessionProfile.shouldBeWrittenToConfig) { + finalContent.addAll(content.subList(0, sessionProfile.endIndex)) + } else { + finalContent.addAll(content.subList(0, sessionProfile.startIndex)) + } + content = content.subList(sessionProfile.endIndex, content.size) + } else { + finalContent.addAll(content) + break + } + } + return finalContent + } + + private fun checkIfProfileIsPartOfSession(content: List, sessionName: String): ProfileLimitsInConfig? { + val pos = content.indexOfFirst { it.startsWith("[profile") } + // if no matching profile section found + if (pos == -1) return null + + // if matching profile section found which is an sso-profile + val contentAfterProfileHeader = content.subList(pos + 1, content.size) + val checkIfProfileIsValid = isProfileSso(contentAfterProfileHeader, sessionName) + + return ProfileLimitsInConfig(pos, pos + checkIfProfileIsValid.endIndex + 1, shouldBeWrittenToConfig = !checkIfProfileIsValid.isProfileSso) + } + + private fun isProfileSso(configContent: List, sessionName: String): CurrentProfileLimitsInConfig { + val nextSectionHeaderIndex = configContent.indexOfFirst { it.startsWith("[") } + val endIndex = if (nextSectionHeaderIndex == -1) configContent.size else nextSectionHeaderIndex + val currentProfile = configContent.subList(0, endIndex) + currentProfile.forEach { + if (it.startsWith("sso_session")) { + return if (it.substringAfter("=").trim() == sessionName) { + CurrentProfileLimitsInConfig(isProfileSso = true, endIndex) + } else { + CurrentProfileLimitsInConfig( + isProfileSso = false, + endIndex + ) + } + } + } + return CurrentProfileLimitsInConfig(isProfileSso = false, endIndex) + } + + data class ProfileLimitsInConfig( + val startIndex: Int, + val endIndex: Int, + val shouldBeWrittenToConfig: Boolean = true, + ) + + data class CurrentProfileLimitsInConfig( + val isProfileSso: Boolean, + val endIndex: Int = 0, + ) + + private fun appendSection(path: Path, sectionName: String, profile: Profile) { + val isConfigFile = path.fileName.toString() != "credentials" + if (sectionName == "sso-session" && !isConfigFile) { + error("sso-session is only allowed in 'config'") + } + + // "credentials" file doesn't have the "profile" prefix + // and "sso-session" is not allowed in the "config" file + // https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format + val sectionTitle = if (!isConfigFile || profile.name().trim() == "default") { + profile.name() + } else { + "$sectionName ${profile.name()}" + }.trim() + + val body = buildString { + appendLine() + appendLine("[$sectionTitle]") + profile.properties().forEach { k, v -> + appendLine("$k=$v") + } + } + + path.tryDirOp(LOG) { createParentDirectories() } + path.tryFileOp(LOG) { + touch(restrictToOwner = true) + appendText(body) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt new file mode 100644 index 00000000000..cea895ac550 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt @@ -0,0 +1,103 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.fileTypes.FileTypes +import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.vfs.LocalFileSystem +import icons.AwsIcons +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.profiles.ProfileFileLocation +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.exists +import software.aws.toolkits.telemetry.AwsTelemetry +import java.nio.file.Path + +class CreateOrUpdateCredentialProfilesAction @TestOnly constructor( + private val writer: ConfigFilesFacade, +) : AnAction( + AwsCoreBundle.message("configure.toolkit.upsert_credentials.action"), + null, + AwsIcons.Logos.AWS +), + DumbAware { + @Suppress("unused") + constructor() : this( + DefaultConfigFilesFacade( + configPath = ProfileFileLocation.configurationFilePath(), + credentialsPath = ProfileFileLocation.credentialsFilePath() + ) + ) + + private val localFileSystem = LocalFileSystem.getInstance() + + override fun actionPerformed(e: AnActionEvent) { + val project = e.getRequiredData(PlatformDataKeys.PROJECT) + + // if both config and credential files do not exist, create a new config file + if (!writer.configPath.exists() && !writer.credentialsPath.exists()) { + if (confirm(project, writer.configPath)) { + try { + writer.createConfigFile() + } finally { + AwsTelemetry.createCredentials(project) + } + } else { + return + } + } + + // open both config and credential files, if they exist + // credential file is opened last since it takes precedence over the config file + val virtualFiles = listOf(writer.configPath.toFile(), writer.credentialsPath.toFile()).filter { it.exists() }.map { + localFileSystem.refreshAndFindFileByIoFile(it) ?: throw RuntimeException( + AwsCoreBundle.message( + "credentials.could_not_open", + it + ) + ) + } + + val fileEditorManager = FileEditorManager.getInstance(project) + + localFileSystem.refreshFiles(virtualFiles, false, false) { + virtualFiles.forEach { + if (it.fileType == FileTypes.UNKNOWN) { + ApplicationManager.getApplication().runWriteAction { + FileTypeManagerEx.getInstanceEx().associatePattern( + FileTypes.PLAIN_TEXT, + it.name + ) + } + } + + if (fileEditorManager.openTextEditor(OpenFileDescriptor(project, it), true) == null) { + AwsTelemetry.openCredentials(project, success = false) + throw RuntimeException(AwsCoreBundle.message("credentials.could_not_open", it)) + } + AwsTelemetry.openCredentials(project, success = true) + } + } + } + + private fun confirm(project: Project, file: Path): Boolean = Messages.showOkCancelDialog( + project, + AwsCoreBundle.message("configure.toolkit.upsert_credentials.confirm_file_create", file), + AwsCoreBundle.message("configure.toolkit.upsert_credentials.confirm_file_create.title"), + AwsCoreBundle.message("configure.toolkit.upsert_credentials.confirm_file_create.okay"), + Messages.getCancelButton(), + AllIcons.General.QuestionDialog, + null + ) == Messages.OK +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt new file mode 100644 index 00000000000..9f97f03b85f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt @@ -0,0 +1,86 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.extensions.ExtensionPointName +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.core.credentials.CredentialProviderFactory +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.telemetry.AwsTelemetry +import java.util.concurrent.atomic.AtomicInteger + +typealias CredentialManager = migration.software.amazon.q.jetbrains.core.credentials.CredentialManager + +class DefaultCredentialManager : CredentialManager(), Disposable { + private val extensionMap: Map + get() = EP_NAME.extensionList.associateBy { + it.id + } + + init { + extensionMap.values.forEach { providerFactory -> + val count = AtomicInteger(0) + LOG.tryOrNull("Failed to set up $providerFactory") { + providerFactory.setUp { change -> + change.added.forEach { + addProvider(it) + count.incrementAndGet() + } + + change.modified.forEach { + modifyProvider(it) + } + + change.removed.forEach { + removeProvider(it) + count.decrementAndGet() + } + + change.ssoAdded.forEach { + addSsoSession(it) + } + + change.ssoModified.forEach { + modifySsoSession(it) + } + + change.ssoRemoved.forEach { + removeSsoSession(it) + } + + AwsTelemetry.loadCredentials( + credentialSourceId = providerFactory.credentialSourceId.toTelemetryCredentialSourceId(), + value = count.get().toDouble() + ) + } + } + } + + // sync bearer changes back to any profiles with a dependency + ApplicationManager.getApplication().messageBus.connect(this).subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun onProviderChange(providerId: String, newScopes: List?) { + modifyDependentProviders(providerId) + } + + override fun invalidate(providerId: String) { + modifyDependentProviders(providerId) + } + } + ) + } + + override fun dispose() {} + + override fun factoryMapping(): Map = extensionMap + + companion object { + val EP_NAME = ExtensionPointName.create("amazon.q.credentialProviderFactory") + private val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialTelemetryUtil.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialTelemetryUtil.kt new file mode 100644 index 00000000000..01b9a407e01 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialTelemetryUtil.kt @@ -0,0 +1,29 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import software.amazon.q.core.credentials.CredentialSourceId +import software.amazon.q.core.credentials.CredentialType +import software.aws.toolkits.telemetry.CredentialType as TelemetryCredentialType + +fun CredentialType?.toTelemetryType(): TelemetryCredentialType = when (this) { + CredentialType.StaticProfile -> TelemetryCredentialType.StaticProfile + CredentialType.StaticSessionProfile -> TelemetryCredentialType.StaticSessionProfile + CredentialType.CredentialProcessProfile -> TelemetryCredentialType.CredentialProcessProfile + CredentialType.AssumeRoleProfile -> TelemetryCredentialType.AssumeRoleProfile + CredentialType.AssumeMfaRoleProfile -> TelemetryCredentialType.AssumeMfaRoleProfile + CredentialType.SsoProfile -> TelemetryCredentialType.SsoProfile + CredentialType.Ec2Metadata -> TelemetryCredentialType.Ec2Metadata + CredentialType.EcsMetadata -> TelemetryCredentialType.EcsMetatdata + null -> TelemetryCredentialType.Other +} + +fun CredentialSourceId?.toTelemetryCredentialSourceId(): software.aws.toolkits.telemetry.CredentialSourceId = when (this) { + CredentialSourceId.SharedCredentials -> software.aws.toolkits.telemetry.CredentialSourceId.SharedCredentials + CredentialSourceId.SdkStore -> software.aws.toolkits.telemetry.CredentialSourceId.SdkStore + CredentialSourceId.Ec2 -> software.aws.toolkits.telemetry.CredentialSourceId.Ec2 + CredentialSourceId.Ecs -> software.aws.toolkits.telemetry.CredentialSourceId.Ecs + CredentialSourceId.EnvVars -> software.aws.toolkits.telemetry.CredentialSourceId.EnvVars + else -> software.aws.toolkits.telemetry.CredentialSourceId.Other +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt new file mode 100644 index 00000000000..0d3b085b9dc --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt @@ -0,0 +1,65 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.notification.NotificationAction +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.region.AwsRegion + +/** + * Encapsulates logic for handling of regions when a new credential identifier is selected + */ +interface CredentialsRegionHandler { + fun determineSelectedRegion(identifier: CredentialIdentifier, selectedRegion: AwsRegion?): AwsRegion? + + companion object { + fun getInstance(project: Project): CredentialsRegionHandler = project.service() + } +} + +internal class DefaultCredentialsRegionHandler(private val project: Project) : CredentialsRegionHandler { + override fun determineSelectedRegion(identifier: CredentialIdentifier, selectedRegion: AwsRegion?): AwsRegion? { + val settings = AwsSettings.getInstance() + if (settings.useDefaultCredentialRegion == UseAwsCredentialRegion.Never) { + return selectedRegion + } + + val regionProvider = AwsRegionProvider.getInstance() + val defaultCredentialRegion = identifier.defaultRegionId?.let { regionProvider[it] } ?: return selectedRegion + when { + selectedRegion == defaultCredentialRegion -> return defaultCredentialRegion + selectedRegion?.partitionId != defaultCredentialRegion.partitionId -> return defaultCredentialRegion + settings.useDefaultCredentialRegion == UseAwsCredentialRegion.Always -> return defaultCredentialRegion + settings.useDefaultCredentialRegion == UseAwsCredentialRegion.Prompt -> promptForRegionChange(defaultCredentialRegion) + } + return selectedRegion + } + + private fun promptForRegionChange(defaultCredentialRegion: AwsRegion) { + notifyInfo( + AwsCoreBundle.message("aws.notification.title"), + AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch", defaultCredentialRegion.id), + project = project, + notificationActions = listOf( + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch.yes")) { + AwsConnectionManager.getInstance(project).changeRegion(defaultCredentialRegion) + }, + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch.always")) { + AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Always + AwsConnectionManager.getInstance(project).changeRegion(defaultCredentialRegion) + }, + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch.never")) { + AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Never + } + ) + ) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt new file mode 100644 index 00000000000..5bcf7b4283f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt @@ -0,0 +1,64 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import kotlinx.coroutines.launch +import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope +import software.amazon.q.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.core.utils.tryOrNull + +data class ConnectionSettingsState( + var activeProfile: String? = null, + var activeRegion: String? = null, + var recentlyUsedProfiles: List = mutableListOf(), + var recentlyUsedRegions: List = mutableListOf(), +) + +@State(name = "qAccountSettings", storages = [Storage("amazonq.xml")]) +class DefaultAwsConnectionManager(project: Project) : + AwsConnectionManager(project), + PersistentStateComponent { + private val coroutineScope = disposableCoroutineScope(this) + + override fun getState(): ConnectionSettingsState = ConnectionSettingsState( + activeProfile = selectedCredentialIdentifier?.id, + activeRegion = selectedRegion?.id, + recentlyUsedProfiles = recentlyUsedProfiles.elements(), + recentlyUsedRegions = recentlyUsedRegions.elements() + ) + + override fun noStateLoaded() { + loadState(ConnectionSettingsState()) + } + + override fun loadState(state: ConnectionSettingsState) { + // This can be called more than once, so we need to re-do our init sequence + connectionState = ConnectionState.InitializingToolkit + + // Load reversed so that oldest is as the bottom + state.recentlyUsedRegions.reversed() + .forEach { recentlyUsedRegions.add(it) } + + state.recentlyUsedProfiles.reversed() + .forEach { recentlyUsedProfiles.add(it) } + + // Load all the initial state on BG thread, so we don't block the UI or loading of other components + coroutineScope.launch { + val credentialId = state.activeProfile ?: DEFAULT_PROFILE_ID + val credentials = tryOrNull { + CredentialManager.getInstance().getCredentialIdentifierById(credentialId) + } + + val regionId = state.activeRegion ?: credentials?.defaultRegionId ?: AwsRegionProvider.getInstance().defaultRegion().id + val region = AwsRegionProvider.getInstance().allRegions()[regionId] + + changeConnectionSettings(credentials, region) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt new file mode 100644 index 00000000000..53e493f1319 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt @@ -0,0 +1,307 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.util.Disposer +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileSsoSessionIdentifier +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.core.credentials.SsoSessionIdentifier +import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import java.util.Collections + +typealias ToolkitAuthManager = migration.software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager + +// TODO: unify with CredentialManager +@State(name = "qAuthManager", storages = [Storage("amazonq.xml")]) +class DefaultToolkitAuthManager : ToolkitAuthManager, PersistentStateComponent, Disposable { + private var state = ToolkitAuthManagerState() + private val connections = Collections.synchronizedSet(linkedSetOf()) + private val transientConnections = let { + val factoryConnections = mutableListOf() + ToolkitStartupAuthFactory.EP_NAME.forEachExtensionSafe { factory -> + factoryConnections.addAll( + factory.buildConnections().also { connections -> + LOG.info { "Found transient connections from $factory: ${connections.map { it.toString() }}" } + } + ) + } + + factoryConnections.toMutableSet() + } + + init { + // initial load then subscribe to bus for future changes + CredentialManager.getInstance().getSsoSessionIdentifiers().forEach { + createConnectionFromIdentifier(it) + } + + ApplicationManager.getApplication().messageBus + .connect(this) + .subscribe( + CredentialManager.CREDENTIALS_CHANGED, + object : ToolkitCredentialsChangeListener { + override fun ssoSessionAdded(identifier: SsoSessionIdentifier) { + createConnectionFromIdentifier(identifier) + } + + override fun ssoSessionModified(identifier: SsoSessionIdentifier) { + transientConnections.removeAll { connection -> + (connection.id == identifier.id).also { + if (it && connection is Disposable) { + // don't invalidate because we kill the token we just retrieved + ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC) + .onProviderChange(connection.id) + Disposer.dispose(connection) + } + } + } + + ssoSessionAdded(identifier) + } + + override fun ssoSessionRemoved(identifier: SsoSessionIdentifier) { + transientConnections.removeAll { connection -> + (connection.id == identifier.id).also { + if (it && connection is Disposable) { + disposeAndNotify(connection) + } + } + } + } + } + ) + } + + override fun listConnections(): List = connections.toList() + transientConnections + + override fun tryCreateTransientSsoConnection(profile: AuthProfile, callback: (AwsBearerTokenConnection) -> Unit): AwsBearerTokenConnection { + val connection = (connectionFromProfile(profile) as AwsBearerTokenConnection).also { + callback(it) + + if (profile is ManagedSsoProfile) { + disposeStaleConnections(it) + + connections.add(it) + } else { + disposeStaleConnections(it) + + transientConnections.add(it) + } + } + + return connection + } + + override fun getOrCreateSsoConnection(profile: UserConfigSsoSessionProfile): AwsBearerTokenConnection { + (transientConnections.firstOrNull { it.id == profile.id } as? AwsBearerTokenConnection)?.let { + return it + } + + val connection = connectionFromProfile(profile) as AwsBearerTokenConnection + transientConnections.add(connection) + + return connection + } + + private fun createConnectionFromIdentifier(identifier: SsoSessionIdentifier) { + (identifier as? ProfileSsoSessionIdentifier)?.let { + getOrCreateSsoConnection( + UserConfigSsoSessionProfile( + configSessionName = it.profileName, + ssoRegion = it.ssoRegion, + startUrl = it.startUrl, + scopes = it.scopes.toList() + ) + ) + } + } + + override fun createConnection(profile: AuthProfile): ToolkitConnection { + val connection = connectionFromProfile(profile) + connections.firstOrNull { it.id == connection.id }?.let { + LOG.warn { "$it already exists in connection list" } + if (connection is Disposable) { + Disposer.dispose(connection) + } + + return it + } + + connections.add(connection) + return connection + } + + private fun disposeStaleConnections(newConnection: AwsBearerTokenConnection) { + connections.removeAll { existOldConn -> + (existOldConn.id == newConnection.id).also { isDuplicate -> + if (isDuplicate && existOldConn is Disposable) { + ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC) + .onProviderChange(existOldConn.id, newConnection.scopes) + Disposer.dispose(existOldConn) + } + } + } + + transientConnections.removeAll { existOldConn -> + (existOldConn.id == newConnection.id).also { isDuplicate -> + if (isDuplicate && existOldConn is Disposable) { + ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC) + .onProviderChange(existOldConn.id, newConnection.scopes) + Disposer.dispose(existOldConn) + } + } + } + } + + private fun deleteConnection(predicate: (ToolkitConnection) -> Boolean) { + val deleted = mutableListOf() + connections.removeAll { connection -> + predicate(connection).also { + if (it) { + deleted.add(connection) + } + } + } + + transientConnections.removeAll { connection -> + predicate(connection).also { + if (it) { + deleted.add(connection) + } + } + } + + for (connection in deleted) { + if (connection is Disposable) { + disposeAndNotify(connection) + } + } + } + + private fun disposeAndNotify(connection: T) where T : ToolkitConnection, T : Disposable { + ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC) + .invalidate(connection.id) + Disposer.dispose(connection) + } + + override fun deleteConnection(connection: ToolkitConnection) { + deleteConnection { it == connection } + } + + override fun deleteConnection(connectionId: String) { + deleteConnection { it.id == connectionId } + } + + override fun getConnection(connectionId: String) = listConnections().firstOrNull { it.id == connectionId } + override fun getLastLoginIdcInfo(): LastLoginIdcInfo = state.lastLoginIdcInfo.copy() + + override fun getState(): ToolkitAuthManagerState? { + val data = connections.mapNotNull { + when (it) { + is ManagedBearerSsoConnection -> { + ManagedSsoProfile( + startUrl = it.startUrl, + ssoRegion = it.region, + scopes = it.scopes + ) + } + + else -> { + LOG.error { "Couldn't serialize $it" } + null + } + } + } + + state.ssoProfiles = data + + return state + } + + override fun loadState(state: ToolkitAuthManagerState) { + this.state = state + val newConnections = linkedSetOf(*state.ssoProfiles.toTypedArray()).filterNotNull().map { + connectionFromProfile(it) + } + + if (newConnections.size != state.ssoProfiles.size) { + LOG.warn { "Persisted state had duplicate profiles" } + } + + connections.clear() + connections.addAll(newConnections) + } + + override fun dispose() { + listConnections().forEach { + if (it is Disposable) { + Disposer.dispose(it) + } + } + } + + private fun connectionFromProfile(profile: AuthProfile): ToolkitConnection = when (profile) { + is ManagedSsoProfile -> { + if (profile.startUrl != SONO_URL) { + state.lastLoginIdcInfo = LastLoginIdcInfo("", profile.startUrl, profile.ssoRegion) + } + LegacyManagedBearerSsoConnection( + startUrl = profile.startUrl, + region = profile.ssoRegion, + scopes = profile.scopes + ) + } + + is UserConfigSsoSessionProfile -> { + if (profile.startUrl != SONO_URL) { + state.lastLoginIdcInfo = LastLoginIdcInfo(profile.configSessionName, profile.startUrl, profile.ssoRegion) + } + ProfileSsoManagedBearerSsoConnection( + startUrl = profile.startUrl, + region = profile.ssoRegion, + scopes = profile.scopes, + id = profile.id, + configSessionName = profile.configSessionName + ) + } + + is DetectedDiskSsoSessionProfile -> { + if (profile.startUrl != SONO_URL) { + state.lastLoginIdcInfo = LastLoginIdcInfo(profile.profileName, profile.startUrl, profile.ssoRegion) + } + DetectedDiskSsoSessionConnection( + sessionName = profile.profileName, + startUrl = profile.startUrl, + region = profile.ssoRegion, + scopes = profile.scopes + ) + } + } + + companion object { + private val LOG = getLogger() + } +} + +data class ToolkitAuthManagerState( + // TODO: can't figure out how to make deserializer work with polymorphic types + var ssoProfiles: List = emptyList(), + var lastLoginIdcInfo: LastLoginIdcInfo = LastLoginIdcInfo(), +) + +data class LastLoginIdcInfo( + var profileName: String = "", + var startUrl: String = "", + var region: String = "", +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManager.kt new file mode 100644 index 00000000000..6415354f57c --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManager.kt @@ -0,0 +1,166 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.ide.ActivityTracker +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager +import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener + +// TODO: unify with AwsConnectionManager +@State(name = "qConnectionManager", storages = [Storage("amazonq.xml")]) +class DefaultToolkitConnectionManager : ToolkitConnectionManager, PersistentStateComponent { + private val project: Project? + + constructor(project: Project) { + this.project = project + } + + constructor() { + this.project = null + } + + init { + ApplicationManager.getApplication().messageBus.connect(this).subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun invalidate(providerId: String) { + if (activeConnection()?.id == providerId) { + switchConnection(null) + ActivityTracker.getInstance().inc() + } + } + } + ) + } + + private var connection: ToolkitConnection? = null + + private val pinningManager + get() = ConnectionPinningManager.getInstance() + + private val defaultConnection: ToolkitConnection? + get() { + if (CredentialManager.getInstance().getCredentialIdentifiers().isNotEmpty() && project != null) { + return AwsConnectionManagerConnection(project) + } + + ToolkitAuthManager.getInstance().listConnections().firstOrNull()?.let { + return it + } + + return null + } + + @Deprecated( + "Fragile API. Probably leads to unexpected behavior. Use only for toolkit explorer dropdown state.", + replaceWith = ReplaceWith("activeConnectionForFeature(feature)") + ) + @Synchronized + override fun activeConnection() = connection ?: defaultConnection + + @Synchronized + override fun activeConnectionForFeature(feature: FeatureWithPinnedConnection): ToolkitConnection? { + val pinnedConnection = pinningManager.getPinnedConnection(feature) + if (pinnedConnection != null) { + return pinnedConnection + } + + return connection?.let { + return@let if (feature.supportsConnectionType(it)) { + it + } else { + null + } + } ?: defaultConnection?.let { + if (ApplicationInfo.getInstance().build.productCode == "GW") return null + if (feature.supportsConnectionType(it)) { + return it + } + + null + } + } + + override fun connectionStateForFeature(feature: FeatureWithPinnedConnection): BearerTokenAuthState { + val conn = activeConnectionForFeature(feature) as? AwsBearerTokenConnection? ?: return BearerTokenAuthState.NOT_AUTHENTICATED + val provider = conn.getConnectionSettings().tokenProvider.delegate as? BearerTokenProvider ?: return BearerTokenAuthState.NOT_AUTHENTICATED + + return provider.state() + } + + override fun getState() = ToolkitConnectionManagerState( + connection?.id + ) + + override fun loadState(state: ToolkitConnectionManagerState) { + state.activeConnectionId?.let { + val idSegments = it.split(";") + val activeConnectionIdWithRegion = + if (idSegments.size == 2) { + "${idSegments[0]};us-east-1;${idSegments[1]}" + } else { + it + } + connection = ToolkitAuthManager.getInstance().getConnection(activeConnectionIdWithRegion) + } + } + + @Synchronized + override fun switchConnection(newConnection: ToolkitConnection?) { + val oldConnection = this.connection + + if (oldConnection != newConnection) { + val application = ApplicationManager.getApplication() + this.connection = newConnection + + if (newConnection != null) { + val featuresToPin = mutableListOf() + FeatureWithPinnedConnection.EP_NAME.forEachExtensionSafe { + if (!pinningManager.isFeaturePinned(it) && + ( + ( + oldConnection == null && it.supportsConnectionType(newConnection) + ) || + ( + oldConnection != null && it.supportsConnectionType(oldConnection) != it.supportsConnectionType(newConnection) + ) + ) + ) { + featuresToPin.add(it) + } else if ( + newConnection is AwsBearerTokenConnection && + oldConnection is AwsBearerTokenConnection && + oldConnection.id == newConnection.id && + oldConnection.scopes.all { s -> s in newConnection.scopes } + ) { + // TODO: ugly + // scope update case + featuresToPin.add(it) + } + } + + if (featuresToPin.isNotEmpty()) { + application.executeOnPooledThread { + pinningManager.pinFeatures(oldConnection, newConnection, featuresToPin) + } + } + } + + application.messageBus.syncPublisher(ToolkitConnectionManagerListener.TOPIC).activeConnectionChanged(newConnection) + } + } +} + +data class ToolkitConnectionManagerState( + var activeConnectionId: String? = null, +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/InteractiveCredential.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/InteractiveCredential.kt new file mode 100644 index 00000000000..67bdbaf416f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/InteractiveCredential.kt @@ -0,0 +1,22 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.actionSystem.AnAction +import software.amazon.q.core.credentials.CredentialIdentifier + +/** + * Interface that indicates that [CredentialIdentifier] may require interaction from a user before they can be used + */ +interface InteractiveCredential : CredentialIdentifier { + val userActionDisplayMessage: String + val userActionShortDisplayMessage: String get() = userActionDisplayMessage + + val userAction: AnAction + + /** + * Determines if user action is required at this time (e.g. may check expiry of cookies, etc) + */ + fun userActionRequired(): Boolean +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt new file mode 100644 index 00000000000..b30d8206ad0 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt @@ -0,0 +1,262 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFileManager +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.awssdk.profiles.internal.ProfileFileReader +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.amazon.awssdk.services.ssooidc.model.InvalidRequestException +import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sono.isSono +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.credentials.validatedSsoIdentifierFromUrl +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.CredentialSourceId +import java.io.IOException + +private val LOG = getLogger>() + +sealed class Login { + abstract val id: CredentialSourceId + abstract val onError: (Exception) -> Unit + protected abstract fun doLogin(project: Project): T + + fun login(project: Project): T { + LOG.debug { "Starting login with request: $this" } + try { + check(!ToolkitOAuthService.getInstance().hasPendingRequest()) { + LOG.warn { "$this attempt initiated with pending request: ${ToolkitOAuthService.getInstance().pendingRequest()}" } + AwsCoreBundle.message("toolkit.login.singleton") + } + return doLogin(project) + } catch (e: Exception) { + onError(e) + throw e + } + } + + data class BuilderId( + val scopes: List, + val onPendingToken: (InteractiveBearerTokenProvider) -> Unit, + override val onError: (Exception) -> Unit, + val onSuccess: () -> Unit, + ) : Login() { + override val id: CredentialSourceId = CredentialSourceId.AwsId + + override fun doLogin(project: Project) { + loginSso(project, SONO_URL, SONO_REGION, scopes, onPendingToken, onError, onSuccess) != null + } + } + + data class IdC( + val startUrl: String, + val region: AwsRegion, + val scopes: List, + val onPendingToken: (InteractiveBearerTokenProvider) -> Unit, + val onSuccess: () -> Unit, + override val onError: (Exception) -> Unit, + ) : Login() { + override val id: CredentialSourceId = CredentialSourceId.IamIdentityCenter + private val configFilesFacade = DefaultConfigFilesFacade() + + override fun doLogin(project: Project): AwsBearerTokenConnection? { + // we have this check here so we blow up early if user has an invalid config file + try { + configFilesFacade.readSsoSessions() + } catch (e: Exception) { + onError(ConfigFacadeException(e)) + return null + } + + val profile = UserConfigSsoSessionProfile( + configSessionName = validatedSsoIdentifierFromUrl(startUrl), + ssoRegion = region.id, + startUrl = startUrl, + scopes = scopes + ) + + // expect 'authAndUpdateConfig' to call onError on failure + val conn = authAndUpdateConfig(project, profile, configFilesFacade, onPendingToken, onSuccess, onError) ?: return null + + // TODO: delta, make sure we are good to switch immediately +// if (!promptForIdcPermissionSet) { +// ToolkitConnectionManager.getInstance(project).switchConnection(connection) +// close(DialogWrapper.OK_EXIT_CODE) +// return +// } + ToolkitConnectionManager.getInstance(project).switchConnection(conn) + + return conn + } + } + + data class LongLivedIAM( + val profileName: String, + val accessKey: String, + val secretKey: String, + val onConfigFileFacadeError: (Exception) -> Unit, + val onProfileAlreadyExist: () -> Unit, + val onConnectionValidationError: (Exception) -> Unit, + ) : Login() { + override val onError: (Exception) -> Unit = {} + + override val id: CredentialSourceId = CredentialSourceId.SharedCredentials + private val configFilesFacade = DefaultConfigFilesFacade() + + override fun doLogin(project: Project): Boolean { + val existingProfiles = try { + configFilesFacade.readAllProfiles() + } catch (e: Exception) { + onConfigFileFacadeError(ConfigFacadeException(e)) + return false + } + + if (existingProfiles.containsKey(profileName)) { + onProfileAlreadyExist() + return false + } + + try { + runUnderProgressIfNeeded(project, AwsCoreBundle.message("settings.states.validating.short"), cancelable = true) { + AwsClientManager.getInstance().createUnmanagedClient( + StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)), + Region.AWS_GLOBAL + ).use { client -> + client.getCallerIdentity() + } + } + } catch (e: Exception) { + onConnectionValidationError(e) + return false + } + + val profile = Profile.builder() + .name(profileName) + .properties( + mapOf( + "aws_access_key_id" to accessKey, + "aws_secret_access_key" to secretKey + ) + ) + .build() + + configFilesFacade.appendProfileToCredentials(profile) + + // TODO: how to refresh partially? + // TODO: should it live in configFileFacade + VirtualFileManager.getInstance().refreshWithoutFileWatcher(false) + + return true + } + } +} + +fun authAndUpdateConfig( + project: Project?, + profile: UserConfigSsoSessionProfile, + configFilesFacade: ConfigFilesFacade, + onPendingToken: (InteractiveBearerTokenProvider) -> Unit, + onSuccess: () -> Unit, + onError: (Exception) -> Unit, +): AwsBearerTokenConnection? { + val requestedScopes = profile.scopes + val allScopes = requestedScopes.toMutableSet() + + val oldScopeOrEmpty = ToolkitAuthManager.getInstance().getConnection(profile.id)?.let { existingConn -> + if (existingConn is AwsBearerTokenConnection) { + existingConn.scopes + } else { + null + } + }.orEmpty() + + // TODO: what if the old scope is deprecated? + if (!oldScopeOrEmpty.all { it in requestedScopes }) { + allScopes.addAll(oldScopeOrEmpty) + } + + val updatedProfile = profile.copy(scopes = allScopes.toList()) + + val connection = try { + ToolkitAuthManager.getInstance().tryCreateTransientSsoConnection(updatedProfile) { connection -> + reauthConnectionIfNeeded(project, connection, onPendingToken, reauthSource = ReauthSource.FRESH_AUTH) + } + } catch (e: Exception) { + onError(e) + return null + } + + configFilesFacade.updateSectionInConfig( + SsoSessionConstants.SSO_SESSION_SECTION_NAME, + Profile.builder() + .name(updatedProfile.configSessionName) + .properties( + mapOf( + ProfileProperty.SSO_START_URL to updatedProfile.startUrl, + ProfileProperty.SSO_REGION to updatedProfile.ssoRegion, + SsoSessionConstants.SSO_REGISTRATION_SCOPES to updatedProfile.scopes.joinToString(",") + ) + ).build() + ) + onSuccess() + + return connection +} + +fun ssoErrorMessageFromException(e: Exception) = when (e) { + is IllegalStateException -> e.message ?: AwsCoreBundle.message("general.unknown_error") + is ProcessCanceledException -> AwsCoreBundle.message("codewhisperer.credential.login.dialog.exception.cancel_login") + is InvalidRequestException -> AwsCoreBundle.message("codewhisperer.credential.login.exception.invalid_input") + is InvalidGrantException, is SsoOidcException -> e.message ?: AwsCoreBundle.message("codewhisperer.credential.login.exception.invalid_grant") + is ConfigFacadeException -> e.message + else -> { + val baseMessage = when (e) { + is IOException -> "codewhisperer.credential.login.exception.io" + else -> "codewhisperer.credential.login.exception.general" + } + + AwsCoreBundle.message(baseMessage, "${e.javaClass.name}: ${e.message}") + } +} + +class ConfigFacadeException(override val cause: Exception) : Exception() { + override val message: String + get() = messageFromConfigFacadeError(cause).first + + override fun getStackTrace() = cause.stackTrace +} + +fun messageFromConfigFacadeError(e: Exception): Pair { + // we'll consider nested exceptions and exception loops to be out of scope + val (errorTemplate, errorType) = if (e.stackTrace.any { it.className == ProfileFileReader::class.java.canonicalName }) { + "gettingstarted.auth.config.issue" to "ConfigParseError" + } else { + "codewhisperer.credential.login.exception.general" to e::class.java.name + } + + val errorMessage = AwsCoreBundle.message(errorTemplate, e.localizedMessage ?: e::class.java.name) + + return errorMessage to errorType +} + +fun getCredentialIdForTelemetry(connection: ToolkitConnection): CredentialSourceId = + if (connection.isSono()) CredentialSourceId.AwsId else CredentialSourceId.IamIdentityCenter diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaRequiredInteractiveCredentials.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaRequiredInteractiveCredentials.kt new file mode 100644 index 00000000000..33d12645d4a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaRequiredInteractiveCredentials.kt @@ -0,0 +1,16 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.actionSystem.AnAction +import software.amazon.q.resources.AwsCoreBundle + +interface MfaRequiredInteractiveCredentials : InteractiveCredential { + override val userActionDisplayMessage: String get() = AwsCoreBundle.message("credentials.mfa.display", displayName) + override val userActionShortDisplayMessage: String get() = AwsCoreBundle.message("credentials.mfa.display.short") + + override val userAction: AnAction get() = RefreshConnectionAction(AwsCoreBundle.message("credentials.mfa.action")) + + override fun userActionRequired(): Boolean = true +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaSupport.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaSupport.kt new file mode 100644 index 00000000000..bbbc1ac8a4e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaSupport.kt @@ -0,0 +1,17 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.ui.Messages +import software.amazon.q.jetbrains.utils.computeOnEdt +import software.amazon.q.resources.AwsCoreBundle + +fun promptForMfaToken(name: String, mfaSerial: String): String = computeOnEdt { + Messages.showInputDialog( + AwsCoreBundle.message("credentials.mfa.message", mfaSerial), + AwsCoreBundle.message("credentials.mfa.title", name), + null + ) ?: throw ProcessCanceledException(IllegalStateException("MFA challenge is required")) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/PostValidateInteractiveCredential.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/PostValidateInteractiveCredential.kt new file mode 100644 index 00000000000..03de0a476b8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/PostValidateInteractiveCredential.kt @@ -0,0 +1,11 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +/** + * Marker interface for credentials that may require interactivity after [AwsConnectionManager] attempts validation + */ +interface PostValidateInteractiveCredential { + fun handleValidationException(e: Exception): ConnectionState.RequiresUserAction? +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/RefreshConnectionAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/RefreshConnectionAction.kt new file mode 100644 index 00000000000..a1b5bb99548 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/RefreshConnectionAction.kt @@ -0,0 +1,38 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAware +import kotlinx.coroutines.launch +import software.amazon.q.jetbrains.core.AwsResourceCache +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.AwsTelemetry + +class RefreshConnectionAction(text: String = AwsCoreBundle.message("settings.refresh.description")) : + AnAction(text, null, AllIcons.Actions.Refresh), + DumbAware { + + override fun getActionUpdateThread() = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + val project = e.project ?: return + e.presentation.isEnabled = when (val state = AwsConnectionManager.getInstance(project).connectionState) { + is ConnectionState.IncompleteConfiguration -> false + else -> state.isTerminal + } + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + AwsTelemetry.refreshExplorer(project) + val scope = projectCoroutineScope(project) + scope.launch { AwsResourceCache.getInstance().clear() } + AwsConnectionManager.getInstance(project).refreshConnectionState() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoRequiredInteractiveCredentials.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoRequiredInteractiveCredentials.kt new file mode 100644 index 00000000000..a5efd2acfaf --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoRequiredInteractiveCredentials.kt @@ -0,0 +1,34 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.actionSystem.AnAction +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sso.LazyAccessTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.SsoCache +import software.amazon.q.resources.AwsCoreBundle + +interface SsoRequiredInteractiveCredentials : InteractiveCredential { + val ssoCache: SsoCache + val ssoUrl: String + val ssoRegion: String + + override val userActionDisplayMessage: String get() = AwsCoreBundle.message("credentials.sso.display", displayName) + override val userActionShortDisplayMessage: String get() = AwsCoreBundle.message("credentials.sso.display.short") + + override val userAction: AnAction get() = RefreshConnectionAction(AwsCoreBundle.message("credentials.sso.action")) + + private val lazyTokenProvider: LazyAccessTokenProvider + get() = LazyAccessTokenProvider( + ssoCache, + ssoUrl, + ssoRegion, + listOf(IDENTITY_CENTER_ROLE_ACCESS_SCOPE) + ) + + // assumes single scope if we're going through this interface + override fun userActionRequired(): Boolean = lazyTokenProvider.resolveToken() == null + + fun invalidateCurrentToken() = lazyTokenProvider.invalidate() +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoSupport.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoSupport.kt new file mode 100644 index 00000000000..958f5722fe7 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoSupport.kt @@ -0,0 +1,11 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import software.amazon.q.jetbrains.core.credentials.sso.DiskCache + +/** + * Shared disk cache for SSO for the IDE + */ +val diskCache by lazy { DiskCache() } diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt new file mode 100644 index 00000000000..ec68c9f1f60 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt @@ -0,0 +1,423 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.project.Project +import migration.software.amazon.q.jetbrains.telemetry.TelemetryService +import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit +import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifierSso +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants.SSO_SESSION_SECTION_NAME +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.AuthTelemetry +import software.aws.toolkits.telemetry.CredentialSourceId +import software.aws.toolkits.telemetry.CredentialType +import software.aws.toolkits.telemetry.Result +import java.time.Duration +import java.time.Instant + +sealed interface ToolkitConnection { + val id: String + val label: String + + fun getConnectionSettings(): ClientConnectionSettings<*> +} + +interface AwsCredentialConnection : ToolkitConnection { + override fun getConnectionSettings(): ConnectionSettings +} + +interface AwsBearerTokenConnection : ToolkitConnection { + val sessionName: String + val startUrl: String + val region: String + val scopes: List + + override fun getConnectionSettings(): TokenConnectionSettings +} + +sealed interface AuthProfile + +data class ManagedSsoProfile( + var ssoRegion: String = "", + var startUrl: String = "", + var scopes: List = emptyList(), +) : AuthProfile + +data class UserConfigSsoSessionProfile( + var configSessionName: String = "", + var ssoRegion: String = "", + var startUrl: String = "", + var scopes: List = emptyList(), +) : AuthProfile { + val id + get() = "$SSO_SESSION_SECTION_NAME:$configSessionName" +} + +data class DetectedDiskSsoSessionProfile( + var profileName: String = "", + var startUrl: String = "", + var ssoRegion: String = "", + var scopes: List = emptyList(), +) : AuthProfile + +/** + * Used to contribute connections to [ToolkitAuthManager] on service initialization + */ +interface ToolkitStartupAuthFactory { + fun buildConnections(): List + + companion object { + val EP_NAME = ExtensionPointName.create("amazon.q.startupAuthFactory") + } +} + +interface ToolkitConnectionManager : Disposable { + @Deprecated( + "Fragile API. Probably leads to unexpected behavior. Use only for toolkit explorer dropdown state.", + ReplaceWith("activeConnectionForFeature(feature)") + ) + fun activeConnection(): ToolkitConnection? + + fun activeConnectionForFeature(feature: FeatureWithPinnedConnection): ToolkitConnection? + + fun connectionStateForFeature(feature: FeatureWithPinnedConnection): BearerTokenAuthState + + fun switchConnection(newConnection: ToolkitConnection?) + + override fun dispose() {} + + companion object { + fun getInstance(project: Project) = project.service() + } +} + +/** + * Individual service should subscribe [ToolkitConnectionManagerListener.TOPIC] to fire their service activation / UX update + * + * IMPORTANT: Do *not* update this method signature without consultation; an internal partner consumes this method. + * Inter-plugin calls require relinking on each method signature change, even if it looks safe from a Kotlin standpoint. + */ +@Deprecated("Connections created through this function are not written to the user's ~/.aws/config file") +fun loginSso( + project: Project?, + startUrl: String, + region: String, + requestedScopes: List, + onPendingToken: (InteractiveBearerTokenProvider) -> Unit = {}, + onError: (Exception) -> Unit = {}, + onSuccess: () -> Unit = {}, + metadata: ConnectionMetadata? = null, +): AwsBearerTokenConnection? { + fun createAndAuthNewConnection(profile: AuthProfile): AwsBearerTokenConnection? { + val authManager = ToolkitAuthManager.getInstance() + val connection = try { + authManager.tryCreateTransientSsoConnection(profile) { transientConnection -> + reauthConnectionIfNeeded( + project = project, + connection = transientConnection, + onPendingToken = onPendingToken, + isReAuth = false, + source = metadata?.sourceId, + reauthSource = ReauthSource.FRESH_AUTH + ) + } + } catch (e: Exception) { + onError(e) + null + } + + if (connection != null) { + onSuccess() + } + + project?.let { ToolkitConnectionManager.getInstance(it).switchConnection(connection) } + return connection + } + + val connectionId = ToolkitBearerTokenProvider.ssoIdentifier(startUrl, region) + + val manager = ToolkitAuthManager.getInstance() + val allScopes = requestedScopes.toMutableSet() + val connection = manager.getConnection(connectionId)?.let { connection -> + val logger = getLogger() + + if (connection !is AwsBearerTokenConnection) { + return@let null + } + + // There is an existing connection we can use + if (!requestedScopes.all { it in connection.scopes }) { + allScopes.addAll(connection.scopes) + + logger.info { + """ + Forcing reauth on ${connection.id} since requested scopes ($requestedScopes) + are not a complete subset of current scopes (${connection.scopes}) + """.trimIndent() + } + // can't reuse since requested scopes are not in current connection. forcing reauth + return@let null + } + + // For the case when the existing connection is in invalid state, we need to re-auth + reauthConnectionIfNeeded( + project = project, + connection = connection, + onPendingToken = onPendingToken, + isReAuth = true, + source = metadata?.sourceId, + reauthSource = ReauthSource.COMMON_LOGIN + ) + return@let connection + } + + if (connection != null) { + return connection + } + + // No existing connection, start from scratch + return createAndAuthNewConnection( + ManagedSsoProfile( + region, + startUrl, + allScopes.toList() + ) + ) +} + +@Suppress("UnusedParameter") +fun logoutFromSsoConnection(project: Project?, connection: AwsBearerTokenConnection, callback: () -> Unit = {}) { + try { + ToolkitAuthManager.getInstance().deleteConnection(connection.id) + if (connection is ProfileSsoManagedBearerSsoConnection) { + deleteSsoConnection(connection) + } + } finally { + callback() + } +} + +fun lazyGetUnauthedBearerConnections() = + ToolkitAuthManager.getInstance().listConnections().filterIsInstance().filter { + it.lazyIsUnauthedBearerConnection() + } + +fun AwsBearerTokenConnection.lazyIsUnauthedBearerConnection(): Boolean { + val provider = (getConnectionSettings().tokenProvider.delegate as? BearerTokenProvider) + + if (provider != null) { + if (provider.currentToken() == null) { + // provider is unauthed if no token + return true + } + + // or token can't be used directly without interaction + return provider.state() != BearerTokenAuthState.AUTHORIZED + } + + // not a bearer token provider + return false +} + +fun reauthConnectionIfNeeded( + project: Project?, + connection: ToolkitConnection, + onPendingToken: (InteractiveBearerTokenProvider) -> Unit = {}, + isReAuth: Boolean = false, + source: String? = null, + reauthSource: ReauthSource? = ReauthSource.TOOLKIT, +): BearerTokenProvider { + val tokenProvider = (connection.getConnectionSettings() as TokenConnectionSettings).tokenProvider.delegate as BearerTokenProvider + if (tokenProvider is InteractiveBearerTokenProvider) { + onPendingToken(tokenProvider) + } + + val startUrl = (connection as AwsBearerTokenConnection).startUrl + var didReauth = false + maybeReauthProviderIfNeeded(project, reauthSource, tokenProvider) { + didReauth = true + runUnderProgressIfNeeded(project, AwsCoreBundle.message("credentials.pending.title"), true) { + try { + tokenProvider.reauthenticate() + if (isReAuth) { + recordLoginWithBrowser( + credentialStartUrl = startUrl, + credentialSourceId = getCredentialIdForTelemetry(connection), + isReAuth = true, + result = Result.Succeeded, + source = source, + tokenProvider = tokenProvider, + ) + recordAddConnection( + credentialSourceId = getCredentialIdForTelemetry(connection), + isReAuth = true, + result = Result.Succeeded, + source = source, + ) + } + } catch (e: Exception) { + if (isReAuth) { + val result = if (e is ProcessCanceledException) Result.Cancelled else Result.Failed + recordLoginWithBrowser( + credentialStartUrl = startUrl, + credentialSourceId = getCredentialIdForTelemetry(connection), + isReAuth = true, + result = result, + source = source, + tokenProvider = tokenProvider, + ) + recordAddConnection( + credentialSourceId = getCredentialIdForTelemetry(connection), + isReAuth = true, + result = result, + source = source, + ) + } + + throw e + } + } + } + + if (!didReauth) { + // webview is stuck if reauth was not needed (i.e. token on disk is valid) + project?.let { ToolkitConnectionManager.getInstance(it).switchConnection(connection) } + } + return tokenProvider +} + +// Return true if need to re-auth, false otherwise +fun maybeReauthProviderIfNeeded( + project: Project?, + reauthSource: ReauthSource? = ReauthSource.TOOLKIT, + tokenProvider: BearerTokenProvider, + onReauthRequired: (SsoOidcException?) -> Any, +): Boolean { + val state = tokenProvider.state() + when (state) { + BearerTokenAuthState.NOT_AUTHENTICATED -> { + getLogger().info { "Token provider NOT_AUTHENTICATED, requesting login" } + onReauthRequired(null) + return true + } + + BearerTokenAuthState.NEEDS_REFRESH -> { + try { + getLogger().warn { "Starting token refresh" } + return runUnderProgressIfNeeded(project, AwsCoreBundle.message("credentials.refreshing"), true) { + tokenProvider.resolveToken() + BearerTokenProviderListener.notifyCredUpdate(tokenProvider.id) + return@runUnderProgressIfNeeded false + } + } catch (e: SsoOidcException) { + AuthTelemetry.sourceOfRefresh(authRefreshSource = reauthSource.toString()) + getLogger().warn(e) { "Redriving bearer token login flow since token could not be refreshed" } + onReauthRequired(e) + return true + } + } + + BearerTokenAuthState.AUTHORIZED -> { + return false + } + } +} + +enum class ReauthSource { + CODEWHISPERER, + TOOLKIT, + Q_CHAT, + LOGIN_BROWSER, + CODEWHISPERER_STATUSBAR, + CODECATALYST, + COMMON_LOGIN, + FRESH_AUTH, +} + +fun deleteSsoConnection(connection: ProfileSsoManagedBearerSsoConnection) = + deleteSsoConnection(connection.configSessionName) + +fun deleteSsoConnection(connection: CredentialIdentifier) = + deleteSsoConnection(getSsoSessionProfileNameFromCredentials(connection)) + +fun deleteSsoConnection(sessionName: String) = DefaultConfigFilesFacade().deleteSsoConnectionFromConfig(sessionName) + +private fun getSsoSessionProfileNameFromCredentials(connection: CredentialIdentifier): String { + connection as ProfileCredentialsIdentifierSso + return connection.ssoSessionName +} + +private fun recordLoginWithBrowser( + credentialStartUrl: String? = null, + credentialSourceId: CredentialSourceId? = null, + reason: String? = null, + isReAuth: Boolean, + result: Result, + source: String? = null, + tokenProvider: BearerTokenProvider? = null, +) { + TelemetryService.getInstance().record(null as Project?) { + datum("aws_loginWithBrowser") { + createTime(Instant.now()) + unit(MetricUnit.NONE) + value(1.0) + passive(false) + credentialSourceId?.let { metadata("credentialSourceId", it.toString()) } + credentialStartUrl?.let { metadata("credentialStartUrl", it) } + metadata("credentialType", CredentialType.BearerToken.toString()) + metadata("isReAuth", isReAuth.toString()) + reason?.let { metadata("reason", it) } + metadata("result", result.toString()) + source?.let { metadata("source", it) } + tokenProvider?.currentToken()?.let { token -> + metadata("sessionDuration", Duration.between(token.createdAt, Instant.now()).toMillis().toString()) + } + } + } +} + +private fun recordAddConnection( + credentialSourceId: CredentialSourceId? = null, + reason: String? = null, + isReAuth: Boolean, + result: Result, + source: String? = null, +) { + TelemetryService.getInstance().record(null as Project?) { + datum("auth_addConnection") { + createTime(Instant.now()) + unit(MetricUnit.NONE) + value(1.0) + passive(false) + credentialSourceId?.let { metadata("credentialSourceId", it.toString()) } + metadata("isReAuth", isReAuth.toString()) + reason?.let { metadata("reason", it) } + metadata("result", result.toString()) + source?.let { metadata("source", it) } + } + } +} + +data class ConnectionMetadata( + val sourceId: String? = null, +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt new file mode 100644 index 00000000000..740de635fd2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt @@ -0,0 +1,123 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.fasterxml.jackson.annotation.JsonIgnore +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import software.amazon.q.jetbrains.core.credentials.sso.DiskCache +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.ProfileSdkTokenProviderWrapper +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider + +/** + * An SSO bearer connection created through a `sso-session` declaration in a user's ~/.aws/config + */ +class ProfileSsoManagedBearerSsoConnection( + id: String, + val configSessionName: String, + startUrl: String, + region: String, + scopes: List, + cache: DiskCache = diskCache, +) : ManagedBearerSsoConnection( + configSessionName, + startUrl, + region, + scopes, + cache, + id, + ToolkitBearerTokenProvider.diskSessionDisplayName(configSessionName) +) + +/** + * An SSO bearer connection created through [loginSso] + */ +class LegacyManagedBearerSsoConnection( + startUrl: String, + region: String, + scopes: List, + cache: DiskCache = diskCache, +) : ManagedBearerSsoConnection( + "", + startUrl, + region, + scopes, + cache, + ToolkitBearerTokenProvider.ssoIdentifier(startUrl, region), + ToolkitBearerTokenProvider.ssoDisplayName(startUrl) +) + +sealed class ManagedBearerSsoConnection( + override val sessionName: String, + override val startUrl: String, + override val region: String, + override val scopes: List, + cache: DiskCache = diskCache, + override val id: String, + override val label: String, +) : AwsBearerTokenConnection, Disposable { + + private val provider = + tokenConnection( + InteractiveBearerTokenProvider( + startUrl, + region, + scopes, + id, + cache + ), + region + ) + + @JsonIgnore + override fun getConnectionSettings(): TokenConnectionSettings = provider + + override fun dispose() { + disposeProviderIfRequired(provider) + } +} + +class DetectedDiskSsoSessionConnection( + override val sessionName: String, + override val startUrl: String, + override val region: String, + override val scopes: List, + displayNameOverride: String? = null, +) : AwsBearerTokenConnection, Disposable { + override val id = ToolkitBearerTokenProvider.diskSessionIdentifier(sessionName) + override val label = displayNameOverride ?: ToolkitBearerTokenProvider.diskSessionDisplayName(sessionName) + + private val provider = + tokenConnection( + ProfileSdkTokenProviderWrapper( + sessionName = sessionName, + region = region + ), + region + ) + + @JsonIgnore + override fun getConnectionSettings(): TokenConnectionSettings = provider + + override fun dispose() { + disposeProviderIfRequired(provider) + } +} + +private fun tokenConnection(provider: BearerTokenProvider, region: String) = + TokenConnectionSettings( + ToolkitBearerTokenProvider(provider), + AwsRegionProvider.getInstance().get(region) ?: error("Partition data is missing for $region") + ) + +private fun disposeProviderIfRequired(settings: TokenConnectionSettings) { + val delegate = settings.tokenProvider.delegate + if (delegate is Disposable) { + Disposer.dispose(delegate) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionManagerListener.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionManagerListener.kt new file mode 100644 index 00000000000..6b15ff7fa96 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionManagerListener.kt @@ -0,0 +1,17 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.util.messages.Topic +import java.util.EventListener + +// TODO: unify with [ConnectionSettingsStateChangeNotifier] +interface ToolkitConnectionManagerListener : EventListener { + fun activeConnectionChanged(newConnection: ToolkitConnection?) + + companion object { + @Topic.AppLevel + val TOPIC = Topic.create("ToolkitConnectionManagerListener active connection change", ToolkitConnectionManagerListener::class.java) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitCredentialProcessProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitCredentialProcessProvider.kt new file mode 100644 index 00000000000..9908d6d6692 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitCredentialProcessProvider.kt @@ -0,0 +1,112 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.execution.configurations.CommandLineTokenizer +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.ProcessOutput +import com.intellij.execution.util.ExecUtil +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.registry.Registry +import com.intellij.util.execution.ParametersListUtil +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.awssdk.utils.cache.CachedSupplier +import software.amazon.awssdk.utils.cache.RefreshResult +import software.amazon.q.resources.AwsCoreBundle +import java.time.Instant +import java.util.Enumeration + +/** + * Similar to the SDKs ProcessCredentialsProvider, but ties in the env var system of the IDE such as getting $PATH + */ +class ToolkitCredentialProcessProvider @TestOnly constructor( + private val command: String, + private val parser: CredentialProcessOutputParser, +) : AwsCredentialsProvider, SdkAutoCloseable { + constructor(command: String) : this(command, DefaultCredentialProcessOutputParser) + + private val entrypoint by lazy { + ParametersListUtil.parse(command).first() + } + private val cmd by lazy { + if (SystemInfo.isWindows) { + @Suppress("UNCHECKED_CAST") + GeneralCommandLine("cmd", "/C", *(CommandLineTokenizer(command) as Enumeration).toList().toTypedArray()) + } else { + GeneralCommandLine("sh", "-c", command) + } + } + private val processCredentialCache = CachedSupplier.builder { refresh() }.build() + + override fun resolveCredentials(): AwsCredentials = processCredentialCache.get() + + private fun refresh(): RefreshResult { + val timeout = Registry.intValue("aws.credentialProcess.timeout", DEFAULT_TIMEOUT) + val output = ExecUtil.execAndGetOutput(cmd, timeout) + + if (output.isTimeout) { + handleException(AwsCoreBundle.message("credentials.profile.credential_process.timeout_exception_prefix", entrypoint), output) + } + + if (output.exitCode != 0) { + handleException(AwsCoreBundle.message("credentials.profile.credential_process.execution_exception_prefix", entrypoint), output) + } + + val result = try { + parser.parse(output.stdout) + } catch (e: Exception) { + handleException(AwsCoreBundle.message("credentials.profile.credential_process.parse_exception_prefix"), output) + } + val credentials: AwsCredentials = when (val token = result.sessionToken) { + null -> AwsBasicCredentials.create(result.accessKeyId, result.secretAccessKey) + else -> AwsSessionCredentials.create(result.accessKeyId, result.secretAccessKey, token) + } + return RefreshResult.builder(credentials).staleTime(result.expiration ?: Instant.MAX).build() + } + + private fun handleException(msgPrefix: String, process: ProcessOutput): Nothing { + val errorOutput = process.stderr.takeIf { it.isNotBlank() } + val msg = "$msgPrefix${errorOutput?.let { ": $it" } ?: ""}" + throw RuntimeException(msg) + } + + override fun close() { + processCredentialCache.close() + } + + private companion object { + private const val DEFAULT_TIMEOUT = 30000 + } +} + +data class CredentialProcessOutput(val accessKeyId: String, val secretAccessKey: String, val sessionToken: String?, val expiration: Instant?) + +abstract class CredentialProcessOutputParser { + abstract fun parse(input: String): CredentialProcessOutput +} + +object DefaultCredentialProcessOutputParser : CredentialProcessOutputParser() { + private val mapper = jacksonObjectMapper() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .registerModule(JavaTimeModule()) + + override fun parse(input: String): CredentialProcessOutput = try { + mapper.readValue(input) + } catch (e: JsonProcessingException) { + e.clearLocation() + throw e + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/actions/SsoLogoutAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/actions/SsoLogoutAction.kt new file mode 100644 index 00000000000..0415ef6074c --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/actions/SsoLogoutAction.kt @@ -0,0 +1,34 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.ui.MessageDialogBuilder +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ProfileSsoManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener +import software.amazon.q.jetbrains.core.credentials.logoutFromSsoConnection +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.UiTelemetry + +class SsoLogoutAction(private val value: AwsBearerTokenConnection) : DumbAwareAction(AwsCoreBundle.message("credentials.individual_identity.signout")) { + override fun actionPerformed(e: AnActionEvent) { + UiTelemetry.click(e.project, "signOut") + if (value is ProfileSsoManagedBearerSsoConnection) { + val confirmDeletion = MessageDialogBuilder.okCancel( + AwsCoreBundle.message("gettingstarted.auth.idc.sign.out.confirmation.title"), + AwsCoreBundle.message("gettingstarted.auth.idc.sign.out.confirmation") + ).yesText(AwsCoreBundle.message("general.confirm")).ask(e.project) + if (!confirmDeletion) { + return + } + } + logoutFromSsoConnection(e.project, value) + ApplicationManager.getApplication().messageBus.syncPublisher( + ToolkitConnectionManagerListener.TOPIC + ).activeConnectionChanged(null) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt new file mode 100644 index 00000000000..80dae1dee87 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt @@ -0,0 +1,31 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.pinning + +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.sono.CODECATALYST_SCOPES +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger + +class CodeCatalystConnection : FeatureWithPinnedConnection { + override val featureId: String = "aws.codecatalyst" + override val featureName: String = "CodeCatalyst" + override fun supportsConnectionType(connection: ToolkitConnection): Boolean { + if (connection !is AwsBearerTokenConnection) { + LOG.debug { "Rejecting ${connection.id} since it's not a bearer connection" } + return false + } + if (!CODECATALYST_SCOPES.all { it in connection.scopes }) { + LOG.debug { "Rejecting ${connection.id} since it's missing a required scope" } + return false + } + return true + } + + companion object { + private val LOG = getLogger() + fun getInstance() = FeatureWithPinnedConnection.EP_NAME.findExtensionOrFail(CodeCatalystConnection::class.java) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt new file mode 100644 index 00000000000..67d8862c680 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt @@ -0,0 +1,136 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.pinning + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.extensions.ExtensionPointName +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import java.util.concurrent.ConcurrentHashMap + +typealias ConnectionPinningManager = migration.software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager + +interface FeatureWithPinnedConnection { + val featureId: String + val featureName: String + + fun supportsConnectionType(connection: ToolkitConnection): Boolean + + companion object { + val EP_NAME = ExtensionPointName.create("amazon.q.connection.pinned.feature") + } +} + +@State(name = "qConnectionPinningManager", storages = [Storage("amazonq.xml")]) +class DefaultConnectionPinningManager : + ConnectionPinningManager, + PersistentStateComponent, + Disposable { + + private val pinnedConnections = ConcurrentHashMap() + + init { + ApplicationManager.getApplication().messageBus.connect(this).subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun invalidate(providerId: String) { + pinnedConnections.entries.removeIf { (_, v) -> v.id == providerId } + } + } + ) + } + + override fun isFeaturePinned(feature: FeatureWithPinnedConnection) = getPinnedConnection(feature) != null + + override fun getPinnedConnection(feature: FeatureWithPinnedConnection): ToolkitConnection? = + pinnedConnections[feature.featureId].let { connection -> + if (connection == null) { + null + } else { + // fetch connection again in case it is no longer valid + val connectionInstance = ToolkitAuthManager.getInstance().getConnection(connection.id) + if (connectionInstance == null || !feature.supportsConnectionType(connectionInstance)) { + null + } else { + connection + } + } + } + + override fun setPinnedConnection(feature: FeatureWithPinnedConnection, newConnection: ToolkitConnection?) { + if (newConnection == null) { + pinnedConnections.remove(feature.featureId) + } else { + pinnedConnections[feature.featureId] = newConnection + } + + ApplicationManager.getApplication().messageBus.syncPublisher(ConnectionPinningManagerListener.TOPIC).pinnedConnectionChanged(feature, newConnection) + } + + override fun pinFeatures(oldConnection: ToolkitConnection?, newConnection: ToolkitConnection, features: List) { + // pin to newConnection if the feature is supported, otherwise stay with old connection + val newConnectionFeatures = mutableListOf() + val oldConnectionFeatures = mutableListOf() + features.forEach { + if (it.supportsConnectionType(newConnection)) { + newConnectionFeatures.add(it) + } else if (oldConnection != null && it.supportsConnectionType(oldConnection)) { + oldConnectionFeatures.add(it) + } else { + LOG.error { "Feature '$it' does not support either old: '$oldConnection' or new: '$newConnection'" } + } + } + + val pinConnections = { featuresToPin: List, connectionToPin: ToolkitConnection -> + featuresToPin.forEach { + setPinnedConnection(it, connectionToPin) + } + + // TODO: don't know if we still want to keep this for CodeCatalyst +// notifyInfo(AwsCoreBundle.message("credentials.switch.notification.title", featuresString, connectionToPin.label)) + } + + if (newConnectionFeatures.isNotEmpty()) { + pinConnections(newConnectionFeatures, newConnection) + } + + if (oldConnectionFeatures.isNotEmpty()) { + if (oldConnection != null) { + pinConnections(oldConnectionFeatures, oldConnection) + } + } + } + + override fun getState() = ConnectionPinningManagerState( + pinnedConnections.entries.associate { (k, v) -> k to v.id } + ) + + override fun loadState(state: ConnectionPinningManagerState) { + val authManager = ToolkitAuthManager.getInstance() + + pinnedConnections.clear() + pinnedConnections.putAll( + state.pinnedConnections.entries.mapNotNull { (k, v) -> + authManager.getConnection(v)?.let { k to it } + } + ) + } + + override fun dispose() {} + + companion object { + private val LOG = getLogger() + } +} + +data class ConnectionPinningManagerState( + var pinnedConnections: Map = emptyMap(), +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerListener.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerListener.kt new file mode 100644 index 00000000000..489c9a5a33d --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerListener.kt @@ -0,0 +1,17 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.pinning + +import com.intellij.util.messages.Topic +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import java.util.EventListener + +interface ConnectionPinningManagerListener : EventListener { + fun pinnedConnectionChanged(feature: FeatureWithPinnedConnection, newConnection: ToolkitConnection?) + + companion object { + @Topic.AppLevel + val TOPIC = Topic.create("Feature pinned active connection change", ConnectionPinningManagerListener::class.java) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/QConnection.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/QConnection.kt new file mode 100644 index 00000000000..761c2ff37e2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/QConnection.kt @@ -0,0 +1,25 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.pinning + +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES + +class QConnection : FeatureWithPinnedConnection { + override val featureId = "aws.q" + override val featureName = "Amazon Q" + + override fun supportsConnectionType(connection: ToolkitConnection): Boolean { + if (connection is AwsBearerTokenConnection) { + return Q_SCOPES.all { it in connection.scopes } + } + + return false + } + + companion object { + fun getInstance() = FeatureWithPinnedConnection.EP_NAME.findExtensionOrFail(QConnection::class.java) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/CredentialSourceType.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/CredentialSourceType.kt new file mode 100644 index 00000000000..4aa37711f0c --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/CredentialSourceType.kt @@ -0,0 +1,21 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +enum class CredentialSourceType { + EC2_INSTANCE_METADATA, ECS_CONTAINER, ENVIRONMENT; + + companion object { + fun parse(value: String): CredentialSourceType { + if (value.equals("Ec2InstanceMetadata", ignoreCase = true)) { + return EC2_INSTANCE_METADATA + } else if (value.equals("EcsContainer", ignoreCase = true)) { + return ECS_CONTAINER + } else if (value.equals("Environment", ignoreCase = true)) { + return ENVIRONMENT + } + throw IllegalArgumentException("'$value' is not a valid credential_source") + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/DEFAULT_PROFILE_ID.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/DEFAULT_PROFILE_ID.kt new file mode 100644 index 00000000000..b94d2d3145b --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/DEFAULT_PROFILE_ID.kt @@ -0,0 +1,6 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +const val DEFAULT_PROFILE_ID = "profile:default" diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProvider.kt new file mode 100644 index 00000000000..18521eeeb52 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProvider.kt @@ -0,0 +1,63 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import software.amazon.awssdk.core.SdkSystemSetting +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty + +/** + * Retrieves the EC2 metadata endpoint based on profile file, env var, and Java system properties + * + * https://github.com/aws/aws-sdk-java-v2/blob/5fb447594313ab1ab9b9c0ead0ed7cb906b06e93/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/Ec2MetadataConfigProviderEndpointResolutionTest.java + */ +object Ec2MetadataConfigProvider { + /** + * Default IPv4 endpoint for the Amazon EC2 Instance Metadata Service. + */ + private const val EC2_METADATA_SERVICE_URL_IPV4 = "http://169.254.169.254" + + /** + * Default IPv6 endpoint for the Amazon EC2 Instance Metadata Service. + */ + private const val EC2_METADATA_SERVICE_URL_IPV6 = "http://[fd00:ec2::254]" + + private enum class EndpointMode { + IPV4, IPV6; + + companion object { + fun fromValue(s: String?): EndpointMode = s?.let { _ -> + values().find { it.name.equals(s, ignoreCase = true) } + } ?: throw IllegalArgumentException("Unrecognized value for endpoint mode: '$s'") + } + } + + fun Profile.getEc2MedataEndpoint(): String = this.getEndpointOverride() ?: when (this.getEndpointMode()) { + EndpointMode.IPV4 -> EC2_METADATA_SERVICE_URL_IPV4 + EndpointMode.IPV6 -> EC2_METADATA_SERVICE_URL_IPV6 + } + + private fun Profile.getEndpointMode(): EndpointMode { + val endpointMode = SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE.nonDefaultStringValue + val endpointModeString = if (endpointMode.isPresent) { + endpointMode.get() + } else { + configFileEndpointMode(this) ?: SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE.defaultValue() + } + return EndpointMode.fromValue(endpointModeString) + } + + private fun Profile.getEndpointOverride(): String? { + val endpointOverride = SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.nonDefaultStringValue + return if (endpointOverride.isPresent) { + endpointOverride.get() + } else { + configFileEndpointOverride(this) + } + } + + private fun configFileEndpointMode(profile: Profile): String? = profile.property(ProfileProperty.EC2_METADATA_SERVICE_ENDPOINT_MODE).orElse(null) + + private fun configFileEndpointOverride(profile: Profile): String? = profile.property(ProfileProperty.EC2_METADATA_SERVICE_ENDPOINT).orElse(null) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt new file mode 100644 index 00000000000..f2241c69f6e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt @@ -0,0 +1,84 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider +import software.amazon.awssdk.services.sts.model.AssumeRoleRequest +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.promptForMfaToken +import software.amazon.q.core.region.AwsRegion +import java.util.function.Supplier + +class ProfileAssumeRoleProvider(@get:TestOnly internal val parentProvider: AwsCredentialsProvider, region: AwsRegion, profile: Profile) : + AwsCredentialsProvider, SdkAutoCloseable { + private val stsClient: StsClient + private val credentialsProvider: StsAssumeRoleCredentialsProvider + + init { + val roleArn = profile.requiredProperty(ProfileProperty.ROLE_ARN) + val roleSessionName = profile.property(ProfileProperty.ROLE_SESSION_NAME).orElseGet { "aws-toolkit-jetbrains-${System.currentTimeMillis()}" } + val externalId = profile.property(ProfileProperty.EXTERNAL_ID).orElse(null) + val mfaSerial = profile.property(ProfileProperty.MFA_SERIAL).orElse(null) + + // https://docs.aws.amazon.com/sdkref/latest/guide/setting-global-duration_seconds.html + val durationSecs = profile.property(ProfileProperty.DURATION_SECONDS).map { it.toIntOrNull() }.orElse(null) ?: 3600 + + stsClient = AwsClientManager.getInstance().createUnmanagedClient(parentProvider, Region.of(region.id)) + + credentialsProvider = StsAssumeRoleCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest( + Supplier { + createAssumeRoleRequest( + profile.name(), + mfaSerial, + roleArn, + roleSessionName, + externalId, + durationSecs + ) + } + ) + .build() + } + + private fun createAssumeRoleRequest( + profileName: String, + mfaSerial: String?, + roleArn: String, + roleSessionName: String?, + externalId: String?, + durationSeconds: Int, + ): AssumeRoleRequest { + val requestBuilder = AssumeRoleRequest.builder() + .roleArn(roleArn) + .roleSessionName(roleSessionName) + .externalId(externalId) + .durationSeconds(durationSeconds) + + mfaSerial?.let { _ -> + requestBuilder + .serialNumber(mfaSerial) + .tokenCode(promptForMfaToken(profileName, mfaSerial)) + } + + return requestBuilder.build() + } + + override fun resolveCredentials(): AwsCredentials = credentialsProvider.resolveCredentials() + + override fun close() { + credentialsProvider.close() + (parentProvider as? SdkAutoCloseable)?.close() + stsClient.close() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt new file mode 100644 index 00000000000..735c98bab78 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt @@ -0,0 +1,522 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.util.messages.Topic +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider +import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider +import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.awssdk.services.sso.model.UnauthorizedException +import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.q.jetbrains.core.credentials.ChangeConnectionSettingIfValid +import software.amazon.q.jetbrains.core.credentials.ConnectionState +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.InteractiveCredential +import software.amazon.q.jetbrains.core.credentials.MfaRequiredInteractiveCredentials +import software.amazon.q.jetbrains.core.credentials.PostValidateInteractiveCredential +import software.amazon.q.jetbrains.core.credentials.ReauthSource +import software.amazon.q.jetbrains.core.credentials.RefreshConnectionAction +import software.amazon.q.jetbrains.core.credentials.SsoRequiredInteractiveCredentials +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitCredentialProcessProvider +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile +import software.amazon.q.jetbrains.core.credentials.diskCache +import software.amazon.q.jetbrains.core.credentials.profiles.Ec2MetadataConfigProvider.getEc2MedataEndpoint +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants.SSO_SESSION_SECTION_NAME +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.jetbrains.core.credentials.sso.SsoCache +import software.amazon.q.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.ProfilesNotification +import software.amazon.q.jetbrains.utils.createNotificationExpiringAction +import software.amazon.q.jetbrains.utils.createShowMoreInfoDialogAction +import software.amazon.q.jetbrains.utils.notifyError +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialIdentifierBase +import software.amazon.q.core.credentials.CredentialProviderFactory +import software.amazon.q.core.credentials.CredentialSourceId +import software.amazon.q.core.credentials.CredentialType +import software.amazon.q.core.credentials.CredentialsChangeEvent +import software.amazon.q.core.credentials.CredentialsChangeListener +import software.amazon.q.core.credentials.SsoSessionBackedCredentialIdentifier +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn + +const val DEFAULT_PROFILE_NAME = "default" + +private const val PROFILE_FACTORY_ID = "ProfileCredentialProviderFactory" + +open class ProfileCredentialsIdentifier internal constructor(val profileName: String, override val defaultRegionId: String?, credentialType: CredentialType?) : + CredentialIdentifierBase(credentialType) { + override val id = "profile:$profileName" + override val displayName = AwsCoreBundle.message("credentials.profile.name", profileName) + override val factoryId = PROFILE_FACTORY_ID + override val shortName: String = profileName +} + +private class ProfileCredentialsIdentifierMfa(profileName: String, defaultRegionId: String?, credentialType: CredentialType?) : + ProfileCredentialsIdentifier(profileName, defaultRegionId, credentialType), MfaRequiredInteractiveCredentials + +private class ProfileCredentialsIdentifierLegacySso( + profileName: String, + defaultRegionId: String?, + override val ssoCache: SsoCache, + override val ssoUrl: String, + override val ssoRegion: String, + credentialType: CredentialType?, +) : ProfileCredentialsIdentifier(profileName, defaultRegionId, credentialType), + SsoRequiredInteractiveCredentials, + PostValidateInteractiveCredential { + // react to failure to use the local credential set + override fun handleValidationException(e: Exception) = ifReAuthNeeded(e) { + ConnectionState.RequiresUserAction( + object : InteractiveCredential, CredentialIdentifier by this { + override val userActionDisplayMessage = AwsCoreBundle.message("credentials.sso.display", displayName) + override val userActionShortDisplayMessage = AwsCoreBundle.message("credentials.sso.display.short") + override val userAction = object : AnAction(AwsCoreBundle.message("credentials.sso.action")), DumbAware { + override fun actionPerformed(e: AnActionEvent) { + invalidateCurrentToken() + + RefreshConnectionAction().actionPerformed(e) + } + } + + override fun userActionRequired() = true + } + ) + } +} + +class ProfileCredentialsIdentifierSso @TestOnly constructor( + profileName: String, + val ssoSessionName: String, + defaultRegionId: String?, + credentialType: CredentialType?, +) : ProfileCredentialsIdentifier(profileName, defaultRegionId, credentialType), PostValidateInteractiveCredential, SsoSessionBackedCredentialIdentifier { + override val sessionIdentifier = "$SSO_SESSION_SECTION_NAME:$ssoSessionName" + + override fun handleValidationException(e: Exception): ConnectionState.RequiresUserAction? = + ifReAuthNeeded(e) { + ConnectionState.RequiresUserAction( + object : InteractiveCredential, CredentialIdentifier by this { + override val userActionDisplayMessage = AwsCoreBundle.message("credentials.sso.display", displayName) + override val userActionShortDisplayMessage = AwsCoreBundle.message("credentials.sso.display.short") + override val userAction = object : AnAction(AwsCoreBundle.message("credentials.sso.login.session", ssoSessionName)), DumbAware { + override fun actionPerformed(e: AnActionEvent) { + val session = CredentialManager.getInstance() + .getSsoSessionIdentifiers() + .first { it.id == sessionIdentifier } + val connection = ToolkitAuthManager.getInstance().getOrCreateSsoConnection( + UserConfigSsoSessionProfile( + configSessionName = ssoSessionName, + ssoRegion = session.ssoRegion, + startUrl = session.startUrl, + scopes = session.scopes.toList() + ) + ) + reauthConnectionIfNeeded(e.project, connection, reauthSource = ReauthSource.TOOLKIT) + RefreshConnectionAction().actionPerformed(e) + } + } + + override fun userActionRequired() = true + } + ) + } +} + +// in the new SSO flow, we must attempt validation before knowing if user action is truly required +private fun ifReAuthNeeded(e: Exception, action: () -> ConnectionState.RequiresUserAction?): ConnectionState.RequiresUserAction? { + if (findUpException(e) || + findUpException(e) || + findUpException(e) || + findUpException(e) + ) { + return action() + } + + return null +} + +// true exception could be further up the chain +private inline fun findUpException(e: Throwable?): Boolean { + // inline fun can't use recursion + var throwable = e + while (throwable != null) { + if (throwable is T) { + return true + } + throwable = throwable.cause + } + + return false +} + +private class NeverShowAgain : DumbAwareAction(AwsCoreBundle.message("settings.never_show_again")) { + override fun actionPerformed(e: AnActionEvent) { + AwsSettings.getInstance().profilesNotification = ProfilesNotification.Never + } +} + +class ProfileCredentialProviderFactory(private val ssoCache: SsoCache = diskCache) : CredentialProviderFactory { + private val profileHolder = ProfileHolder() + + override val id = PROFILE_FACTORY_ID + override val credentialSourceId: CredentialSourceId = CredentialSourceId.SharedCredentials + + override fun setUp(credentialLoadCallback: CredentialsChangeListener) { + // Load the initial data, then start the background watcher + loadProfiles(credentialLoadCallback, true) + + ProfileWatcher.getInstance().addListener { + loadProfiles(credentialLoadCallback, false) + } + } + + private fun loadProfiles(credentialLoadCallback: CredentialsChangeListener, initialLoad: Boolean) { + val profilesAdded = mutableListOf() + val profilesModified = mutableListOf() + val ssoAdded = mutableListOf() + val ssoModified = mutableListOf() + + val previousConfig = profileHolder.snapshot() + val currentConfig = profileHolder.snapshot() + + val newProfiles = try { + validateAndGetProfiles() + } catch (e: Exception) { + notifyUserOfLoadFailure(e) + + return + } + + newProfiles.validProfiles.forEach { + val previousProfile = currentConfig.profiles.remove(it.key) + if (previousProfile == null) { + // It was not in the snapshot, so it must be new + profilesAdded.add(it.value.asId(newProfiles.validProfiles)) + } else { + // If the profile was modified, notify listeners, else do nothing + if (previousProfile != it.value) { + profilesModified.add(it.value.asId(newProfiles.validProfiles)) + } + } + } + + newProfiles.validSsoSessions.forEach { + val previousProfile = currentConfig.ssoSessions.remove(it.key) + if (previousProfile == null) { + // It was not in the snapshot, so it must be new + ssoAdded.add(it.value.asSsoSessionId()) + } else { + // If the profile was modified, notify listeners, else do nothing + if (previousProfile != it.value) { + ssoModified.add(it.value.asSsoSessionId()) + } + } + } + + // any profiles with a modified 'source_profile' need to be marked as well + newProfiles.validProfiles.forEach { (_, profile) -> + val profileId = profile.asId(newProfiles.validProfiles) + if (profileId in profilesModified) { + // already marked; skip + return@forEach + } + for (source in profile.traverseCredentialChain(newProfiles.validProfiles)) { + if (source != profile && source.asId(newProfiles.validProfiles) in profilesModified) { + profilesModified.add(profileId) + break + } + } + } + + // any profiles with a modified 'sso_session' need to be marked as well + newProfiles.validProfiles.forEach { (_, profile) -> + val profileId = profile.asId(newProfiles.validProfiles) + if (profileId in profilesModified) { + // already marked; skip + return@forEach + } + + val sessionProperty = profile.property(SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY) + if (sessionProperty.isPresent) { + val session = sessionProperty.get() + if (ssoModified.any { it.profileName == session }) { + profilesModified.add(profileId) + } + } + } + + // Any remaining profiles must have either become invalid or removed from the cred/config files + val profilesRemoved = currentConfig.profiles.values.map { it.asId(previousConfig.profiles) } + val ssoRemoved = currentConfig.ssoSessions.values.map { it.asSsoSessionId() } + + profileHolder.updateState(newProfiles.validProfiles, newProfiles.validSsoSessions) + credentialLoadCallback(CredentialsChangeEvent(profilesAdded, profilesModified, profilesRemoved, ssoAdded, ssoModified, ssoRemoved)) + + notifyUserOfResult(newProfiles, initialLoad) + if (profilesAdded.isNotEmpty() && newProfiles.validProfiles.size == 1) { + ApplicationManager.getApplication().messageBus.syncPublisher(NEW_PROFILE_ADDED).changeConnection(profilesAdded.first()) + } + } + + private fun notifyUserOfLoadFailure(e: Exception) { + val loadingFailureMessage = AwsCoreBundle.message("credentials.profile.failed_load") + + val detail = e.message?.let { + ": $it" + } ?: "" + + LOG.warn(e) { loadingFailureMessage } + + if (AwsSettings.getInstance().profilesNotification != ProfilesNotification.Never) { + notifyError( + title = AwsCoreBundle.message("credentials.profile.refresh_ok_title"), + content = "$loadingFailureMessage$detail", + notificationActions = listOf( + createNotificationExpiringAction(ActionManager.getInstance().getAction("aws.settings.upsertCredentials")), + createNotificationExpiringAction(NeverShowAgain()) + ) + ) + } + } + + private fun notifyUserOfResult(newProfiles: Profiles, initialLoad: Boolean) { + val refreshTitle = AwsCoreBundle.message("credentials.profile.refresh_ok_title") + val totalProfiles = newProfiles.validProfiles.size + newProfiles.invalidProfiles.size + val refreshBaseMessage = AwsCoreBundle.message("credentials.profile.refresh_ok_message", totalProfiles) + + // All provides were valid + if (newProfiles.invalidProfiles.isEmpty()) { + // Don't report we load creds on start to avoid spam + if (!initialLoad && AwsSettings.getInstance().profilesNotification == ProfilesNotification.Always) { + notifyInfo( + title = AwsCoreBundle.message("credentials.profile.refresh_ok_title"), + content = refreshBaseMessage, + notificationActions = listOf( + createNotificationExpiringAction(NeverShowAgain()) + ) + ) + + return + } + } + + // Some profiles failed to load + if (newProfiles.invalidProfiles.isNotEmpty()) { + val message = newProfiles.invalidProfiles.values.joinToString("\n") + + val errorDialogTitle = AwsCoreBundle.message("credentials.profile.failed_load") + val numErrorMessage = AwsCoreBundle.message("credentials.profile.refresh_errors", newProfiles.invalidProfiles.size) + + if (AwsSettings.getInstance().profilesNotification != ProfilesNotification.Never) { + notifyInfo( + title = refreshTitle, + content = "$refreshBaseMessage $numErrorMessage", + notificationActions = listOf( + createNotificationExpiringAction(ActionManager.getInstance().getAction("aws.settings.upsertCredentials")), + createNotificationExpiringAction(NeverShowAgain()), + createShowMoreInfoDialogAction(AwsCoreBundle.message("credentials.invalid.more_info"), errorDialogTitle, numErrorMessage, message) + ) + ) + } + } + } + + override fun createAwsCredentialProvider(providerId: CredentialIdentifier, region: AwsRegion): AwsCredentialsProvider { + val profileProviderId = providerId as? ProfileCredentialsIdentifier + ?: throw IllegalStateException("ProfileCredentialProviderFactory can only handle ProfileCredentialsIdentifier, but got ${providerId::class}") + + val profile = profileHolder.getProfile(profileProviderId.profileName) + ?: throw IllegalStateException("Profile ${profileProviderId.profileName} looks to have been removed") + return createAwsCredentialProvider(profile, region) + } + + private fun createAwsCredentialProvider(profile: Profile, region: AwsRegion) = when { + profile.propertyExists(PROFILE_SSO_SESSION_PROPERTY) -> createSsoSessionProfileProvider(profile) + profile.propertyExists(ProfileProperty.SSO_START_URL) -> createLegacySsoProvider(profile) + profile.propertyExists(ProfileProperty.ROLE_ARN) -> createAssumeRoleProvider(profile, region) + profile.propertyExists(ProfileProperty.AWS_SESSION_TOKEN) -> createStaticSessionProvider(profile) + profile.propertyExists(ProfileProperty.AWS_ACCESS_KEY_ID) -> createBasicProvider(profile) + profile.propertyExists(ProfileProperty.CREDENTIAL_PROCESS) -> createCredentialProcessProvider(profile) + else -> { + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.unsupported", profile.name())) + } + } + + private fun createLegacySsoProvider(profile: Profile): AwsCredentialsProvider = ProfileLegacySsoProvider(ssoCache, profile) + + private fun createSsoSessionProfileProvider(profile: Profile): AwsCredentialsProvider { + val ssoSessionName = profile.requiredProperty(SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY) + val ssoSession = profileHolder.getSsoSession(ssoSessionName) + ?: error("Profile ${profile.name()} refers to sso-session $ssoSessionName which appears to have been removed") + + return ProfileSsoSessionProvider(ssoSession, profile) + } + + private fun createAssumeRoleProvider(profile: Profile, region: AwsRegion): AwsCredentialsProvider { + val sourceProfileName = profile.property(ProfileProperty.SOURCE_PROFILE) + val credentialSource = profile.property(ProfileProperty.CREDENTIAL_SOURCE) + + val parentCredentialProvider = when { + sourceProfileName.isPresent -> { + val sourceProfile = profileHolder.getProfile(sourceProfileName.get()) + ?: throw IllegalStateException("Profile $sourceProfileName looks to have been removed") + createAwsCredentialProvider(sourceProfile, region) + } + credentialSource.isPresent -> { + // Can we parse the credential_source + credentialSourceCredentialProvider(CredentialSourceType.parse(credentialSource.get()), profile) + } + else -> { + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.assume_role.missing_source", profile.name())) + } + } + + return ProfileAssumeRoleProvider(parentCredentialProvider, region, profile) + } + + private fun credentialSourceCredentialProvider(credentialSource: CredentialSourceType, profile: Profile): AwsCredentialsProvider = + when (credentialSource) { + CredentialSourceType.ECS_CONTAINER -> ContainerCredentialsProvider.builder().build() + CredentialSourceType.EC2_INSTANCE_METADATA -> { + // The IMDS credentials provider should source the endpoint config properties from the currently active profile + InstanceProfileCredentialsProvider.builder() + .endpoint(profile.getEc2MedataEndpoint()) + .build() + } + CredentialSourceType.ENVIRONMENT -> AwsCredentialsProviderChain.builder() + .addCredentialsProvider(SystemPropertyCredentialsProvider.create()) + .addCredentialsProvider(EnvironmentVariableCredentialsProvider.create()) + .build() + } + + private fun createBasicProvider(profile: Profile) = StaticCredentialsProvider.create( + AwsBasicCredentials.create( + profile.requiredProperty(ProfileProperty.AWS_ACCESS_KEY_ID), + profile.requiredProperty(ProfileProperty.AWS_SECRET_ACCESS_KEY) + ) + ) + + private fun createStaticSessionProvider(profile: Profile) = StaticCredentialsProvider.create( + AwsSessionCredentials.create( + profile.requiredProperty(ProfileProperty.AWS_ACCESS_KEY_ID), + profile.requiredProperty(ProfileProperty.AWS_SECRET_ACCESS_KEY), + profile.requiredProperty(ProfileProperty.AWS_SESSION_TOKEN) + ) + ) + + private fun createCredentialProcessProvider(profile: Profile) = + ToolkitCredentialProcessProvider(profile.requiredProperty(ProfileProperty.CREDENTIAL_PROCESS)) + + private fun Profile.asId(profiles: Map): ProfileCredentialsIdentifier { + val name = this.name() + val defaultRegion = this.properties()[ProfileProperty.REGION] + val requestedProfileType = this.toCredentialType() + + return when { + this.requiresMfa(profiles) -> ProfileCredentialsIdentifierMfa(name, defaultRegion, requestedProfileType) + this.requiresLegacySso(profiles) -> ProfileCredentialsIdentifierLegacySso( + name, + defaultRegion, + ssoCache, + this.traverseCredentialChain(profiles).map { it.property(ProfileProperty.SSO_START_URL) }.first { it.isPresent }.get(), + this.traverseCredentialChain(profiles).map { it.property(ProfileProperty.SSO_REGION) }.first { it.isPresent }.get(), + requestedProfileType + ) + this.requiresSso() -> ProfileCredentialsIdentifierSso( + name, + requiredProperty(SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY), + defaultRegion, + requestedProfileType + ) + else -> ProfileCredentialsIdentifier(name, defaultRegion, requestedProfileType) + } + } + + private fun Profile.asSsoSessionId() = ProfileSsoSessionIdentifier( + name(), + requiredProperty(ProfileProperty.SSO_START_URL), + requiredProperty(ProfileProperty.SSO_REGION), + ssoScopes() + ) + + private fun Profile.requiresMfa(profiles: Map) = this.traverseCredentialChain(profiles) + .any { it.propertyExists(ProfileProperty.MFA_SERIAL) } + + private fun Profile.requiresLegacySso(profiles: Map) = this.traverseCredentialChain(profiles) + .any { it.propertyExists(ProfileProperty.SSO_START_URL) } + + private fun Profile.requiresSso() = propertyExists(SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY) + + companion object { + private val LOG = getLogger() + + val NEW_PROFILE_ADDED: Topic = Topic.create( + "Change to newly added profile", + ChangeConnectionSettingIfValid::class.java + ) + } +} + +private fun Profile.toCredentialType(): CredentialType? = when { + this.propertyExists(ProfileProperty.SSO_START_URL) -> CredentialType.SsoProfile + this.propertyExists(SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY) -> CredentialType.SsoProfile + this.propertyExists(ProfileProperty.ROLE_ARN) -> { + if (this.propertyExists(ProfileProperty.MFA_SERIAL)) { + CredentialType.AssumeMfaRoleProfile + } else { + CredentialType.AssumeRoleProfile + } + } + this.propertyExists(ProfileProperty.AWS_SESSION_TOKEN) -> CredentialType.StaticSessionProfile + this.propertyExists(ProfileProperty.AWS_ACCESS_KEY_ID) -> CredentialType.StaticProfile + this.propertyExists(ProfileProperty.CREDENTIAL_PROCESS) -> CredentialType.CredentialProcessProfile + else -> null +} + +private data class ProfileHolder( + val profiles: MutableMap = mutableMapOf(), + val ssoSessions: MutableMap = mutableMapOf(), +) { + fun snapshot() = copy( + profiles = profiles.toMutableMap(), + ssoSessions = ssoSessions.toMutableMap() + ) + + /** + * Update the holder with the latest view of valid state + */ + fun updateState(validProfiles: Map, validSsoSessions: Map) { + profiles.clear() + profiles.putAll(validProfiles) + + ssoSessions.clear() + ssoSessions.putAll(validSsoSessions) + } + + fun getProfile(profileName: String): Profile? = profiles[profileName] + + fun getSsoSession(sessionName: String): Profile? = ssoSessions[sessionName] +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileLegacySsoProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileLegacySsoProvider.kt new file mode 100644 index 00000000000..9a36d946f3e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileLegacySsoProvider.kt @@ -0,0 +1,57 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sso.SsoAccessTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.SsoCache +import software.amazon.q.jetbrains.core.credentials.sso.SsoCredentialProvider + +class ProfileLegacySsoProvider(ssoCache: SsoCache, profile: Profile) : AwsCredentialsProvider, SdkAutoCloseable { + private val ssoClient: SsoClient + private val ssoOidcClient: SsoOidcClient + private val credentialsProvider: SsoCredentialProvider + + init { + val ssoRegion = profile.requiredProperty(ProfileProperty.SSO_REGION) + val clientManager = AwsClientManager.getInstance() + + ssoClient = clientManager.createUnmanagedClient(AnonymousCredentialsProvider.create(), Region.of(ssoRegion)) + ssoOidcClient = clientManager.createUnmanagedClient(AnonymousCredentialsProvider.create(), Region.of(ssoRegion)) + + val ssoAccessTokenProvider = SsoAccessTokenProvider( + profile.requiredProperty(ProfileProperty.SSO_START_URL), + ssoRegion, + ssoCache, + ssoOidcClient, + isAlwaysShowDeviceCode = true, + scopes = listOf(IDENTITY_CENTER_ROLE_ACCESS_SCOPE), + ) + + credentialsProvider = SsoCredentialProvider( + profile.requiredProperty(ProfileProperty.SSO_ACCOUNT_ID), + profile.requiredProperty(ProfileProperty.SSO_ROLE_NAME), + ssoClient, + ssoAccessTokenProvider + ) + } + + override fun resolveCredentials(): AwsCredentials = credentialsProvider.resolveCredentials() + + override fun close() { + credentialsProvider.close() + ssoClient.close() + ssoOidcClient.close() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReader.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReader.kt new file mode 100644 index 00000000000..94bd5234aa8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReader.kt @@ -0,0 +1,125 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileFile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants.PROFILE_SSO_SESSION_PROPERTY +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants.SSO_SESSION_SECTION_NAME +import software.amazon.q.resources.AwsCoreBundle + +data class Profiles( + val validProfiles: Map, + val invalidProfiles: Map, + val validSsoSessions: Map, + val invalidSsoSessions: Map, +) + +/** + * Reads the AWS shared credentials files and produces what profiles are valid and if not why it is not + */ +fun validateAndGetProfiles(): Profiles { + val profileFile = ProfileFile.defaultProfileFile() + val allProfiles = profileFile.profiles().orEmpty() + val ssoSessions = profileFile.ssoSessions() + + val validProfiles = mutableMapOf() + val invalidProfiles = mutableMapOf() + val validSsoSessions = mutableMapOf() + val invalidSsoSessions = mutableMapOf() + + allProfiles.values.forEach { + try { + validateProfile(it, allProfiles) + validProfiles[it.name()] = it + } catch (e: Exception) { + invalidProfiles[it.name()] = e + } + } + + ssoSessions.values.forEach { + try { + validateSsoSession(it) + validSsoSessions[it.name()] = it + } catch (e: Exception) { + invalidSsoSessions[it.name()] = e + } + } + + return Profiles(validProfiles, invalidProfiles, validSsoSessions, invalidSsoSessions) +} + +private fun validateProfile(profile: Profile, allProfiles: Map) { + when { + profile.propertyExists(ProfileProperty.SSO_START_URL) -> validateLegacySsoProfile(profile) + profile.propertyExists(ProfileProperty.ROLE_ARN) -> validateAssumeRoleProfile(profile, allProfiles) + profile.propertyExists(ProfileProperty.AWS_SESSION_TOKEN) -> validateStaticSessionProfile(profile) + profile.propertyExists(ProfileProperty.AWS_ACCESS_KEY_ID) -> validateBasicProfile(profile) + profile.propertyExists(PROFILE_SSO_SESSION_PROPERTY) -> validateSsoProfile(profile) + profile.propertyExists(ProfileProperty.CREDENTIAL_PROCESS) -> { + // NO-OP Always valid + } + else -> { + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.unsupported", profile.name())) + } + } +} + +fun validateLegacySsoProfile(profile: Profile) { + profile.requiredProperty(ProfileProperty.SSO_ACCOUNT_ID) + profile.requiredProperty(ProfileProperty.SSO_REGION) + profile.requiredProperty(ProfileProperty.SSO_ROLE_NAME) +} + +private fun validateAssumeRoleProfile(profile: Profile, allProfiles: Map) { + val rootProfile = profile.traverseCredentialChain(allProfiles).last() + val credentialSource = rootProfile.property(ProfileProperty.CREDENTIAL_SOURCE) + + if (credentialSource.isPresent) { + try { + CredentialSourceType.parse(credentialSource.get()) + } catch (e: Exception) { + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.assume_role.invalid_credential_source", rootProfile.name())) + } + } else { + validateProfile(rootProfile, allProfiles) + } +} + +private fun validateStaticSessionProfile(profile: Profile) { + profile.requiredProperty(ProfileProperty.AWS_ACCESS_KEY_ID) + profile.requiredProperty(ProfileProperty.AWS_SECRET_ACCESS_KEY) + profile.requiredProperty(ProfileProperty.AWS_SESSION_TOKEN) +} + +private fun validateBasicProfile(profile: Profile) { + profile.requiredProperty(ProfileProperty.AWS_ACCESS_KEY_ID) + profile.requiredProperty(ProfileProperty.AWS_SECRET_ACCESS_KEY) +} + +private fun validateSsoProfile(profile: Profile) { + val ssoSessionName = profile.requiredProperty(PROFILE_SSO_SESSION_PROPERTY) + profile.requiredProperty(ProfileProperty.SSO_ACCOUNT_ID) + profile.requiredProperty(ProfileProperty.SSO_ROLE_NAME) + + val sessionSection = ProfileFile.defaultProfileFile().getSection(SSO_SESSION_SECTION_NAME, ssoSessionName).orElse(null) + ?: error(AwsCoreBundle.message("credentials.ssoSession.validation_error", profile.name(), ssoSessionName)) + + validateSsoSession(sessionSection) +} + +private fun validateSsoSession(profile: Profile) { + profile.requiredProperty(ProfileProperty.SSO_START_URL) + profile.requiredProperty(ProfileProperty.SSO_REGION) +} + +fun ProfileFile.ssoSessions(): Map { + // we could also manually parse the file to avoid reflection, but the SDK encodes a lot of logic that we don't want to try to duplicate + val rawProfilesField = javaClass.declaredFields.first { it.name == "profilesAndSectionsMap" }.apply { + isAccessible = true + } + val rawProfiles = rawProfilesField.get(this) as Map> + return rawProfiles.get(SSO_SESSION_SECTION_NAME).orEmpty() +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionIdentifier.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionIdentifier.kt new file mode 100644 index 00000000000..7b89bb4f40f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionIdentifier.kt @@ -0,0 +1,15 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import software.amazon.q.core.credentials.SsoSessionIdentifier + +data class ProfileSsoSessionIdentifier( + val profileName: String, + override val startUrl: String, + override val ssoRegion: String, + override val scopes: Set, +) : SsoSessionIdentifier { + override val id = "${SsoSessionConstants.SSO_SESSION_SECTION_NAME}:$profileName" +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionProvider.kt new file mode 100644 index 00000000000..35dc13391e8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionProvider.kt @@ -0,0 +1,61 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile +import software.amazon.q.jetbrains.core.credentials.sso.SsoCredentialProvider + +class ProfileSsoSessionProvider(ssoSession: Profile, profile: Profile) : AwsCredentialsProvider, SdkAutoCloseable { + private val ssoClient: SsoClient + private val ssoOidcClient: SsoOidcClient + private val credentialsProvider: SsoCredentialProvider + init { + val clientManager = AwsClientManager.getInstance() + + val ssoRegion = ssoSession.requiredProperty(ProfileProperty.SSO_REGION) + val startUrl = ssoSession.requiredProperty(ProfileProperty.SSO_START_URL) + val scopes = ssoSession.ssoScopes() + + val accountId = profile.requiredProperty(ProfileProperty.SSO_ACCOUNT_ID) + val roleName = profile.requiredProperty(ProfileProperty.SSO_ROLE_NAME) + + ssoClient = clientManager.createUnmanagedClient(AnonymousCredentialsProvider.create(), Region.of(ssoRegion)) + ssoOidcClient = clientManager.createUnmanagedClient(AnonymousCredentialsProvider.create(), Region.of(ssoRegion)) + + val authProfile = UserConfigSsoSessionProfile( + configSessionName = ssoSession.name(), + ssoRegion = ssoRegion, + startUrl = startUrl, + scopes = scopes.toList() + ) + + val ssoAccessTokenProvider = ToolkitAuthManager.getInstance().getOrCreateSsoConnection(authProfile).getConnectionSettings().tokenProvider + + credentialsProvider = SsoCredentialProvider( + accountId, + roleName, + ssoClient, + ssoAccessTokenProvider + ) + } + + override fun resolveCredentials(): AwsCredentials = credentialsProvider.resolveCredentials() + + override fun close() { + credentialsProvider.close() + ssoClient.close() + ssoOidcClient.close() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileUtils.kt new file mode 100644 index 00000000000..43e9aec2826 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileUtils.kt @@ -0,0 +1,69 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.util.text.nullize +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.resources.AwsCoreBundle + +fun Profile.traverseCredentialChain(profiles: Map): Sequence = sequence { + val profileChain = linkedSetOf() + var currentProfile = this@traverseCredentialChain + + yield(currentProfile) + + while (currentProfile.propertyExists(ProfileProperty.ROLE_ARN)) { + val currentProfileName = currentProfile.name() + if (!profileChain.add(currentProfileName)) { + val chain = profileChain.joinToString("->", postfix = "->$currentProfileName") + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.circular_profiles", name(), chain)) + } + + val sourceProfile = currentProfile.property(ProfileProperty.SOURCE_PROFILE) + val credentialSource = currentProfile.property(ProfileProperty.CREDENTIAL_SOURCE) + + if (sourceProfile.isPresent && credentialSource.isPresent) { + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.assume_role.duplicate_source", currentProfileName)) + } + + if (sourceProfile.isPresent) { + val sourceProfileName = sourceProfile.get() + currentProfile = profiles[sourceProfileName] + ?: throw IllegalArgumentException( + AwsCoreBundle.message( + "credentials.profile.source_profile_not_found", + currentProfileName, + sourceProfileName + ) + ) + + yield(currentProfile) + } else if (credentialSource.isPresent) { + return@sequence + } else { + throw IllegalArgumentException(AwsCoreBundle.message("credentials.profile.assume_role.missing_source", currentProfileName)) + } + } +} + +fun Profile.propertyExists(propertyName: String): Boolean = this.property(propertyName).isPresent + +fun Profile.requiredProperty(propertyName: String): String = this.property(propertyName) + .filter { it.nullize() != null } + .orElseThrow { + IllegalArgumentException( + AwsCoreBundle.message( + "credentials.profile.missing_property", + this.name(), + propertyName + ) + ) + } + +fun Profile.ssoScopes(withDefault: Boolean = true) = property(SsoSessionConstants.SSO_REGISTRATION_SCOPES) + .map { it.trim().split(",") } + .orElse(if (withDefault) listOf(IDENTITY_CENTER_ROLE_ACCESS_SCOPE) else emptyList()) + .toSet() diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt new file mode 100644 index 00000000000..64b55147eaf --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt @@ -0,0 +1,93 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.AsyncFileListener +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.openapi.vfs.pointers.VirtualFilePointer +import com.intellij.util.containers.ContainerUtil +import software.amazon.awssdk.profiles.ProfileFileLocation +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import java.nio.file.Paths + +typealias ProfileWatcher = migration.software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher + +class DefaultProfileWatcher : AsyncFileListener, Disposable, ProfileWatcher { + private val listeners = ContainerUtil.createLockFreeCopyOnWriteList<() -> Unit>() + private val watchRoots = mutableSetOf() + private val watchPointers = mutableMapOf() + + private val watchLocationsStrings = setOf( + FileUtil.normalize(ProfileFileLocation.configurationFilePath().toAbsolutePath().toString()), + FileUtil.normalize(ProfileFileLocation.credentialsFilePath().toAbsolutePath().toString()) + ) + + init { + LOG.info { "Starting profile watcher, profile locations: $watchLocationsStrings" } + + val localFileSystem = LocalFileSystem.getInstance() + + val watchLocationParents = watchLocationsStrings.map { + val path = Paths.get(FileUtil.toSystemDependentName(it)) + + // Make VFS aware of it + localFileSystem.refreshAndFindFileByIoFile(path.toFile()) + + // Use the parent as the watch root in case file does not exist yet + // Note: This system requires that the parent folder already exists + FileUtil.normalize(path.parent.toString()) + }.toSet() + + watchRoots.addAll(localFileSystem.addRootsToWatch(watchLocationParents, true)) + + LOG.info { "Added watch roots: $watchRoots" } + + VirtualFileManager.getInstance().addAsyncFileListener(this, this) + } + + override fun prepareChange(events: List): AsyncFileListener.ChangeApplier? { + LOG.debug { "Received events: $events" } + val isRelevant = events.any { watchLocationsStrings.contains(it.path) } + + return if (isRelevant) { + LOG.info { "Profile file change detected, scheduling refresh" } + object : AsyncFileListener.ChangeApplier { + override fun afterVfsChange() { + // Off load this, since this is called under a write lock + pluginAwareExecuteOnPooledThread { + listeners.forEach { it() } + } + } + } + } else { + null + } + } + + override fun forceRefresh() { + listeners.forEach { it() } + } + + override fun addListener(listener: () -> Unit) { + listeners.add(listener) + } + + override fun dispose() { + LOG.info { "Stopping profile watcher, removing roots $watchRoots" } + LocalFileSystem.getInstance().removeWatchedRoots(watchRoots) + listeners.clear() + watchPointers.clear() + } + + private companion object { + val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/SsoSessionConstants.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/SsoSessionConstants.kt new file mode 100644 index 00000000000..11da2abaab5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/SsoSessionConstants.kt @@ -0,0 +1,10 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +object SsoSessionConstants { + const val PROFILE_SSO_SESSION_PROPERTY = "sso_session" + const val SSO_SESSION_SECTION_NAME = "sso-session" + const val SSO_REGISTRATION_SCOPES: String = "sso_registration_scopes" +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sono/SonoConstants.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sono/SonoConstants.kt new file mode 100644 index 00000000000..c48789b7897 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sono/SonoConstants.kt @@ -0,0 +1,38 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sono + +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection + +const val SONO_REGION = "us-east-1" +const val SONO_URL = "https://view.awsapps.com/start" + +const val IDENTITY_CENTER_ROLE_ACCESS_SCOPE = "sso:account:access" + +@Deprecated("pending removal, merge into Q_SCOPES") +val CODEWHISPERER_SCOPES = listOf( + "codewhisperer:completions", + "codewhisperer:analysis", +) + +val Q_SCOPES = listOf( + "codewhisperer:conversations", + "codewhisperer:transformations", + "codewhisperer:taskassist", + "codewhisperer:completions", + "codewhisperer:analysis", +) + +val CODECATALYST_SCOPES = listOf( + "codecatalyst:read_write" +) + +fun ToolkitConnection?.isSono() = if (this == null) { + false +} else { + this is AwsBearerTokenConnection && this.startUrl == SONO_URL +} + +fun isInternalUser(startUrl: String?): Boolean = startUrl == "https://amzn.awsapps.com/start" diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/AccessToken.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/AccessToken.kt new file mode 100644 index 00000000000..71a7c1d9e37 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/AccessToken.kt @@ -0,0 +1,94 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.intellij.collaboration.auth.credentials.Credentials +import software.amazon.awssdk.auth.token.credentials.SdkToken +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.core.utils.SensitiveField +import software.amazon.q.core.utils.redactedString +import java.time.Instant +import java.util.Optional + +/** + * Access token returned from [SsoOidcClient.createToken] used to retrieve AWS Credentials from [SsoClient.getRoleCredentials]. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(value = [JsonSubTypes.Type(DeviceAuthorizationGrantToken::class), JsonSubTypes.Type(PKCEAuthorizationGrantToken::class) ]) +sealed interface AccessToken : SdkToken, Credentials { + val region: String + + @SensitiveField + override val accessToken: String + + @SensitiveField + @get:JsonInclude(JsonInclude.Include.NON_NULL) + val refreshToken: String? + + val expiresAt: Instant + val createdAt: Instant + + override fun token() = accessToken + + override fun expirationTime() = Optional.of(expiresAt) + + @get:JsonIgnore + val ssoUrl: String +} + +data class DeviceAuthorizationGrantToken( + val startUrl: String, + override val region: String, + override val accessToken: String, + override val refreshToken: String? = null, + override val expiresAt: Instant, + override val createdAt: Instant = Instant.EPOCH, +) : AccessToken { + override val ssoUrl: String + get() = startUrl + + override fun toString() = redactedString(this) +} + +data class PKCEAuthorizationGrantToken( + val issuerUrl: String, + override val region: String, + override val accessToken: String, + override val refreshToken: String, + override val expiresAt: Instant, + override val createdAt: Instant, +) : AccessToken { + override val ssoUrl: String + get() = issuerUrl + + override fun toString() = redactedString(this) +} + +// we really don't need to differentitate since they refresh the same way, but to save some mental cycles, +// treat them as independent so we don't need to worry about intermingling the token/registration combos +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(value = [JsonSubTypes.Type(DeviceGrantAccessTokenCacheKey::class), JsonSubTypes.Type(PKCEAccessTokenCacheKey::class) ]) +sealed interface AccessTokenCacheKey { + val scopes: List +} + +// diverging from SDK/CLI impl here since they do: sha1sum(sessionName ?: startUrl) +// which isn't good enough for us +// only used in scoped case +data class DeviceGrantAccessTokenCacheKey( + val connectionId: String, + val startUrl: String, + override val scopes: List, +) : AccessTokenCacheKey + +data class PKCEAccessTokenCacheKey( + val issuerUrl: String, + val region: String, + override val scopes: List, +) : AccessTokenCacheKey diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/Authorization.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/Authorization.kt new file mode 100644 index 00000000000..05fdd33cfad --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/Authorization.kt @@ -0,0 +1,26 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.core.utils.SensitiveField +import software.amazon.q.core.utils.redactedString +import java.time.Instant + +/** + * Returned by [SsoOidcClient.startDeviceAuthorization] that contains the required data to construct the user visible SSO login flow. + */ +@Deprecated("Device authorization grant flow is deprecated") +data class Authorization( + @SensitiveField + val deviceCode: String, + val userCode: String, + val verificationUri: String, + val verificationUriComplete: String, + val expiresAt: Instant, + val pollInterval: Long, + val createdAt: Instant, +) { + override fun toString(): String = redactedString(this) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistration.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistration.kt new file mode 100644 index 00000000000..c418e558573 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistration.kt @@ -0,0 +1,77 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.core.utils.SensitiveField +import software.amazon.q.core.utils.redactedString +import java.time.Instant + +/** + * Client registration that represents the toolkit returned from [SsoOidcClient.registerClient]. + * + * It should be persisted for reuse through many authentication requests. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, defaultImpl = DeviceAuthorizationClientRegistration::class) +@JsonSubTypes(value = [JsonSubTypes.Type(DeviceAuthorizationClientRegistration::class), JsonSubTypes.Type(PKCEClientRegistration::class) ]) +sealed interface ClientRegistration { + val clientId: String + + @SensitiveField + val clientSecret: String + + val expiresAt: Instant + + @get:JsonInclude(JsonInclude.Include.NON_EMPTY) + val scopes: List +} + +data class DeviceAuthorizationClientRegistration( + override val clientId: String, + override val clientSecret: String, + override val expiresAt: Instant, + override val scopes: List = emptyList(), +) : ClientRegistration { + override fun toString(): String = redactedString(this) +} + +data class PKCEClientRegistration( + override val clientId: String, + override val clientSecret: String, + override val expiresAt: Instant, + override val scopes: List, + // fields below are implied from the key, but trying reverse the key is annoying + val issuerUrl: String, + val region: String, + val clientType: String, + val grantTypes: List, + val redirectUris: List, +) : ClientRegistration { + override fun toString(): String = redactedString(this) +} + +sealed interface ClientRegistrationCacheKey { + val region: String +} + +// only applicable in scoped registration path +// based on internal development branch @da780a4,L2574-2586 +data class DeviceAuthorizationClientRegistrationCacheKey( + val startUrl: String, + val scopes: List, + override val region: String, +) : ClientRegistrationCacheKey + +data class PKCEClientRegistrationCacheKey( + val issuerUrl: String, + override val region: String, + val scopes: List, + // assume clientType, grantTypes, redirectUris are static, but throw them in just in case + val clientType: String, + val grantTypes: List, + val redirectUris: List, +) : ClientRegistrationCacheKey diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt new file mode 100644 index 00000000000..5af117bc456 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt @@ -0,0 +1,322 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.BeanProperty +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.JavaType +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +import com.fasterxml.jackson.databind.util.Converter +import com.fasterxml.jackson.databind.util.StdConverter +import com.fasterxml.jackson.databind.util.StdDateFormat +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import software.amazon.q.jetbrains.services.telemetry.scrubNames +import software.amazon.q.core.utils.createParentDirectories +import software.amazon.q.core.utils.deleteIfExists +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.inputStreamIfExists +import software.amazon.q.core.utils.outputStream +import software.amazon.q.core.utils.toHexString +import software.amazon.q.core.utils.touch +import software.amazon.q.core.utils.tryDirOp +import software.amazon.q.core.utils.tryFileOp +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.telemetry.AuthTelemetry +import software.aws.toolkits.telemetry.Result +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.Path +import java.nio.file.Paths +import java.security.MessageDigest +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter.ISO_INSTANT +import java.util.TimeZone + +/** + * Caches the [AccessToken] to disk to allow it to be re-used with other tools such as the CLI. + */ +class DiskCache( + private val cacheDir: Path = Paths.get(System.getProperty("user.home"), ".aws", "sso", "cache"), + private val clock: Clock = Clock.systemUTC(), +) : SsoCache { + private val objectMapper = jacksonObjectMapper().also { + it.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + + it.registerModule(JavaTimeModule()) + val customDateModule = SimpleModule() + customDateModule.addDeserializer(Instant::class.java, CliCompatibleInstantDeserializer()) + it.registerModule(customDateModule) // Override the Instant deserializer with custom one + it.dateFormat = StdDateFormat().withTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)) + } + + // only used for computing cache key names + private val cacheNameMapper = jacksonObjectMapper() + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .apply { + // giant dance to automatically sort lists during serialization for deterministic cache keys + val defaultSerializers = serializerProviderInstance + + class SortedSerializer : StdConverter>, List>>() { + override fun convert(list: List>): List> = + list.sorted() + } + + class DelegatingSerializer : StdDelegatingSerializer(SortedSerializer()) { + override fun withDelegate( + converter: Converter?, + delegateType: JavaType?, + delegateSerializer: JsonSerializer<*>?, + ): StdDelegatingSerializer = + StdDelegatingSerializer(converter, delegateType, delegateSerializer) + + override fun createContextual(provider: SerializerProvider?, property: BeanProperty?): JsonSerializer<*> = + // break infinite recursion so we only apply the SortedSerializer once + super.createContextual(defaultSerializers, property) + } + + registerModule( + SimpleModule().apply { + addSerializer(List::class.java, DelegatingSerializer()) + } + ) + } + + override fun invalidateClientRegistration(ssoRegion: String) { + LOG.info { "invalidateClientRegistration for $ssoRegion" } + clientRegistrationCache(ssoRegion).tryDeleteIfExists() + } + + override fun loadClientRegistration(cacheKey: ClientRegistrationCacheKey, source: String): ClientRegistration? { + LOG.info { "loadClientRegistration:$source for $cacheKey" } + val inputStream = clientRegistrationCache(cacheKey).tryInputStreamIfExists() + if (inputStream == null) { + val stage = LoadCredentialStage.ACCESS_FILE + LOG.info { "Failed to load Client Registration: cache file does not exist" } + AuthTelemetry.modifyConnection( + action = "Load cache file", + source = "loadClientRegistration:$source", + result = Result.Failed, + reason = "Failed to load Client Registration", + reasonDesc = "Load Step:$stage failed. Cache file does not exist" + ) + return null + } + return loadClientRegistration(inputStream) + } + + override fun saveClientRegistration(cacheKey: ClientRegistrationCacheKey, registration: ClientRegistration) { + LOG.info { "saveClientRegistration for $cacheKey" } + val registrationCache = clientRegistrationCache(cacheKey) + writeKey(registrationCache) { + objectMapper.writeValue(it, registration) + } + } + + override fun invalidateClientRegistration(cacheKey: ClientRegistrationCacheKey) { + LOG.info { "invalidateClientRegistration for $cacheKey" } + try { + clientRegistrationCache(cacheKey).tryDeleteIfExists() + } catch (e: Exception) { + AuthTelemetry.modifyConnection( + action = "Delete cache file", + source = "invalidateClientRegistration", + result = Result.Failed, + reason = "Failed to invalidate Client Registration", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name + ) + throw e + } + } + + override fun invalidateAccessToken(ssoUrl: String) { + LOG.info { "invalidateAccessToken for $ssoUrl" } + try { + accessTokenCache(ssoUrl).tryDeleteIfExists() + } catch (e: Exception) { + AuthTelemetry.modifyConnection( + action = "Delete cache file", + source = "invalidateAccessToken", + result = Result.Failed, + reason = "Failed to invalidate Access Token", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name + ) + throw e + } + } + + override fun loadAccessToken(cacheKey: AccessTokenCacheKey): AccessToken? { + LOG.info { "loadAccessToken for $cacheKey" } + val cacheFile = accessTokenCache(cacheKey) + val inputStream = cacheFile.tryInputStreamIfExists() ?: return null + + val token = loadAccessToken(inputStream) + + return token + } + + override fun saveAccessToken(cacheKey: AccessTokenCacheKey, accessToken: AccessToken) { + LOG.info { "saveAccessToken for $cacheKey" } + val accessTokenCache = accessTokenCache(cacheKey) + writeKey(accessTokenCache) { + objectMapper.writeValue(it, accessToken) + } + } + + override fun invalidateAccessToken(cacheKey: AccessTokenCacheKey) { + LOG.info { "invalidateAccessToken for $cacheKey" } + try { + accessTokenCache(cacheKey).tryDeleteIfExists() + } catch (e: Exception) { + AuthTelemetry.modifyConnection( + action = "Delete cache file", + source = "invalidateAccessToken", + result = Result.Failed, + reason = "Failed to invalidate Access Token", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name + ) + throw e + } + } + + private fun clientRegistrationCache(ssoRegion: String): Path = cacheDir.resolve("aws-toolkit-jetbrains-client-id-$ssoRegion.json") + + private fun clientRegistrationCache(cacheKey: ClientRegistrationCacheKey): Path = + cacheNameMapper.valueToTree(cacheKey).apply { + // session is omitted to keep the key deterministic since we attach an epoch + put("tool", "aws-toolkit-jetbrains") + }.let { + val sha = sha1(cacheNameMapper.writeValueAsString(it)) + + cacheDir.resolve("$sha.json").also { + LOG.info { "$cacheKey resolves to $it" } + } + } + + private fun accessTokenCache(ssoUrl: String): Path { + val fileName = "${sha1(ssoUrl)}.json" + return cacheDir.resolve(fileName) + } + + private fun accessTokenCache(cacheKey: AccessTokenCacheKey): Path { + val fileName = "${sha1(cacheNameMapper.writeValueAsString(cacheKey))}.json" + return cacheDir.resolve(fileName) + } + + private fun loadClientRegistration(inputStream: InputStream): ClientRegistration? { + var stage = LoadCredentialStage.VALIDATE_CREDENTIALS + try { + val clientRegistration = objectMapper.readValue(inputStream) + stage = LoadCredentialStage.CHECK_EXPIRATION + if (clientRegistration.expiresAt.isNotExpired()) { + return clientRegistration + } else { + LOG.info { "Client Registration is expired" } + AuthTelemetry.modifyConnection( + action = "Validate Credentials", + source = "loadClientRegistration", + result = Result.Failed, + reason = "Failed to load Client Registration", + reasonDesc = "Load Step:$stage failed: Client Registration is expired" + ) + return null + } + } catch (e: Exception) { + LOG.info { "Client Registration could not be read" } + AuthTelemetry.modifyConnection( + action = "Validate Credentials", + source = "loadClientRegistration", + result = Result.Failed, + reason = "Failed to load Client Registration", + reasonDesc = "Load Step:$stage failed: File could not be read" + ) + return null + } + } + + private fun loadAccessToken(inputStream: InputStream) = tryOrNull { + val accessToken = objectMapper.readValue(inputStream) + // Use same expiration logic as client registration even though RFC/SEP does not specify it. + // This prevents a cache entry being returned as valid and then expired when we go to use it. + if (!accessToken.isDefinitelyExpired()) { + accessToken + } else { + null + } + } + + private fun Path.tryDeleteIfExists(): Boolean = tryFileOp(LOG) { deleteIfExists() } + + private fun Path.tryInputStreamIfExists(): InputStream? = tryFileOp(LOG) { inputStreamIfExists() } + + private fun sha1(string: String): String { + val digest = MessageDigest.getInstance("SHA-1") + return digest.digest(string.toByteArray(Charsets.UTF_8)).toHexString() + } + + private fun writeKey(path: Path, consumer: (OutputStream) -> Unit) { + LOG.info { "writing to $path" } + try { + path.tryDirOp(LOG) { createParentDirectories() } + + path.tryFileOp(LOG) { + touch(restrictToOwner = true) + outputStream().use(consumer) + } + } catch (e: Exception) { + AuthTelemetry.modifyConnection( + action = "Write file", + source = "writeKey", + result = Result.Failed, + reason = "Failed to write to cache", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name + ) + throw e + } + } + + // If the item is going to expire in the next 15 mins, we must treat it as already expired + private fun Instant.isNotExpired(): Boolean = this.isAfter(Instant.now(clock).plus(EXPIRATION_THRESHOLD)) + + private fun AccessToken.isDefinitelyExpired(): Boolean = refreshToken == null && !expiresAt.isNotExpired() + + private class CliCompatibleInstantDeserializer : StdDeserializer(Instant::class.java) { + override fun deserialize(parser: JsonParser, context: DeserializationContext): Instant { + val dateString = parser.valueAsString + + // CLI appends UTC, which Java refuses to parse. Convert it to a Z + val sanitized = if (dateString.endsWith("UTC")) { + dateString.dropLast(3) + 'Z' + } else { + dateString + } + + return ISO_INSTANT.parse(sanitized) { Instant.from(it) } + } + } + + private enum class LoadCredentialStage { + ACCESS_FILE, + VALIDATE_CREDENTIALS, + CHECK_EXPIRATION, + } + + companion object { + val EXPIRATION_THRESHOLD = Duration.ofMinutes(15) + private val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt new file mode 100644 index 00000000000..4cc16623f66 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt @@ -0,0 +1,636 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.intellij.openapi.components.service +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.util.registry.Registry +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.awscore.exception.AwsServiceException +import software.amazon.awssdk.core.exception.SdkServiceException +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.services.ssooidc.model.AuthorizationPendingException +import software.amazon.awssdk.services.ssooidc.model.CreateTokenResponse +import software.amazon.awssdk.services.ssooidc.model.InvalidClientException +import software.amazon.awssdk.services.ssooidc.model.InvalidRequestException +import software.amazon.awssdk.services.ssooidc.model.SlowDownException +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.pkce.PKCE_CLIENT_NAME +import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService +import software.amazon.q.jetbrains.core.webview.getAuthType +import software.amazon.q.jetbrains.services.telemetry.scrubNames +import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread +import software.amazon.q.jetbrains.utils.sleepWithCancellation +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.AuthTelemetry +import software.aws.toolkits.telemetry.AuthType +import software.aws.toolkits.telemetry.AwsTelemetry +import software.aws.toolkits.telemetry.CredentialSourceId +import software.aws.toolkits.telemetry.Result +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.util.concurrent.CompletableFuture +import java.util.concurrent.atomic.AtomicReference + +sealed interface PendingAuthorization { + val progressIndicator: ProgressIndicator + + data class DAGAuthorization(val authorization: Authorization, override val progressIndicator: ProgressIndicator) : PendingAuthorization + data class PKCEAuthorization(val future: CompletableFuture<*>, override val progressIndicator: ProgressIndicator) : PendingAuthorization +} + +abstract class SsoAccessTokenCacheAccessor { + protected abstract val cache: SsoCache + protected abstract val ssoUrl: String + protected abstract val ssoRegion: String + protected abstract val scopes: List + + protected val dagClientRegistrationCacheKey by lazy { + DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + } + + protected val pkceClientRegistrationCacheKey by lazy { + PKCEClientRegistrationCacheKey( + issuerUrl = ssoUrl, + region = ssoRegion, + scopes = scopes, + clientType = PUBLIC_CLIENT_REGISTRATION_TYPE, + grantTypes = PKCE_GRANT_TYPES, + redirectUris = PKCE_REDIRECT_URIS + ) + } + + protected val dagAccessTokenCacheKey by lazy { + DeviceGrantAccessTokenCacheKey( + connectionId = ssoRegion, + startUrl = ssoUrl, + scopes = scopes + ) + } + + protected val pkceAccessTokenCacheKey by lazy { + PKCEAccessTokenCacheKey( + issuerUrl = ssoUrl, + region = ssoRegion, + scopes = scopes + ) + } + + protected val isNewAuthPkce: Boolean + get() = !Registry.`is`("aws.dev.useDAG", false) + + internal fun loadAccessToken(): AccessToken? { + // load DAG if exists, otherwise PKCE + cache.loadAccessToken(dagAccessTokenCacheKey)?.let { + return it + } + + if (isNewAuthPkce) { + // don't check existence of PKCE if we're not planning on starting flows with PKCE + cache.loadAccessToken(pkceAccessTokenCacheKey)?.let { + return it + } + } + + return null + } + + fun invalidate() { + cache.invalidateAccessToken(dagAccessTokenCacheKey) + cache.invalidateAccessToken(pkceAccessTokenCacheKey) + } + + internal companion object { + const val PUBLIC_CLIENT_REGISTRATION_TYPE = "public" + const val DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code" + const val REFRESH_GRANT_TYPE = "refresh_token" + val PKCE_GRANT_TYPES = listOf("authorization_code", "refresh_token") + val PKCE_REDIRECT_URIS = listOf("http://127.0.0.1/oauth/callback") + } +} + +/** + * Handles cases where we need to check for the existance of a token, but do not want interactivity + */ +class LazyAccessTokenProvider( + override val cache: SsoCache, + override val ssoUrl: String, + override val ssoRegion: String, + override val scopes: List, +) : SsoAccessTokenCacheAccessor(), SdkTokenProvider { + override fun resolveToken() = loadAccessToken() +} + +/** + * Takes care of creating/refreshing the SSO access token required to fetch SSO-based credentials. + */ +class SsoAccessTokenProvider( + override val ssoUrl: String, + override val ssoRegion: String, + override val cache: SsoCache, + private val client: SsoOidcClient, + private val isAlwaysShowDeviceCode: Boolean = false, + override val scopes: List = emptyList(), + private val clock: Clock = Clock.systemUTC(), +) : SsoAccessTokenCacheAccessor(), SdkTokenProvider { + init { + check(scopes.isNotEmpty()) { "Scopes should not be empty" } + // identity does not want us to use the scope-less path + cache.invalidateClientRegistration(ssoRegion) + cache.invalidateAccessToken(ssoUrl) + } + + private val _authorization = AtomicReference() + val authorization: PendingAuthorization? + get() = _authorization.get() + + override fun resolveToken() = accessToken() + + fun accessToken(): AccessToken { + assertIsNonDispatchThread() + + loadAccessToken()?.let { + return it + } + + val token = if (getAuthType(ssoRegion) == AuthType.PKCE) { + pollForPkceToken() + } else { + pollForDAGToken() + } + + try { + saveAccessToken(token) + AuthTelemetry.modifyConnection( + action = "Write file", + source = "accessToken", + result = Result.Succeeded + ) + } catch (e: Exception) { + LOG.warn { "Failed to save access token ${e.message}" } + AuthTelemetry.modifyConnection( + action = "Write file", + source = "accessToken", + result = Result.Failed, + reason = "Failed to write AccessToken to cache", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name, + ) + throw e + } + + return token + } + + @Deprecated("Device authorization grant flow is deprecated") + private fun registerDAGClient(): ClientRegistration { + loadDagClientRegistration(SourceOfLoadRegistration.REGISTER_CLIENT)?.let { + return it + } + + // Based on botocore: https://github.com/boto/botocore/blob/5dc8ee27415dc97cfff75b5bcfa66d410424e665/botocore/utils.py#L1753 + val registerResponse = client.registerClient { + it.clientType(PUBLIC_CLIENT_REGISTRATION_TYPE) + it.scopes(scopes) + it.clientName(PKCE_CLIENT_NAME) + } + + val registeredClient = DeviceAuthorizationClientRegistration( + registerResponse.clientId(), + registerResponse.clientSecret(), + Instant.ofEpochSecond(registerResponse.clientSecretExpiresAt()), + scopes + ) + + try { + saveClientRegistration(registeredClient) + AuthTelemetry.modifyConnection( + action = "Write file", + source = "registerDAGClient", + result = Result.Succeeded + ) + } catch (e: Exception) { + LOG.warn { "Failed to save client registration ${e.message}" } + AuthTelemetry.modifyConnection( + action = "Write file", + source = "registerDAGClient", + result = Result.Failed, + reason = "Failed to write DeviceAuthorizationClientRegistration to cache", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name + ) + throw e + } + + return registeredClient + } + + private fun registerPkceClient(): PKCEClientRegistration { + loadPkceClientRegistration(SourceOfLoadRegistration.REGISTER_CLIENT)?.let { + return it + } + + if (!ssoUrl.contains("identitycenter")) { + LOG.warn { "$ssoUrl does not appear to be a valid issuer URL" } + } + + val registerResponse = client.registerClient { + it.clientName(PKCE_CLIENT_NAME) + it.clientType(PUBLIC_CLIENT_REGISTRATION_TYPE) + it.scopes(scopes) + it.grantTypes(PKCE_GRANT_TYPES) + it.redirectUris(PKCE_REDIRECT_URIS) + it.issuerUrl(ssoUrl) + } + + val registeredClient = PKCEClientRegistration( + clientId = registerResponse.clientId(), + clientSecret = registerResponse.clientSecret(), + expiresAt = Instant.ofEpochSecond(registerResponse.clientSecretExpiresAt()), + scopes = scopes, + issuerUrl = ssoUrl, + region = ssoRegion, + clientType = PUBLIC_CLIENT_REGISTRATION_TYPE, + grantTypes = PKCE_GRANT_TYPES, + redirectUris = PKCE_REDIRECT_URIS + ) + + try { + saveClientRegistration(registeredClient) + AuthTelemetry.modifyConnection( + action = "Write file", + source = "registerPkceClient", + result = Result.Succeeded + ) + } catch (e: Exception) { + LOG.warn { "Failed to save client registration${e.message}" } + AuthTelemetry.modifyConnection( + action = "Write file", + source = "registerPkceClient", + result = Result.Failed, + reason = "Failed to write PKCEClientRegistration to cache", + reasonDesc = e.message?.let { scrubNames(it) } ?: e::class.java.name + ) + throw e + } + + return registeredClient + } + + @Deprecated("Device authorization grant flow is deprecated") + private fun authorizeDAGClient(clientId: ClientRegistration): Authorization { + // Should not be cached, only good for 1 token and short lived + val authorizationResponse = try { + client.startDeviceAuthorization { + it.startUrl(ssoUrl) + it.clientId(clientId.clientId) + it.clientSecret(clientId.clientSecret) + } + } catch (e: InvalidClientException) { + invalidateClientRegistration() + throw e + } + + val createTime = Instant.now(clock) + + return Authorization( + authorizationResponse.deviceCode(), + authorizationResponse.userCode(), + authorizationResponse.verificationUri(), + authorizationResponse.verificationUriComplete(), + createTime.plusSeconds(authorizationResponse.expiresIn().toLong()), + authorizationResponse.interval()?.toLong() + ?: DEFAULT_INTERVAL_SECS, + createTime, + ) + } + + private fun progressIndicator() = + ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator() + + @Deprecated("Device authorization grant flow is deprecated") + private fun pollForDAGToken(): AccessToken { + val onPendingToken = service().getProvider(isAlwaysShowDeviceCode, ssoUrl) + val progressIndicator = progressIndicator() + val registration = registerDAGClient() + val authorization = authorizeDAGClient(registration) + + progressIndicator.text2 = AwsCoreBundle.message("aws.sso.signing.device.waiting", authorization.userCode) + _authorization.set(PendingAuthorization.DAGAuthorization(authorization, progressIndicator)) + + onPendingToken.tokenPending(authorization) + + var backOffTime = Duration.ofSeconds(authorization.pollInterval) + + while (true) { + try { + if (_authorization.get() == null || progressIndicator.isCanceled()) { + _authorization.set(null) + throw ProcessCanceledException(IllegalStateException("Login canceled by user")) + } + + val startTime = clock.instant() + val tokenResponse = try { + client.createToken { + it.clientId(registration.clientId) + it.clientSecret(registration.clientSecret) + it.grantType(DEVICE_GRANT_TYPE) + it.deviceCode(authorization.deviceCode) + }.also { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Succeeded, + grantType = DEVICE_GRANT_TYPE, + duration = duration + ) + LOG.info { "SSO token operation succeeded: grantType=$DEVICE_GRANT_TYPE, duration=${duration}ms" } + } + } catch (e: Exception) { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Failed, + grantType = DEVICE_GRANT_TYPE, + duration = duration, + reason = e::class.simpleName, + reasonDesc = e.message?.let { scrubNames(it) }, + httpStatusCode = (e as? SdkServiceException)?.statusCode()?.toString() + ) + LOG.warn { "SSO token operation failed: grantType=$DEVICE_GRANT_TYPE, duration=${duration}ms, error=${e::class.simpleName}" } + throw e + } + + onPendingToken.tokenRetrieved() + _authorization.set(null) + + return tokenResponse.toDAGAccessToken(authorization.createdAt) + } catch (e: SlowDownException) { + backOffTime = backOffTime.plusSeconds(SLOW_DOWN_DELAY_SECS) + } catch (e: AuthorizationPendingException) { + // Do nothing, keep polling + } catch (e: ProcessCanceledException) { + // Don't want to notify this in tokenRetrievalFailure + throw e + } catch (e: Exception) { + onPendingToken.tokenRetrievalFailure(e) + throw e + } + + try { + sleepWithCancellation(backOffTime, progressIndicator) + } catch (e: ProcessCanceledException) { + _authorization.set(null) + throw ProcessCanceledException(IllegalStateException("Login canceled by user")) + } + } + } + + private fun pollForPkceToken(): AccessToken { + val future = ToolkitOAuthService.getInstance().authorize(registerPkceClient()) + val progressIndicator = progressIndicator() + _authorization.set(PendingAuthorization.PKCEAuthorization(future, progressIndicator)) + + while (true) { + if (future.isDone) { + _authorization.set(null) + return future.get() + } + + try { + sleepWithCancellation(Duration.ofMillis(100), progressIndicator) + } catch (e: ProcessCanceledException) { + future.cancel(true) + _authorization.set(null) + throw ProcessCanceledException(IllegalStateException(AwsCoreBundle.message("credentials.pending.user_cancel.message"))) + } + } + } + + private fun sendRefreshCredentialsMetric( + currentToken: AccessToken, + reason: String?, + reasonDesc: String?, + requestId: String? = null, + result: Result, + ) { + val tokenCreationTime = currentToken.createdAt + val sessionDuration = Duration.between(tokenCreationTime, Instant.now(clock)) + val credentialSourceId = if (currentToken.ssoUrl == SONO_URL) CredentialSourceId.AwsId else CredentialSourceId.IamIdentityCenter + + if (tokenCreationTime != Instant.EPOCH) { + AwsTelemetry.refreshCredentials( + project = null, + result = result, + sessionDuration = sessionDuration.toMillis(), + credentialSourceId = credentialSourceId, + reason = reason, + reasonDesc = reasonDesc, + requestId = requestId + ) + } + } + + fun refreshToken(currentToken: AccessToken): AccessToken { + var stageName = RefreshCredentialStage.VALIDATE_REFRESH_TOKEN + if (currentToken.refreshToken == null) { + val message = "Requested token refresh, but refresh token was null" + sendRefreshCredentialsMetric( + currentToken, + reason = "Refresh access token request failed: $stageName", + reasonDesc = message, + result = Result.Failed + ) + throw InvalidRequestException.builder().message(message).build() + } + + stageName = RefreshCredentialStage.LOAD_REGISTRATION + val registration = try { + when (currentToken) { + is DeviceAuthorizationGrantToken -> loadDagClientRegistration(SourceOfLoadRegistration.REFRESH_TOKEN) + is PKCEAuthorizationGrantToken -> loadPkceClientRegistration(SourceOfLoadRegistration.REFRESH_TOKEN) + } + } catch (e: Exception) { + val message = e.message ?: "$stageName: ${e::class.java.name}" + sendRefreshCredentialsMetric( + currentToken, + reason = "Refresh access token request failed: $stageName", + reasonDesc = message, + result = Result.Failed + ) + throw InvalidClientException.builder().message(message).cause(e).build() + } + + stageName = RefreshCredentialStage.VALIDATE_REGISTRATION + if (registration == null) { + val message = "Unable to load client registration from cache" + sendRefreshCredentialsMetric( + currentToken, + reason = "Refresh access token request failed: $stageName", + reasonDesc = message, + result = Result.Failed + ) + throw InvalidClientException.builder().message(message).build() + } + + stageName = RefreshCredentialStage.CREATE_TOKEN + try { + val startTime = clock.instant() + val newToken = try { + client.createToken { + it.clientId(registration.clientId) + it.clientSecret(registration.clientSecret) + it.grantType(REFRESH_GRANT_TYPE) + it.refreshToken(currentToken.refreshToken) + }.also { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Succeeded, + grantType = REFRESH_GRANT_TYPE, + duration = duration + ) + LOG.info { "SSO token operation succeeded: grantType=$REFRESH_GRANT_TYPE, duration=${duration}ms" } + } + } catch (e: Exception) { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Failed, + grantType = REFRESH_GRANT_TYPE, + duration = duration, + reason = e::class.simpleName, + reasonDesc = e.message?.let { scrubNames(it) }, + httpStatusCode = (e as? SdkServiceException)?.statusCode()?.toString() + ) + LOG.warn { "SSO token operation failed: grantType=$REFRESH_GRANT_TYPE, duration=${duration}ms, error=${e::class.simpleName}" } + throw e + } + + stageName = RefreshCredentialStage.GET_TOKEN_DETAILS + val token = when (currentToken) { + is DeviceAuthorizationGrantToken -> newToken.toDAGAccessToken(currentToken.createdAt) + is PKCEAuthorizationGrantToken -> newToken.toPKCEAccessToken(currentToken.createdAt) + } + + stageName = RefreshCredentialStage.SAVE_TOKEN + saveAccessToken(token) + + sendRefreshCredentialsMetric( + currentToken, + result = Result.Succeeded, + reason = null, + reasonDesc = null + ) + + return token + } catch (e: Exception) { + val requestId = when (e) { + is AwsServiceException -> e.requestId() + else -> null + } + // AwsServiceException#message will automatically pull in AwsServiceException#awsErrorDetails + // we expect messages for SsoOidcException to be populated in e.message using execution executor added in + // https://github.com/aws/aws-toolkit-jetbrains/commit/cc9ed87fa9391dd39ac05cbf99b4437112fa3d10 + val message = e.message ?: "$stageName: ${e::class.java.name}" + + sendRefreshCredentialsMetric( + currentToken, + reason = "Refresh access token request failed: $stageName", + reasonDesc = message, + requestId = requestId, + result = Result.Failed + ) + LOG.info { "RefreshAccessTokenFailed: ${e.message}" } + throw e + } + } + + enum class SourceOfLoadRegistration { + REGISTER_CLIENT, + REFRESH_TOKEN, + } + + private enum class RefreshCredentialStage { + VALIDATE_REFRESH_TOKEN, + LOAD_REGISTRATION, + VALIDATE_REGISTRATION, + CREATE_TOKEN, + GET_TOKEN_DETAILS, + SAVE_TOKEN, + } + + private fun loadDagClientRegistration(source: SourceOfLoadRegistration): ClientRegistration? = + cache.loadClientRegistration(dagClientRegistrationCacheKey, source.toString())?.let { + return it + } + + private fun loadPkceClientRegistration(source: SourceOfLoadRegistration): PKCEClientRegistration? = + cache.loadClientRegistration(pkceClientRegistrationCacheKey, source.toString())?.let { + return it as PKCEClientRegistration + } + + private fun saveClientRegistration(registration: ClientRegistration) { + when (registration) { + is DeviceAuthorizationClientRegistration -> { + cache.saveClientRegistration(dagClientRegistrationCacheKey, registration) + } + is PKCEClientRegistration -> { + cache.saveClientRegistration(pkceClientRegistrationCacheKey, registration) + } + } + } + + private fun invalidateClientRegistration() { + cache.invalidateClientRegistration(dagClientRegistrationCacheKey) + cache.invalidateClientRegistration(pkceClientRegistrationCacheKey) + } + + private fun saveAccessToken(token: AccessToken) { + when (token) { + is DeviceAuthorizationGrantToken -> { + cache.saveAccessToken(dagAccessTokenCacheKey, token) + } + is PKCEAuthorizationGrantToken -> cache.saveAccessToken(pkceAccessTokenCacheKey, token) + } + } + + private fun CreateTokenResponse.toDAGAccessToken(creationTime: Instant): DeviceAuthorizationGrantToken { + val expirationTime = Instant.now(clock).plusSeconds(expiresIn().toLong()) + + return DeviceAuthorizationGrantToken( + startUrl = ssoUrl, + region = ssoRegion, + accessToken = accessToken(), + refreshToken = refreshToken(), + expiresAt = expirationTime, + createdAt = creationTime + ) + } + + private fun CreateTokenResponse.toPKCEAccessToken(creationTime: Instant): PKCEAuthorizationGrantToken { + val expirationTime = Instant.now(clock).plusSeconds(expiresIn().toLong()) + + return PKCEAuthorizationGrantToken( + issuerUrl = ssoUrl, + region = ssoRegion, + accessToken = accessToken(), + refreshToken = refreshToken(), + expiresAt = expirationTime, + createdAt = creationTime + ) + } + + private companion object { + // Default number of seconds to poll for token, https://tools.ietf.org/html/draft-ietf-oauth-device-flow-15#section-3.5 + const val DEFAULT_INTERVAL_SECS = 5L + const val SLOW_DOWN_DELAY_SECS = 5L + private val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCache.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCache.kt new file mode 100644 index 00000000000..532a24cecbe --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCache.kt @@ -0,0 +1,17 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +interface SsoCache { + fun invalidateClientRegistration(ssoRegion: String) + fun invalidateAccessToken(ssoUrl: String) + + fun loadClientRegistration(cacheKey: ClientRegistrationCacheKey, source: String): ClientRegistration? + fun saveClientRegistration(cacheKey: ClientRegistrationCacheKey, registration: ClientRegistration) + fun invalidateClientRegistration(cacheKey: ClientRegistrationCacheKey) + + fun loadAccessToken(cacheKey: AccessTokenCacheKey): AccessToken? + fun saveAccessToken(cacheKey: AccessTokenCacheKey, accessToken: AccessToken) + fun invalidateAccessToken(cacheKey: AccessTokenCacheKey) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProvider.kt new file mode 100644 index 00000000000..a410fcbca0b --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProvider.kt @@ -0,0 +1,77 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +@file:Suppress("BannedImports") + +package software.amazon.q.jetbrains.core.credentials.sso + +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.sso.model.UnauthorizedException +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.awssdk.utils.cache.CachedSupplier +import software.amazon.awssdk.utils.cache.RefreshResult +import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread +import java.time.Duration +import java.time.Instant + +/** + * [AwsCredentialsProvider] that contains all the needed hooks to perform an end to end flow of an SSO-based credential. + * + * This credential provider will trigger an SSO login if required, unlike the low level SDKs. + */ +class SsoCredentialProvider( + private val ssoAccount: String, + private val ssoRole: String, + private val ssoClient: SsoClient, + private val ssoAccessTokenProvider: SdkTokenProvider, +) : AwsCredentialsProvider, SdkAutoCloseable { + private val sessionCache: CachedSupplier = CachedSupplier.builder(this::refreshCredentials).build() + + override fun resolveCredentials(): AwsCredentials = sessionCache.get().credentials + + private fun refreshCredentials(): RefreshResult { + assertIsNonDispatchThread() + + val roleCredentials = try { + val accessToken = ssoAccessTokenProvider.resolveToken() + + ssoClient.getRoleCredentials { + it.accessToken(accessToken.token()) + it.accountId(ssoAccount) + it.roleName(ssoRole) + } + } catch (e: UnauthorizedException) { + // OIDC access token was rejected, invalidate the cache if applicable and throw + if (ssoAccessTokenProvider is SsoAccessTokenProvider) { + ssoAccessTokenProvider.invalidate() + } + + throw e + } + + val awsCredentials = AwsSessionCredentials.create( + roleCredentials.roleCredentials().accessKeyId(), + roleCredentials.roleCredentials().secretAccessKey(), + roleCredentials.roleCredentials().sessionToken() + ) + + val expirationTime = Instant.ofEpochMilli(roleCredentials.roleCredentials().expiration()) + + val ssoCredentials = SsoCredentialsHolder(awsCredentials, expirationTime) + + return RefreshResult.builder(ssoCredentials) + .staleTime(expirationTime.minus(Duration.ofMinutes(1))) + .prefetchTime(expirationTime.minus(Duration.ofMinutes(5))) + .build() + } + + override fun close() { + sessionCache.close() + } + + private data class SsoCredentialsHolder(val credentials: AwsSessionCredentials, val expirationTime: Instant) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallback.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallback.kt new file mode 100644 index 00000000000..d3e72485fcd --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallback.kt @@ -0,0 +1,24 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +/** + * Callback interface to allow for UI elements to react to the different stages of the SSO login flow + */ +interface SsoLoginCallback { + /** + * Called when a new authorization is pending within SSO service. User should be notified so they can perform the login flow. + */ + fun tokenPending(authorization: Authorization) + + /** + * Called when the user successfully logs into the SSO service. + */ + fun tokenRetrieved() + + /** + * Called when the SSO login fails + */ + fun tokenRetrievalFailure(e: Exception) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt new file mode 100644 index 00000000000..b31c30917d6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt @@ -0,0 +1,139 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.intellij.ide.BrowserUtil +import com.intellij.openapi.progress.ProcessCanceledException +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.bearer.ConfirmUserCodeLoginDialog +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.utils.computeOnEdt +import software.amazon.q.jetbrains.utils.isQWebviewsAvailable +import software.amazon.q.jetbrains.utils.notifyError +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.AuthType +import software.aws.toolkits.telemetry.AwsTelemetry +import software.aws.toolkits.telemetry.CredentialType +import software.aws.toolkits.telemetry.Result + +typealias SsoLoginCallbackProvider = migration.software.amazon.q.jetbrains.core.credentials.sso.SsoLoginCallbackProvider + +class DefaultSsoLoginCallbackProvider : SsoLoginCallbackProvider { + override fun getProvider(isAlwaysShowDeviceCode: Boolean, ssoUrl: String): SsoLoginCallback { + val deviceCodeProvider = if (ssoUrl == SONO_URL) { + DefaultBearerTokenPrompt + } else { + DefaultSsoPrompt + } + + if (isAlwaysShowDeviceCode) { + return deviceCodeProvider + } + + return when { + isQWebviewsAvailable() -> SsoPromptWithBrowserSupport + else -> deviceCodeProvider + } + } +} + +interface SsoPrompt : SsoLoginCallback { + override fun tokenRetrieved() { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Succeeded, + credentialType = CredentialType.SsoProfile, + authType = AuthType.DeviceCode, + source = SourceOfEntry.UNKNOWN.toString(), + ) + } + + override fun tokenRetrievalFailure(e: Exception) { + e.notifyError(AwsCoreBundle.message("credentials.sso.login.failed")) + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Failed, + credentialType = CredentialType.SsoProfile, + authType = AuthType.DeviceCode, + source = SourceOfEntry.UNKNOWN.toString(), + ) + } +} + +object DefaultSsoPrompt : SsoPrompt { + override fun tokenPending(authorization: Authorization) { + computeOnEdt { + val result = ConfirmUserCodeLoginDialog( + authorization.userCode, + AwsCoreBundle.message("credentials.sso.login.title"), + ).showAndGet() + + if (result) { + BrowserUtil.browse(authorization.verificationUriComplete) + } else { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Cancelled, + credentialType = CredentialType.SsoProfile, + authType = AuthType.DeviceCode, + source = SourceOfEntry.UNKNOWN.toString(), + ) + throw ProcessCanceledException(IllegalStateException(AwsCoreBundle.message("credentials.sso.login.cancelled"))) + } + } + } +} + +object SsoPromptWithBrowserSupport : SsoPrompt { + override fun tokenPending(authorization: Authorization) { + computeOnEdt { + BrowserUtil.browse(authorization.verificationUriComplete) + } + } +} + +interface BearerTokenPrompt : SsoLoginCallback { + override fun tokenRetrieved() { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Succeeded, + credentialType = CredentialType.BearerToken, + authType = AuthType.DeviceCode, + source = "", + ) + } + + override fun tokenRetrievalFailure(e: Exception) { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Failed, + credentialType = CredentialType.BearerToken, + authType = AuthType.DeviceCode, + source = "", + ) + } +} + +object DefaultBearerTokenPrompt : BearerTokenPrompt { + override fun tokenPending(authorization: Authorization) { + computeOnEdt { + val codeCopied = ConfirmUserCodeLoginDialog( + authorization.userCode, + AwsCoreBundle.message("credentials.sono.login"), + ).showAndGet() + + if (codeCopied) { + BrowserUtil.browse(authorization.verificationUriComplete) + } else { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Cancelled, + credentialType = CredentialType.BearerToken, + authType = AuthType.DeviceCode, + source = SourceOfEntry.UNKNOWN.toString(), + ) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt new file mode 100644 index 00000000000..ed013bcec9d --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt @@ -0,0 +1,323 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.util.containers.orNull +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkToken +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.core.interceptor.Context +import software.amazon.awssdk.core.interceptor.ExecutionAttributes +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.services.ssooidc.SsoOidcTokenProvider +import software.amazon.awssdk.services.ssooidc.internal.OnDiskTokenManager +import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.awssdk.utils.cache.CachedSupplier +import software.amazon.awssdk.utils.cache.NonBlocking +import software.amazon.awssdk.utils.cache.RefreshResult +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.diskCache +import software.amazon.q.jetbrains.core.credentials.sso.AccessToken +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.DiskCache +import software.amazon.q.jetbrains.core.credentials.sso.PendingAuthorization +import software.amazon.q.jetbrains.core.credentials.sso.SsoAccessTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener.Companion.TOPIC +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.clients.nullDefaultProfileFile +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitBearerTokenProviderDelegate +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import java.time.Clock +import java.time.Duration +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference +import java.util.function.Supplier + +internal interface BearerTokenLogoutSupport + +interface BearerTokenProvider : SdkTokenProvider, SdkAutoCloseable, ToolkitBearerTokenProviderDelegate { + /** + * @return The best available [SdkToken] to the provider without making network calls or prompting for user input + */ + fun currentToken(): AccessToken? + + /** + * @return The authentication state of [currentToken] + */ + fun state(): BearerTokenAuthState = state(currentToken()) + + /** + * Request provider to interactively request user input to obtain a new [AccessToken] + */ + fun reauthenticate() { + throw UnsupportedOperationException("Provider is not interactive and cannot reauthenticate") + } + + fun supportsLogout() = this is BearerTokenLogoutSupport + + fun invalidate() { + throw UnsupportedOperationException("Provider is not interactive and cannot be invalidated") + } + + companion object { + private fun tokenExpired(accessToken: AccessToken, clock: Clock) = clock.instant().isAfter(accessToken.expiresAt) + + internal fun state(accessToken: AccessToken?, clock: Clock = Clock.systemUTC()) = when { + accessToken == null -> BearerTokenAuthState.NOT_AUTHENTICATED + tokenExpired(accessToken, clock) -> { + if (accessToken.refreshToken != null) { + BearerTokenAuthState.NEEDS_REFRESH + } else { + // token is invalid if there is no refresh token + BearerTokenAuthState.NOT_AUTHENTICATED + } + } + else -> BearerTokenAuthState.AUTHORIZED + } + } +} + +class InteractiveBearerTokenProvider( + val startUrl: String, + val region: String, + val scopes: List, + override val id: String, + cache: DiskCache = diskCache, + private val clock: Clock = Clock.systemUTC(), +) : BearerTokenProvider, BearerTokenLogoutSupport, Disposable { + override val displayName = ToolkitBearerTokenProvider.ssoDisplayName(startUrl) + + private val ssoOidcClient: SsoOidcClient = buildUnmanagedSsoOidcClient(region) + private val accessTokenProvider = + SsoAccessTokenProvider( + startUrl, + region, + cache, + ssoOidcClient, + scopes = scopes + ) + + private var supplier = supplier() + + val pendingAuthorization: PendingAuthorization? + get() = accessTokenProvider.authorization + + init { + ApplicationManager.getApplication().messageBus.connect(this).subscribe( + TOPIC, + object : BearerTokenProviderListener { + override fun invalidate(providerId: String) { + if (id == providerId) { + invalidate() + } + } + + override fun onProviderChange(providerId: String, newScopes: List?) { + newScopes?.let { + if (id == providerId && it.toSet() != scopes.toSet()) { + invalidate() + } + } + } + } + ) + } + + private data class SupplierHolder( + val supplier: SupplierWithInitialValue, + val cachedSupplier: CachedSupplier, + ) + + private fun supplier(initialValue: AccessToken? = null) = + SupplierWithInitialValue(initialValue, accessTokenProvider).let { + SupplierHolder( + it, + CachedSupplier.builder(it).clock(clock).prefetchStrategy(NonBlocking("AWS SSO bearer token refresher")).build() + ) + } + + private inner class SupplierWithInitialValue( + initial: AccessToken?, + val accessTokenProvider: SsoAccessTokenProvider, + ) : Supplier> { + private val hasCalledAtLeastOnce = AtomicBoolean(false) + private val initialValue = initial ?: accessTokenProvider.loadAccessToken() + val lastToken = AtomicReference(initialValue) + + // we need to seed CachedSupplier with an initial value, then subsequent calls need to hit the network + override fun get(): RefreshResult { + val token = if (hasCalledAtLeastOnce.getAndSet(true)) { + refresh() + } else { + // on initial call, refresh if needed + if (initialValue != null && initialValue.expiresAt.minus(DEFAULT_PREFETCH_DURATION) < clock.instant()) { + refresh() + } else { + initialValue ?: throw NoTokenInitializedException("Token provider initialized with no token") + } + } + + return RefreshResult.builder(token) + .staleTime(token.expiresAt.minus(DEFAULT_STALE_DURATION)) + .prefetchTime(token.expiresAt.minus(DEFAULT_PREFETCH_DURATION)) + .build() + } + + fun refresh(): AccessToken { + val lastToken = lastToken.get() ?: throw NoTokenInitializedException("Token refresh started before session initialized") + return try { + accessTokenProvider.refreshToken(lastToken).also { + this.lastToken.set(it) + ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onTokenModified(id) + } + } catch (e: InvalidGrantException) { + LOG.warn { "Invalidated token due to $e" } + invalidate() + + throw e + } + } + } + + override fun state() = BearerTokenProvider.state(currentToken(), clock) + + // how we expect consumers to obtain a token + override fun resolveToken() = supplier.cachedSupplier.get() + + override fun close() { + ssoOidcClient.close() + supplier.cachedSupplier.close() + } + + override fun dispose() { + close() + } + + // internal nonsense so we can query the token without triggering a refresh + override fun currentToken() = supplier.supplier.lastToken.get() + + override fun invalidate() { + accessTokenProvider.invalidate() + supplier.cachedSupplier.close() + supplier = supplier() + BearerTokenProviderListener.notifyCredUpdate(id) + } + + override fun reauthenticate() { + // we probably don't need to invalidate this, but we might as well since we need to login again anyways + invalidate() + accessTokenProvider.accessToken().also { + supplier.cachedSupplier.close() + supplier = supplier(it) + BearerTokenProviderListener.notifyCredUpdate(id) + } + } + + companion object { + private val LOG = getLogger() + } +} + +class NoTokenInitializedException(message: String) : Exception(message) + +enum class BearerTokenAuthState { + AUTHORIZED, + NEEDS_REFRESH, + NOT_AUTHENTICATED, +} + +class ProfileSdkTokenProviderWrapper(private val sessionName: String, region: String) : BearerTokenProvider, Disposable { + override val id = ToolkitBearerTokenProvider.diskSessionIdentifier(sessionName) + override val displayName = ToolkitBearerTokenProvider.diskSessionDisplayName(sessionName) + + private val sdkTokenManager = OnDiskTokenManager.create(sessionName) + private val ssoOidcClient = lazy { buildUnmanagedSsoOidcClient(region) } + private val tokenProvider = lazy { + SsoOidcTokenProvider.builder() + .ssoOidcClient(ssoOidcClient.value) + .sessionName(sessionName) + .staleTime(DEFAULT_STALE_DURATION) + .prefetchTime(DEFAULT_PREFETCH_DURATION) + .build() + } + + override fun resolveToken(): SdkToken = tokenProvider.value.resolveToken() + + override fun currentToken(): AccessToken? = sdkTokenManager.loadToken().orNull()?.let { + DeviceAuthorizationGrantToken( + startUrl = it.startUrl(), + region = it.region(), + accessToken = it.token(), + refreshToken = it.refreshToken(), + expiresAt = it.expirationTime().orElseThrow() + ) + } + + override fun close() { + sdkTokenManager.close() + if (ssoOidcClient.isInitialized()) { + ssoOidcClient.value.close() + } + if (tokenProvider.isInitialized()) { + tokenProvider.value.close() + } + } + + override fun dispose() { + close() + } +} + +internal val DEFAULT_STALE_DURATION = Duration.ofMinutes(15) +internal val DEFAULT_PREFETCH_DURATION = Duration.ofMinutes(20) + +val ssoOidcClientConfigurationBuilder: (ClientOverrideConfiguration.Builder) -> ClientOverrideConfiguration.Builder = { configuration -> + configuration.nullDefaultProfileFile() + + configuration.addExecutionInterceptor(object : ExecutionInterceptor { + override fun modifyException(context: Context.FailedExecution, executionAttributes: ExecutionAttributes): Throwable { + val exception = context.exception() + if (exception !is SsoOidcException) { + return exception + } + + // SSO OIDC service generally has useful messages in the "errorDescription" field, but this is considered non-standard, + // so Java SDK does not find it and instead provides a generic default exception string + try { + val clazz = exception::class.java + val errorDescription = clazz.methods.firstOrNull { it.name == "errorDescription" }?.invoke(exception) as? String + ?: return exception + + // include the type of exception so we don't lose that information if we're only looking at the message and not the stack trace + val oidcError = clazz.methods.firstOrNull { it.name == "error" }?.invoke(exception) as? String + ?: exception.message?.substringBeforeLast('(')?.trimEnd() ?: clazz.name + + return exception.toBuilder().message("$oidcError: $errorDescription").build() + } catch (e: Exception) { + getLogger().warn(e) { "Encountered error while augmenting service error message" } + return exception + } + } + }) +} + +fun buildUnmanagedSsoOidcClient(region: String): SsoOidcClient = + AwsClientManager.getInstance() + .createUnmanagedClient( + AnonymousCredentialsProvider.create(), + Region.of(region), + clientCustomizer = ToolkitClientCustomizer { _, _, _, _, configuration -> + configuration.apiCallTimeout(Duration.ofSeconds(12)) + ssoOidcClientConfigurationBuilder(configuration) + } + ) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderListener.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderListener.kt new file mode 100644 index 00000000000..7b195893b30 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderListener.kt @@ -0,0 +1,34 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.util.messages.Topic +import java.util.EventListener + +interface BearerTokenProviderListener : EventListener { + /** + * Called when token permissions have potentially changed, or is no longer logged in + */ + fun onProviderChange(providerId: String, newScopes: List? = null) {} + + /** + * Called when token has changed but connection properties are the same + */ + fun onTokenModified(providerId: String) {} + + /** + * Called when provider is being deleted + */ + fun invalidate(providerId: String) {} + + companion object { + @Topic.AppLevel + val TOPIC = Topic.create("AWS SSO bearer token provider status change", BearerTokenProviderListener::class.java) + + fun notifyCredUpdate(providerId: String) { + ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onProviderChange(providerId) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt new file mode 100644 index 00000000000..5e25dc7308a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt @@ -0,0 +1,72 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.ActionToolbar +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.impl.ActionButton +import com.intellij.openapi.editor.colors.EditorColorsUtil +import com.intellij.openapi.ide.CopyPasteManager +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.components.JBLabel +import com.intellij.ui.dsl.builder.AlignX +import com.intellij.ui.dsl.builder.panel +import com.intellij.util.ui.JBFont +import com.intellij.util.ui.components.BorderLayoutPanel +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.tryOrNull +import java.awt.datatransfer.StringSelection +import javax.swing.JComponent + +class ConfirmUserCodeLoginDialog( + private val authCode: String, + dialogTitle: String, +) : DialogWrapper(null) { + + private val pane = panel { + row { + label(AwsCoreBundle.message("aws.sso.signing.device.code.copy.dialog.text")) + } + + row { + cell( + BorderLayoutPanel(5, 0).apply { + val action = CopyUserCodeForLogin(authCode) + addToCenter( + JBLabel(authCode).apply { + tryOrNull { + JBFont.create(JBFont.decode(EditorColorsUtil.getGlobalOrDefaultColorScheme().consoleFontName)).biggerOn(9f).asBold() + }?.let { + font = it + } + setCopyable(true) + } + ) + addToRight(ActionButton(action, action.templatePresentation.clone(), ActionPlaces.UNKNOWN, ActionToolbar.NAVBAR_MINIMUM_BUTTON_SIZE)) + } + ).align(AlignX.CENTER) + } + } + + override fun createCenterPanel(): JComponent? = pane + + init { + title = dialogTitle + setOKButtonText(AwsCoreBundle.message("aws.sso.signing.device.code")) + super.init() + } + + override fun doCancelAction() { + super.doCancelAction() + } +} + +class CopyUserCodeForLogin(private val authCode: String) : AnAction(AwsCoreBundle.message("aws.sso.signing.device.code.copy"), "", AllIcons.Actions.Copy) { + override fun actionPerformed(e: AnActionEvent) { + CopyPasteManager.getInstance().setContents(StringSelection(authCode)) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt new file mode 100644 index 00000000000..cd959c61309 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt @@ -0,0 +1,280 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.pkce + +import com.intellij.collaboration.auth.OAuthCallbackHandlerBase +import com.intellij.collaboration.auth.services.OAuthCredentialsAcquirer +import com.intellij.collaboration.auth.services.OAuthRequest +import com.intellij.collaboration.auth.services.OAuthService +import com.intellij.collaboration.auth.services.OAuthServiceBase +import com.intellij.collaboration.auth.services.PkceUtils +import com.intellij.openapi.application.ApplicationNamesInfo +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.wm.IdeFocusManager +import com.intellij.util.Url +import com.intellij.util.Urls.newFromEncoded +import com.intellij.util.io.DigestUtil +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.http.FullHttpRequest +import io.netty.handler.codec.http.QueryStringDecoder +import org.jetbrains.ide.BuiltInServerManager +import org.jetbrains.ide.RestService +import org.jetbrains.io.response +import software.amazon.awssdk.core.exception.SdkServiceException +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.ssooidc.endpoints.SsoOidcEndpointParams +import software.amazon.awssdk.services.ssooidc.endpoints.internal.DefaultSsoOidcEndpointProvider +import software.amazon.q.jetbrains.core.credentials.sso.AccessToken +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.PKCEClientRegistration +import software.amazon.q.jetbrains.core.credentials.sso.bearer.buildUnmanagedSsoOidcClient +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.services.telemetry.scrubNames +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.AuthTelemetry +import software.aws.toolkits.telemetry.AuthType +import software.aws.toolkits.telemetry.AwsTelemetry +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Result +import java.math.BigInteger +import java.time.Instant +import java.util.Base64 +import java.util.concurrent.CompletableFuture + +const val PKCE_CLIENT_NAME = "AWS IDE Plugins for JetBrains" + +@Service +class ToolkitOAuthService : OAuthServiceBase() { + override val name: String = "aws/toolkit" + + internal fun pendingRequest() = (currentRequest.get()?.request as? ToolkitOAuthRequest)?.registration + + fun hasPendingRequest() = currentRequest.get() != null + + fun authorize(registration: PKCEClientRegistration): CompletableFuture { + val currentRequest = currentRequest.get() + val toolkitRequest = currentRequest?.request as? ToolkitOAuthRequest + + if (toolkitRequest != null) { + check(toolkitRequest.registration == registration) { + """ + Attempting to start a new authorization with a different client registration while one is pending + Current: ${toolkitRequest.registration} + New: $registration + """.trimIndent() + } + } + + return authorize(ToolkitOAuthRequest(registration)) + } + + override fun handleOAuthServerCallback(path: String, parameters: Map>): OAuthService.OAuthResult? { + val request = currentRequest.get() ?: return OAuthService.OAuthResult(null, false) + val toolkitRequest = request.request as? ToolkitOAuthRequest ?: return OAuthService.OAuthResult(request.request, false) + + val callbackState = parameters["state"]?.firstOrNull() + if (toolkitRequest.csrfToken != callbackState) { + request.result.completeExceptionally(RuntimeException("Invalid CSRF token")) + return OAuthService.OAuthResult(toolkitRequest, false) + } + + if (parameters["code"] == null) { + val error = parameters["error"]?.firstOrNull() + val errorDescription = parameters["error_description"]?.firstOrNull() + toolkitRequest.error = OAuthError(error = error, errorDescription = errorDescription) + } + + return super.handleOAuthServerCallback(path, parameters) + } + + override fun revokeToken(token: String) { + TODO("Not yet implemented") + } + + companion object { + fun getInstance() = service() + } +} + +private data class OAuthError( + val error: String?, + val errorDescription: String?, +) + +private class ToolkitOAuthRequest(internal val registration: PKCEClientRegistration) : OAuthRequest { + private val port: Int get() = BuiltInServerManager.getInstance().port + private val base64Encoder = Base64.getUrlEncoder().withoutPadding() + + // 160 bits of entropy, per https://datatracker.ietf.org/doc/html/rfc6749#section-10.10 + internal val csrfToken = randB64url(160) + + // 256 bits of entropy, per https://datatracker.ietf.org/doc/html/rfc7636#section-7.1 + private val codeVerifier = randB64url(256) + + private val codeChallenge = PkceUtils.generateShaCodeChallenge(codeVerifier, base64Encoder) + + override val authorizationCodeUrl: Url + get() = newFromEncoded("http://127.0.0.1:$port/oauth/callback") + + val redirectUri + get() = authorizationCodeUrl.toExternalForm() + + private val serviceUri + get() = DefaultSsoOidcEndpointProvider().resolveEndpoint(SsoOidcEndpointParams.builder().region(Region.of(registration.region)).build()).get() + + override val credentialsAcquirer: OAuthCredentialsAcquirer = ToolkitOauthCredentialsAcquirer(registration, codeVerifier, redirectUri) + + override val authUrlWithParameters: Url + get() = newFromEncoded(serviceUri.url().resolve("authorize").toString()).addParameters( + mapOf( + "response_type" to "code", + "client_id" to registration.clientId, + "redirect_uri" to redirectUri, + "scopes" to registration.scopes.sorted().joinToString(" "), + "state" to csrfToken, + "code_challenge" to codeChallenge, + "code_challenge_method" to "S256" + ) + ) + + private fun randB64url(bits: Int): String = base64Encoder.encodeToString(BigInteger(bits, DigestUtil.random).toByteArray()) + + internal var error: OAuthError? = null +} + +// exchange for real token +internal class ToolkitOauthCredentialsAcquirer( + private val registration: PKCEClientRegistration, + private val codeVerifier: String, + private val redirectUri: String, +) : OAuthCredentialsAcquirer { + override fun acquireCredentials(code: String): OAuthCredentialsAcquirer.AcquireCredentialsResult { + val grantType = "authorization_code" + val startTime = Instant.now() + val token = try { + buildUnmanagedSsoOidcClient(registration.region).use { client -> + client.createToken { + it.clientId(registration.clientId) + it.clientSecret(registration.clientSecret) + it.grantType(grantType) + it.redirectUri(redirectUri) + it.codeVerifier(codeVerifier) + it.code(code) + }.also { + val duration = java.time.Duration.between(startTime, Instant.now()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Succeeded, + grantType = grantType, + duration = duration + ) + } + } + } catch (e: Exception) { + val duration = java.time.Duration.between(startTime, Instant.now()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Failed, + grantType = grantType, + duration = duration, + reason = e::class.simpleName, + reasonDesc = e.message?.let { scrubNames(it) }, + httpStatusCode = (e as? SdkServiceException)?.statusCode()?.toString() + ) + throw e + } + + return OAuthCredentialsAcquirer.AcquireCredentialsResult.Success( + PKCEAuthorizationGrantToken( + issuerUrl = registration.issuerUrl, + region = registration.region, + accessToken = token.accessToken(), + refreshToken = token.refreshToken(), + expiresAt = Instant.now().plusSeconds(token.expiresIn().toLong()), + createdAt = Instant.now() + ) + ) + } +} + +internal class ToolkitOAuthCallbackHandler : OAuthCallbackHandlerBase() { + override fun oauthService() = ToolkitOAuthService.getInstance() + + // on success / fail + override fun handleOAuthResult(oAuthResult: OAuthService.OAuthResult<*>): AcceptCodeHandleResult { + // focus should be on requesting component? + runInEdt { + IdeFocusManager.getGlobalInstance().getLastFocusedIdeWindow()?.toFront() + } + + val urlBase = newFromEncoded( + "http://127.0.0.1:${BuiltInServerManager.getInstance().port}/api/${ToolkitOAuthCallbackResultService.SERVICE_NAME}/index.html" + ) + val params = if (oAuthResult.isAccepted) { + mapOf( + "productName" to PKCE_CLIENT_NAME, + // we don't have the request context to get the requested scopes in this callback until 233 + "scopes" to ApplicationNamesInfo.getInstance().fullProductName + ) + } else { + val toolkitRequest = (oAuthResult.request as? ToolkitOAuthRequest) + val (error, errorDescription) = toolkitRequest?.error ?: OAuthError(null, null) + val errorString = if (error != null && errorDescription != null) { + "$error: $errorDescription" + } else { + errorDescription ?: error ?: AwsCoreBundle.message("general.unknown_error") + } + + AwsTelemetry.loginWithBrowser( + project = null, + credentialStartUrl = toolkitRequest?.registration?.issuerUrl, + result = MetricResult.Failed, + reason = error, + reasonDesc = errorDescription, + authType = AuthType.PKCE, + source = SourceOfEntry.UNKNOWN.toString(), + ) + + mapOf( + "error" to errorString + ) + } + + return AcceptCodeHandleResult.Redirect(urlBase.addParameters(params)) + } + + override fun isSupported(request: FullHttpRequest): Boolean { + // only handle if we're actively waiting on a redirect + if (!oauthService().hasPendingRequest()) { + return false + } + + // only handle the /oauth/callback endpoint + return request.uri().trim('/').startsWith("oauth/callback") + } +} + +internal class ToolkitOAuthCallbackResultService : RestService() { + override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? { + val path = urlDecoder.path().substringAfter(getServiceName()).trim('/') + val type = when { + path.endsWith(".css") -> "text/css" + else -> "text/html" + } + val content = ToolkitOAuthCallbackResultService::class.java.getResourceAsStream("/oauthCallback/$path")?.readAllBytes() ?: return "Unknown resource" + + val response = response(type, Unpooled.wrappedBuffer(content)) + sendResponse(request, context, response) + + // return null on success + return null + } + + override fun getServiceName() = SERVICE_NAME + + companion object { + const val SERVICE_NAME = "aws/toolkit/oauthResult" + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt new file mode 100644 index 00000000000..52b16f700c5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt @@ -0,0 +1,246 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.openapi.project.Project +import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ProfileSsoManagedBearerSsoConnection +import software.amazon.q.jetbrains.core.credentials.ReauthSource +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.loginSso +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.core.gettingstarted.editor.getAuthScopes +import software.amazon.q.jetbrains.core.gettingstarted.editor.getAuthStatus +import software.amazon.q.jetbrains.core.gettingstarted.editor.getConnectionCount +import software.amazon.q.jetbrains.core.gettingstarted.editor.getEnabledConnections +import software.amazon.q.jetbrains.core.gettingstarted.editor.getSourceOfEntry +import software.amazon.q.jetbrains.core.gettingstarted.editor.getStartupState +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.telemetry.AuthTelemetry +import software.aws.toolkits.telemetry.FeatureId +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Result +import software.aws.toolkits.telemetry.Telemetry + +fun requestCredentialsForCodeWhisperer( + project: Project, + popupBuilderIdTab: Boolean = true, + initialConnectionCount: Long = getConnectionCount(), + initialAuthConnections: String = getEnabledConnections( + project + ), + isFirstInstance: Boolean = false, + connectionInitiatedFromExplorer: Boolean = false, + isReauth: Boolean = false, +): Boolean { + val authenticationDialog = SetupAuthenticationDialog( + project, + state = SetupAuthenticationDialogState().also { + if (popupBuilderIdTab) { + it.selectedTab.set(SetupAuthenticationTabs.BUILDER_ID) + } + }, + tabSettings = mapOf( + SetupAuthenticationTabs.IDENTITY_CENTER to AuthenticationTabSettings( + disabled = false, + notice = SetupAuthenticationNotice( + SetupAuthenticationNotice.NoticeType.WARNING, + AwsCoreBundle.message("gettingstarted.setup.codewhisperer.use_builder_id"), + CODEWHISPERER_AUTH_LEARN_MORE_LINK + ) + ), + SetupAuthenticationTabs.BUILDER_ID to AuthenticationTabSettings( + disabled = false, + notice = SetupAuthenticationNotice( + SetupAuthenticationNotice.NoticeType.WARNING, + AwsCoreBundle.message("gettingstarted.setup.codewhisperer.use_identity_center"), + CODEWHISPERER_AUTH_LEARN_MORE_LINK + ) + ), + SetupAuthenticationTabs.IAM_LONG_LIVED to AuthenticationTabSettings( + disabled = true, + notice = SetupAuthenticationNotice( + SetupAuthenticationNotice.NoticeType.ERROR, + AwsCoreBundle.message("gettingstarted.setup.auth.no_iam"), + CODEWHISPERER_AUTH_LEARN_MORE_LINK + + ) + ) + ), + scopes = Q_SCOPES, + promptForIdcPermissionSet = false, + sourceOfEntry = SourceOfEntry.CODEWHISPERER, + featureId = FeatureId.Codewhisperer, + isFirstInstance = isFirstInstance, + connectionInitiatedFromExplorer = connectionInitiatedFromExplorer + ) + val isAuthenticationSuccessful = authenticationDialog.showAndGet() + if (isAuthenticationSuccessful) { + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(SourceOfEntry.CODEWHISPERER, isFirstInstance, connectionInitiatedFromExplorer)) + .featureId(FeatureId.Codewhisperer) + .credentialSourceId(authenticationDialog.authType) + .isAggregated(true) + .attempts(authenticationDialog.attempts + 1) + .result(MetricResult.Succeeded) + .isReAuth(isReauth) + } + AuthTelemetry.addedConnections( + project, + source = getSourceOfEntry(SourceOfEntry.CODEWHISPERER, isFirstInstance, connectionInitiatedFromExplorer), + authConnectionsCount = initialConnectionCount, + newAuthConnectionsCount = getConnectionCount() - initialConnectionCount, + enabledAuthConnections = initialAuthConnections, + newEnabledAuthConnections = getEnabledConnections(project), + attempts = authenticationDialog.attempts + 1, + result = Result.Succeeded + ) + } else { + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(SourceOfEntry.CODEWHISPERER, isFirstInstance, connectionInitiatedFromExplorer)) + .featureId(FeatureId.Codewhisperer) + .credentialSourceId(authenticationDialog.authType) + .isAggregated(false) + .attempts(authenticationDialog.attempts + 1) + .result(MetricResult.Cancelled) + .isReAuth(isReauth) + } + } + return isAuthenticationSuccessful +} + +@Deprecated("pending moving to Q package") +fun requestCredentialsForQ( + project: Project, + initialConnectionCount: Long = getConnectionCount(), + initialAuthConnections: String = getEnabledConnections( + project + ), + isFirstInstance: Boolean = false, + connectionInitiatedFromExplorer: Boolean = false, + connectionInitiatedFromQChatPanel: Boolean = false, + isReauth: Boolean, +): Boolean { + // try to scope upgrade if we have a codewhisperer connection + val qConnection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) + if (qConnection is LegacyManagedBearerSsoConnection) { + qConnection.let { + return tryOrNull { + loginSso(project, it.startUrl, it.region, Q_SCOPES) + } != null + } + } + + val dialogState = SetupAuthenticationDialogState().apply { + (qConnection as? ProfileSsoManagedBearerSsoConnection)?.let { connection -> + idcTabState.apply { + profileName = connection.configSessionName + startUrl = connection.startUrl + region = AwsRegionProvider.getInstance().let { it.get(connection.region) ?: it.defaultRegion() } + } + + // default selected tab is IdC, but just in case + selectedTab.set(SetupAuthenticationTabs.IDENTITY_CENTER) + } ?: run { + selectedTab.set(SetupAuthenticationTabs.BUILDER_ID) + } + } + + val authenticationDialog = SetupAuthenticationDialog( + project, + state = dialogState, + tabSettings = mapOf( + SetupAuthenticationTabs.IDENTITY_CENTER to AuthenticationTabSettings( + disabled = false, + notice = SetupAuthenticationNotice( + SetupAuthenticationNotice.NoticeType.WARNING, + AwsCoreBundle.message("gettingstarted.setup.codewhisperer.use_builder_id"), + CODEWHISPERER_AUTH_LEARN_MORE_LINK + ) + ), + SetupAuthenticationTabs.BUILDER_ID to AuthenticationTabSettings( + disabled = false, + notice = SetupAuthenticationNotice( + SetupAuthenticationNotice.NoticeType.WARNING, + AwsCoreBundle.message("gettingstarted.setup.codewhisperer.use_identity_center"), + CODEWHISPERER_AUTH_LEARN_MORE_LINK + ) + ), + SetupAuthenticationTabs.IAM_LONG_LIVED to AuthenticationTabSettings( + disabled = true, + notice = SetupAuthenticationNotice( + SetupAuthenticationNotice.NoticeType.ERROR, + AwsCoreBundle.message("gettingstarted.setup.auth.no_iam"), + CODEWHISPERER_AUTH_LEARN_MORE_LINK + ) + ) + ), + scopes = Q_SCOPES, + promptForIdcPermissionSet = false, + sourceOfEntry = SourceOfEntry.Q, + featureId = FeatureId.AmazonQ, + connectionInitiatedFromQChatPanel = connectionInitiatedFromQChatPanel + ) + + val isAuthenticationSuccessful = authenticationDialog.showAndGet() + if (isAuthenticationSuccessful) { + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(SourceOfEntry.Q, isFirstInstance, connectionInitiatedFromExplorer, connectionInitiatedFromQChatPanel)) + .featureId(FeatureId.AmazonQ) + .credentialSourceId(authenticationDialog.authType) + .isAggregated(true) + .attempts(authenticationDialog.attempts + 1) + .result(MetricResult.Succeeded) + .isReAuth(isReauth) + } + AuthTelemetry.addedConnections( + project, + source = getSourceOfEntry(SourceOfEntry.Q, isFirstInstance, connectionInitiatedFromExplorer, connectionInitiatedFromQChatPanel), + authConnectionsCount = initialConnectionCount, + newAuthConnectionsCount = getConnectionCount() - initialConnectionCount, + enabledAuthConnections = initialAuthConnections, + newEnabledAuthConnections = getEnabledConnections(project), + attempts = authenticationDialog.attempts + 1, + result = Result.Succeeded + ) + } else { + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(SourceOfEntry.Q, isFirstInstance, connectionInitiatedFromExplorer, connectionInitiatedFromQChatPanel)) + .featureId(FeatureId.AmazonQ) + .credentialSourceId(authenticationDialog.authType) + .isAggregated(false) + .attempts(authenticationDialog.attempts + 1) + .result(MetricResult.Cancelled) + .isReAuth(isReauth) + } + } + return isAuthenticationSuccessful +} + +fun reauthenticateWithQ(project: Project) { + val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) + if (connection !is ManagedBearerSsoConnection) return + pluginAwareExecuteOnPooledThread { + reauthConnectionIfNeeded(project, connection, isReAuth = true, reauthSource = ReauthSource.Q_CHAT) + } +} + +fun emitUserState(project: Project) { + AuthTelemetry.userState( + project, + authEnabledConnections = getEnabledConnections(project), + authScopes = getAuthScopes(project), + authStatus = getAuthStatus(project), + source = getStartupState().toString() + ) +} + +const val CODEWHISPERER_AUTH_LEARN_MORE_LINK = "https://docs.aws.amazon.com/codewhisperer/latest/userguide/codewhisperer-auth.html" diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt new file mode 100644 index 00000000000..d7082f75490 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt @@ -0,0 +1,158 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.util.Disposer +import com.intellij.ui.dsl.builder.AlignX +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.toNullableProperty +import org.jetbrains.annotations.VisibleForTesting +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.sso.model.RoleInfo +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManagerConnection +import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.DefaultConfigFilesFacade +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher +import software.amazon.q.jetbrains.ui.AsyncComboBox +import software.amazon.q.jetbrains.utils.ui.selected +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn + +data class IdcRolePopupState( + var roleInfo: RoleInfo? = null, +) + +class IdcRolePopup( + private val project: Project, + private val region: String, + private val sessionName: String, + private val tokenProvider: SdkTokenProvider, + val state: IdcRolePopupState = IdcRolePopupState(), + private val configFilesFacade: ConfigFilesFacade = DefaultConfigFilesFacade(), +) : DialogWrapper(project) { + init { + title = AwsCoreBundle.message("gettingstarted.setup.idc.role.title") + init() + } + + override fun showAndGet(): Boolean { + if (ApplicationManager.getApplication().isUnitTestMode) { + return false + } + + return super.showAndGet() + } + + private val client = AwsClientManager.getInstance().createUnmanagedClient( + AnonymousCredentialsProvider.create(), + Region.of(region) + ) + + override fun dispose() { + client.close() + super.dispose() + } + + override fun createCenterPanel() = panel { + row { + label(AwsCoreBundle.message("gettingstarted.setup.idc.roleLabel")) + } + + row { + val combo = AsyncComboBox { label, value, _ -> + value ?: return@AsyncComboBox + label.text = "${value.roleName()} (${value.accountId()})" + } + + Disposer.register(myDisposable, combo) + combo.proposeModelUpdate { model -> + val token = tokenProvider.resolveToken().token() + + val rolesList = client.listAccountsPaginator { it.accessToken(token) } + .accountList() + .flatMap { account -> + client.listAccountRolesPaginator { + it.accessToken(token) + it.accountId(account.accountId()) + }.roleList() + } + + rolesList.sortedBy { it.roleName() } + .forEach { + model.addElement(it) + } + + state.roleInfo?.let { + model.selectedItem = it + } + } + + cell(combo) + .align(AlignX.FILL) + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_selected")) { it.selected() == null } + .bindItem(state::roleInfo.toNullableProperty()) + } + } + + @VisibleForTesting + public override fun doOKAction() { + if (!okAction.isEnabled) { + return + } + applyFields() + + val roleInfo = state.roleInfo + checkNotNull(roleInfo) + + doOkActionWithRoleInfo(roleInfo) + + close(OK_EXIT_CODE) + } + + @VisibleForTesting + internal fun doOkActionWithRoleInfo(roleInfo: RoleInfo) { + val profileName = "$sessionName-${roleInfo.accountId()}-${roleInfo.roleName()}" + if (profileName !in configFilesFacade.readAllProfiles().keys) { + configFilesFacade.appendProfileToConfig( + Profile.builder() + .name(profileName) + .properties( + mapOf( + "sso_session" to sessionName, + "sso_account_id" to roleInfo.accountId(), + "sso_role_name" to roleInfo.roleName() + ) + ) + .build() + ) + } + + // force CredentialManager to pick up change + ProfileWatcher.getInstance().forceRefresh() + + CredentialManager.getInstance().getCredentialIdentifierById("profile:$profileName")?.let { + ToolkitConnectionManager.getInstance(project).switchConnection(AwsConnectionManagerConnection(project)) + AwsConnectionManager.getInstance(project).changeCredentialProvider(it) + } ?: let { + LOG.warn { "Could not autoswitch to profile $profileName" } + } + } + + companion object { + private val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt new file mode 100644 index 00000000000..e8088a5658a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt @@ -0,0 +1,499 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.observable.properties.PropertyGraph +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.ui.SimpleListCellRenderer +import com.intellij.ui.components.BrowserLink +import com.intellij.ui.components.JBTabbedPane +import com.intellij.ui.dsl.builder.AlignX +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.toMutableProperty +import com.intellij.ui.dsl.builder.toNullableProperty +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.components.BorderLayoutPanel +import org.jetbrains.annotations.VisibleForTesting +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.jetbrains.ToolkitPlaces +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade +import software.amazon.q.jetbrains.core.credentials.DefaultConfigFilesFacade +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile +import software.amazon.q.jetbrains.core.credentials.authAndUpdateConfig +import software.amazon.q.jetbrains.core.credentials.loginSso +import software.amazon.q.jetbrains.core.credentials.messageFromConfigFacadeError +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.core.gettingstarted.editor.getSourceOfEntry +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.jetbrains.utils.ui.editorNotificationCompoundBorder +import software.amazon.q.jetbrains.utils.ui.selected +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.telemetry.CredentialSourceId +import software.aws.toolkits.telemetry.FeatureId +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Telemetry +import java.awt.BorderLayout +import java.util.Optional +import javax.swing.Action +import javax.swing.BorderFactory +import javax.swing.JComponent +import javax.swing.JLabel +import kotlin.reflect.KMutableProperty0 + +data class SetupAuthenticationDialogState( + var idcTabState: IdentityCenterTabState = IdentityCenterTabState(), + val builderIdTabState: BuilderIdTabState = BuilderIdTabState, + var iamTabState: IamLongLivedCredentialsState = IamLongLivedCredentialsState(), +) { + private val graph = PropertyGraph() + val selectedTab = graph.property(SetupAuthenticationTabs.IDENTITY_CENTER) + data class IdentityCenterTabState( + var profileName: String = "", + var startUrl: String = "", + var region: AwsRegion = AwsRegionProvider.getInstance().defaultRegion(), + var rolePopupState: IdcRolePopupState = IdcRolePopupState(), + ) + + // has no state yet + object BuilderIdTabState + + data class IamLongLivedCredentialsState( + // should be blank if default profile exists + var profileName: String = "default", + var accessKey: String = "", + var secretKey: String = "", + ) +} + +enum class SetupAuthenticationTabs { + IDENTITY_CENTER, + BUILDER_ID, + IAM_LONG_LIVED, +} + +data class AuthenticationTabSettings( + val disabled: Boolean = false, + val notice: SetupAuthenticationNotice, +) + +data class SetupAuthenticationNotice( + val type: NoticeType, + val message: String, + val learnMore: String, +) { + enum class NoticeType { + WARNING, + ERROR, + } +} + +interface AuthenticationDialog { + val attempts: Long + val authType: CredentialSourceId +} + +class SetupAuthenticationDialog( + private val project: Project, + private val scopes: List = emptyList(), + private val state: SetupAuthenticationDialogState = SetupAuthenticationDialogState(), + private val tabSettings: Map = emptyMap(), + private val promptForIdcPermissionSet: Boolean = false, + private val configFilesFacade: ConfigFilesFacade = DefaultConfigFilesFacade(), + private val sourceOfEntry: SourceOfEntry, + private val featureId: FeatureId, + private val isFirstInstance: Boolean = false, + private val connectionInitiatedFromExplorer: Boolean = false, + private val connectionInitiatedFromQChatPanel: Boolean = false, +) : DialogWrapper(project), AuthenticationDialog { + private val rootTabPane = JBTabbedPane() + private val idcTab = IdcTabPanelBuilder( + startUrl = state.idcTabState::startUrl, + region = state.idcTabState::region, + profileName = Optional.of(state.idcTabState::profileName) + ).build() + private val builderIdTab = BuilderIdTabPanelBuilder().build() + private val iamTab = iamTab() + private val wrappers = SetupAuthenticationTabs.values().associateWith { BorderLayoutPanel() } + override var attempts = 0L + private set + override var authType = CredentialSourceId.IamIdentityCenter + private set + + init { + title = AwsCoreBundle.message("gettingstarted.setup.title") + init() + + // actions don't exist until after init + okAction.putValue(Action.NAME, AwsCoreBundle.message("gettingstarted.setup.connect")) + } + + // called as part of init() + override fun createCenterPanel(): JComponent { + wrappers[SetupAuthenticationTabs.IDENTITY_CENTER]?.addToCenter(idcTab) + wrappers[SetupAuthenticationTabs.BUILDER_ID]?.addToCenter(builderIdTab) + wrappers[SetupAuthenticationTabs.IAM_LONG_LIVED]?.addToCenter(iamTab) + + idcTab.registerValidators(myDisposable) { validations -> + if (selectedTab() == SetupAuthenticationTabs.IDENTITY_CENTER) { + setOKActionEnabled(validations.values.all { it.okEnabled }) + } + } + + builderIdTab.registerValidators(myDisposable) { validations -> + if (selectedTab() == SetupAuthenticationTabs.BUILDER_ID) { + setOKActionEnabled(validations.values.all { it.okEnabled }) + } + } + + iamTab.registerValidators(myDisposable) { validations -> + if (selectedTab() == SetupAuthenticationTabs.IAM_LONG_LIVED) { + setOKActionEnabled(validations.values.all { it.okEnabled }) + } + } + + tabSettings.forEach { tab, settings -> + val notice = settings.notice + + wrappers[tab]?.addToTop( + BorderLayoutPanel().apply { + add(JLabel(notice.message + "\u00a0"), BorderLayout.CENTER) + add(BrowserLink(AwsCoreBundle.message("gettingstarted.setup.learnmore"), notice.learnMore), BorderLayout.EAST) + + background = when (notice.type) { + SetupAuthenticationNotice.NoticeType.WARNING -> JBUI.CurrentTheme.NotificationWarning.backgroundColor() + SetupAuthenticationNotice.NoticeType.ERROR -> JBUI.CurrentTheme.NotificationError.backgroundColor() + } + + val borderColor = when (notice.type) { + SetupAuthenticationNotice.NoticeType.WARNING -> JBUI.CurrentTheme.NotificationWarning.borderColor() + SetupAuthenticationNotice.NoticeType.ERROR -> JBUI.CurrentTheme.NotificationError.borderColor() + } + + border = editorNotificationCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, borderColor)) + } + ) + } + + rootTabPane.add(AwsCoreBundle.message("gettingstarted.setup.tabs.idc"), wrappers[SetupAuthenticationTabs.IDENTITY_CENTER]) + rootTabPane.add(AwsCoreBundle.message("gettingstarted.setup.tabs.builderid"), wrappers[SetupAuthenticationTabs.BUILDER_ID]) + rootTabPane.add(AwsCoreBundle.message("gettingstarted.setup.tabs.iam"), wrappers[SetupAuthenticationTabs.IAM_LONG_LIVED]) + + rootTabPane.selectedComponent = wrappers[state.selectedTab.get()] + + rootTabPane.addChangeListener { + val selectedTab = selectedTab() + + state.selectedTab.set(selectedTab) + okAction.isEnabled = tabSettings[selectedTab]?.disabled?.not() ?: true + } + + return rootTabPane + } + + override fun applyFields() { + when (selectedTab()) { + SetupAuthenticationTabs.IDENTITY_CENTER -> { + idcTab.apply() + } + + SetupAuthenticationTabs.IAM_LONG_LIVED -> { + iamTab.apply() + } + + SetupAuthenticationTabs.BUILDER_ID -> { + builderIdTab.apply() + } + } + } + + override fun doValidateAll(): List = + when (selectedTab()) { + SetupAuthenticationTabs.IDENTITY_CENTER -> { + idcTab.validateAll() + } + + SetupAuthenticationTabs.IAM_LONG_LIVED -> { + iamTab.validateAll() + } + + SetupAuthenticationTabs.BUILDER_ID -> { + emptyList() + } + } + + @VisibleForTesting + public override fun doOKAction() { + if (!okAction.isEnabled) { + return + } + + applyFields() + val scopes = if (promptForIdcPermissionSet) { + (scopes + IDENTITY_CENTER_ROLE_ACCESS_SCOPE).toSet().toList() + } else { + scopes + } + + when (selectedTab()) { + SetupAuthenticationTabs.IDENTITY_CENTER -> { + authType = CredentialSourceId.IamIdentityCenter + val profileName = state.idcTabState.profileName + // we have this check here so we blow up early if user has an invalid config file + try { + configFilesFacade.readSsoSessions() + } catch (e: Exception) { + handleConfigFacadeError(e) + return + } + + val profile = UserConfigSsoSessionProfile( + configSessionName = profileName, + ssoRegion = state.idcTabState.region.id, + startUrl = state.idcTabState.startUrl, + scopes = scopes + ) + + val connection = authAndUpdateConfig(project, profile, configFilesFacade, {}, {}) { e -> + Messages.showErrorDialog(project, e.message, title) + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(sourceOfEntry, isFirstInstance, connectionInitiatedFromExplorer, connectionInitiatedFromQChatPanel)) + .featureId(featureId) + .credentialSourceId(CredentialSourceId.IamIdentityCenter) + .isAggregated(false) + .attempts(++attempts) + .result(MetricResult.Failed) + .reason("ConnectionUnsuccessful") + .isReAuth(false) + } + } ?: return + + if (!promptForIdcPermissionSet) { + ToolkitConnectionManager.getInstance(project).switchConnection(connection) + close(OK_EXIT_CODE) + return + } + + val tokenProvider = connection.getConnectionSettings().tokenProvider + + val rolePopup = IdcRolePopup( + project, + state.idcTabState.region.id, + profileName, + tokenProvider, + state.idcTabState.rolePopupState, + configFilesFacade = configFilesFacade + ) + + if (!rolePopup.showAndGet()) { + // don't close window if role is needed but was not confirmed + return + } + } + + SetupAuthenticationTabs.BUILDER_ID -> { + authType = CredentialSourceId.AwsId + loginSso(project, SONO_URL, SONO_REGION, scopes) + } + + SetupAuthenticationTabs.IAM_LONG_LIVED -> { + authType = CredentialSourceId.SharedCredentials + val profileName = state.iamTabState.profileName + val existingProfiles = try { + configFilesFacade.readAllProfiles() + } catch (e: Exception) { + handleConfigFacadeError(e) + return + } + + if (existingProfiles.containsKey(profileName)) { + Messages.showErrorDialog(project, AwsCoreBundle.message("gettingstarted.setup.iam.profile.exists", profileName), title) + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(sourceOfEntry, isFirstInstance, connectionInitiatedFromExplorer)) + .featureId(featureId) + .credentialSourceId(CredentialSourceId.SharedCredentials) + .isAggregated(false) + .attempts(++attempts) + .result(MetricResult.Failed) + .reason("DuplicateProfileName") + .isReAuth(false) + } + return + } + + val callerIdentity = tryOrNull { + runUnderProgressIfNeeded(project, AwsCoreBundle.message("settings.states.validating.short"), cancelable = true) { + AwsClientManager.getInstance().createUnmanagedClient( + StaticCredentialsProvider.create(AwsBasicCredentials.create(state.iamTabState.accessKey, state.iamTabState.secretKey)), + Region.AWS_GLOBAL + ).use { client -> + client.getCallerIdentity() + } + } + } + + if (callerIdentity == null) { + Messages.showErrorDialog(project, AwsCoreBundle.message("gettingstarted.setup.iam.profile.invalid_credentials"), title) + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(sourceOfEntry, isFirstInstance, connectionInitiatedFromExplorer)) + .featureId(featureId) + .credentialSourceId(CredentialSourceId.SharedCredentials) + .isAggregated(false) + .attempts(++attempts) + .result(MetricResult.Failed) + .reason("InvalidCredentials") + .isReAuth(false) + } + return + } + + val profile = Profile.builder() + .name(profileName) + .properties( + mapOf( + "aws_access_key_id" to state.iamTabState.accessKey, + "aws_secret_access_key" to state.iamTabState.secretKey + ) + ) + .build() + + configFilesFacade.appendProfileToCredentials(profile) + } + } + + close(OK_EXIT_CODE) + } + + private fun selectedTab() = wrappers.entries.firstOrNull { (_, wrapper) -> wrapper == rootTabPane.selectedComponent }?.key + ?: error("Could not determine selected tab") + + // https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html + private val accessKeyRegex = "\\w{16,128}".toRegex() + + private fun iamTab() = panel { + row { + text(AwsCoreBundle.message("gettingstarted.setup.iam.notice")) { hyperlinkEvent -> + val actionEvent = AnActionEvent.createFromInputEvent( + hyperlinkEvent.inputEvent, + ToolkitPlaces.ADD_CONNECTION_DIALOG, + null, + DataContext { if (PlatformDataKeys.PROJECT.`is`(it)) project else null } + ) + ActionManager.getInstance().getAction("aws.settings.upsertCredentials").actionPerformed(actionEvent) + } + } + + row(AwsCoreBundle.message("gettingstarted.setup.iam.profile")) { + textField() + .comment(AwsCoreBundle.message("gettingstarted.setup.iam.profile.comment")) + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_empty")) { it.text.isBlank() } + .bindText(state.iamTabState::profileName) + } + + row(AwsCoreBundle.message("gettingstarted.setup.iam.access_key")) { + textField() + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_empty")) { it.text.isBlank() } + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.iam.access_key.invalid")) { !accessKeyRegex.matches(it.text) } + .bindText(state.iamTabState::accessKey) + } + + row(AwsCoreBundle.message("gettingstarted.setup.iam.secret_key")) { + passwordField() + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_empty")) { it.password.isEmpty() } + .bindText(state.iamTabState::secretKey) + } + } + + private fun handleConfigFacadeError(e: Exception) { + val (errorMessage, errorType) = messageFromConfigFacadeError(e) + Telemetry.auth.addConnection.use { + it.source(getSourceOfEntry(sourceOfEntry, isFirstInstance, connectionInitiatedFromExplorer, connectionInitiatedFromQChatPanel)) + .featureId(featureId) + .credentialSourceId(CredentialSourceId.IamIdentityCenter) + .isAggregated(false) + .attempts(++attempts) + .result(MetricResult.Failed) + .reason(errorType) + .isReAuth(false) + } + + LOG.error(e) { errorMessage } + Messages.showErrorDialog(project, errorMessage, title) + } + + companion object { + private val LOG = getLogger() + } +} + +class BuilderIdTabPanelBuilder { + fun build() = panel { + row { + text(AwsCoreBundle.message("gettingstarted.setup.builderid.notice")) + } + + indent { + AwsCoreBundle.message("gettingstarted.setup.builderid.bullets").split("\n").forEach { + row { + text(" $it") + } + } + } + } +} +class IdcTabPanelBuilder( + private val startUrl: KMutableProperty0, + private val region: KMutableProperty0, + private var profileName: Optional> = Optional.empty(), +) { + fun build() = panel { + profileName.ifPresent { + row(AwsCoreBundle.message("gettingstarted.setup.iam.profile")) { + textField() + .comment(AwsCoreBundle.message("gettingstarted.setup.idc.profile.comment")) + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_empty")) { it.text.isBlank() } + .bindText(profileName.get()) + } + } + + row(AwsCoreBundle.message("gettingstarted.setup.idc.startUrl")) { + textField() + .comment(AwsCoreBundle.message("gettingstarted.setup.idc.startUrl.comment")) + .align(AlignX.FILL) + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_empty")) { it.text.isBlank() } + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.idc.no_builder_id")) { it.text == SONO_URL } + .bind({ it.text.trim() }, { t, v -> t.text = v.trim() }, startUrl.toMutableProperty()) + } + + row(AwsCoreBundle.message("gettingstarted.setup.idc.region")) { + comboBox( + AwsRegionProvider.getInstance().allRegionsForService("sso").values, + SimpleListCellRenderer.create("null") { it.displayName } + ).bindItem(region.toNullableProperty()) + .errorOnApply(AwsCoreBundle.message("gettingstarted.setup.error.not_selected")) { it.selected() == null } + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt new file mode 100644 index 00000000000..182d23270e7 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt @@ -0,0 +1,212 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted.editor + +import com.intellij.openapi.project.Project +import com.intellij.ui.dsl.builder.Panel +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.ConnectionState +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.lazyIsUnauthedBearerConnection +import software.amazon.q.jetbrains.core.credentials.pinning.CodeCatalystConnection +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialType +import java.util.Locale + +enum class ActiveConnectionType { + BUILDER_ID, + IAM_IDC, + IAM, + UNKNOWN, +} + +enum class BearerTokenFeatureSet { + CODECATALYST, + Q, +} + +fun controlPanelVisibility(currentPanel: Panel, newPanel: Panel) { + currentPanel.visible(false) + newPanel.visible(true) +} + +sealed interface ActiveConnection { + val activeConnectionBearer: AwsBearerTokenConnection? + val connectionType: ActiveConnectionType? + val activeConnectionIam: CredentialIdentifier? + + data class ExpiredBearer( + override val activeConnectionBearer: AwsBearerTokenConnection?, + override val connectionType: ActiveConnectionType?, + override val activeConnectionIam: CredentialIdentifier? = null, + ) : ActiveConnection + + data class ExpiredIam( + override val activeConnectionBearer: AwsBearerTokenConnection? = null, + override val connectionType: ActiveConnectionType?, + override val activeConnectionIam: CredentialIdentifier?, + ) : ActiveConnection + + data class ValidBearer( + override val activeConnectionBearer: AwsBearerTokenConnection?, + override val connectionType: ActiveConnectionType?, + override val activeConnectionIam: CredentialIdentifier? = null, + ) : ActiveConnection + + data class ValidIam( + override val activeConnectionBearer: AwsBearerTokenConnection? = null, + override val connectionType: ActiveConnectionType?, + override val activeConnectionIam: CredentialIdentifier?, + ) : ActiveConnection + + object NotConnected : ActiveConnection { + override val activeConnectionBearer: AwsBearerTokenConnection? + get() = null + + override val connectionType: ActiveConnectionType? + get() = null + + override val activeConnectionIam: CredentialIdentifier? + get() = null + } +} + +fun checkBearerConnectionValidity(project: Project, source: BearerTokenFeatureSet): ActiveConnection { + val connections = ToolkitAuthManager.getInstance().listConnections().filterIsInstance() + if (connections.isEmpty()) return ActiveConnection.NotConnected + + val activeConnection = when (source) { + BearerTokenFeatureSet.CODECATALYST -> ToolkitConnectionManager.getInstance(project).activeConnectionForFeature( + CodeCatalystConnection.getInstance() + ) + BearerTokenFeatureSet.Q -> ToolkitConnectionManager.getInstance(project).activeConnectionForFeature( + QConnection.getInstance() + ) + } ?: return ActiveConnection.NotConnected + + activeConnection as AwsBearerTokenConnection + val connectionType = if (activeConnection.startUrl == SONO_URL) ActiveConnectionType.BUILDER_ID else ActiveConnectionType.IAM_IDC + return if (activeConnection.lazyIsUnauthedBearerConnection()) { + ActiveConnection.ExpiredBearer(activeConnection, connectionType) + } else { + ActiveConnection.ValidBearer(activeConnection, connectionType) + } +} + +fun checkIamConnectionValidity(project: Project): ActiveConnection { + val currConn = AwsConnectionManager.getInstance(project).selectedCredentialIdentifier ?: return ActiveConnection.NotConnected + val invalidConnection = AwsConnectionManager.getInstance(project).connectionState.let { it.isTerminal && it !is ConnectionState.ValidConnection } + return if (invalidConnection) { + ActiveConnection.ExpiredIam(connectionType = isCredentialSso(currConn.shortName), activeConnectionIam = currConn) + } else { + ActiveConnection.ValidIam(connectionType = isCredentialSso(currConn.shortName), activeConnectionIam = currConn) + } +} + +fun checkIamProfileByCredentialType(project: Project): ActiveConnection { + val currConn = AwsConnectionManager.getInstance(project).selectedCredentialIdentifier ?: return ActiveConnection.NotConnected + val invalidConnection = AwsConnectionManager.getInstance(project).connectionState.let { it.isTerminal && it !is ConnectionState.ValidConnection } + val connectionType = when (currConn.credentialType) { + CredentialType.SsoProfile -> ActiveConnectionType.IAM_IDC + else -> ActiveConnectionType.IAM + } + return if (invalidConnection) { + ActiveConnection.ExpiredIam(connectionType = connectionType, activeConnectionIam = currConn) + } else { + ActiveConnection.ValidIam(connectionType = connectionType, activeConnectionIam = currConn) + } +} + +/** + * Finds the first valid [ActiveConnection] and returns it. + * + * Inspects IAM connection first and subsequently traverses all bearer connection types. + * If a valid connection is not found, an expired state will be returned if at least one + * connection resolved to expired. Otherwise [ActiveConnection.NotConnected] is returned. + */ +fun checkConnectionValidity(project: Project): ActiveConnection { + val tokenFeatureSets = listOf( + BearerTokenFeatureSet.CODECATALYST, + BearerTokenFeatureSet.Q, + ) + var result = checkIamConnectionValidity(project) + + if (result !is ActiveConnection.ValidIam) { + for (featureSet in tokenFeatureSets) { + when (val bearerConnectionStatus = checkBearerConnectionValidity(project, featureSet)) { + is ActiveConnection.ExpiredBearer -> result = bearerConnectionStatus + is ActiveConnection.ValidBearer -> { + result = bearerConnectionStatus + return result + } + else -> continue + } + } + } + + return result +} + +@Deprecated("Does not work for current config file setup. Old versions still utilize this logic.") +fun isCredentialSso(providerId: String): ActiveConnectionType { + val profileName = providerId.split("-").first() + val ssoSessionIds = CredentialManager.getInstance().getSsoSessionIdentifiers().map { + it.id.substringAfter( + "${SsoSessionConstants.SSO_SESSION_SECTION_NAME}:" + ) + } + return if (profileName in ssoSessionIds) ActiveConnectionType.IAM_IDC else ActiveConnectionType.IAM +} + +fun getSourceOfEntry( + sourceOfEntry: SourceOfEntry, + isStartup: Boolean = false, + connectionInitiatedFromExplorer: Boolean = false, + connectionInitiatedFromQChatPanel: Boolean = false, +): String { + val src = if (connectionInitiatedFromExplorer) { + SourceOfEntry.EXPLORER.toString() + } else if (connectionInitiatedFromQChatPanel) { + SourceOfEntry.AMAZONQ_CHAT_PANEL.toString() + } else { + sourceOfEntry.toString() + } + val source = if (isStartup) SourceOfEntry.FIRST_STARTUP.toString() else src + return if (isRunningOnRemoteBackend()) "REMOTE_$source" else source +} + +enum class SourceOfEntry { + RESOURCE_EXPLORER, + CODECATALYST, + CODEWHISPERER, + EXPLORER, + FIRST_STARTUP, + Q, + AMAZONQ_CHAT_PANEL, + LOGIN_BROWSER, + UNKNOWN, + ; + override fun toString(): String { + val value = this.name.lowercase() + // If the string in lowercase contains an _ eg RESOURCE_EXPLORER, this function returns camelCase of the string i.e resourceExplorer + return if (value.contains("_")) { + // convert to camelCase + ( + value.substringBefore("_") + + value.substringAfter("_").replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() + } + ) + } else { + value + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt new file mode 100644 index 00000000000..3d84a3890c1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt @@ -0,0 +1,136 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted.editor + +import com.intellij.configurationStore.getPersistentStateComponentStorageLocation +import com.intellij.openapi.project.Project +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifierSso +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.telemetry.AuthStatus +import software.aws.toolkits.telemetry.StartUpState + +fun getConnectionCount(): Long { + val bearerTokenCount = ToolkitAuthManager.getInstance().listConnections().size + val iamCredentialCount = CredentialManager.getInstance().getCredentialIdentifiers().count { it !is ProfileCredentialsIdentifierSso } + return (bearerTokenCount + iamCredentialCount).toLong() +} + +fun getEnabledConnectionsForTelemetry(project: Project?): Set { + project ?: return emptySet() + val enabledConnections = mutableSetOf() + + addConnectionInfoToSet( + checkIamConnectionValidity(project), + enabledConnections, + AuthFormId.IDENTITYCENTER_EXPLORER, + AuthFormId.IAMCREDENTIALS_EXPLORER + ) + + addConnectionInfoToSet( + checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODECATALYST), + enabledConnections, + AuthFormId.IDENTITYCENTER_CODECATALYST, + AuthFormId.BUILDERID_CODECATALYST + ) + + addConnectionInfoToSet( + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q), + enabledConnections, + AuthFormId.IDENTITYCENTER_Q, + AuthFormId.BUILDERID_Q + ) + return enabledConnections.mapTo(mutableSetOf()) { it as AuthFormId } +} + +fun getEnabledConnections(project: Project?): String = + getEnabledConnectionsForTelemetry(project).joinToString(",") + +fun getAuthScopesForTelemetry(project: Project?): Set { + project ?: return emptySet() + val scopes = mutableSetOf() + + val explorerConnection = checkIamProfileByCredentialType(project) + if (explorerConnection !is ActiveConnection.NotConnected && explorerConnection.connectionType == ActiveConnectionType.IAM_IDC) { + scopes.add(IDENTITY_CENTER_ROLE_ACCESS_SCOPE) + } + + addConnectionInfoToSet( + checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODECATALYST), + dataSet = scopes + ) + + addConnectionInfoToSet( + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q), + dataSet = scopes + ) + + return scopes.mapTo(mutableSetOf()) { it as String } +} + +fun getAuthScopes(project: Project?): String = + getAuthScopesForTelemetry(project).joinToString(",") + +fun getStartupState(): StartUpState { + val hasStartedToolkitBefore = tryOrNull { + getPersistentStateComponentStorageLocation(AwsSettings::class.java)?.exists() + } ?: true + return if (hasStartedToolkitBefore) StartUpState.Reloaded else StartUpState.FirstStartUp +} + +fun getAuthStatus(project: Project) = when (checkConnectionValidity(project)) { + is ActiveConnection.ExpiredIam, + is ActiveConnection.ExpiredBearer, + -> AuthStatus.Expired + is ActiveConnection.ValidIam, + is ActiveConnection.ValidBearer, + -> AuthStatus.Connected + else -> AuthStatus.NotConnected +} + +fun addConnectionInfoToSet( + activeConnection: ActiveConnection, + dataSet: MutableSet, + idcConnection: AuthFormId? = null, + defaultConnection: AuthFormId? = null, +) { + if (activeConnection is ActiveConnection.NotConnected) { + return + } + + // add enabled connections + when (activeConnection.connectionType) { + ActiveConnectionType.IAM_IDC -> { + idcConnection ?.let { + dataSet.add(idcConnection) + return + } + } else -> { + defaultConnection?.let { + dataSet.add(defaultConnection) + return + } + } + } + + // add scopes + val connectionScopes = activeConnection.activeConnectionBearer?.scopes + if (!connectionScopes.isNullOrEmpty()) { + dataSet.addAll(connectionScopes) + } +} + +enum class AuthFormId { + IAMCREDENTIALS_EXPLORER, + IDENTITYCENTER_EXPLORER, + BUILDERID_CODECATALYST, + IDENTITYCENTER_CODECATALYST, + BUILDERID_Q, + IDENTITYCENTER_Q, + UNKNOWN, +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIdTranslator.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIdTranslator.kt new file mode 100644 index 00000000000..19fff73f8a4 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIdTranslator.kt @@ -0,0 +1,25 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.help + +import com.intellij.openapi.help.WebHelpProvider +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn + +class HelpIdTranslator : WebHelpProvider() { + override fun getHelpTopicPrefix() = HELP_ID_PREFIX + + override fun getHelpPageUrl(helpTopicId: String) = HELP_REGISTRY.getOrElse(helpTopicId) { + LOGGER.warn { "Missing id $helpTopicId" } + DEFAULT_LOCATION + } + + private companion object { + const val DEFAULT_LOCATION = "https://docs.aws.amazon.com/console/toolkit-for-jetbrains" + val LOGGER = getLogger() + private val HELP_REGISTRY by lazy { + HelpIds.values().asSequence().map { it.id to it.url }.toMap() + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIds.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIds.kt new file mode 100644 index 00000000000..d427115270c --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIds.kt @@ -0,0 +1,138 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.help + +enum class HelpIds(shortId: String, val url: String) { + // App Runner + APPRUNNER_PAUSE_RESUME( + "appRunnerPauseResume", + "https://docs.aws.amazon.com/console/apprunner/manage-pause" + ), + APPRUNNER_CODE_CONFIG( + "appRunnerCodeConfig", + "https://docs.aws.amazon.com/console/apprunner/config-file" + ), + APPRUNNER_CONNECTIONS( + "appRunnnerServiceConnections", + "https://docs.aws.amazon.com/console/apprunner/manage-connections" + ), + + // Explorer + EXPLORER_WINDOW( + "explorerWindow", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/aws-explorer" + ), + EXPLORER_CREDS_HELP( + "explorerCredsHelp", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/setup-credentials.html" + ), + + // Lambda + CREATE_FUNCTION_DIALOG( + "createFunctionDialog", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/create-function-dialog" + ), + UPDATE_FUNCTION_CONFIGURATION_DIALOG( + "updateFunctionConfigurationDialog", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/update-configuration-dialog" + ), + UPDATE_FUNCTION_CODE_DIALOG( + "updateFunctionCodeDialog", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/update-code-dialog" + ), + + // Serverless + NEW_SERVERLESS_PROJECT_DIALOG( + "newServerlessProjectDialog", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/new-project-dialog" + ), + DEPLOY_SERVERLESS_APPLICATION_DIALOG( + "deployServerlessApplicationDialog", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/deploy-serverless-application-dialog" + ), + + // Schema code download + DOWNLOAD_CODE_FOR_SCHEMA_DIALOG( + "downloadCodeForSchemaDialog", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/eventbridge-schemas.html" + ), + + // Schema search + SCHEMA_SEARCH_DIALOG( + "schemaSearchDialog", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/eventbridge-schemas.html" + ), + + // Others + RUN_DEBUG_CONFIGURATIONS_DIALOG( + "runDebugConfigurationsDialog", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/run-debug-configurations-dialog.html" + ), + SETUP_CREDENTIALS( + "setupCredentials", + "https://docs.aws.amazon.com/console/toolkit-for-jetbrains/credentials" + ), + SAM_CLI_INSTALL( + "sam.install", + "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" + ), + + // RDS + RDS_SETUP_IAM_AUTH( + "rdsIamAuth", + "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html" + ), + + // AWS CLI + AWS_CLI_INSTALL( + "awsCli.install", + "https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html" + ), + + // Ecs Exec + ECS_EXEC_PERMISSIONS_REQUIRED( + "ecsExecPermissions", + "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-enabling-and-using" + ), + + // What is AWS Toolkit? + AWS_TOOLKIT_GETTING_STARTED( + "awsToolkitGettingStarted", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html" + ), + + // CodeWhisperer + CODEWHISPERER_TOKEN( + "CodeWhispererToken", + "https://aws.amazon.com/codewhisperer" + ), + + // TODO: update this + CODEWHISPERER_LOGIN_YES_NO( + "CodeWhispererLoginYesNoDialog", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/setup-credentials.html" + ), + + // TODO: update this + CODEWHISPERER_LOGIN_DIALOG( + "CodeWhispererLoginDialog", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/setup-credentials.html" + ), + + // TODO: update this + TOOLKIT_ADD_CONNECTIONS_DIALOG( + "ToolkitAddConnectionsDialog", + "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/setup-credentials.html" + ), + + Q_SWITCH_PROFILES_DIALOG( + "QSwitchProfilesDialog", + "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/subscribe-understanding-profile.html" + ), + ; + + val id = "$HELP_ID_PREFIX.$shortId" +} + +const val HELP_ID_PREFIX = "aws.toolkit" diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/CustomizeNotificationsUi.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/CustomizeNotificationsUi.kt new file mode 100644 index 00000000000..2eebd33d800 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/CustomizeNotificationsUi.kt @@ -0,0 +1,129 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.icons.AllIcons +import com.intellij.ide.BrowserUtil +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.ui.EditorNotificationPanel +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit +import software.amazon.q.jetbrains.core.plugin.PluginUpdateManager +import software.amazon.q.resources.AwsCoreBundle + +fun checkSeverity(notificationSeverity: String): NotificationSeverity = when (notificationSeverity) { + "Critical" -> NotificationSeverity.CRITICAL + "Warning" -> NotificationSeverity.WARNING + "Info" -> NotificationSeverity.INFO + else -> NotificationSeverity.INFO +} + +object NotificationManager { + fun createActions( + project: Project, + followupActions: List?, + message: String, + title: String, + + ): List = buildList { + var url: String? = null + followupActions?.forEach { action -> + if (action.type == "ShowUrl") { + url = action.content.locale.url + } + + if (action.type == "UpdateExtension") { + add( + NotificationActionList(AwsCoreBundle.message("notification.update")) { + updatePlugins() + } + ) + } + + if (action.type == "OpenChangelog") { + add( + NotificationActionList(AwsCoreBundle.message("notification.changelog")) { + BrowserUtil.browse(AwsToolkit.GITHUB_CHANGELOG) + } + ) + } + } + add( + NotificationActionList(AwsCoreBundle.message("general.more_dialog")) { + if (url == null) { + Messages.showMessageDialog( + project, + message, + title, + AllIcons.General.Error + ) + } else { + val link = url ?: AwsToolkit.GITHUB_URL + val openLink = Messages.showYesNoDialog( + project, + message, + title, + AwsCoreBundle.message(AwsCoreBundle.message("notification.learn_more")), + AwsCoreBundle.message("general.cancel"), + AllIcons.General.Error + ) + if (openLink == 0) { + BrowserUtil.browse(link) + } + } + } + ) + } + + fun buildNotificationActions(actions: List): List = actions.map { (title, block) -> + object : AnAction(title) { + override fun actionPerformed(e: AnActionEvent) { + block() + } + } + } + + fun buildBannerPanel(panel: EditorNotificationPanel, actions: List): EditorNotificationPanel { + actions.forEach { (actionTitle, block) -> + panel.createActionLabel(actionTitle) { + block() + } + } + + return panel + } + private fun updatePlugins() { + val pluginUpdateManager = PluginUpdateManager() + runInEdt { + ProgressManager.getInstance().run(object : Task.Backgroundable( + null, + AwsCoreBundle.message("aws.settings.auto_update.progress.message") + ) { + override fun run(indicator: ProgressIndicator) { + pluginUpdateManager.checkForUpdates(indicator, AwsPlugin.Q) + } + }) + } + } +} + +data class NotificationActionList( + val title: String, + val blockToExecute: () -> Unit, +) + +data class BannerContent( + val id: String, + val title: String, + val message: String, + val actions: List = emptyList(), + val severity: NotificationSeverity = NotificationSeverity.INFO, +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/DisplayToastNotifications.kt new file mode 100644 index 00000000000..e72fac70958 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -0,0 +1,6 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +object DisplayToastNotifications diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationCustomDeserializers.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationCustomDeserializers.kt new file mode 100644 index 00000000000..bdf6539ff12 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationCustomDeserializers.kt @@ -0,0 +1,127 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonMappingException +import com.fasterxml.jackson.databind.JsonNode + +class OperationConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.OperationCondition = when (parser.currentToken) { + JsonToken.VALUE_STRING -> { + // Handle direct string value + NotificationExpression.OperationCondition(parser.valueAsString) + } + else -> throw JsonMappingException(parser, "Cannot deserialize OperatingCondition") + } +} + +class ComparisonConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComparisonCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.ComparisonCondition(op.value) + } +} + +class NotEqualsConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.NotEqualsCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.NotEqualsCondition(op.value) + } +} +class GreaterThanConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.GreaterThanCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.GreaterThanCondition(op.value) + } +} +class GreaterThanOrEqualsConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.GreaterThanOrEqualsCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.GreaterThanOrEqualsCondition(op.value) + } +} +class LessThanConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.LessThanCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.LessThanCondition(op.value) + } +} +class LessThanOrEqualsConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.LessThanOrEqualsCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.LessThanOrEqualsCondition(op.value) + } +} +class ComplexOperationConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComplexOperationCondition { + val node = parser.codec.readTree(parser) + if (!node.isArray) { + throw JsonMappingException(parser, "anyOf/noneOf must contain an array of values") + } + val values = node.map { it.asText() } + return NotificationExpression.ComplexOperationCondition(values) + } +} +class AnyOfConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.AnyOfCondition { + val op = ComplexOperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.AnyOfCondition(op.value) + } +} + +class NoneOfConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.NoneOfCondition { + val op = ComplexOperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.NoneOfCondition(op.value) + } +} + +class ComplexConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComplexCondition { + val node = parser.codec.readTree(parser) + if (!node.isArray) { + throw JsonMappingException(parser, "or/and must contain an array of values") + } + return NotificationExpression.ComplexCondition(node.toNotificationExpressions(parser)) + } +} +class OrConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.OrCondition { + val op = ComplexConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.OrCondition(op.expectedValueList) + } +} + +class AndConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.AndCondition { + val op = ComplexConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.AndCondition(op.expectedValueList) + } +} + +class NotConditionDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): NotificationExpression.NotCondition { + val node = p.codec.readTree(p) + val parser = node.traverse(p.codec) + parser.nextToken() + + return NotificationExpression.NotCondition(parser.readValueAs(NotificationExpression::class.java)) + } +} + +// Create a custom deserializer if needed +class NotificationTypeDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): NotificationScheduleType = + NotificationScheduleType.fromString(p.valueAsString) +} + +private fun JsonNode.toNotificationExpressions(p: JsonParser): List = this.map { element -> + val parser = element.traverse(p.codec) + parser.nextToken() + parser.readValueAs(NotificationExpression::class.java) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtils.kt new file mode 100644 index 00000000000..f78aae6cd1f --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtils.kt @@ -0,0 +1,200 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.databind.annotation.JsonDeserialize + +data class NotificationsList( + val schema: Schema, + val notifications: List?, +) + +data class Schema( + val version: String, +) + +data class NotificationData( + val id: String, + val schedule: NotificationSchedule, + val severity: String, + val condition: NotificationDisplayCondition?, + val content: NotificationContentDescriptionLocale, + val actions: List? = emptyList(), +) + +data class NotificationSchedule( + @JsonDeserialize(using = NotificationTypeDeserializer::class) + val type: NotificationScheduleType, +) { + constructor(type: String) : this(NotificationScheduleType.fromString(type)) +} + +enum class NotificationSeverity { + INFO, + WARNING, + CRITICAL, +} + +enum class NotificationScheduleType { + STARTUP, + EMERGENCY, + ; + + companion object { + fun fromString(value: String): NotificationScheduleType = + when (value.lowercase()) { + "startup" -> STARTUP + else -> EMERGENCY + } + } +} + +data class NotificationContentDescriptionLocale( + @JsonProperty("en-US") + val locale: NotificationContentDescription, +) + +data class NotificationContentDescription( + val title: String, + val description: String, +) + +data class NotificationFollowupActions( + val type: String, + val content: NotificationFollowupActionsContent, +) + +data class NotificationFollowupActionsContent( + @JsonProperty("en-US") + val locale: NotificationActionDescription, +) + +data class NotificationActionDescription( + val title: String, + val url: String?, +) + +data class NotificationDisplayCondition( + val compute: ComputeType?, + val os: SystemType?, + val ide: SystemType?, + val extension: List?, + val authx: List?, +) + +data class ComputeType( + val type: NotificationExpression?, + val architecture: NotificationExpression?, +) + +data class SystemType( + val type: NotificationExpression?, + val version: NotificationExpression?, +) + +data class ExtensionType( + val id: String?, + val version: NotificationExpression?, +) + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.WRAPPER_OBJECT +) +@JsonSubTypes( + JsonSubTypes.Type(value = NotificationExpression.ComparisonCondition::class, name = "=="), + JsonSubTypes.Type(value = NotificationExpression.NotEqualsCondition::class, name = "!="), + JsonSubTypes.Type(value = NotificationExpression.GreaterThanCondition::class, name = ">"), + JsonSubTypes.Type(value = NotificationExpression.GreaterThanOrEqualsCondition::class, name = ">="), + JsonSubTypes.Type(value = NotificationExpression.LessThanCondition::class, name = "<"), + JsonSubTypes.Type(value = NotificationExpression.LessThanOrEqualsCondition::class, name = "<="), + JsonSubTypes.Type(value = NotificationExpression.AnyOfCondition::class, name = "anyOf"), + JsonSubTypes.Type(value = NotificationExpression.NotCondition::class, name = "not"), + JsonSubTypes.Type(value = NotificationExpression.OrCondition::class, name = "or"), + JsonSubTypes.Type(value = NotificationExpression.AndCondition::class, name = "and"), + JsonSubTypes.Type(value = NotificationExpression.NoneOfCondition::class, name = "noneOf") +) +sealed interface NotificationExpression { + @JsonDeserialize(using = NotConditionDeserializer::class) + data class NotCondition( + val expectedValue: NotificationExpression, + ) : NotificationExpression + + @JsonDeserialize(using = OrConditionDeserializer::class) + data class OrCondition( + val expectedValueList: List, + ) : NotificationExpression + + @JsonDeserialize(using = AndConditionDeserializer::class) + data class AndCondition( + val expectedValueList: List, + ) : NotificationExpression + + @JsonDeserialize(using = ComplexConditionDeserializer::class) + data class ComplexCondition( + val expectedValueList: List, + ) : NotificationExpression + + // General class for comparison operators + @JsonDeserialize(using = OperationConditionDeserializer::class) + data class OperationCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = ComplexOperationConditionDeserializer::class) + data class ComplexOperationCondition( + val value: List, + ) : NotificationExpression + + @JsonDeserialize(using = ComparisonConditionDeserializer::class) + data class ComparisonCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = NotEqualsConditionDeserializer::class) + data class NotEqualsCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = GreaterThanConditionDeserializer::class) + data class GreaterThanCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = GreaterThanOrEqualsConditionDeserializer::class) + data class GreaterThanOrEqualsCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = LessThanConditionDeserializer::class) + data class LessThanCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = LessThanOrEqualsConditionDeserializer::class) + data class LessThanOrEqualsCondition( + val value: String, + ) : NotificationExpression + + @JsonDeserialize(using = AnyOfConditionDeserializer::class) + data class AnyOfCondition( + val value: List, + ) : NotificationExpression + + @JsonDeserialize(using = NoneOfConditionDeserializer::class) + data class NoneOfCondition( + val value: List, + ) : NotificationExpression +} + +data class AuthxType( + val feature: String, + val type: NotificationExpression?, + val region: NotificationExpression?, + val connectionState: NotificationExpression?, + val ssoScopes: NotificationExpression?, +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPanel.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPanel.kt new file mode 100644 index 00000000000..fcc6a35e4b3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPanel.kt @@ -0,0 +1,44 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.openapi.application.runInEdt +import com.intellij.ui.EditorNotificationPanel +import com.intellij.ui.components.panels.Wrapper +import com.intellij.util.ui.components.BorderLayoutPanel + +class NotificationPanel : BorderLayoutPanel() { + private val wrapper = Wrapper() + init { + isOpaque = false + addToCenter(wrapper) + BannerNotificationService.getInstance().getNotifications().forEach { (_, content) -> + updateNotificationPanel(content) + } + } + + private fun removeNotificationPanel(notificationId: String) = runInEdt { + BannerNotificationService.getInstance().removeNotification(notificationId) + NotificationDismissalState.getInstance().dismissNotification(notificationId) + wrapper.setContent(null) + } + + fun updateNotificationPanel(bannerContent: BannerContent) { + val panel = EditorNotificationPanel( + when (bannerContent.severity) { + NotificationSeverity.CRITICAL -> EditorNotificationPanel.Status.Error + NotificationSeverity.WARNING -> EditorNotificationPanel.Status.Warning + NotificationSeverity.INFO -> EditorNotificationPanel.Status.Info + } + ) + panel.text = bannerContent.title + + val panelWithActions = NotificationManager.buildBannerPanel(panel, bannerContent.actions) + panelWithActions.setCloseAction { + removeNotificationPanel(bannerContent.id) + } + + wrapper.setContent(panelWithActions) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt new file mode 100644 index 00000000000..6b9bf7de5ac --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt @@ -0,0 +1,143 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.util.registry.Registry +import com.intellij.util.Alarm +import com.intellij.util.AlarmFactory +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import software.amazon.q.jetbrains.core.DefaultRemoteResourceResolverProvider +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider +import software.amazon.q.core.utils.RemoteResolveParser +import software.amazon.q.core.utils.RemoteResource +import software.amazon.q.core.utils.UpdateCheckResult +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.Component +import software.aws.toolkits.telemetry.ToolkitTelemetry +import java.io.InputStream +import java.time.Duration + +private const val MAX_RETRIES = 3 +private const val RETRY_DELAY_MS = 1000L +internal const val FILENAME = "notifications.json" + +object NotificationFileValidator : RemoteResolveParser { + override fun canBeParsed(data: InputStream): Boolean = + try { + NotificationMapperUtil.mapper.readValue(data) + true + } catch (e: Exception) { + false + } +} + +object NotificationEndpoint { + fun getEndpoint(): String = + Registry.get("aws.toolkit.notification.endpoint").asString() +} + +@Service(Service.Level.APP) +internal final class NotificationPollingService : Disposable { + private val observers = mutableListOf<() -> Unit>() + private val alarm = AlarmFactory.getInstance().create(Alarm.ThreadToUse.POOLED_THREAD, this) + private val pollingIntervalMs = Duration.ofMinutes(10).toMillis() + private val resourceResolver: RemoteResourceResolverProvider = DefaultRemoteResourceResolverProvider() + private val notificationsResource = object : RemoteResource { + override val name: String = FILENAME + override val urls: List = listOf(NotificationEndpoint.getEndpoint()) + override val remoteResolveParser: RemoteResolveParser = NotificationFileValidator + override val ttl: Duration = Duration.ofMillis(1) + // ttl forces resolver to fetch from endpoint every time + } + + fun startPolling() { + val newNotifications = runBlocking { pollForNotifications() } + if (newNotifications) { + notifyObservers() + } + alarm.addRequest( + { startPolling() }, + pollingIntervalMs + ) + } + + private suspend fun pollForNotifications(): Boolean { + var retryCount = 0 + var lastException: Exception? = null + while (retryCount < MAX_RETRIES) { + LOG.info { "Polling for notifications" } + try { + when ( + resourceResolver.get().checkForUpdates( + NotificationEndpoint.getEndpoint(), + NotificationEtagState.getInstance() + ) + ) { + is UpdateCheckResult.HasUpdates -> { + resourceResolver.get() + .resolve(notificationsResource) + .toCompletableFuture() + .get() + LOG.info { "New notifications fetched" } + return true + } + is UpdateCheckResult.FirstPollCheck -> { + LOG.info { "No new notifications, checking cached notifications on first poll" } + return true + } + is UpdateCheckResult.NoUpdates -> { + LOG.info { "No new notifications to fetch" } + return false + } + } + } catch (e: Exception) { + lastException = e + LOG.warn { "Failed to poll for notifications (attempt ${retryCount + 1}/$MAX_RETRIES)" } + retryCount++ + if (retryCount < MAX_RETRIES) { + val backoffDelay = RETRY_DELAY_MS * (1L shl (retryCount - 1)) + delay(backoffDelay) + } + } + } + emitFailureMetric(lastException) + return false + } + + private fun emitFailureMetric(e: Exception?) { + ToolkitTelemetry.showNotification( + project = null, + component = Component.Filesystem, + id = "", + reason = "Failed to poll for notifications", + success = false, + reasonDesc = "${e?.javaClass?.simpleName ?: "Unknown"}: ${e?.message ?: "No message"}", + ) + } + + fun addObserver(observer: () -> Unit) = observers.add(observer) + + private fun notifyObservers() { + observers.forEach { observer -> + observer() + } + } + + override fun dispose() { + alarm.dispose() + } + + companion object { + private val LOG = getLogger() + fun getInstance(): NotificationPollingService = + ApplicationManager.getApplication().getService(NotificationPollingService::class.java) + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationServiceInitializer.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationServiceInitializer.kt new file mode 100644 index 00000000000..9d6f505beb7 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationServiceInitializer.kt @@ -0,0 +1,23 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import java.util.concurrent.atomic.AtomicBoolean + +internal class NotificationServiceInitializer : ProjectActivity { + + private val initialized = AtomicBoolean(false) + + override suspend fun execute(project: Project) { + ProcessNotificationsBase.getInstance(project) + if (ApplicationManager.getApplication().isUnitTestMode) return + if (initialized.compareAndSet(false, true)) { + val service = NotificationPollingService.getInstance() + service.startPolling() + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationStateUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationStateUtils.kt new file mode 100644 index 00000000000..28b8f92b62b --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationStateUtils.kt @@ -0,0 +1,109 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service +import software.amazon.q.core.utils.ETagProvider +import java.time.Duration +import java.time.Instant + +data class DismissedNotification( + var id: String = "", + var dismissedAt: String = Instant.now().toEpochMilli().toString(), +) + +data class NotificationDismissalConfiguration( + var dismissedNotifications: MutableSet = mutableSetOf(), +) + +@Service +@State(name = "qNotificationDismissals", storages = [Storage("amazonq.xml")]) +class NotificationDismissalState : PersistentStateComponent { + private var state = NotificationDismissalConfiguration() + private val retentionPeriod = Duration.ofDays(60) // 2 months + + override fun getState(): NotificationDismissalConfiguration = state + + override fun loadState(state: NotificationDismissalConfiguration) { + this.state = state + cleanExpiredNotifications() + } + + fun isDismissed(notificationId: String): Boolean = + state.dismissedNotifications.any { it.id == notificationId } + + fun dismissNotification(notificationId: String) { + state.dismissedNotifications.add( + DismissedNotification( + id = notificationId + ) + ) + } + + private fun cleanExpiredNotifications() { + val now = Instant.now() + state.dismissedNotifications.removeAll { notification -> + Duration.between(Instant.ofEpochMilli(notification.dismissedAt.toLong()), now) > retentionPeriod + } + } + + companion object { + fun getInstance(): NotificationDismissalState = service() + } +} + +@Service +@State(name = "qNotificationEtag", storages = [Storage("amazonq.xml")]) +class NotificationEtagState : PersistentStateComponent, ETagProvider { + private val state = NotificationEtagConfiguration() + + override fun updateETag(newETag: String?) { + etag = newETag + } + + override fun getState(): NotificationEtagConfiguration = state + + override fun loadState(state: NotificationEtagConfiguration) { + this.state.etag = state.etag + } + + override var etag: String? + get() = state.etag + set(value) { + state.etag = value + } + + companion object { + fun getInstance(): NotificationEtagState = + service() + } +} + +data class NotificationEtagConfiguration( + var etag: String? = null, +) + +@Service +class BannerNotificationService { + private val notifications = mutableMapOf() + + fun addNotification(id: String, content: BannerContent) { + notifications[id] = content + } + + fun getNotifications(): Map = notifications + + fun removeNotification(id: String) { + notifications.remove(id) + } + + companion object { + fun getInstance(): BannerNotificationService = + service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt new file mode 100644 index 00000000000..cb577a697a9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt @@ -0,0 +1,139 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.notification.NotificationType +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider +import software.amazon.q.jetbrains.utils.notifyStickyWithData +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.inputStream +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.Component +import software.aws.toolkits.telemetry.Result +import software.aws.toolkits.telemetry.ToolkitTelemetry +import java.util.concurrent.atomic.AtomicBoolean + +object NotificationMapperUtil { + val mapper: ObjectMapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) +} +private var isStartup: AtomicBoolean = AtomicBoolean(true) + +@Service(Service.Level.PROJECT) +class ProcessNotificationsBase( + private val project: Project, +) { + private val notifListener = mutableListOf() + init { + LOG.info { "Initializing ProcessNotificationsBase" } + NotificationPollingService.getInstance().addObserver { + retrieveStartupAndEmergencyNotifications() + } + } + + private fun getNotificationsFromFile(): NotificationsList? { + try { + val path = RemoteResourceResolverProvider + .getInstance() + .get() + .getLocalResourcePath(FILENAME) + if (path == null) { + LOG.warn { "Notifications file not found" } + return null + } + val content = path.inputStream().bufferedReader().use { it.readText() } + if (content.isEmpty()) { + return null + } + return NotificationMapperUtil.mapper.readValue(content) + } catch (e: Exception) { + LOG.warn { "Error reading notifications file: $e" } + return null + } + } + + fun retrieveStartupAndEmergencyNotifications() { + val isStartupPoll = isStartup.compareAndSet(true, false) + LOG.info { "Retrieving notifications for processing. StartUp notifications included: $isStartupPoll" } + val notifications = getNotificationsFromFile() + notifications?.let { notificationsList -> + val activeNotifications = notificationsList.notifications + ?.filter { notification -> + // Keep notification if: + // - it's not a startup notification, OR + // - it is a startup notification AND this is the first poll + notification.schedule.type != NotificationScheduleType.STARTUP || isStartupPoll + } + ?.filter { notification -> + !NotificationDismissalState.getInstance().isDismissed(notification.id) + } + .orEmpty() + + activeNotifications.forEach { processNotification(project, it) } + } + LOG.info { "Finished processing notifications" } + } + + fun processNotification(project: Project, notificationData: NotificationData) { + val shouldShow = RulesEngine.displayNotification(project, notificationData) + if (shouldShow) { + LOG.info { "Showing notification with id: ${notificationData.id}" } + val notificationContent = notificationData.content.locale + val severity = checkSeverity(notificationData.severity) + val followupActions = NotificationManager.createActions( + project, + notificationData.actions, + notificationContent.description, + notificationContent.title + ) + showToast( + notificationContent.title, + notificationContent.description, + NotificationManager.buildNotificationActions(followupActions), + severity, + notificationData.id + ) + if (severity == NotificationSeverity.CRITICAL) { + val bannerContent = BannerContent(notificationData.id, notificationContent.title, notificationContent.description, followupActions, severity) + BannerNotificationService.getInstance().addNotification(notificationData.id, bannerContent) + notifyListenerForNotification(bannerContent) + } + ToolkitTelemetry.showNotification( + id = notificationData.id, + result = Result.Succeeded, + component = Component.Infobar + ) + } + } + + private fun showToast(title: String, message: String, action: List, notificationType: NotificationSeverity, notificationId: String) { + val notifyType = when (notificationType) { + NotificationSeverity.CRITICAL -> NotificationType.ERROR + NotificationSeverity.WARNING -> NotificationType.WARNING + NotificationSeverity.INFO -> NotificationType.INFORMATION + } + notifyStickyWithData(notifyType, title, message, null, action, notificationId) + } + + fun notifyListenerForNotification(bannerContent: BannerContent) = + notifListener.forEach { it(bannerContent) } + + fun addListenerForNotification(newNotifListener: NotifListener) = + notifListener.add(newNotifListener) + + companion object { + private val LOG = getLogger() + fun getInstance(project: Project): ProcessNotificationsBase = project.service() + } +} + +typealias NotifListener = (bannerContent: BannerContent) -> Unit diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/RulesEngine.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/RulesEngine.kt new file mode 100644 index 00000000000..8f894e7536a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/RulesEngine.kt @@ -0,0 +1,209 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SystemInfo +import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection +import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnectionType +import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet +import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity +import software.amazon.q.jetbrains.core.gettingstarted.editor.checkIamProfileByCredentialType +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend + +object RulesEngine { + + fun displayNotification(project: Project, notification: NotificationData): Boolean { + // If no conditions provided, show display the notification to everyone + val shouldShow = notification.condition?.let { matchesAllRules(it, project) } ?: true + return shouldShow + } + + fun matchesAllRules(notificationConditions: NotificationDisplayCondition, project: Project): Boolean { + val sysDetails = getCurrentSystemAndConnectionDetails() + // If any of these conditions are null, we assume the condition to be true + val compute = notificationConditions.compute?.let { matchesCompute(it, sysDetails.computeType, sysDetails.computeArchitecture) } ?: true + val os = notificationConditions.os?.let { matchesOs(it, sysDetails.osType, sysDetails.osVersion) } ?: true + val ide = notificationConditions.ide?.let { matchesIde(it, sysDetails.ideType, sysDetails.ideVersion) } ?: true + val extension = matchesExtension(notificationConditions.extension, sysDetails.pluginVersions) + val authx = matchesAuth(notificationConditions.authx, project) + return compute && os && ide && extension && authx + } + + private fun matchesCompute(notificationCompute: ComputeType, actualCompute: String, actualArchitecture: String): Boolean { + val type = notificationCompute.type?.let { evaluateNotificationExpression(it, actualCompute) } ?: true + val architecture = notificationCompute.architecture?.let { evaluateNotificationExpression(it, actualArchitecture) } ?: true + return type && architecture + } + + private fun matchesOs(notificationOs: SystemType, actualOs: String, actualOsVersion: String): Boolean { + val os = notificationOs.type?.let { evaluateNotificationExpression(it, actualOs) } ?: true + val osVersion = notificationOs.version?.let { evaluateNotificationExpression(it, actualOsVersion) } ?: true + return os && osVersion + } + + private fun matchesIde(notificationIde: SystemType, actualIde: String, actualIdeVersion: String): Boolean { + val ide = notificationIde.type?.let { evaluateNotificationExpression(it, actualIde) } ?: true + val ideVersion = notificationIde.version?.let { evaluateNotificationExpression(it, actualIdeVersion) } ?: true + return ide && ideVersion + } + + private fun matchesExtension(notificationExtension: List?, actualPluginVersions: Map): Boolean { + if (notificationExtension.isNullOrEmpty()) return true + val extensionsToBeChecked = notificationExtension.map { it.id } + val pluginVersions = actualPluginVersions.filterKeys { extensionsToBeChecked.contains(it) } + if (pluginVersions.isEmpty()) return false + return notificationExtension.all { extension -> + val actualVersion = pluginVersions[extension.id] + if (actualVersion == null) { + true + } else { + extension.version?.let { evaluateNotificationExpression(it, actualVersion) } ?: true + } + } + } + + private fun matchesAuth(notificationAuth: List?, project: Project): Boolean { + if (notificationAuth.isNullOrEmpty()) return true + return notificationAuth.all { feature -> + val actualConnection = when (feature.feature) { + "q" -> getConnectionDetailsForFeature(project, BearerTokenFeatureSet.Q) + "codeCatalyst" -> getConnectionDetailsForFeature(project, BearerTokenFeatureSet.CODECATALYST) + "toolkit" -> getConnectionDetailsForToolkit(project) + else -> return true + } + + if (actualConnection == null) { + false + } else { + val authType = feature.type?.let { evaluateNotificationExpression(it, actualConnection.connectionType) } ?: true + val authRegion = feature.region?.let { evaluateNotificationExpression(it, actualConnection.region) } ?: true + val connectionState = feature.connectionState?.let { evaluateNotificationExpression(it, actualConnection.connectionState) } ?: true + // TODO: Add condition for matching scopes + authType && authRegion && connectionState + } + } + } + + private fun evaluateNotificationExpression(notificationExpression: NotificationExpression, value: String): Boolean = when (notificationExpression) { + is NotificationExpression.NotCondition -> performNotOp(notificationExpression, value) + is NotificationExpression.OrCondition -> performOrOp(notificationExpression, value) + is NotificationExpression.AndCondition -> performAndOp(notificationExpression, value) + is NotificationExpression.ComparisonCondition -> notificationExpression.value == value + is NotificationExpression.NotEqualsCondition -> notificationExpression.value != value + is NotificationExpression.GreaterThanCondition -> value > notificationExpression.value + is NotificationExpression.LessThanCondition -> value < notificationExpression.value + is NotificationExpression.GreaterThanOrEqualsCondition -> value >= notificationExpression.value + is NotificationExpression.LessThanOrEqualsCondition -> value <= notificationExpression.value + is NotificationExpression.AnyOfCondition -> notificationExpression.value.contains(value) + is NotificationExpression.NoneOfCondition -> !notificationExpression.value.contains(value) + else -> true + } + + private fun performNotOp(notificationOperation: NotificationExpression.NotCondition, actualValue: String): Boolean = + !evaluateNotificationExpression(notificationOperation.expectedValue, actualValue) + + private fun performOrOp(notificationOperation: NotificationExpression.OrCondition, actualValue: String): Boolean = + notificationOperation.expectedValueList.any { evaluateNotificationExpression(it, actualValue) } + + private fun performAndOp(notificationOperation: NotificationExpression.AndCondition, actualValue: String): Boolean = + notificationOperation.expectedValueList.all { evaluateNotificationExpression(it, actualValue) } +} + +fun getCurrentSystemAndConnectionDetails(): SystemDetails { + val computeType: String = if (isRunningOnRemoteBackend()) "Remote" else "Local" + val computeArchitecture: String = SystemInfo.OS_ARCH + + val osType: String = SystemInfo.OS_NAME + val osVersion: String = SystemInfo.OS_VERSION + + val ideInfo = ApplicationInfo.getInstance() + val ideType: String = ideInfo.build.productCode + val ideVersion = ideInfo.shortVersion + + val pluginVersionMap = createPluginVersionMap() + + return SystemDetails(computeType, computeArchitecture, osType, osVersion, ideType, ideVersion, pluginVersionMap) +} + +data class FeatureAuthDetails( + val connectionType: String, + val region: String, + val connectionState: String, +) + +data class SystemDetails( + val computeType: String, + val computeArchitecture: String, + val osType: String, + val osVersion: String, + val ideType: String, + val ideVersion: String, + val pluginVersions: Map, +) + +fun createPluginVersionMap(): Map { + val pluginVersionMap = mutableMapOf() + val pluginIds = listOf( + "amazon.q", + "aws.toolkit" + ) + pluginIds.forEach { pluginId -> + val plugin = PluginManagerCore.getPlugin(PluginId.getId(pluginId)) + val pluginVersion = plugin?.version + if (pluginVersion != null) { + pluginVersionMap[pluginId] = pluginVersion + } + } + return pluginVersionMap +} + +private fun getConnectionDetailsForToolkit(project: Project): FeatureAuthDetails? { + val connection = checkIamProfileByCredentialType(project) + if (connection.activeConnectionIam == null) return null + val authType = when (connection.connectionType) { + ActiveConnectionType.IAM_IDC -> "Idc" + ActiveConnectionType.IAM -> "Iam" + else -> "Unknown" + } + val authRegion = connection.activeConnectionIam?.defaultRegionId ?: "Unknown" + + val connectionState = when (connection) { + is ActiveConnection.NotConnected -> "NotConnected" + is ActiveConnection.ValidIam -> "Connected" + is ActiveConnection.ExpiredIam -> "Expired" + else -> "Unknown" + } + return FeatureAuthDetails( + authType, + authRegion, + connectionState + ) +} + +fun getConnectionDetailsForFeature(project: Project, featureId: BearerTokenFeatureSet): FeatureAuthDetails? { + val connection = checkBearerConnectionValidity(project, featureId) + if (connection.activeConnectionBearer == null) return null + val authType = when (connection.connectionType) { + ActiveConnectionType.BUILDER_ID -> "BuilderId" + ActiveConnectionType.IAM_IDC -> "Idc" + else -> "Unknown" + } + val authRegion = connection.activeConnectionBearer?.region ?: "Unknown" + + val connectionState = when (connection) { + is ActiveConnection.NotConnected -> "NotConnected" + is ActiveConnection.ValidBearer -> "Connected" + is ActiveConnection.ExpiredBearer -> "Expired" + else -> "Unknown" + } + return FeatureAuthDetails( + authType, + authRegion, + connectionState + ) +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginAutoUpdater.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginAutoUpdater.kt new file mode 100644 index 00000000000..281537e5ac1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginAutoUpdater.kt @@ -0,0 +1,24 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.plugin + +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import software.amazon.q.jetbrains.settings.AwsSettings +import java.util.concurrent.atomic.AtomicBoolean + +class PluginAutoUpdater : ProjectActivity { + private val autoUpdateRunOnce = AtomicBoolean(false) + + override suspend fun execute(project: Project) { + // We want the auto-update feature to be triggered only once per running application + if (!autoUpdateRunOnce.getAndSet(true)) { + PluginUpdateManager.getInstance().scheduleAutoUpdate() + if (!AwsSettings.getInstance().isAutoUpdateFeatureNotificationShownOnce) { + PluginUpdateManager.getInstance().notifyAutoUpdateFeature(project) + AwsSettings.getInstance().isAutoUpdateFeatureNotificationShownOnce = true + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt new file mode 100644 index 00000000000..0d078be5298 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt @@ -0,0 +1,232 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.plugin + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.ide.plugins.InstalledPluginsState +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.notification.NotificationAction +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.options.ShowSettingsUtil +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.updateSettings.impl.PluginDownloader +import com.intellij.openapi.updateSettings.impl.UpdateChecker +import com.intellij.util.Alarm +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.AwsSettingsSharedConfigurable +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.aws.toolkits.telemetry.Component +import software.aws.toolkits.telemetry.Result +import software.aws.toolkits.telemetry.ToolkitTelemetry + +@Service +class PluginUpdateManager : Disposable { + private val alarm = Alarm(Alarm.ThreadToUse.SWING_THREAD, this) + + fun scheduleAutoUpdate() { + if (alarm.isDisposed) return + scheduleUpdateTask() + + val enabled = AwsSettings.getInstance().isAutoUpdateEnabled + LOG.debug { "AWS plugins checking for new updates. Auto update enabled: $enabled" } + + if (!enabled) return + + runInEdt { + ProgressManager.getInstance().run(object : Task.Backgroundable( + null, + AwsCoreBundle.message("aws.settings.auto_update.progress.message") + ) { + override fun run(indicator: ProgressIndicator) { + checkForUpdates(indicator, AwsPlugin.Q) + } + }) + } + } + + private fun scheduleUpdateTask() { + alarm.addRequest({ scheduleAutoUpdate() }, UPDATE_CHECK_INTERVAL_IN_MS) + } + + @RequiresBackgroundThread + fun checkForUpdates(progressIndicator: ProgressIndicator, plugin: AwsPlugin) { + val pluginInfo = AwsToolkit.PLUGINS_INFO[plugin] ?: return + val pluginDescriptor = pluginInfo.descriptor as? IdeaPluginDescriptor ?: return + val pluginName = pluginInfo.name + // Note: This will need to handle exceptions and ensure thread-safety + try { + // If plugin is not installed, do not perform auto-update + if (!PluginManagerCore.isPluginInstalled(pluginDescriptor.pluginId)) { + LOG.debug { "$pluginName is not detected as installed, not performing auto-update" } + return + } + + if (!updatePlugin(pluginDescriptor, progressIndicator)) return + + // TODO: distinguish telemetry + ToolkitTelemetry.showAction( + project = null, + success = true, + id = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + source = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + component = Component.Filesystem + ) + } catch (e: Exception) { + LOG.debug(e) { "Unable to update $pluginName" } + // TODO: distinguish telemetry + ToolkitTelemetry.showAction( + project = null, + success = false, + id = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + source = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + component = Component.Filesystem, + reason = e.message + ) + return + } catch (e: Error) { + // Handle cases like NoSuchMethodError when the API is not available in certain versions + LOG.debug(e) { "Unable to update $pluginName" } + // TODO: distinguish telemetry + ToolkitTelemetry.showAction( + project = null, + success = false, + id = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + source = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + component = Component.Filesystem, + reason = e.message + ) + return + } + if (!AwsSettings.getInstance().isAutoUpdateNotificationEnabled) return + notifyInfo( + title = AwsCoreBundle.message("aws.notification.auto_update.title", pluginName), + content = AwsCoreBundle.message("aws.settings.auto_update.notification.message"), + project = null, + notificationActions = listOf( + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("aws.settings.auto_update.notification.yes")) { + // TODO: distinguish telemetry + ToolkitTelemetry.invokeAction( + project = null, + result = Result.Succeeded, + id = "autoUpdateActionRestart", + source = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + component = Component.Filesystem, + action = "restart" + ) + ApplicationManager.getApplication().restart() + }, + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("aws.settings.auto_update.notification.no")) { + // TODO: distinguish telemetry + ToolkitTelemetry.invokeAction( + project = null, + result = Result.Succeeded, + id = "autoUpdateActionNotNow", + source = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + component = Component.Filesystem, + action = "notNow" + ) + }, + NotificationAction.createSimple(AwsCoreBundle.message("aws.notification.auto_update.settings.title")) { + // TODO: distinguish telemetry + ToolkitTelemetry.invokeAction( + project = null, + result = Result.Succeeded, + id = ID_ACTION_AUTO_UPDATE_SETTINGS, + source = SOURCE_AUTO_UPDATE_FINISH_NOTIFY, + component = Component.Filesystem, + action = "showSettingsDialog" + ) + ShowSettingsUtil.getInstance().showSettingsDialog(null, AwsSettingsSharedConfigurable::class.java) + } + ) + ) + } + + fun notifyAutoUpdateFeature(project: Project) { + notifyInfo( + title = AwsCoreBundle.message("aws.notification.auto_update.feature_intro.title"), + project = project, + notificationActions = listOf( + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("aws.notification.auto_update.feature_intro.ok")) {}, + NotificationAction.createSimple(AwsCoreBundle.message("aws.notification.auto_update.settings.title")) { + ToolkitTelemetry.invokeAction( + project = null, + result = Result.Succeeded, + id = ID_ACTION_AUTO_UPDATE_SETTINGS, + source = SOURCE_AUTO_UPDATE_FEATURE_INTRO_NOTIFY, + component = Component.Filesystem, + action = "showSettingsDialog" + ) + ShowSettingsUtil.getInstance().showSettingsDialog(project, AwsSettingsSharedConfigurable::class.java) + } + ) + ) + } + + override fun dispose() {} + + companion object { + fun getInstance(): PluginUpdateManager = service() + private val LOG = getLogger() + private const val UPDATE_CHECK_INTERVAL_IN_MS = 4 * 60 * 60 * 1000 // 4 hours + private const val SOURCE_AUTO_UPDATE_FINISH_NOTIFY = "autoUpdateFinishNotification" + const val SOURCE_AUTO_UPDATE_FEATURE_INTRO_NOTIFY = "autoUpdateFeatureIntroNotification" + const val ID_ACTION_AUTO_UPDATE_SETTINGS = "autoUpdateActionSettings" + + fun getUpdate(pluginDescriptor: IdeaPluginDescriptor): PluginDownloader? = + getUpdateInfo().firstOrNull { + it.id == pluginDescriptor.pluginId && + PluginDownloader.compareVersionsSkipBrokenAndIncompatible(it.pluginVersion, pluginDescriptor) > 0 + } + + // TODO: Optimize this to only search the result for AWS plugins + fun getUpdateInfo(): Collection = UpdateChecker.getPluginUpdates() ?: emptyList() + + fun updatePlugin(pluginDescriptor: IdeaPluginDescriptor, progressIndicator: ProgressIndicator): Boolean { + val pluginName = pluginDescriptor.name + + // wasUpdatedWithRestart means that, it was an update and it needs to restart to apply + if (InstalledPluginsState.getInstance().wasUpdatedWithRestart(pluginDescriptor.pluginId)) { + LOG.info { "$pluginName was recently updated and needed restart, not performing auto-update again" } + return false + } + + if (pluginDescriptor.version.contains("SNAPSHOT", ignoreCase = true)) { + LOG.info { "$pluginName is a SNAPSHOT version, not performing auto-update" } + return false + } + if (!pluginDescriptor.isEnabled) { + LOG.info { "$pluginName is disabled, not performing auto-update" } + return false + } + LOG.debug { "Current version: ${pluginDescriptor.version}" } + val latestPluginDownloader = getUpdate(pluginDescriptor) + if (latestPluginDownloader == null) { + LOG.info { "$pluginName no newer version found, not performing auto-update" } + return false + } else { + LOG.info { "$pluginName found newer version: ${latestPluginDownloader.pluginVersion}" } + } + + if (!latestPluginDownloader.prepareToInstall(progressIndicator)) return false + latestPluginDownloader.install() + + return true + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt new file mode 100644 index 00000000000..6abf6d81cbd --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt @@ -0,0 +1,67 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.region + +import com.intellij.openapi.components.service +import software.amazon.awssdk.regions.providers.AwsProfileRegionProvider +import software.amazon.awssdk.regions.providers.AwsRegionProviderChain +import software.amazon.awssdk.regions.providers.SystemSettingsRegionProvider +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider +import software.amazon.q.core.region.AwsPartition +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.region.PartitionParser +import software.amazon.q.core.region.ServiceEndpointResource +import software.amazon.q.core.region.ToolkitRegionProvider +import software.amazon.q.core.utils.inputStream +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.resources.BundledResources + +class AwsRegionProvider : ToolkitRegionProvider() { + private val regionChain by lazy { + // Querying the instance metadata is expensive due to high timeouts and retries + AwsRegionProviderChain(SystemSettingsRegionProvider(), AwsProfileRegionProvider()) + } + private val partitions: Map by lazy { + val inputStream = RemoteResourceResolverProvider.getInstance().get().resolve(ServiceEndpointResource).toCompletableFuture().get()?.inputStream() + val partitions = inputStream?.use { PartitionParser.parse(it) }?.partitions + ?: BundledResources.ENDPOINTS_FILE.use { PartitionParser.parse(BundledResources.ENDPOINTS_FILE) }?.partitions + ?: throw Exception("Failed to retrieve partitions.") + + partitions.asSequence().associateBy { it.partition }.mapValues { + PartitionData( + it.value.partitionName, + it.value.services, + it.value.regions.asSequence().associate { region -> region.key to AwsRegion(region.key, region.value.description, it.key) } + ) + } + } + + override fun partitionData(): Map = partitions + + override fun defaultPartition(): AwsPartition = partitions().getValue(defaultRegion().partitionId) + + override fun defaultRegion(): AwsRegion { + val regionIdFromChain = tryOrNull { + regionChain.region.id() + } + + val regionFromChain = regionIdFromChain?.let { regionId -> + tryOrNull { + this[regionId] + } + } + + return regionFromChain + ?: this[DEFAULT_REGION] + ?: allRegions().values.firstOrNull() + ?: throw IllegalStateException("Region provider data is missing default data") + } + + companion object { + private const val DEFAULT_REGION = "us-east-1" + + @JvmStatic + fun getInstance(): ToolkitRegionProvider = service() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/BrowserMessage.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/BrowserMessage.kt new file mode 100644 index 00000000000..41bb3ff59aa --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/BrowserMessage.kt @@ -0,0 +1,78 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.webview + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo + +/** + * Message received from the login browser + * property name "command", please refer to [AwsLoginBrowser.getWebviewHTML] and Webview package [defs.d.ts] + */ +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "command" +) +@JsonSubTypes( + JsonSubTypes.Type(value = BrowserMessage.ToggleBrowser::class, name = "toggleBrowser"), + JsonSubTypes.Type(value = BrowserMessage.PrepareUi::class, name = "prepareUi"), + JsonSubTypes.Type(value = BrowserMessage.SelectConnection::class, name = "selectConnection"), + JsonSubTypes.Type(value = BrowserMessage.LoginBuilderId::class, name = "loginBuilderId"), + JsonSubTypes.Type(value = BrowserMessage.LoginIdC::class, name = "loginIdC"), + JsonSubTypes.Type(value = BrowserMessage.LoginIAM::class, name = "loginIAM"), + JsonSubTypes.Type(value = BrowserMessage.CancelLogin::class, name = "cancelLogin"), + JsonSubTypes.Type(value = BrowserMessage.Signout::class, name = "signout"), + JsonSubTypes.Type(value = BrowserMessage.Reauth::class, name = "reauth"), + JsonSubTypes.Type(value = BrowserMessage.SendUiClickTelemetry::class, name = "sendUiClickTelemetry"), + JsonSubTypes.Type(value = BrowserMessage.SwitchProfile::class, name = "switchProfile"), + JsonSubTypes.Type(value = BrowserMessage.PublishWebviewTelemetry::class, name = "webviewTelemetry"), + JsonSubTypes.Type(value = BrowserMessage.OpenUrl::class, name = "openUrl"), + JsonSubTypes.Type(value = BrowserMessage.ListProfiles::class, name = "listProfiles") +) +sealed interface BrowserMessage { + + // FIX_WHEN_MIN_IS_233: data objects are not stable until Kotlin 1.9 + // https://kotlinlang.org/docs/whatsnew19.html#stable-data-objects-for-symmetry-with-data-classes + object PrepareUi : BrowserMessage + + data class SelectConnection(val connectionId: String) : BrowserMessage + + object ToggleBrowser : BrowserMessage + + object LoginBuilderId : BrowserMessage + + data class LoginIdC( + val url: String, + val region: String, + val feature: String, + ) : BrowserMessage + + data class LoginIAM( + val profileName: String, + val accessKey: String, + val secretKey: String, + ) : BrowserMessage + + object CancelLogin : BrowserMessage + + object Signout : BrowserMessage + + object Reauth : BrowserMessage + + data class OpenUrl(val externalLink: String) : BrowserMessage + + data class SwitchProfile( + val profileName: String, + val accountId: String, + val region: String, + val arn: String, + ) : BrowserMessage + + object ListProfiles : BrowserMessage + + data class SendUiClickTelemetry(val signInOptionClicked: String?) : BrowserMessage + + data class PublishWebviewTelemetry(val event: String) : BrowserMessage +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LocalAssetJBCefRequestHandler.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LocalAssetJBCefRequestHandler.kt new file mode 100644 index 00000000000..d322e1a6610 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LocalAssetJBCefRequestHandler.kt @@ -0,0 +1,83 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.webview + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import com.intellij.ui.jcef.JBCefBrowserBase +import contrib.org.intellij.images.editor.impl.jcef.JBCefLocalRequestHandler +import contrib.org.intellij.images.editor.impl.jcef.JBCefStreamResourceHandler +import org.cef.browser.CefBrowser +import org.cef.browser.CefFrame +import org.cef.handler.CefResourceHandler +import org.cef.handler.CefResourceRequestHandler +import org.cef.misc.BoolRef +import org.cef.network.CefRequest +import java.io.InputStream +import java.net.URI +import java.net.URLConnection +import kotlin.io.byteInputStream + +class LocalAssetJBCefRequestHandler(jbCefBrowser: JBCefBrowserBase) : JBCefLocalRequestHandler(PROTOCOL, AUTHORITY), Disposable { + init { + jbCefBrowser.jbCefClient.addRequestHandler(this, jbCefBrowser.cefBrowser) + + Disposer.register(jbCefBrowser, this) + } + + private val wildcardHandlers = mutableMapOf CefResourceHandler?>() + + private fun streamHandler(path: String, stream: InputStream) = + JBCefStreamResourceHandler( + stream, + if (path.endsWith(".wasm") == true) "application/wasm" else URLConnection.getFileNameMap().getContentTypeFor(path), + this + ) + + fun addWildcardHandler(prefix: String, handler: (String) -> InputStream?) { + wildcardHandlers[prefix] = { path -> + handler(path)?.let { stream -> + streamHandler(path, stream) + } + } + } + + fun addResource(path: String, stream: InputStream?) = + addResource(path) { + stream?.let { streamHandler(path, it) } + } + + fun createResource(path: String, stream: InputStream?) = + createResource(path) { + stream?.let { streamHandler(path, it) } + } + + fun createResource(path: String, content: String) = + createResource(path, content.byteInputStream()) + + override fun getResourceRequestHandler( + browser: CefBrowser?, + frame: CefFrame?, + request: CefRequest?, + isNavigation: Boolean, + isDownload: Boolean, + requestInitiator: String?, + disableDefaultHandling: BoolRef?, + ): CefResourceRequestHandler { + val resource = request?.url?.let { URI(it).path.trim('/') } + val handler = wildcardHandlers.entries.firstOrNull { (k, _) -> resource?.startsWith(k) == true } + if (handler != null) { + return resourceHandlerWrapper { path -> handler.value(path) } + } + return super.getResourceRequestHandler(browser, frame, request, isNavigation, isDownload, requestInitiator, disableDefaultHandling) + } + + override fun dispose() { + } + + companion object { + const val PROTOCOL = "https" + const val AUTHORITY = "toolkitasset" + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt new file mode 100644 index 00000000000..8200e203ee0 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt @@ -0,0 +1,460 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.webview + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.blockingContext +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.platform.ide.progress.withBackgroundProgress +import com.intellij.ui.JBColor +import com.intellij.ui.jcef.JBCefBrowserBase +import com.intellij.ui.jcef.JBCefJSQuery +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.intellij.lang.annotations.Language +import org.jetbrains.annotations.VisibleForTesting +import org.slf4j.event.Level +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.Login +import software.amazon.q.jetbrains.core.credentials.ReauthSource +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.CodeCatalystConnection +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded +import software.amazon.q.jetbrains.core.credentials.sono.CODECATALYST_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.PendingAuthorization +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.ssoErrorMessageFromException +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.jetbrains.utils.pollFor +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull +import software.aws.toolkits.telemetry.AuthType +import software.aws.toolkits.telemetry.AwsTelemetry +import software.aws.toolkits.telemetry.CredentialSourceId +import software.aws.toolkits.telemetry.CredentialType +import software.aws.toolkits.telemetry.FeatureId +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Result +import software.aws.toolkits.telemetry.Telemetry +import java.time.Duration +import java.util.Timer +import java.util.TimerTask +import java.util.concurrent.CancellationException +import java.util.concurrent.Future +import java.util.function.Function + +data class BrowserState(val feature: FeatureId, val browserCancellable: Boolean = false, val requireReauth: Boolean = false) + +abstract class LoginBrowser( + private val project: Project, +) { + abstract val jcefBrowser: JBCefBrowserBase + + protected val jcefHandler = Function { + val obj = LOG.tryOrNull("${this::class.simpleName} unable deserialize login browser message: $it", Level.ERROR) { + objectMapper.readValue(it) + } + + handleBrowserMessage(obj) + + JBCefJSQuery.Response(null) + } + protected var currentAuthorization: PendingAuthorization? = null + + @VisibleForTesting + internal val objectMapper = jacksonObjectMapper() + + abstract fun handleBrowserMessage(message: BrowserMessage?) + + protected data class BearerConnectionSelectionSettings(val currentSelection: AwsBearerTokenConnection, val onChange: (AwsBearerTokenConnection) -> Unit) + + protected val selectionSettings = mutableMapOf() + + private var browserOpenTimer: Timer? = null + + private fun startBrowserOpenTimer(startUrl: String, ssoRegion: String, scopes: List) { + browserOpenTimer = Timer() + browserOpenTimer?.schedule( + object : TimerTask() { + override fun run() { + AwsTelemetry.loginWithBrowser( + project = null, + credentialStartUrl = startUrl, + result = Result.Failed, + reason = "Browser authentication idle for more than 15min", + credentialSourceId = if (startUrl == SONO_URL) CredentialSourceId.AwsId else CredentialSourceId.IamIdentityCenter, + authType = getAuthType(ssoRegion), + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + Telemetry.auth.addConnection.use { + it.result(MetricResult.Failed) + .reason("Browser authentication idle for more than 15min") + .credentialSourceId(if (startUrl == SONO_URL) CredentialSourceId.AwsId else CredentialSourceId.IamIdentityCenter) + .isAggregated(false) + .featureId(getFeatureId(scopes)) + .isReAuth(isReAuth(scopes, startUrl)) + .source(SourceOfEntry.LOGIN_BROWSER.toString()) + } + stopAndClearBrowserOpenTimer() + } + }, + Duration.ofMinutes(15).toMillis(), + ) + } + + protected fun stopAndClearBrowserOpenTimer() { + if (browserOpenTimer != null) { + browserOpenTimer?.cancel() + browserOpenTimer?.purge() + browserOpenTimer = null + } + } + + protected val onPendingToken: (InteractiveBearerTokenProvider) -> Unit = { provider -> + startBrowserOpenTimer(provider.startUrl, provider.region, provider.scopes) + + projectCoroutineScope(project).launch { + val authorization = pollForAuthorization(provider) + if (authorization != null) { + executeJS("window.ideClient.updateAuthorization(\"${userCodeFromAuthorization(authorization)}\")") + currentAuthorization = authorization + } + } + } + + abstract fun prepareBrowser(state: BrowserState) + + fun executeJS(@Language("JS") jsScript: String) { + this.jcefBrowser.cefBrowser.let { + it.executeJavaScript(jsScript, it.url, 0) + } + } + + // TODO: Dumb and will be addressed in followup PRs + protected fun writeValueAsString(o: Any) = objectMapper.writeValueAsString(o) + + protected fun cancelLogin() { + stopAndClearBrowserOpenTimer() + // Essentially Authorization becomes a mutable that allows browser and auth to communicate canceled + // status. There might be a risk of race condition here by changing this global, for which effort + // has been made to avoid it (e.g. Cancel button is only enabled if Authorization has been given + // to browser.). The worst case is that the user will see a stale user code displayed, but not + // affecting the current login flow. + currentAuthorization?.progressIndicator?.cancel() + // TODO: telemetry + } + + fun userCodeFromAuthorization(authorization: PendingAuthorization) = when (authorization) { + is PendingAuthorization.DAGAuthorization -> authorization.authorization.userCode + else -> "" + } + + private fun isReAuth(scopes: List, requestedStartUrl: String): Boolean = ToolkitConnectionManager.getInstance(project) + .let { + val qConnection = it.activeConnectionForFeature(QConnection.getInstance()) as AwsBearerTokenConnection? + val codeCatalystConnection = it.activeConnectionForFeature(CodeCatalystConnection.getInstance()) as AwsBearerTokenConnection? + if (scopes.toSet().intersect(Q_SCOPES.toSet()).isNotEmpty()) { + qConnection != null && qConnection.startUrl == requestedStartUrl + } else if (scopes.toSet().intersect(CODECATALYST_SCOPES.toSet()).isNotEmpty()) { + codeCatalystConnection != null && codeCatalystConnection.startUrl == requestedStartUrl + } else { + if (it.activeConnection() is AwsBearerTokenConnection) { + val activeCon = it.activeConnection() as? AwsBearerTokenConnection + return activeCon?.scopes?.toSet()?.intersect(IDENTITY_CENTER_ROLE_ACCESS_SCOPE.toSet())?.isNotEmpty() == true && + activeCon != qConnection && + activeCon != codeCatalystConnection + } else { + return false + } + } + } + + open fun loginBuilderId(scopes: List) { + val isReauth = isReAuth(scopes, SONO_URL) + val featureId = getFeatureId(scopes) + val onError: (Exception) -> Unit = { e -> + stopAndClearBrowserOpenTimer() + isUserCancellation(e) + AwsTelemetry.loginWithBrowser( + project = null, + credentialStartUrl = SONO_URL, + result = Result.Failed, + reason = e.message, + credentialSourceId = CredentialSourceId.AwsId, + isReAuth = isReauth, + authType = getAuthType(SONO_REGION), + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + Telemetry.auth.addConnection.use { + it.result(MetricResult.Failed) + .credentialSourceId(CredentialSourceId.AwsId) + .reason(e.message) + .isReAuth(isReauth) + .featureId(featureId) + .isAggregated(false) + .source(SourceOfEntry.LOGIN_BROWSER.toString()) + } + } + val onSuccess: () -> Unit = { + stopAndClearBrowserOpenTimer() + AwsTelemetry.loginWithBrowser( + project = null, + credentialStartUrl = SONO_URL, + result = Result.Succeeded, + credentialSourceId = CredentialSourceId.AwsId, + isReAuth = isReauth, + authType = getAuthType(SONO_REGION), + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + Telemetry.auth.addConnection.use { + it.result(MetricResult.Succeeded) + .credentialSourceId(CredentialSourceId.AwsId) + .isReAuth(isReauth) + .featureId(featureId) + .isAggregated(true) + .source(SourceOfEntry.LOGIN_BROWSER.toString()) + } + } + + loginWithBackgroundContext { + Login + .BuilderId(scopes, onPendingToken, onError, onSuccess) + .login(project) + } + } + + open fun loginIdC(url: String, region: AwsRegion, scopes: List) { + // assumes scopes contains either Q or non-Q permissions but not both + val (onError: (Exception) -> Unit, onSuccess: () -> Unit) = getSuccessAndErrorActionsForIdcLogin(scopes, url, region) + loginWithBackgroundContext { + Login + .IdC(url, region, scopes, onPendingToken, onSuccess, onError) + .login(project) + } + } + + fun getSuccessAndErrorActionsForIdcLogin( + scopes: List, + url: String, + region: AwsRegion, + ): Pair<(Exception) -> Unit, () -> Unit> { + val isReAuth = isReAuth(scopes, url) + val featureId = getFeatureId(scopes) + val onError: (Exception) -> Unit = { e -> + stopAndClearBrowserOpenTimer() + val message = ssoErrorMessageFromException(e) + val result = if (!isUserCancellation(e)) { + runInEdt { + Messages.showErrorDialog(jcefBrowser.component, message, "Failed to Authenticate") + } + executeJS("window.ideClient.reset()") + LOG.error(e) { "Failed to authenticate: message: $message; url: $url, region: $region, scopes: $scopes" } + + Result.Failed + } else { + Result.Cancelled + } + + AwsTelemetry.loginWithBrowser( + project = null, + credentialStartUrl = url, + isReAuth = isReAuth, + result = result, + reason = message, + credentialSourceId = CredentialSourceId.IamIdentityCenter, + authType = getAuthType(region.name), + source = SourceOfEntry.LOGIN_BROWSER.toString() + ) + Telemetry.auth.addConnection.use { + it.result(result) + .credentialSourceId(CredentialSourceId.IamIdentityCenter) + .reason(message) + .isReAuth(isReAuth) + .featureId(featureId) + .isAggregated(false) + .source(SourceOfEntry.LOGIN_BROWSER.toString()) + } + } + val onSuccess: () -> Unit = { + stopAndClearBrowserOpenTimer() + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Succeeded, + isReAuth = isReAuth, + credentialType = CredentialType.BearerToken, + credentialStartUrl = url, + credentialSourceId = CredentialSourceId.IamIdentityCenter, + authType = getAuthType(region.name), + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + Telemetry.auth.addConnection.use { + it.result(MetricResult.Succeeded) + .isReAuth(isReAuth) + .credentialSourceId(CredentialSourceId.IamIdentityCenter) + .featureId(featureId) + .isAggregated(true) + .source(SourceOfEntry.LOGIN_BROWSER.toString()) + } + } + return Pair(onError, onSuccess) + } + + open fun loginIAM(profileName: String, accessKey: String, secretKey: String) { + runInEdt { + val result = Login.LongLivedIAM( + profileName, + accessKey, + secretKey, + { error -> + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Failed, + reason = error.message, + credentialType = CredentialType.StaticProfile, + authType = AuthType.IAM, + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + LOG.error(error) { "Profile file error" } + Messages.showErrorDialog(jcefBrowser.component, error.message, AwsCoreBundle.message("gettingstarted.auth.failed")) + }, + { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Failed, + reason = "Profile already exists", + authType = AuthType.IAM, + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + }, + { error -> + val reason = "Connection validation error" + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Failed, + reason = reason, + authType = AuthType.IAM, + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + LOG.error(error) { reason } + Messages.showErrorDialog(jcefBrowser.component, error.message, AwsCoreBundle.message("gettingstarted.auth.failed")) + } + ).login(project) + + // failure already handled + if (result) { + AwsTelemetry.loginWithBrowser( + project = null, + result = Result.Succeeded, + credentialType = CredentialType.StaticProfile, + authType = AuthType.IAM, + source = SourceOfEntry.LOGIN_BROWSER.toString(), + ) + } + } + } + + protected fun loginWithBackgroundContext(action: () -> T): Future = + pluginAwareExecuteOnPooledThread { + runBlocking { + withBackgroundProgress(project, AwsCoreBundle.message("credentials.pending.title")) { + blockingContext { + action() + } + } + } + } + + abstract fun loadWebView(query: JBCefJSQuery) + + private suspend fun pollForAuthorization(provider: InteractiveBearerTokenProvider): PendingAuthorization? = pollFor { + provider.pendingAuthorization + } + + protected fun reauth(connection: ToolkitConnection?) { + if (connection is AwsBearerTokenConnection) { + loginWithBackgroundContext { + reauthConnectionIfNeeded(project, connection, onPendingToken, isReAuth = true, reauthSource = ReauthSource.LOGIN_BROWSER) + } + stopAndClearBrowserOpenTimer() + } + } + + protected fun isUserCancellation(e: Exception): Boolean { + if (e is ProcessCanceledException || e is CancellationException) { + LOG.debug(e) { "User canceled login" } + return true + } + return false + } + + // TODO: should test via handleMessage, however because we can't initiate Q/ToolkitLoginBrowser in test due to jcef not supported in test env + // plus handleMessage is abstract so as a interim, exposing it for testing purpose + @VisibleForTesting + fun publishTelemetry(message: BrowserMessage.PublishWebviewTelemetry) { + val jsonNode = this.objectMapper.readTree(message.event) ?: return + if (jsonNode["metricName"].asText() == "toolkit_didLoadModule") { + val moduleNode = jsonNode["module"] ?: return + val resultNode = jsonNode["result"] ?: return + val result = MetricResult.from(resultNode.asText()) + val reasonNode = jsonNode["reason"] + val durationNode = jsonNode["duration"] + Telemetry.toolkit.didLoadModule.use { span -> + span.module(moduleNode.asText()) + span.result(result) + span.reason(reasonNode?.asText()) + span.duration(durationNode?.asDouble()) + } + } + } + + companion object { + private val LOG = getLogger() + fun getWebviewHTML(webScriptUri: String, query: JBCefJSQuery): String { + val colorMode = if (JBColor.isBright()) "jb-light" else "jb-dark" + val postMessageToJavaJsCode = query.inject("JSON.stringify(message)") + + val jsScripts = """ + + + """.trimIndent() + + return """ + + + + AWS Q + + +
+ $jsScripts + + + """.trimIndent() + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/WebviewTelemetryUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/WebviewTelemetryUtils.kt new file mode 100644 index 00000000000..36e7526e2e9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/WebviewTelemetryUtils.kt @@ -0,0 +1,28 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.webview + +import com.intellij.openapi.util.registry.Registry +import software.amazon.q.jetbrains.core.credentials.sono.CODECATALYST_SCOPES +import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.aws.toolkits.telemetry.AuthType +import software.aws.toolkits.telemetry.FeatureId + +fun getAuthType(region: String): AuthType { + val isCommercialRegion = !region.startsWith("us-gov") && !region.startsWith("us-iso") && !region.startsWith("cn") + if (!Registry.`is`("aws.dev.useDAG") && isCommercialRegion) { + return AuthType.PKCE + } else { + return AuthType.DeviceCode + } +} + +fun getFeatureId(scopes: List): FeatureId = + if (scopes.intersect(Q_SCOPES.toSet()).isNotEmpty()) { + FeatureId.AmazonQ + } else if (scopes.intersect(CODECATALYST_SCOPES.toSet()).isNotEmpty()) { + FeatureId.Codecatalyst + } else { + FeatureId.AwsExplorer + } diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/QConstants.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/QConstants.kt new file mode 100644 index 00000000000..f9bb1005a6d --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/QConstants.kt @@ -0,0 +1,10 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.amazonq + +object QConstants { + const val Q_MARKETPLACE_URI = "https://aws.amazon.com/q/developer/" + const val CODEWHISPERER_LOGIN_HELP_URI = "https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/auth-access.html" + const val MAX_FILE_SIZE_BYTES = 1024 * 1024 +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/explorerActions/QLearnMoreAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/explorerActions/QLearnMoreAction.kt new file mode 100644 index 00000000000..41aa5d183e4 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/explorerActions/QLearnMoreAction.kt @@ -0,0 +1,25 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.amazonq.explorerActions + +import com.intellij.icons.AllIcons +import com.intellij.ide.BrowserUtil +import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.extensions.PluginId +import software.amazon.q.jetbrains.AwsToolkit +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.UiTelemetry + +class QLearnMoreAction : AnAction(AwsCoreBundle.message("q.learn.more"), "", AllIcons.Actions.Help) { + override fun actionPerformed(e: AnActionEvent) { + if (!PluginManager.isPluginInstalled(PluginId.getId(AwsToolkit.Q_PLUGIN_ID))) { + BrowserUtil.browse("https://plugins.jetbrains.com/plugin/24267-amazon-q") + } else { + BrowserUtil.browse("https://aws.amazon.com/q") + } + UiTelemetry.click(e.project, "q_learnMore") + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/sts/StsResources.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/sts/StsResources.kt new file mode 100644 index 00000000000..1feeee00df5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/sts/StsResources.kt @@ -0,0 +1,17 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.sts + +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.jetbrains.core.ClientBackedCachedResource +import java.time.Duration + +object StsResources { + val ACCOUNT = ClientBackedCachedResource(StsClient::class, "sts.account", expiry = Duration.ofDays(1)) { + callerIdentity.account() + } + val USER = ClientBackedCachedResource(StsClient::class, "sts.user", expiry = Duration.ofDays(1)) { + callerIdentity.userId() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProvider.kt new file mode 100644 index 00000000000..331aa3aec66 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProvider.kt @@ -0,0 +1,87 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.services.cognitoidentity.CognitoIdentityClient +import software.amazon.awssdk.services.cognitoidentity.model.Credentials +import software.amazon.awssdk.services.cognitoidentity.model.GetCredentialsForIdentityRequest +import software.amazon.awssdk.services.cognitoidentity.model.GetIdRequest +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.awssdk.utils.cache.CachedSupplier +import software.amazon.awssdk.utils.cache.NonBlocking +import software.amazon.awssdk.utils.cache.RefreshResult +import software.amazon.q.core.telemetry.CachedIdentityStorage +import java.time.temporal.ChronoUnit + +/** + * [AwsCredentialsProvider] implementation that uses the Amazon Cognito Identity + * service to create temporary, short-lived sessions to use for authentication + * + * @constructor Creates a new AwsCredentialsProvider that uses credentials from a Cognito Identity pool. + * @property identityPool The name of the pool to create users from + * @param cacheStorage A storage solution to cache an identity ID, disabled if null + */ +class AwsCognitoCredentialsProvider( + private val identityPool: String, + private val cognitoClient: CognitoIdentityClient, + cacheStorage: CachedIdentityStorage? = null, +) : AwsCredentialsProvider, SdkAutoCloseable { + private val identityIdProvider = AwsCognitoIdentityProvider(cognitoClient, identityPool, cacheStorage) + private val cacheSupplier = CachedSupplier.builder(this::updateCognitoCredentials) + .prefetchStrategy(NonBlocking("Cognito Identity Credential Refresh")) + .build() + + override fun resolveCredentials(): AwsCredentials = cacheSupplier.get() + + private fun updateCognitoCredentials(): RefreshResult { + val credentialsForIdentity = credentialsForIdentity() + val sessionCredentials = AwsSessionCredentials.create( + credentialsForIdentity.accessKeyId(), + credentialsForIdentity.secretKey(), + credentialsForIdentity.sessionToken() + ) + val actualExpiration = credentialsForIdentity.expiration() + + return RefreshResult.builder(sessionCredentials) + .staleTime(actualExpiration.minus(1, ChronoUnit.MINUTES)) + .prefetchTime(actualExpiration.minus(5, ChronoUnit.MINUTES)) + .build() + } + + private fun credentialsForIdentity(): Credentials { + val identityId = identityIdProvider.identityId + val request = GetCredentialsForIdentityRequest.builder().identityId(identityId).build() + + return cognitoClient.getCredentialsForIdentity(request).credentials() + } + + override fun close() { + cognitoClient.close() + cacheSupplier.close() + } +} + +private class AwsCognitoIdentityProvider( + private val cognitoClient: CognitoIdentityClient, + private val identityPoolId: String, + private val cacheStorage: CachedIdentityStorage? = null, +) { + val identityId: String by lazy { + loadFromCache() ?: createNewIdentity() + } + + private fun loadFromCache(): String? = cacheStorage?.loadIdentity(identityPoolId) + + private fun createNewIdentity(): String { + val request = GetIdRequest.builder().identityPoolId(identityPoolId).build() + val newIdentityId = cognitoClient.getId(request).identityId() + + cacheStorage?.storeIdentity(identityPoolId, newIdentityId) + + return newIdentityId + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsToolkitStartupMetrics.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsToolkitStartupMetrics.kt new file mode 100644 index 00000000000..b3415c58c5d --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsToolkitStartupMetrics.kt @@ -0,0 +1,17 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.ide.util.RunOnceUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import software.aws.toolkits.telemetry.SessionTelemetry + +internal class AwsToolkitStartupMetrics : ProjectActivity { + override suspend fun execute(project: Project) { + RunOnceUtil.runOnceForApp(this::class.qualifiedName.toString()) { + SessionTelemetry.start(project) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/ClientMetadata.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/ClientMetadata.kt new file mode 100644 index 00000000000..ad6faea8f49 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/ClientMetadata.kt @@ -0,0 +1,30 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.application.ApplicationNamesInfo +import com.intellij.openapi.util.SystemInfo +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.q.jetbrains.settings.AwsSettings + +data class ClientMetadata( + val awsProduct: AWSProduct, + val awsVersion: String, + val clientId: String = AwsSettings.getInstance().clientId.toString(), + val parentProduct: String = ApplicationNamesInfo.getInstance().fullProductNameWithEdition, + val parentProductVersion: String = ApplicationInfo.getInstance().build.baselineVersion.toString(), + val os: String = SystemInfo.OS_NAME, + val osVersion: String = SystemInfo.OS_VERSION, +) { + companion object { + fun getDefault(): ClientMetadata { + val pluginResolver = PluginResolver.fromCurrentThread() + return ClientMetadata( + awsProduct = pluginResolver.product, + awsVersion = pluginResolver.version + ) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt new file mode 100644 index 00000000000..4b4a7af5d35 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt @@ -0,0 +1,134 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.openapi.util.registry.Registry +import kotlinx.coroutines.withContext +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.cognitoidentity.CognitoIdentityClient +import software.amazon.awssdk.services.toolkittelemetry.ToolkitTelemetryClient +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.awssdk.services.toolkittelemetry.model.MetadataEntry +import software.amazon.awssdk.services.toolkittelemetry.model.MetricDatum +import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.AwsSdkClient +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.core.clients.nullDefaultProfileFile +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryPublisher + +class DefaultTelemetryPublisher( + private val clientProvider: () -> ToolkitTelemetryClient, + private val clientMetadataProvider: (AWSProduct, String) -> ClientMetadata, +) : TelemetryPublisher { + constructor() : this( + clientProvider = { createDefaultTelemetryClient() }, + clientMetadataProvider = { product, version -> ClientMetadata(product, version) } + ) + + private val lazyClient = lazy { clientProvider() } + private val client by lazyClient + + override suspend fun publish(metricEvents: Collection) { + withContext(getCoroutineBgContext()) { + metricEvents.groupBy { Pair(it.awsProduct, it.awsVersion) } + .forEach { (productName, productVersion), events -> + val clientMetadata = clientMetadataProvider(productName, productVersion) + client.postMetrics { + it.awsProduct(clientMetadata.awsProduct) + it.awsProductVersion(clientMetadata.awsVersion) + it.clientID(clientMetadata.clientId) + it.os(clientMetadata.os) + it.osVersion(clientMetadata.osVersion) + it.parentProduct(clientMetadata.parentProduct) + it.parentProductVersion(clientMetadata.parentProductVersion) + it.metricData(events.toMetricData()) + } + } + } + } + + override suspend fun sendFeedback(sentiment: Sentiment, comment: String, metadata: Map) { + withContext(getCoroutineBgContext()) { + val pluginResolver = PluginResolver.fromCurrentThread() + val clientMetadata = clientMetadataProvider(pluginResolver.product, pluginResolver.version) + client.postFeedback { + it.awsProduct(clientMetadata.awsProduct) + it.awsProductVersion(clientMetadata.awsVersion) + it.os(clientMetadata.os) + it.osVersion(clientMetadata.osVersion) + it.parentProduct(clientMetadata.parentProduct) + it.parentProductVersion(clientMetadata.parentProductVersion) + it.sentiment(sentiment) + it.comment(comment) + if (metadata.isNotEmpty()) { + it.metadata(metadata.map { (k, v) -> MetadataEntry.builder().key(k).value(v).build() }) + } + } + } + } + + private fun Collection.toMetricData(): Collection = this + .flatMap { metricEvent -> + metricEvent.data.map { datum -> + val metricName = datum.name + MetricDatum.builder() + .epochTimestamp(metricEvent.createTime.toEpochMilli()) + .metricName(metricName) + .unit(datum.unit) + .value(datum.value) + .passive(datum.passive) + .metadata( + datum.metadata.entries.stream().map { + MetadataEntry.builder() + .key(it.key) + .value(it.value) + .build() + }.toList() + listOf( + MetadataEntry.builder() + .key(METADATA_AWS_ACCOUNT) + .value(metricEvent.awsAccount) + .build(), + MetadataEntry.builder() + .key(METADATA_AWS_REGION) + .value(metricEvent.awsRegion) + .build() + ) + ) + .build() + } + } + + override fun close() { + if (lazyClient.isInitialized()) { + client.close() + } + } + + private companion object { + private const val METADATA_AWS_ACCOUNT = "awsAccount" + private const val METADATA_AWS_REGION = "awsRegion" + + private fun createDefaultTelemetryClient(): ToolkitTelemetryClient { + val region = Region.of(Registry.get("aws.telemetry.region").asString()) + val sdkClient = AwsSdkClient.getInstance() + + return AwsClientManager.getInstance().createUnmanagedClient( + credProvider = AwsCognitoCredentialsProvider( + Registry.get("aws.telemetry.identityPool").asString(), + CognitoIdentityClient.builder() + .credentialsProvider(AnonymousCredentialsProvider.create()) + .region(region) + .httpClient(sdkClient.sharedSdkClient()) + .nullDefaultProfileFile() + .build() + ), + region = region, + endpointOverride = Registry.get("aws.telemetry.endpoint").asString() + ) { _, _, _, _, clientOverrideConfiguration -> clientOverrideConfiguration.nullDefaultProfileFile() } + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt new file mode 100644 index 00000000000..87bb5457a2a --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt @@ -0,0 +1,112 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.execution.filters.TextConsoleBuilderFactory +import com.intellij.execution.ui.ConsoleView +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.project.DefaultProjectFactory +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.ui.FrameWrapper +import com.intellij.openapi.util.Disposer +import com.intellij.ui.DocumentAdapter +import com.intellij.ui.JBColor +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBTextField +import com.intellij.util.ui.components.BorderLayoutPanel +import software.amazon.q.jetbrains.isDeveloperMode +import software.amazon.q.core.telemetry.MetricEvent +import java.awt.BorderLayout +import javax.swing.BorderFactory +import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.event.DocumentEvent + +class OpenTelemetryAction : DumbAwareAction() { + override fun actionPerformed(event: AnActionEvent) { + TelemetryDialog().show() + } + + override fun getActionUpdateThread() = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = isDeveloperMode() + } + + private class TelemetryDialog : FrameWrapper(null), TelemetryListener { + private val consoleView: ConsoleView by lazy { + TextConsoleBuilderFactory.getInstance().createBuilder(DefaultProjectFactory.getInstance().defaultProject).apply { + setViewer(true) + }.console + } + private val telemetryEvents = mutableListOf() + private var currentFilter = "" + + init { + title = "Telemetry Viewer" + component = createContent() + } + + private fun createContent(): JComponent { + val panel = BorderLayoutPanel() + val consoleComponent = consoleView.component + + // Add search/filter bar at the top + val filterPanel = JPanel(BorderLayout()) + val filterField = JBTextField().apply { + emptyText.text = "Filter telemetry events..." + } + filterPanel.add(JBLabel("Filter: "), BorderLayout.WEST) + filterPanel.add(filterField, BorderLayout.CENTER) + + val actionGroup = DefaultActionGroup(*consoleView.createConsoleActions()) + val toolbar = ActionManager.getInstance().createActionToolbar("AWS.TelemetryViewer", actionGroup, false) + + toolbar.setTargetComponent(consoleComponent) + + panel.addToTop(filterPanel) + panel.addToLeft(toolbar.component) + panel.addToCenter(consoleComponent) + + // Add a border to make things look nicer. + consoleComponent.border = BorderFactory.createLineBorder(JBColor.GRAY) + + val telemetryService = TelemetryService.getInstance() + telemetryService.addListener(this) + Disposer.register(this) { telemetryService.removeListener(this) } + Disposer.register(this, consoleView) + + // Implement filtering logic + filterField.document.addDocumentListener(object : DocumentAdapter() { + override fun textChanged(e: DocumentEvent) { + // Filter console content based on filterField.text + applyFilter(filterField.text) + } + }) + + return panel + } + + private fun applyFilter(filterText: String) { + currentFilter = filterText + consoleView.clear() + telemetryEvents.filter { + it.toString().contains(filterText, ignoreCase = true) + }.forEach { event -> + consoleView.print(event.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT) + } + } + + override fun onTelemetryEvent(event: MetricEvent) { + telemetryEvents.add(event) + if (event.toString().contains(currentFilter, ignoreCase = true)) { + consoleView.print(event.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypesMetrics.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypesMetrics.kt new file mode 100644 index 00000000000..6b48bea4665 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypesMetrics.kt @@ -0,0 +1,66 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.Alarm +import org.jetbrains.annotations.TestOnly +import software.aws.toolkits.telemetry.IdeTelemetry + +class OpenedFileTypesMetricsListener : FileEditorManagerListener { + override fun fileOpened(source: FileEditorManager, file: VirtualFile) { + val extension = file.extension ?: return + source.project.service().addToExistingTelemetryBatch(extension) + } +} + +@Service(Service.Level.PROJECT) +class OpenedFileTypesMetricsService : Disposable { + private val currentOpenedFileTypes = mutableSetOf() + private val alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) + override fun dispose() {} + + init { + scheduleNextMetricEvent() + } + + private fun scheduleNextMetricEvent() { + alarm.addRequest(this::emitFileTypeMetric, INTERVAL_BETWEEN_METRICS) + } + + @Synchronized + fun emitFileTypeMetric() { + currentOpenedFileTypes.forEach { + emitMetric(it) + } + currentOpenedFileTypes.clear() + if (!ApplicationManager.getApplication().isUnitTestMode) { + scheduleNextMetricEvent() + } + } + + @TestOnly + fun getOpenedFileTypes(): Set = currentOpenedFileTypes + + @Synchronized + fun addToExistingTelemetryBatch(fileExt: String) { + if (fileExt in ALLOWED_CODE_EXTENSIONS) { + val extension = ".$fileExt" + currentOpenedFileTypes.add(extension) + } + } + + private fun emitMetric(openFileExtension: String) = + IdeTelemetry.editCodeFile(project = null, filenameExt = openFileExtension) + + companion object { + private const val INTERVAL_BETWEEN_METRICS = 30 * 60 * 1000 + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/PluginResolver.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/PluginResolver.kt new file mode 100644 index 00000000000..5804c5a75fb --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/PluginResolver.kt @@ -0,0 +1,52 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.ide.plugins.PluginManagerCore +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct + +/** + * Responsible for resolving the plugin descriptor and determining the AWS product + * and version based on the stack trace of the calling thread or a provided stack trace. + */ +class PluginResolver private constructor(callerStackTrace: Array) { + private val pluginDescriptor by lazy { + callerStackTrace + .reversed() + .filter { it.className.startsWith("software.aws.toolkits") } + .firstNotNullOfOrNull { PluginManagerCore.getPluginDescriptorOrPlatformByClassName(it.className) } + } + + val product: AWSProduct + get() = when (pluginDescriptor?.pluginId?.idString) { + "amazon.q" -> AWSProduct.AMAZON_Q_FOR_JET_BRAINS + else -> AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS + } + + val version: String + get() = pluginDescriptor?.version ?: "unknown" + + companion object { + private val threadLocalResolver = ThreadLocal() + + /** + * Creates a new PluginResolver instance off the current thread's stack trace, or retrieves + * the thread-local resolver if one is set. + */ + fun fromCurrentThread() = threadLocalResolver.get() ?: PluginResolver(Thread.currentThread().stackTrace) + + /** + * Creates a new PluginResolver instance from a provided stack trace. + */ + fun fromStackTrace(stackTrace: Array) = PluginResolver(stackTrace) + + /** + * Sets a PluginResolver instance to a thread-local for the current thread. + * This value will be retrieved by subsequent calls to fromCurrentThread. + */ + fun setThreadLocal(value: PluginResolver?) { + threadLocalResolver.set(value) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryService.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryService.kt new file mode 100644 index 00000000000..6a06c7670ec --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryService.kt @@ -0,0 +1,31 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_NA +import software.amazon.q.core.telemetry.DefaultTelemetryBatcher +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher + +typealias TelemetryService = migration.software.amazon.q.jetbrains.telemetry.TelemetryService + +data class MetricEventMetadata( + val awsAccount: String = METADATA_NA, + val awsRegion: String = METADATA_NA, + var awsProduct: AWSProduct = AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS, + var awsVersion: String = METADATA_NA, +) + +interface TelemetryListener { + fun onTelemetryEvent(event: MetricEvent) +} + +class DefaultTelemetryService : TelemetryService(publisher, batcher) { + private companion object { + private val publisher: TelemetryPublisher by lazy { DefaultTelemetryPublisher() } + private val batcher: TelemetryBatcher by lazy { DefaultTelemetryBatcher(publisher) } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryUtils.kt new file mode 100644 index 00000000000..fe07786105e --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryUtils.kt @@ -0,0 +1,282 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +val ALLOWED_CODE_EXTENSIONS = setOf( + "abap", + "ada", + "adb", + "ads", + "apl", + "asm", + "awk", + "b", + "bas", + "bash", + "bat", + "boo", + "bms", + "c", + "cbl", + "cc", + "cfc", + "cfg", + "cfm", + "cjs", + "clj", + "cljc", + "cljs", + "cls", + "cmake", + "cmd", + "cob", + "cobra", + "coffee", + "config", + "cpp", + "cpy", + "cr", + "cs", + "css", + "csx", + "cxx", + "d", + "dart", + "dfm", + "dockerfile", + "dpr", + "e", + "el", + "elm", + "env", + "erl", + "ex", + "exs", + "f", + "f03", + "f08", + "f77", + "f90", + "f95", + "flow", + "for", + "fs", + "fsi", + "fsx", + "gd", + "gitignore", + "go", + "gql", + "gradle", + "graphql", + "groovy", + "gs", + "gsp", + "gst", + "gsx", + "gvy", + "h", + "hack", + "hh", + "hpp", + "hrl", + "hs", + "htm", + "html", + "hy", + "idl", + "ini", + "io", + "java", + "jl", + "js", + "json", + "jsx", + "kt", + "kts", + "lean", + "lgt", + "lhs", + "lisp", + "lock", + "logtalk", + "lsp", + "lua", + "m", + "ma", + "mak", + "makefile", + "md", + "mjs", + "ml", + "mli", + "mpl", + "ms", + "mu", + "mv", + "n", + "nb", + "nim", + "nix", + "oot", + "oz", + "pas", + "pasm", + "perl", + "php", + "phtml", + "pike", + "pir", + "pl", + "pli", + "pm", + "pmod", + "pp", + "pro", + "prolog", + "properties", + "ps1", + "psd1", + "psm1", + "purs", + "py", + "pyw", + "qs", + "r", + "raku", + "rakumod", + "rakutest", + "rb", + "rbw", + "rdata", + "re", + "red", + "reds", + "res", + "rex", + "rexx", + "ring", + "rkt", + "rktl", + "rlib", + "rm", + "rmd", + "roff", + "ron", + "rs", + "ruby", + "s", + "sas", + "sb", + "sb2", + "sb3", + "sc", + "scala", + "scd", + "scm", + "scss", + "sass", + "sh", + "shen", + "sig", + "sml", + "sol", + "sql", + "ss", + "st", + "sv", + "svg", + "swift", + "t", + "tcl", + "tf", + "toml", + "trigger", + "ts", + "tsx", + "tu", + "txt", + "v", + "vala", + "vapi", + "vb", + "vba", + "vbx", + "vhd", + "vhdl", + "vue", + "x", + "xc", + "xi", + "xml", + "yaml", + "yml", + "zig", +) + +fun scrubNames(messageToBeScrubbed: String, username: String? = getSystemUserName()): String { + var scrubbedMessage = "" + var processedMessage = messageToBeScrubbed + if (!username.isNullOrEmpty() && username.length > 2) { + processedMessage = processedMessage.replace(username, "x") + } + + // Replace contiguous whitespace with 1 space. + processedMessage = processedMessage.replace(Regex("""\s+"""), " ") + + // 1. split on whitespace. + // 2. scrub words that match username or look like filepaths. + val words = processedMessage.split(Regex("""\s+""")) + for (word in words) { + val pathSegments = word.split(Regex("""[/\\]""")).filter { it != "" } + if (pathSegments.size < 2) { + // Not a filepath. + scrubbedMessage += " $word" + continue + } + + // Replace all (non-allowlisted) ASCII filepath segments with "x". + // "/foo/bar/aws/sso/" => "/x/x/aws/sso/" + var scrubbed = "" + // Get the frontmatter ("/", "../", "~/", or "./"). + val start = slashdot.find(word.trimStart())?.value.orEmpty() + val firstVal = pathSegments[0].trimStart().replace(slashdot, "") + + val ps = pathSegments.filterIndexed { i, _ -> i != 0 }.toMutableList() + ps.add(0, firstVal) + + for (seg in ps) { + when { + driveLetterRegex.matches(seg) -> scrubbed += seg + commonFilePathPatterns.contains(seg) -> scrubbed += "/$seg" + else -> { + // Save the first non-ASCII (unicode) char, if any. + val nonAscii = Regex("""[^\p{ASCII}]""").find(seg)?.value.orEmpty() + // Replace all chars (except [^…]) with "x" . + val ascii = seg.replace(Regex("""[^$\[\](){}:;'" ]+"""), "x") + scrubbed += "/${ascii}$nonAscii" + } + } + } + + // includes leading '.', eg: '.json' + val fileExt = fileExtRegex.find(pathSegments.last())?.value.orEmpty() + val newString = " ${start.replace(Regex("""\\"""), "/")}${scrubbed.removePrefix("//").removePrefix("/").removePrefix("\\")}$fileExt" + scrubbedMessage += newString + } + + return scrubbedMessage.trim() +} + +val fileExtRegex = Regex("""\.[^./]+$""") +val slashdot = Regex("""^[~.]*[/\\]*""") + +/** Allowlisted filepath segments. */ +val commonFilePathPatterns = setOf( + "~", ".", "..", ".aws", "aws", "sso", "cache", "credentials", "config", + "Users", "users", "home", "tmp", "aws-toolkit-jetbrains" +) +val driveLetterRegex = Regex("""^[a-zA-Z]:""") + +fun getSystemUserName(): String? = System.getProperty("user.name") ?: null diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt new file mode 100644 index 00000000000..850e6ba6765 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt @@ -0,0 +1,262 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry.otel + +import com.intellij.openapi.application.ApplicationManager +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanBuilder +import io.opentelemetry.api.trace.SpanContext +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.context.Context +import io.opentelemetry.context.ContextKey +import io.opentelemetry.context.Scope +import io.opentelemetry.sdk.trace.ReadWriteSpan +import kotlinx.coroutines.CoroutineScope +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit +import software.amazon.q.jetbrains.isDeveloperMode +import software.amazon.q.jetbrains.services.telemetry.PluginResolver +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.aws.toolkits.telemetry.impl.BaseSpan +import java.time.Instant +import java.util.concurrent.TimeUnit +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import com.intellij.platform.diagnostic.telemetry.helpers.use as ijUse +import com.intellij.platform.diagnostic.telemetry.helpers.useWithScope as ijUseWithScope + +val AWS_PRODUCT_CONTEXT_KEY = ContextKey.named("pluginDescriptor") +internal val PLUGIN_NAME_ATTRIBUTE_KEY = AttributeKey.stringKey("pluginName") + +class DefaultSpan(context: Context?, delegate: Span) : BaseSpan(context, delegate) + +class DefaultSpanBuilder(delegate: SpanBuilder) : AbstractSpanBuilder(delegate) { + override fun doStartSpan() = DefaultSpan(parent, delegate.startSpan()) +} + +abstract class AbstractSpanBuilder< + BuilderType : AbstractSpanBuilder, + SpanType : AbstractBaseSpan, + >( + protected val delegate: SpanBuilder, +) : SpanBuilder { + /** + * Same as [com.intellij.platform.diagnostic.telemetry.helpers.use] except downcasts to specific subclass of [BaseSpan] + * + * @inheritdoc + */ + inline fun use(operation: (SpanType) -> T): T = + startSpan().ijUse { span -> + operation(span as SpanType) + } + + /** + * Same as [com.intellij.platform.diagnostic.telemetry.helpers.useWithScope] except downcasts to specific subclass of [BaseSpan] + * + * @inheritdoc + */ + suspend inline fun useWithScope( + context: CoroutineContext = EmptyCoroutineContext, + crossinline operation: suspend CoroutineScope.(SpanType) -> T, + ): T = + ijUseWithScope(context) { span -> + operation(span as SpanType) + } + + protected var parent: Context? = null + override fun setParent(context: Context): BuilderType { + parent = context + delegate.setParent(context) + return this as BuilderType + } + + override fun setNoParent(): BuilderType { + parent = null + delegate.setNoParent() + return this as BuilderType + } + + override fun addLink(spanContext: SpanContext): BuilderType { + delegate.addLink(spanContext) + return this as BuilderType + } + + override fun addLink( + spanContext: SpanContext, + attributes: Attributes, + ): BuilderType { + delegate.addLink(spanContext, attributes) + return this as BuilderType + } + + override fun setAttribute(key: String, value: String): BuilderType { + delegate.setAttribute(key, value) + return this as BuilderType + } + + override fun setAttribute(key: String, value: Long): BuilderType { + delegate.setAttribute(key, value) + return this as BuilderType + } + + override fun setAttribute(key: String, value: Double): BuilderType { + delegate.setAttribute(key, value) + return this as BuilderType + } + + override fun setAttribute(key: String, value: Boolean): BuilderType { + delegate.setAttribute(key, value) + return this as BuilderType + } + + override fun setAttribute( + key: AttributeKey, + value: V & Any, + ): BuilderType { + delegate.setAttribute(key, value) + return this as BuilderType + } + + override fun setAllAttributes(attributes: Attributes): BuilderType { + delegate.setAllAttributes(attributes) + return this as BuilderType + } + + override fun setSpanKind(spanKind: SpanKind): BuilderType { + delegate.setSpanKind(spanKind) + return this as BuilderType + } + + override fun setStartTimestamp(startTimestamp: Long, unit: TimeUnit): BuilderType { + delegate.setStartTimestamp(startTimestamp, unit) + return this as BuilderType + } + + override fun setStartTimestamp(startTimestamp: Instant): BuilderType { + delegate.setStartTimestamp(startTimestamp) + return this as BuilderType + } + + protected abstract fun doStartSpan(): SpanType + + override fun startSpan(): SpanType { + var parent = parent + if (parent == null) { + parent = Context.current() + } + requireNotNull(parent) + + val contextValue = parent.get(AWS_PRODUCT_CONTEXT_KEY) + if (contextValue == null) { + val s = Span.fromContextOrNull(parent) + parent = if (s is AbstractBaseSpan<*> && s.context != null) { + s.context.with(Span.fromContext(parent)) + } else { + parent.with(AWS_PRODUCT_CONTEXT_KEY, resolvePluginName()) + } + setParent(parent) + } + requireNotNull(parent) + + parent.get(AWS_PRODUCT_CONTEXT_KEY)?.toString()?.let { + setAttribute(PLUGIN_NAME_ATTRIBUTE_KEY, it) + } ?: run { + LOG.warn { "Reached setAttribute with null AWS_PRODUCT_CONTEXT_KEY, but should not be possible" } + } + + return doStartSpan() + } + + private companion object { + val LOG = getLogger>() + fun resolvePluginName() = PluginResolver.Companion.fromStackTrace(Thread.currentThread().stackTrace).product + } +} + +abstract class AbstractBaseSpan>(internal val context: Context?, private val delegate: ReadWriteSpan) : Span by delegate { + protected open val requiredFields: Collection = emptySet() + private var passive: Boolean = false + private var unit: MetricUnit = MetricUnit.NONE + private var value: Double = 1.0 + + /** + * Same as [com.intellij.platform.diagnostic.telemetry.helpers.use] except downcasts to specific subclass of [BaseSpan] + * + * @inheritdoc + */ + inline fun use(operation: (SpanType) -> T): T = + ijUse { span -> + operation(span as SpanType) + } + + fun metadata(key: String, value: String?): SpanType { + delegate.setAttribute(key, value) + return this as SpanType + } + + override fun recordException(exception: Throwable): SpanType { + delegate.recordException(exception) + + setAttribute("reason", exception::class.java.canonicalName) + setAttribute("reasonDesc", exception.message) + return this as SpanType + } + + override fun end() { + finalize() + validateRequiredAttributes() + delegate.end() + } + + override fun end(timestamp: Long, unit: TimeUnit) { + finalize() + validateRequiredAttributes() + delegate.end() + } + + fun passive(passive: Boolean): SpanType { + this.passive = passive + return this as SpanType + } + + fun unit(unit: MetricUnit): SpanType { + this.unit = unit + return this as SpanType + } + + fun value(value: Number): SpanType { + this.value = value.toDouble() + return this as SpanType + } + + private fun finalize() { + setAttribute("passive", passive) + setAttribute("unit", unit.toString()) + setAttribute("value", value) + } + + private fun validateRequiredAttributes() { + val missingFields = requiredFields.filter { delegate.getAttribute(AttributeKey.stringKey(it)) == null } + val message = { "${delegate.name} is missing required fields: ${missingFields.joinToString(", ")}" } + + if (missingFields.isNotEmpty()) { + when { + ApplicationManager.getApplication().isUnitTestMode -> error(message()) + isDeveloperMode() -> LOG.error { message() } + else -> LOG.error { message() } + } + } + } + + override fun makeCurrent(): Scope = + context?.with(this)?.makeCurrent() ?: super.makeCurrent() + + private companion object { + val LOG = getLogger>() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessor.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessor.kt new file mode 100644 index 00000000000..1c1a7f17568 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessor.kt @@ -0,0 +1,72 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry.otel + +import io.opentelemetry.context.Context +import io.opentelemetry.sdk.trace.ReadWriteSpan +import io.opentelemetry.sdk.trace.ReadableSpan +import io.opentelemetry.sdk.trace.SpanProcessor +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit +import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit +import software.amazon.q.jetbrains.services.telemetry.MetricEventMetadata +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import java.time.Instant +import kotlin.time.Duration.Companion.nanoseconds + +class ToolkitTelemetryOTelSpanProcessor : SpanProcessor { + override fun isStartRequired() = false + override fun isEndRequired() = true + + override fun onStart(parentContext: Context, span: ReadWriteSpan) {} + + override fun onEnd(span: ReadableSpan) { + val data = span.toSpanData() + val product = data.attributes.get(PLUGIN_NAME_ATTRIBUTE_KEY)?.let { AWSProduct.fromValue(it) } ?: AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS + val version = tryOrNull { + when (product) { + AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS -> AwsToolkit.PLUGINS_INFO[AwsPlugin.TOOLKIT]?.version + AWSProduct.AMAZON_Q_FOR_JET_BRAINS -> AwsToolkit.PLUGINS_INFO[AwsPlugin.Q]?.version + else -> null + } + } ?: "unknown" + + TelemetryService.getInstance().record( + MetricEventMetadata( + awsProduct = product, + awsVersion = version, + ) + ) { + createTime(Instant.ofEpochSecond(0L, data.startEpochNanos)) + + datum(data.name) { + val attributes = data.attributes.asMap().entries.associate { it.key.key to it.value }.toMutableMap() + // goes on root of payload + attributes.remove(PLUGIN_NAME_ATTRIBUTE_KEY.key) + + // special handling attributes + passive(attributes.remove("passive") as Boolean) + unit(MetricUnit.fromValue(attributes.remove("unit") as String)) + value(attributes.remove("value") as Double) + + // everything else + attributes.forEach { t, u -> + metadata(t, u.toString()) + } + + // auto-duration + if (attributes["duration"] == null && data.endEpochNanos != 0L) { + metadata("duration", (data.endEpochNanos - data.startEpochNanos).nanoseconds.inWholeMilliseconds.toString()) + } + + // the reason why we used opentelemetry + metadata("traceId", data.traceId) + metadata("metricId", data.spanId) + metadata("parentId", data.parentSpanId) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettings.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettings.kt new file mode 100644 index 00000000000..af8e8fa09f6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettings.kt @@ -0,0 +1,140 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.settings + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.AwsTelemetry +import software.aws.toolkits.telemetry.UiTelemetry +import java.util.UUID +import java.util.prefs.Preferences + +enum class ProfilesNotification(private val description: String) { + Always(AwsCoreBundle.message("settings.profiles.always")), + OnFailure(AwsCoreBundle.message("settings.profiles.on_failure")), + Never(AwsCoreBundle.message("settings.profiles.never")), + ; + + override fun toString(): String = description +} + +enum class UseAwsCredentialRegion(private val description: String) { + Always(AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch.always.description")), + Prompt(AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch.ask.description")), + Never(AwsCoreBundle.message("settings.credentials.prompt_for_default_region_switch.never.description")), + ; + + override fun toString(): String = description +} + +typealias AwsSettings = migration.software.amazon.q.jetbrains.settings.AwsSettings + +@State(name = "q", storages = [Storage("amazonq.xml")]) +class DefaultAwsSettings : PersistentStateComponent, AwsSettings { + private val preferences = Preferences.userRoot().node(this.javaClass.canonicalName) + private var state = AwsConfiguration() + + override fun getState(): AwsConfiguration = state + + override fun loadState(state: AwsConfiguration) { + this.state = state + } + + override var isTelemetryEnabled: Boolean + get() = state.isTelemetryEnabled ?: true + set(value) { + val enablementElement = if (value) "aws_enabledTelemetry" else "aws_disabledTelemetry" + TelemetryService.getInstance().setTelemetryEnabled(value) { + UiTelemetry.click(null as Project?, enablementElement) + } + state.isTelemetryEnabled = value + } + + override var promptedForTelemetry: Boolean + get() = state.promptedForTelemetry ?: false + set(value) { + state.promptedForTelemetry = value + } + + override var useDefaultCredentialRegion: UseAwsCredentialRegion + get() = state.useDefaultCredentialRegion?.let { UseAwsCredentialRegion.valueOf(it) } ?: UseAwsCredentialRegion.Prompt + set(value) { + state.useDefaultCredentialRegion = value.name + } + + override var profilesNotification: ProfilesNotification + get() = state.profilesNotification?.let { ProfilesNotification.valueOf(it) } ?: ProfilesNotification.Always + set(value) { + state.profilesNotification = value.name + } + + override var isAutoUpdateEnabled: Boolean + get() = state.isAutoUpdateEnabled ?: true + set(value) { + if (state.isAutoUpdateEnabled != value) { + val settingState = if (value) "OPTIN" else "OPTOUT" + AwsTelemetry.modifySetting(project = null, settingId = ID_AUTO_UPDATE, settingState = settingState) + } + state.isAutoUpdateEnabled = value + } + + override var isAutoUpdateNotificationEnabled: Boolean + get() = state.isAutoUpdateNotificationEnabled ?: true + set(value) { + if (isAutoUpdateNotificationEnabled != value) { + val settingsState = if (value) "OPTIN" else "OPTOUT" + AwsTelemetry.modifySetting(project = null, settingId = ID_AUTO_UPDATE_NOTIFY, settingState = settingsState) + } + state.isAutoUpdateNotificationEnabled = value + } + + override var isAutoUpdateFeatureNotificationShownOnce: Boolean + get() = state.isAutoUpdateFeatureNotificationShownOnce ?: false + set(value) { + state.isAutoUpdateFeatureNotificationShownOnce = value + } + + override var isQMigrationNotificationShownOnce: Boolean + get() = state.isQMigrationNotificationShownOnce ?: false + set(value) { + state.isQMigrationNotificationShownOnce = value + } + + override val clientId: UUID + @Synchronized get() { + val id = when { + ApplicationManager.getApplication().isUnitTestMode || System.getProperty("robot-server.port") != null -> "ffffffff-ffff-ffff-ffff-ffffffffffff" + isTelemetryEnabled == false -> "11111111-1111-1111-1111-111111111111" + else -> { + preferences.get(CLIENT_ID_KEY, UUID.randomUUID().toString()).also { + preferences.put(CLIENT_ID_KEY, it.toString()) + } + } + } + + return UUID.fromString(id) + } + + companion object { + const val CLIENT_ID_KEY = "CLIENT_ID" + private const val ID_AUTO_UPDATE = "autoUpdate" + private const val ID_AUTO_UPDATE_NOTIFY = "autoUpdateNotification" + } +} + +data class AwsConfiguration( + var isTelemetryEnabled: Boolean? = null, + var promptedForTelemetry: Boolean? = null, + var useDefaultCredentialRegion: String? = null, + var profilesNotification: String? = null, + var isAutoUpdateEnabled: Boolean? = null, + var isAutoUpdateNotificationEnabled: Boolean? = null, + var isAutoUpdateFeatureNotificationShownOnce: Boolean? = null, + var isQMigrationNotificationShownOnce: Boolean? = null, +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettingsSharedConfigurable.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettingsSharedConfigurable.kt new file mode 100644 index 00000000000..245dec4e8d2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettingsSharedConfigurable.kt @@ -0,0 +1,51 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.settings + +import com.intellij.ide.BrowserUtil +import com.intellij.openapi.options.BoundConfigurable +import com.intellij.openapi.options.SearchableConfigurable +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.layout.selected +import software.amazon.q.resources.AwsCoreBundle + +class AwsSettingsSharedConfigurable : BoundConfigurable("AWS"), SearchableConfigurable { + val enableTelemetry: JBCheckBox = JBCheckBox(AwsCoreBundle.message("aws.settings.telemetry.option")) + private val enableAutoUpdate: JBCheckBox = JBCheckBox(AwsCoreBundle.message("aws.settings.auto_update.text")) + private val enableAutoUpdateNotification: JBCheckBox = JBCheckBox(AwsCoreBundle.message("aws.settings.auto_update.notification_enable.text")) + override fun createPanel() = panel { + group(AwsCoreBundle.message("aws.settings.global_label")) { + row { + cell(enableTelemetry).bindSelected( + AwsSettings.getInstance()::isTelemetryEnabled, + AwsSettings.getInstance()::isTelemetryEnabled::set + ) + text("${AwsCoreBundle.message("general.details")}") { + BrowserUtil.open("https://docs.aws.amazon.com/sdkref/latest/guide/support-maint-idetoolkits.html") + } + } + + row { + cell(enableAutoUpdate).bindSelected( + AwsSettings.getInstance()::isAutoUpdateEnabled, + AwsSettings.getInstance()::isAutoUpdateEnabled::set + ) + } + + indent { + row { + cell(enableAutoUpdateNotification).bindSelected( + AwsSettings.getInstance()::isAutoUpdateNotificationEnabled, + AwsSettings.getInstance()::isAutoUpdateNotificationEnabled::set + ).enabledIf(enableAutoUpdate.selected) + .comment(AwsCoreBundle.message("aws.settings.auto_update.notification_enable.tooltip")) + } + } + } + } + + override fun getId(): String = "aws" +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/AsyncComboBox.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/AsyncComboBox.kt new file mode 100644 index 00000000000..dcf9465657d --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/AsyncComboBox.kt @@ -0,0 +1,187 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.ui + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.AnimatedIcon +import com.intellij.ui.SimpleListCellRenderer +import com.intellij.ui.components.JBLabel +import com.intellij.util.Alarm +import com.intellij.util.AlarmFactory +import kotlinx.coroutines.launch +import org.jetbrains.annotations.TestOnly +import org.jetbrains.concurrency.AsyncPromise +import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope +import software.amazon.q.jetbrains.utils.ui.selected +import software.amazon.q.resources.AwsCoreBundle +import java.awt.Component +import java.util.concurrent.Future +import java.util.concurrent.atomic.AtomicBoolean +import javax.swing.DefaultComboBoxModel +import javax.swing.JList +import javax.swing.ListCellRenderer +import javax.swing.MutableComboBoxModel +import javax.swing.event.ListDataListener + +class AsyncComboBox private constructor( + private val comboBoxModel: MutableComboBoxModel, +) : ComboBox(comboBoxModel), Disposable { + private val loading = AtomicBoolean(false) + private val scope = disposableCoroutineScope(this) + + constructor( + comboBoxModel: MutableComboBoxModel = DefaultComboBoxModel(), + customizer: SimpleListCellRenderer.Customizer? = null, + ) : this(comboBoxModel) { + renderer = object : SimpleListCellRenderer() { + override fun getListCellRendererComponent( + list: JList?, + value: T?, + index: Int, + selected: Boolean, + hasFocus: Boolean, + ): Component { + val component = super.getListCellRendererComponent(list, value, index, selected, hasFocus) as SimpleListCellRenderer<*> + + if (loading.get() && index == -1) { + component.icon = AnimatedIcon.Default.INSTANCE + component.text = AwsCoreBundle.message("loading_resource.loading") + } + + return component + } + + override fun customize(list: JList, value: T, index: Int, selected: Boolean, hasFocus: Boolean) { + customizer?.customize(this, value, index) + } + } + } + + constructor( + comboBoxModel: MutableComboBoxModel = DefaultComboBoxModel(), + customRenderer: ListCellRenderer, + ) : this(comboBoxModel) { + renderer = ListCellRenderer { list, value, index, selected, hasFocus -> + if (loading.get() && index == -1) { + val component = JBLabel(AnimatedIcon.Default.INSTANCE) + component.text = AwsCoreBundle.message("loading_resource.loading") + + return@ListCellRenderer component + } + + customRenderer.getListCellRendererComponent(list, value, index, selected, hasFocus) + } + } + + init { + putClientProperty(AnimatedIcon.ANIMATION_IN_RENDERER_ALLOWED, true) + } + + private val reloadAlarm = AlarmFactory.getInstance().create(Alarm.ThreadToUse.SWING_THREAD, this) + private var currentIndicator: ProgressIndicator? = null + + @Synchronized + fun proposeModelUpdate(newModel: suspend (MutableComboBoxModel) -> Unit) { + reloadAlarm.cancelAllRequests() + currentIndicator?.cancel() + loading.set(true) + removeAllItems() + repaint() + val indicator = EmptyProgressIndicator(ModalityState.any()).also { + currentIndicator = it + } + // delay with magic number to debounce + reloadAlarm.addRequest( + { + ProgressManager.getInstance().runProcess( + { + scope.launch { + newModel.invoke(delegatedComboBoxModel(indicator)) + }.invokeOnCompletion { + loading.set(false) + repaint() + } + }, + indicator + ) + }, + 350, + ModalityState.any() + ) + } + + override fun dispose() { + } + + override fun getSelectedItem(): Any? { + if (loading.get()) { + return null + } + return super.getSelectedItem() + } + + @TestOnly + @Synchronized + internal fun waitForSelection(): Future { + val future = AsyncPromise() + while (loading.get()) { + Thread.onSpinWait() + } + future.setResult(selected()) + + return future + } + + override fun setSelectedItem(anObject: Any?) { + if (loading.get()) { + return + } + super.setSelectedItem(anObject) + } + + private fun delegatedComboBoxModel(indicator: ProgressIndicator) = + object : MutableComboBoxModel { + override fun getSize() = comboBoxModel.size + override fun getElementAt(index: Int): T = comboBoxModel.getElementAt(index) + + override fun addListDataListener(l: ListDataListener?) { + throw NotImplementedError() + } + + override fun removeListDataListener(l: ListDataListener?) { + throw NotImplementedError() + } + + override fun setSelectedItem(anItem: Any?) { + comboBoxModel.selectedItem = anItem + } + + override fun getSelectedItem(): Any = comboBoxModel.selectedItem + + override fun addElement(item: T?) { + indicator.checkCanceled() + comboBoxModel.addElement(item) + } + + override fun removeElement(obj: Any?) { + indicator.checkCanceled() + comboBoxModel.removeElement(item) + } + + override fun insertElementAt(item: T?, index: Int) { + indicator.checkCanceled() + comboBoxModel.insertElementAt(item, index) + } + + override fun removeElementAt(index: Int) { + indicator.checkCanceled() + comboBoxModel.removeElementAt(index) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/KeyValueTextField.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/KeyValueTextField.kt new file mode 100644 index 00000000000..9e3000d6329 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/KeyValueTextField.kt @@ -0,0 +1,126 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.ui + +import com.intellij.execution.configuration.EnvironmentVariablesData +import com.intellij.execution.util.EnvVariablesTable +import com.intellij.execution.util.EnvironmentVariable +import com.intellij.icons.AllIcons +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.TextFieldWithBrowseButton +import com.intellij.openapi.util.text.StringUtil +import com.intellij.ui.DocumentAdapter +import com.intellij.ui.UserActivityProviderComponent +import org.jetbrains.annotations.Nls +import software.amazon.q.resources.AwsCoreBundle +import java.awt.Component +import java.util.LinkedHashMap +import java.util.concurrent.CopyOnWriteArrayList +import javax.swing.Icon +import javax.swing.JComponent +import javax.swing.event.ChangeEvent +import javax.swing.event.ChangeListener +import javax.swing.event.DocumentEvent + +/** + * Our version of [com.intellij.execution.configuration.EnvironmentVariablesTextFieldWithBrowseButton]. + * It has been modified to support our use case of having a compact, generic key-value entry dialog. + * Inheriting system env vars is not supported, but rest of UX is generally the same + */ +class KeyValueTextField( + @Nls dialogTitle: String = AwsCoreBundle.message("environment.variables.dialog.title"), +) : TextFieldWithBrowseButton(), UserActivityProviderComponent { + private var data = EnvironmentVariablesData.create(emptyMap(), false) + private val listeners = CopyOnWriteArrayList() + + var envVars: Map + get() = data.envs + set(value) { + data = EnvironmentVariablesData.create(value, false) + text = stringify(data.envs) + } + + init { + addActionListener { + EnvironmentVariablesDialog(this, dialogTitle).show() + } + + textField.document.addDocumentListener( + object : DocumentAdapter() { + override fun textChanged(e: DocumentEvent) { + if (!StringUtil.equals(stringify(data.envs), text)) { + val textEnvs = EnvVariablesTable.parseEnvsFromText(text) + data = EnvironmentVariablesData.create(textEnvs, data.isPassParentEnvs) + fireStateChanged() + } + } + } + ) + } + + private fun convertToVariables(envVars: Map, readOnly: Boolean): List = envVars.map { (key, value) -> + object : EnvironmentVariable(key, value, readOnly) { + override fun getNameIsWriteable(): Boolean = !readOnly + } + } + + override fun getDefaultIcon(): Icon = AllIcons.General.InlineVariables + + override fun getHoveredIcon(): Icon = AllIcons.General.InlineVariablesHover + + override fun addChangeListener(changeListener: ChangeListener) { + listeners.add(changeListener) + } + + override fun removeChangeListener(changeListener: ChangeListener) { + listeners.remove(changeListener) + } + + private fun fireStateChanged() { + listeners.forEach { + it.stateChanged(ChangeEvent(this)) + } + } + + private fun stringify(envVars: Map): String { + if (envVars.isEmpty()) { + return "" + } + + return buildString { + for ((key, value) in envVars) { + if (isNotEmpty()) { + append(";") + } + append(StringUtil.escapeChar(key, ';')) + append("=") + append(StringUtil.escapeChar(value, ';')) + } + } + } + + private inner class EnvironmentVariablesDialog(parent: Component, title: String) : DialogWrapper(parent, true) { + private val envVarTable = EnvVariablesTable().apply { + setValues(convertToVariables(data.envs, false)) + setPasteActionEnabled(true) + } + + init { + this.title = title + init() + } + + override fun createCenterPanel(): JComponent = envVarTable.component + + override fun doOKAction() { + envVarTable.stopEditing() + val newEnvVars = LinkedHashMap() + for (variable in envVarTable.environmentVariables) { + newEnvVars[variable.name] = variable.value + } + envVars = newEnvVars + super.doOKAction() + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/feedback/FeedbackDialog.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/feedback/FeedbackDialog.kt new file mode 100644 index 00000000000..357916985a6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/feedback/FeedbackDialog.kt @@ -0,0 +1,226 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.ui.feedback + +import com.intellij.icons.AllIcons +import com.intellij.ide.BrowserUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.ui.ColorUtil +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBTextArea +import com.intellij.ui.dsl.builder.Cell +import com.intellij.ui.dsl.builder.bind +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.columns +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.rows +import com.intellij.util.IconUtil +import com.intellij.util.ui.UIUtil +import icons.AwsIcons +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.TestOnly +import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment +import software.amazon.q.jetbrains.AwsToolkit +import software.amazon.q.jetbrains.core.coroutines.getCoroutineUiContext +import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.services.telemetry.ClientMetadata +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.FeedbackTelemetry +import software.aws.toolkits.telemetry.Result +import java.net.URLEncoder + +const val FEEDBACK_SOURCE = "source" +const val ENABLED_EXPERIMENTS = "experimentsEnabled" + +abstract class FeedbackDialog( + protected val project: Project, + initialSentiment: Sentiment = Sentiment.POSITIVE, + initialComment: String = "", +) : DialogWrapper(project) { + abstract suspend fun sendFeedback() + + protected abstract fun notificationTitle(): String + protected abstract fun productName(): String + protected open fun feedbackPrompt(): String = AwsCoreBundle.message("feedback.comment.textbox.title", productName()) + + private val coroutineScope = projectCoroutineScope(project) + protected var sentiment = initialSentiment + private val smileIcon = IconUtil.scale(AwsIcons.Misc.SMILE, null, 3f) + private val sadIcon = IconUtil.scale(AwsIcons.Misc.FROWN, null, 3f) + protected var commentText: String = initialComment + private lateinit var comment: Cell + private var lengthLimitLabel = JBLabel(AwsCoreBundle.message("feedback.comment.textbox.initial.length")).also { + it.foreground = UIUtil.getLabelInfoForeground() + } + + private val dialogPanel by lazy { + panel { + if (isToolkit()) { + row { + text(AwsCoreBundle.message("feedback.initial.help.text")) + } + } + group(AwsCoreBundle.message("feedback.connect.with.github.title")) { + row { + icon(AllIcons.Toolwindows.ToolWindowDebugger) + link(AwsCoreBundle.message("feedback.report.issue.link")) { + BrowserUtil.browse( + "${GITHUB_LINK_BASE}${URLEncoder.encode( + "${comment.component.text}\n\n${getToolkitMetadata()}", + Charsets.UTF_8.name() + )}" + ) + } + } + row { + icon(AllIcons.Actions.IntentionBulbGrey) + + link(AwsCoreBundle.message("feedback.request.feature.link")) { + BrowserUtil.browse( + "${GITHUB_LINK_BASE}${URLEncoder.encode( + "${comment.component.text}\n\n${getToolkitMetadata()}", + Charsets.UTF_8.name() + )}" + ) + } + } + row { + icon(AllIcons.Nodes.Tag) + link(AwsCoreBundle.message("feedback.view.source.code.link")) { + BrowserUtil.browse(TOOLKIT_REPOSITORY_LINK) + } + } + } + + group(AwsCoreBundle.message("feedback.share.feedback.title")) { + buttonsGroup { + row { + radioButton("", value = Sentiment.POSITIVE).applyToComponent { + icon(smileIcon) + } + + radioButton("", value = Sentiment.NEGATIVE).applyToComponent { + icon(sadIcon) + } + } + }.bind({ sentiment }, { sentiment = it }) + + row(feedbackPrompt()) {} + row { comment(AwsCoreBundle.message("feedback.customer.alert.info")) } + row { + comment = textArea().rows(6).columns(52).bindText(::commentText).applyToComponent { + this.emptyText.text = AwsCoreBundle.message("feedback.comment.emptyText") + this.lineWrap = true + + this.document.addUndoableEditListener { + onTextAreaUpdate(this.text) + commentText = this.text + } + } + }.comment(commentText) + row { + cell(lengthLimitLabel) + } + } + } + } + + override fun createCenterPanel() = dialogPanel + + override fun doCancelAction() { + super.doCancelAction() + // kill any remaining coroutines + coroutineScope.coroutineContext.cancel() + FeedbackTelemetry.result(project, result = Result.Cancelled) + } + + override fun doOKAction() { + if (okAction.isEnabled) { + dialogPanel.apply() + setOKButtonText(AwsCoreBundle.message("feedback.submitting")) + isOKActionEnabled = false + var result = Result.Succeeded + coroutineScope.launch { + val edtContext = getCoroutineUiContext() + try { + sendFeedback() + + withContext(edtContext) { + close(OK_EXIT_CODE) + } + + notifyInfo(notificationTitle(), AwsCoreBundle.message("feedback.submit_success"), project) + } catch (e: Exception) { + withContext(edtContext) { + Messages.showMessageDialog( + AwsCoreBundle.message("feedback.submit_failed", e), + AwsCoreBundle.message("feedback.submit_failed_title"), + null + ) + setOKButtonText(AwsCoreBundle.message("feedback.submit_button")) + isOKActionEnabled = true + } + result = Result.Failed + } finally { + FeedbackTelemetry.result(project, result = result) + } + } + } + } + + override fun doValidate(): ValidationInfo? { + super.doValidate() + val comment = commentText + + return when { + comment.isEmpty() -> null + comment.length >= MAX_LENGTH -> ValidationInfo(AwsCoreBundle.message("feedback.validation.comment_too_long")) + else -> null + } + } + + private fun onTextAreaUpdate(commentText: String) { + this.commentText = commentText + val currentLength = this.commentText.length + val lengthText = AwsCoreBundle.message("feedback.limit.label", MAX_LENGTH - currentLength) + lengthLimitLabel.text = if (currentLength >= MAX_LENGTH) { + "$lengthText" + } else { + lengthText + } + } + + init { + super.init() + + title = AwsCoreBundle.message("feedback.title", productName()) + setOKButtonText(AwsCoreBundle.message("feedback.submit_button")) + } + + private fun isToolkit(): Boolean = (productName() == "Toolkit") + + @TestOnly + fun getFeedbackDialog() = dialogPanel + + companion object { + const val MAX_LENGTH = 2000 // backend restriction + private const val TOOLKIT_REPOSITORY_LINK = AwsToolkit.GITHUB_URL + private const val GITHUB_LINK_BASE = "$TOOLKIT_REPOSITORY_LINK/issues/new?body=" + private fun getToolkitMetadata(): String { + val metadata = ClientMetadata.Companion.getDefault() + return """ + --- + Toolkit: ${metadata.awsProduct} ${metadata.awsVersion} + OS: ${metadata.os} ${metadata.osVersion} + IDE: ${metadata.parentProduct} ${metadata.parentProductVersion} + """.trimIndent() + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/DevFileUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/DevFileUtils.kt new file mode 100644 index 00000000000..52eeb46efdd --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/DevFileUtils.kt @@ -0,0 +1,15 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.openapi.vfs.VirtualFile + +fun isDevFile(file: VirtualFile): Boolean = + file.name.matches(Regex("devfile\\.ya?ml", RegexOption.IGNORE_CASE)) + +fun isWorkspaceDevFile(file: VirtualFile, addressableRoot: VirtualFile): Boolean = + isDevFile(file) && file.parent?.path == addressableRoot.path + +fun getWorkspaceDevFile(addressableRoot: VirtualFile): VirtualFile? = + addressableRoot.children.find { isDevFile(it) } diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt new file mode 100644 index 00000000000..3cd6c546c83 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt @@ -0,0 +1,60 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.openapi.project.Project +import kotlinx.coroutines.delay +import kotlinx.coroutines.withTimeoutOrNull +import org.slf4j.LoggerFactory +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.core.utils.debug + +private val LOG = LoggerFactory.getLogger("FunctionUtils") + +suspend fun pollFor(func: () -> T): T? { + val timeoutMillis = 50000L + + val result = withTimeoutOrNull(timeoutMillis) { + while (true) { + val result = func() + if (result != null) { + return@withTimeoutOrNull result + } + + delay(50L) + } + null + } + + return result +} + +// TODO: move to Q package, living here because Codewhisperer package is not moved to Q +/** + * Note: if a connection doesn't have all required scopes for Q, we determine it's NOT_AUTHENTICATED + */ +fun isQConnected(project: Project): Boolean { + val manager = ToolkitConnectionManager.getInstance(project) + val qState = manager.connectionStateForFeature(QConnection.getInstance()) + LOG.debug { + "qConnectionState: $qState" + } + return qState != BearerTokenAuthState.NOT_AUTHENTICATED +} + +fun isQExpired(project: Project): Boolean { + val manager = ToolkitConnectionManager.getInstance(project) + val qState = manager.connectionStateForFeature(QConnection.getInstance()) + LOG.debug { + "qConnectionState: $qState" + } + return qState == BearerTokenAuthState.NEEDS_REFRESH +} + +fun AwsBearerTokenConnection.state(): BearerTokenAuthState = + (getConnectionSettings().tokenProvider.delegate as? BearerTokenProvider)?.state() ?: BearerTokenAuthState.NOT_AUTHENTICATED diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/MRUList.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/MRUList.kt new file mode 100644 index 00000000000..ecb1350b7c3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/MRUList.kt @@ -0,0 +1,26 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +class MRUList(private val maxSize: Int) { + private val internalList = mutableListOf() + + fun add(element: T) { + internalList.remove(element) + internalList.add(0, element) + trimToSize() + } + + fun elements(): List = internalList.toList() + + fun clear() { + internalList.clear() + } + + private fun trimToSize() { + while (internalList.size > maxSize) { + internalList.removeAt(internalList.size - 1) + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt new file mode 100644 index 00000000000..cff959edbf3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt @@ -0,0 +1,185 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationAction +import com.intellij.notification.NotificationListener +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications.Bus.notify +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogBuilder +import com.intellij.openapi.util.text.StringUtil +import com.intellij.ui.ScrollPaneFactory +import org.slf4j.LoggerFactory +import software.amazon.q.jetbrains.core.help.HelpIds +import software.amazon.q.jetbrains.core.notifications.BannerNotificationService +import software.amazon.q.jetbrains.core.notifications.NotificationDismissalState +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.utils.warn +import javax.swing.JLabel +import javax.swing.JTextArea + +private const val GROUP_DISPLAY_ID = "AWS Toolkit" +private const val GROUP_DISPLAY_ID_STICKY = "aws.toolkit_sticky" + +private val LOG = LoggerFactory.getLogger("NotificationUtils.kt") + +fun Throwable.notifyError(title: String = "", project: Project? = null, stripHtml: Boolean = true) { + val message = getCleanedContent(this.message ?: "${this::class.java.name}${this.stackTrace?.joinToString("\n", prefix = "\n")}", stripHtml) + LOG.warn(this) { title.takeIf { it.isNotBlank() }?.let { "$it ($message)" } ?: message } + notify( + Notification( + GROUP_DISPLAY_ID, + title, + message, + NotificationType.ERROR + ), + project + ) +} + +fun notify(type: NotificationType, title: String, content: String = "", project: Project? = null, notificationActions: Collection) { + val notification = Notification(GROUP_DISPLAY_ID, title, content, type) + notificationActions.forEach { + notification.addAction(if (it !is NotificationAction) createNotificationExpiringAction(it) else it) + } + notify(notification, project) +} + +fun notifyStickyWithData( + type: NotificationType, + title: String, + content: String = "", + project: Project? = null, + notificationActions: Collection, + id: String, +) { + val notification = Notification(GROUP_DISPLAY_ID_STICKY, title, content, type) + notificationActions.forEach { + notification.addAction(it) + } + + notification.addAction( + createNotificationExpiringAction( + object : AnAction("Dismiss") { + override fun actionPerformed(e: AnActionEvent) { + BannerNotificationService.getInstance().removeNotification(id) + NotificationDismissalState.getInstance().dismissNotification(id) + } + } + ) + + ) + + notify(notification, project) +} + +private fun notifySticky(type: NotificationType, title: String, content: String = "", project: Project? = null, notificationActions: Collection) { + val notification = Notification(GROUP_DISPLAY_ID_STICKY, title, content, type) + notificationActions.forEach { + notification.addAction(if (it !is NotificationAction) createNotificationExpiringAction(it) else it) + } + notify(notification, project) +} + +fun notifyStickyInfo( + title: String, + content: String = "", + project: Project? = null, + notificationActions: Collection = listOf(), + stripHtml: Boolean = true, +) = notifySticky(NotificationType.INFORMATION, title, getCleanedContent(content, stripHtml), project, notificationActions) + +fun notifyStickyWarn( + title: String, + content: String = "", + project: Project? = null, + notificationActions: Collection = listOf(), + stripHtml: Boolean = true, +) = notifySticky(NotificationType.WARNING, title, getCleanedContent(content, stripHtml), project, notificationActions) + +fun notifyStickyError( + title: String, + content: String = "", + project: Project? = null, + notificationActions: Collection = listOf(), + stripHtml: Boolean = true, +) = notifySticky(NotificationType.ERROR, title, getCleanedContent(content, stripHtml), project, notificationActions) + +fun notifyInfo(title: String, content: String = "", project: Project? = null, listener: NotificationListener? = null, stripHtml: Boolean = true) = + notify(Notification(GROUP_DISPLAY_ID, title, getCleanedContent(content, stripHtml), NotificationType.INFORMATION, listener), project) + +fun notifyInfo(title: String, content: String = "", project: Project? = null, notificationActions: Collection, stripHtml: Boolean = true) = + notify(NotificationType.INFORMATION, title, getCleanedContent(content, stripHtml), project, notificationActions) + +fun notifyWarn(title: String, content: String = "", project: Project? = null, notificationActions: Collection, stripHtml: Boolean = true) = + notify(NotificationType.WARNING, title, getCleanedContent(content, stripHtml), project, notificationActions) + +fun notifyWarn(title: String, content: String = "", project: Project? = null, listener: NotificationListener? = null, stripHtml: Boolean = true) = + notify(Notification(GROUP_DISPLAY_ID, title, getCleanedContent(content, stripHtml), NotificationType.WARNING, listener), project) + +fun notifyError(title: String, content: String = "", project: Project? = null, action: AnAction, stripHtml: Boolean = true) = + notify(NotificationType.ERROR, title, getCleanedContent(content, stripHtml), project, listOf(action)) + +fun notifyError(title: String, content: String = "", project: Project? = null, notificationActions: Collection, stripHtml: Boolean = true) = + notify(NotificationType.ERROR, title, getCleanedContent(content, stripHtml), project, notificationActions) + +fun notifyError( + title: String = AwsCoreBundle.message("aws.notification.title"), + content: String = "", + project: Project? = null, + listener: NotificationListener? = null, + stripHtml: Boolean = true, +) = notify(Notification(GROUP_DISPLAY_ID, title, getCleanedContent(content, stripHtml), NotificationType.ERROR, listener), project) + +fun tryNotify(message: String, block: () -> T): T? = try { + block() +} catch (e: Exception) { + e.notifyError(message) + null +} + +/** + * Creates a Notification Action that will expire a notification after performing some AnAction + */ +fun createNotificationExpiringAction(action: AnAction): NotificationAction = NotificationAction.create( + action.templatePresentation.text +) { actionEvent, notification -> + action.actionPerformed(actionEvent) + notification.expire() +} + +fun createShowMoreInfoDialogAction(actionName: String?, title: String?, message: String?, moreInfo: String?) = + object : AnAction(actionName) { + override fun isDumbAware() = true + + override fun actionPerformed(e: AnActionEvent) { + val dialogTitle = title ?: "" + + val textArea = JTextArea(moreInfo).apply { + columns = 50 + rows = 5 + lineWrap = true + wrapStyleWord = true + isEditable = false + } + + val dialogBuilder = DialogBuilder().apply { + setTitle(dialogTitle) + setNorthPanel(JLabel(message)) + setCenterPanel(ScrollPaneFactory.createScrollPane(textArea)) + setPreferredFocusComponent(textArea) + setHelpId(HelpIds.SETUP_CREDENTIALS.id) + removeAllActions() + addOkAction() + } + + dialogBuilder.show() + } + } + +fun getCleanedContent(content: String, stripHtml: Boolean): String = if (stripHtml) StringUtil.stripHtml(content, true) else content diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/PsiUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/PsiUtils.kt new file mode 100644 index 00000000000..dad7476cb93 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/PsiUtils.kt @@ -0,0 +1,27 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.injected.editor.VirtualFileWindow +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.psi.PsiElement + +fun PsiElement.isTestOrInjectedText(): Boolean { + val project = this.project + val virtualFile = this.containingFile.virtualFile ?: return false + if (this.isInjectedText() || ProjectRootManager.getInstance(project).fileIndex.isInTestSourceContent(virtualFile)) { + return true + } + + return false +} + +fun PsiElement.isInjectedText(): Boolean { + val virtualFile = this.containingFile.virtualFile ?: return false + if (virtualFile is VirtualFileWindow) { + return true + } + + return false +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt new file mode 100644 index 00000000000..7ff62a8fa0b --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt @@ -0,0 +1,39 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.idea.AppMode +import com.intellij.ui.jcef.JBCefApp +import software.amazon.q.jetbrains.isDeveloperMode +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.tryOrNull +import java.nio.file.Paths + +/** + * @return true if running in any type of remote environment + */ +fun isRunningOnRemoteBackend() = AppMode.isRemoteDevHost() + +/** + * @return true if running in a codecatalyst remote environment + */ +fun isCodeCatalystDevEnv() = System.getenv("__DEV_ENVIRONMENT_ID") != null + +/** + * @return low fidelity "is internal compute". is not exact and may fail at any time + */ +private val isInternalAmznLinuxCompute by lazy { + tryOrNull { + Paths.get("/apollo").exists() + } ?: false +} + +/** + * On remote, only enabled experimentally and for internal + */ +fun isQWebviewsAvailable() = JBCefApp.isSupported() && if (!isRunningOnRemoteBackend()) { + true +} else { + isDeveloperMode() || isInternalAmznLinuxCompute +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/SpinUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/SpinUtils.kt new file mode 100644 index 00000000000..4e1e6cbaefc --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/SpinUtils.kt @@ -0,0 +1,42 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import java.time.Duration +import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicReference + +/** + * Keeps checking the condition until the max duration as been reached. Checks every 100ms + */ +fun spinUntil(duration: Duration, interval: Duration = Duration.ofMillis(100), condition: () -> Boolean) { + val start = System.nanoTime() + runBlocking { + while (!condition()) { + if (System.nanoTime() - start > duration.toNanos()) { + throw TimeoutException("Condition not reached within $duration") + } + delay(interval.toMillis()) + } + } +} + +/** + * Keeps checking the block until the max duration as been reached or a non-null value has been returned. Checks every 100ms + */ +fun spinUntilValue(duration: Duration, interval: Duration = Duration.ofMillis(100), block: () -> T?): T { + val ref = AtomicReference() + spinUntil(duration, interval) { + val value = block() + if (value == null) { + return@spinUntil false + } else { + ref.set(value) + return@spinUntil true + } + } + return ref.get() +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/TextUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/TextUtils.kt new file mode 100644 index 00000000000..1c3ebd208f2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/TextUtils.kt @@ -0,0 +1,44 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.lang.Language +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.diff.impl.patch.PatchReader +import com.intellij.openapi.diff.impl.patch.TextFilePatch +import com.intellij.openapi.diff.impl.patch.apply.GenericPatchApplier +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.codeStyle.CodeStyleManager + +fun formatText(project: Project, language: Language, content: String): String { + var result = content + CommandProcessor.getInstance().runUndoTransparentAction { + PsiFileFactory.getInstance(project) + .createFileFromText("foo.bar", language, content, false, true)?.let { + result = CodeStyleManager.getInstance(project).reformat(it).text + } + } + + return result +} + +/** + * Designed to convert underscore separated words (e.g. UPDATE_COMPLETE) into title cased human readable text + * (e.g. Update Complete) + */ +fun String.toHumanReadable() = StringUtil.toTitleCase(lowercase().replace('_', ' ')) + +fun generateUnifiedPatch(patch: String, filePath: String): TextFilePatch { + val unifiedPatch = "--- $filePath\n+++ $filePath\n$patch" + val patchReader = PatchReader(unifiedPatch) + val patches = patchReader.readTextPatches() + return patches[0] +} + +fun applyPatch(patch: String, fileContent: String, filePath: String): String { + val unifiedPatch = generateUnifiedPatch(patch, filePath) + return GenericPatchApplier.applySomehow(fileContent, unifiedPatch.hunks).patchedText +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ThreadingUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ThreadingUtils.kt new file mode 100644 index 00000000000..d60d70a7986 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ThreadingUtils.kt @@ -0,0 +1,90 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.util.ProgressIndicatorUtils +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Ref +import com.intellij.openapi.util.ThrowableComputable +import com.intellij.util.ExceptionUtil +import com.intellij.util.concurrency.AppExecutorUtil +import com.intellij.util.concurrency.Semaphore +import io.opentelemetry.context.Context +import software.amazon.q.jetbrains.services.telemetry.PluginResolver +import java.time.Duration +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit + +// There is a new/experimental API in IJ SDK, but replicate a simpler one here till we can use it +fun assertIsNonDispatchThread() { + if (!ApplicationManager.getApplication().isDispatchThread) return + throw RuntimeException("Access from event dispatch thread is not allowed.") +} + +fun runUnderProgressIfNeeded(project: Project?, title: String, cancelable: Boolean, task: () -> T): T = + if (ApplicationManager.getApplication().isDispatchThread) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(ThrowableComputable { task.invoke() }, title, cancelable, project) + } else { + task.invoke() + } + +fun computeOnEdt(modalityState: ModalityState = ModalityState.any(), supplier: () -> T): T { + val application = ApplicationManager.getApplication() + if (application.isDispatchThread) { + return supplier.invoke() + } + val indicator = ProgressManager.getInstance().progressIndicator + val semaphore = Semaphore(1) + val result = Ref.create() + val error = Ref.create() + val runnable = Runnable { + try { + if (indicator == null || !indicator.isCanceled) { + result.set(supplier.invoke()) + } + } catch (ex: Throwable) { + error.set(ex) + } finally { + semaphore.up() + } + } + + ApplicationManager.getApplication().invokeLater(runnable, modalityState) + + ProgressIndicatorUtils.awaitWithCheckCanceled(semaphore, indicator) + ExceptionUtil.rethrowAllAsUnchecked(error.get()) + + return result.get() +} + +fun sleepWithCancellation(sleepAmount: Duration, indicator: ProgressIndicator?) { + val semaphore = Semaphore(1) + val future = AppExecutorUtil.getAppScheduledExecutorService().schedule( + { semaphore.up() }, + sleepAmount.toMillis(), + TimeUnit.MILLISECONDS + ) + try { + ProgressIndicatorUtils.awaitWithCheckCanceled(semaphore, indicator) + } finally { + future.cancel(true) + } +} + +fun pluginAwareExecuteOnPooledThread(action: () -> T): Future { + /** + * Ensures plugin resolution references parent thread plugin resolver since + * worker thread will not contain original call stack. Necessary for telemetry. + */ + val pluginResolver = PluginResolver.Companion.fromCurrentThread() + val context = Context.current() + return ApplicationManager.getApplication().executeOnPooledThread { + PluginResolver.Companion.setThreadLocal(pluginResolver) + context.wrap(action).call() + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/actions/OpenBrowserAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/actions/OpenBrowserAction.kt new file mode 100644 index 00000000000..ac19f49e6de --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/actions/OpenBrowserAction.kt @@ -0,0 +1,21 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.actions + +import com.intellij.ide.BrowserUtil +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAware +import org.jetbrains.annotations.NotNull +import javax.swing.Icon + +class OpenBrowserAction(title: String, icon: Icon? = null, private val url: String) : AnAction(title, null, icon), DumbAware { + override fun actionPerformed(@NotNull e: AnActionEvent) { + try { + BrowserUtil.browse(url) + } catch (_: Exception) { + // ignore + } + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/ResizingColumnRenderer.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/ResizingColumnRenderer.kt new file mode 100644 index 00000000000..aefd3ecf667 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/ResizingColumnRenderer.kt @@ -0,0 +1,52 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.ui + +import com.intellij.ui.SimpleColoredRenderer +import java.awt.BorderLayout +import java.awt.Component +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JTable +import javax.swing.border.CompoundBorder +import javax.swing.table.DefaultTableCellRenderer +import javax.swing.table.TableCellRenderer + +abstract class ResizingColumnRenderer : TableCellRenderer, SimpleColoredRenderer() { + private val defaultRenderer = DefaultTableCellRenderer() + abstract fun getText(value: Any?): String? + + override fun getTableCellRendererComponent(table: JTable?, value: Any?, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component { + // This wrapper will let us force the component to be at the top instead of in the middle for linewraps + val wrapper = JPanel(BorderLayout()) + + // this is basically what ColoredTableCellRenderer is doing, but we can't override getTableCellRendererComponent on that class + cellState.updateRenderer(defaultRenderer) + + val defaultComponent = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) + if (table == null) { + return defaultComponent + } + val component = defaultComponent as? JLabel ?: return defaultComponent + + // This will set the component text accordingly + component.text = getText(value) + + if (component.preferredSize.width > table.columnModel.getColumn(column).preferredWidth) { + // add 3 pixels of padding. No padding makes it go into ... mode cutting off the end + table.columnModel.getColumn(column).preferredWidth = component.preferredSize.width + 3 + table.columnModel.getColumn(column).maxWidth = component.preferredSize.width + 3 + } + wrapper.add(component, BorderLayout.NORTH) + // Make sure the background matches for selection + wrapper.background = component.background + // if a component is selected, it puts a border on it, move the border to the wrapper instead + if (isSelected) { + // this border has an outside and inside border, take only the outside border + wrapper.border = (component.border as? CompoundBorder)?.outsideBorder + } + component.border = null + return wrapper + } +} diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/UiUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/UiUtils.kt new file mode 100644 index 00000000000..7b3a4de79ba --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/UiUtils.kt @@ -0,0 +1,285 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +@file:JvmName("UiUtils") + +package software.amazon.q.jetbrains.utils.ui + +import com.intellij.lang.Language +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.ui.DialogPanel +import com.intellij.openapi.ui.GraphicsConfig +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.ui.CellRendererPanel +import com.intellij.ui.ClickListener +import com.intellij.ui.EditorTextField +import com.intellij.ui.ExperimentalUI +import com.intellij.ui.JBColor +import com.intellij.ui.JreHiDpiUtil +import com.intellij.ui.components.JBTextArea +import com.intellij.ui.dsl.builder.Cell +import com.intellij.ui.dsl.builder.MutableProperty +import com.intellij.ui.paint.LinePainter2D +import com.intellij.ui.speedSearch.SpeedSearchSupply +import com.intellij.util.text.DateFormatUtil +import com.intellij.util.text.SyncDateFormat +import com.intellij.util.ui.GraphicsUtil +import com.intellij.util.ui.JBInsets +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import software.amazon.q.jetbrains.ui.KeyValueTextField +import software.amazon.q.jetbrains.utils.formatText +import java.awt.AlphaComposite +import java.awt.Color +import java.awt.Component +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.Shape +import java.awt.event.MouseEvent +import java.awt.geom.RoundRectangle2D +import java.text.SimpleDateFormat +import javax.swing.AbstractButton +import javax.swing.BorderFactory +import javax.swing.JComboBox +import javax.swing.JComponent +import javax.swing.JTable +import javax.swing.JTextArea +import javax.swing.JTextField +import javax.swing.ListModel +import javax.swing.border.Border +import javax.swing.table.TableCellRenderer +import javax.swing.text.Highlighter +import javax.swing.text.JTextComponent + +fun JTextField?.blankAsNull(): String? = if (this?.text?.isNotBlank() == true) { + text +} else { + null +} + +@Suppress("UNCHECKED_CAST") +fun JComboBox?.selected(): T? = this?.selectedItem as? T + +fun EditorTextField.formatAndSet(content: String, language: Language) { + CommandProcessor.getInstance().runUndoTransparentAction { + val formatted = formatText(this.project, language, content) + runWriteAction { + document.setText(formatted) + } + } +} + +/** + * Allows triggering [button] selection based on clicking on receiver component + */ +@JvmOverloads +fun JComponent.addQuickSelect(button: AbstractButton, postAction: Runnable? = null) { + object : ClickListener() { + override fun onClick(event: MouseEvent, clickCount: Int): Boolean { + if (button.isSelected) { + return false + } + button.isSelected = true + postAction?.run() + return true + } + }.installOn(this) +} + +fun ListModel.find(predicate: (T) -> Boolean): T? { + for (i in 0 until size) { + val element = getElementAt(i)?.takeIf(predicate) + if (element != null) { + return element + } + } + return null +} + +// Error messages do not work on a disabled component. May be a JetBrains bug? +fun JComponent.validationInfo(message: String) = when { + isEnabled -> ValidationInfo(message, this) + else -> ValidationInfo(message) +} + +val BETTER_GREEN = JBColor(Color(104, 197, 116), JBColor.GREEN.darker()) + +/** + * Fork of JetBrain's intellij-community UIUtils.drawSearchMatch allowing us to highlight multiple a multi-line + * text field (startY was added and used instead of constants). Also auto-converted to Kotlin by Intellij + */ +fun drawSearchMatch( + g: Graphics2D, + startX: Float, + endX: Float, + startY: Float, + height: Int, +) { + val color1 = JBColor.namedColor("SearchMatch.startBackground", JBColor.namedColor("SearchMatch.startColor", 0xffeaa2)) + val color2 = JBColor.namedColor("SearchMatch.endBackground", JBColor.namedColor("SearchMatch.endColor", 0xffd042)) + drawSearchMatch(g, startX, endX, startY, height, color1, color2) +} + +fun drawSearchMatch(graphics2D: Graphics2D, startXf: Float, endXf: Float, startY: Float, height: Int, gradientStart: Color, gradientEnd: Color) { + val config = GraphicsConfig(graphics2D) + var alpha = JBUI.getInt("SearchMatch.transparency", 70) / 100f + alpha = if (alpha < 0 || alpha > 1) 0.7f else alpha + graphics2D.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha) + graphics2D.paint = UIUtil.getGradientPaint(startXf, startY + 2f, gradientStart, startXf, startY - height - 5.toFloat(), gradientEnd) + if (JreHiDpiUtil.isJreHiDPI(graphics2D)) { + val c = GraphicsUtil.setupRoundedBorderAntialiasing(graphics2D) + graphics2D.fill(RoundRectangle2D.Float(startXf, startY + 2, endXf - startXf, (height - 4).toFloat(), 5f, 5f)) + c.restore() + config.restore() + return + } + val startX = startXf.toInt() + val endX = endXf.toInt() + graphics2D.fillRect(startX, startY.toInt() + 3, endX - startX, height - 5) + val drawRound = endXf - startXf > 4 + if (drawRound) { + LinePainter2D.paint(graphics2D, startX - 1.toDouble(), startY + 4.0, startX - 1.toDouble(), startY + height - 4.toDouble()) + LinePainter2D.paint(graphics2D, endX.toDouble(), startY + 4.0, endX.toDouble(), startY + height - 4.toDouble()) + graphics2D.color = Color(100, 100, 100, 50) + LinePainter2D.paint(graphics2D, startX - 1.toDouble(), startY + 4.0, startX - 1.toDouble(), startY + height - 4.toDouble()) + LinePainter2D.paint(graphics2D, endX.toDouble(), startY + 4.0, endX.toDouble(), startY + height - 4.toDouble()) + LinePainter2D.paint(graphics2D, startX.toDouble(), startY + 3.0, endX - 1.toDouble(), startY + 3.0) + LinePainter2D.paint(graphics2D, startX.toDouble(), startY + height - 3.toDouble(), endX - 1.toDouble(), startY + height - 3.toDouble()) + } + config.restore() +} + +fun Component.setSelectionHighlighting(table: JTable, isSelected: Boolean) { + if (isSelected) { + foreground = table.selectionForeground + background = table.selectionBackground + } else { + foreground = table.foreground + background = table.background + } +} + +private class SpeedSearchHighlighter : Highlighter.HighlightPainter { + override fun paint(g: Graphics?, startingPoint: Int, endingPoint: Int, bounds: Shape?, component: JTextComponent?) { + component ?: return + val graphics = g as? Graphics2D ?: return + val beginningRect = component.modelToView(startingPoint) + val endingRect = component.modelToView(endingPoint) + drawSearchMatch(graphics, beginningRect.x.toFloat(), endingRect.x.toFloat(), beginningRect.y.toFloat(), beginningRect.height) + } +} + +private fun JTextArea.speedSearchHighlighter(speedSearchEnabledComponent: JComponent) { + // matchingFragments does work with wrapped text but not around words if they are wrapped, so it will also need to be extended + // in the future + val speedSearch = SpeedSearchSupply.getSupply(speedSearchEnabledComponent) ?: return + val fragments = speedSearch.matchingFragments(text)?.iterator() ?: return + fragments.forEach { + highlighter?.addHighlight(it.startOffset, it.endOffset, SpeedSearchHighlighter()) + } +} + +class WrappingCellRenderer( + private val wrapOnSelection: Boolean = false, + private val wrapOnToggle: Boolean = false, + private val truncateAfterChars: Int? = null, +) : CellRendererPanel(), TableCellRenderer { + var wrap: Boolean = false + + private val textArea = JBTextArea() + + init { + textArea.font = UIUtil.getLabelFont() + textArea.wrapStyleWord = true + + add(textArea) + } + + override fun getTableCellRendererComponent(table: JTable?, value: Any?, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component { + if (table == null) { + return this + } + + textArea.lineWrap = (wrapOnSelection && isSelected) || (wrapOnToggle && wrap) + val text = (value as? String).orEmpty() + textArea.text = if (truncateAfterChars != null) { + text.take(truncateAfterChars) + } else { + text + } + textArea.setSelectionHighlighting(table, isSelected) + + setSize(table.columnModel.getColumn(column).width, preferredSize.height) + if (table.getRowHeight(row) != preferredSize.height) { + table.setRowHeight(row, preferredSize.height) + } + + textArea.speedSearchHighlighter(table) + + return this + } +} + +// TODO: figure out why this has weird hysteresis during rendering causing no text +class ResizingDateColumnRenderer(showSeconds: Boolean) : ResizingColumnRenderer() { + private val formatter: SyncDateFormat = if (showSeconds) { + SyncDateFormat(SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")) + } else { + DateFormatUtil.getDateTimeFormat() + } + + override fun getText(value: Any?): String? = (value as? String)?.toLongOrNull()?.let { formatter.format(it) } +} + +class ResizingTextColumnRenderer : ResizingColumnRenderer() { + override fun getText(value: Any?): String? = (value as? String)?.trim() +} + +/** + * When a panel is made of more than one panel, apply and the validation callbacks do not work as expected for + * the child panels. This function makes it so the validation callbacks and apply are actually called. + * @param applies An additional function that allows control based on visibility of other components or other factors + */ + +fun Cell.installOnParent(applies: () -> Boolean = { true }): Cell { + validationOnApply { + validate(applies, it) + } + return this +} + +private inline fun validate(applies: () -> Boolean, component: DialogPanel): ValidationInfo? = + if (!applies()) { + null + } else { + val errors = component.validateCallbacks.mapNotNull { it() } + if (errors.isEmpty()) { + component.apply() + } + errors.firstOrNull() + } + +/** + * Add a contextual help icon component + */ + +fun Cell.withBinding(binding: MutableProperty>) = + this.bind( + componentGet = { component -> component.envVars }, + componentSet = { component, value -> component.envVars = value }, + binding + ) + +fun editorNotificationCompoundBorder(outsideBorder: Border) = BorderFactory.createCompoundBorder( + // outside border + outsideBorder, + // inside border + // helper util not available in JBUI until 232 + // https://github.com/JetBrains/intellij-community/blob/222/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java#L135-L136 + JBUI.Borders.empty( + JBUI.insets( + "Editor.Notification.borderInsets", + if (ExperimentalUI.isNewUI()) JBInsets.create(9, 16) else JBInsets.create(5, 10) + ) + ) +) diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/resources/AwsCoreBundle.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/resources/AwsCoreBundle.kt new file mode 100644 index 00000000000..388f7b4aa86 --- /dev/null +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/resources/AwsCoreBundle.kt @@ -0,0 +1,19 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.resources + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.PropertyKey + +internal const val BUNDLE_FQN: String = "software.aws.toolkits.resources.MessagesBundle" + +object AwsCoreBundle { + private val BUNDLE = DynamicBundle(AwsCoreBundle::class.java, BUNDLE_FQN) + + fun message(@PropertyKey(resourceBundle = BUNDLE_FQN) key: String, vararg params: Any) = + BUNDLE.getMessage(key, *params) + + fun messagePointer(@PropertyKey(resourceBundle = BUNDLE_FQN) key: String, vararg params: Any) = + BUNDLE.getLazyMessage(key, *params) +} diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/BrowserMessageTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/BrowserMessageTest.kt new file mode 100644 index 00000000000..606c6f5ba1d --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/BrowserMessageTest.kt @@ -0,0 +1,346 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.exc.MismatchedInputException +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.openapi.project.Project +import com.intellij.testFramework.ProjectExtension +import com.intellij.ui.jcef.JBCefBrowserBase +import com.intellij.ui.jcef.JBCefJSQuery +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.ObjectAssert +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.mock +import software.amazon.q.jetbrains.core.webview.BrowserMessage +import software.amazon.q.jetbrains.core.webview.BrowserState +import software.amazon.q.jetbrains.core.webview.LoginBrowser + +class NoOpLoginBrowser(project: Project) : LoginBrowser(project) { + override val jcefBrowser: JBCefBrowserBase = mock() + + override fun prepareBrowser(state: BrowserState) {} + + override fun loadWebView(query: JBCefJSQuery) {} + + override fun handleBrowserMessage(message: BrowserMessage?) {} +} + +class BrowserMessageTest { + private lateinit var objectMapper: ObjectMapper + + companion object { + @JvmField + @RegisterExtension + val projectExtension = ProjectExtension() + } + + private inline fun assertDeserializedInstanceOf(jsonStr: String): ObjectAssert { + val actual = objectMapper.readValue(jsonStr) + return assertThat(actual).isInstanceOf(T::class.java) + } + + private inline fun assertDeserializedWillThrow(jsonStr: String) { + assertThatThrownBy { + objectMapper.readValue(jsonStr) + }.isInstanceOf(T::class.java) + } + + @BeforeEach + fun setup() { + objectMapper = NoOpLoginBrowser(projectExtension.project).objectMapper + } + + @Test + fun `exact match, deserialization return correct BrowserMessage subtype`() { + assertDeserializedInstanceOf( + """ + { + "command": "prepareUi" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "toggleBrowser" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "selectConnection", + "connectionId": "foo" + } + """ + ).isEqualTo(BrowserMessage.SelectConnection("foo")) + + assertDeserializedInstanceOf( + """ + { + "command": "loginBuilderId" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "loginIdC", + "url": "foo", + "region": "bar", + "feature": "baz" + } + """ + ).isEqualTo( + BrowserMessage.LoginIdC( + url = "foo", + region = "bar", + feature = "baz" + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "loginIAM", + "profileName": "foo", + "accessKey": "bar", + "secretKey": "baz" + } + """ + ).isEqualTo( + BrowserMessage.LoginIAM( + profileName = "foo", + accessKey = "bar", + secretKey = "baz" + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "cancelLogin" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "signout" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "reauth" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "sendUiClickTelemetry" + } + """ + ).isEqualTo( + BrowserMessage.SendUiClickTelemetry( + signInOptionClicked = null + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "webviewTelemetry", + "event": "{ \"metricName\": \"foo\" }" + } + """.trimIndent() + ).isEqualTo( + BrowserMessage.PublishWebviewTelemetry( + event = "{ \"metricName\": \"foo\" }" + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "openUrl", + "externalLink": "foo" + } + """ + ).isEqualTo( + BrowserMessage.OpenUrl("foo") + ) + } + + @Test + fun `unrecognizable command - deserialize should throw MismatchedInputException`() { + assertDeserializedWillThrow( + """ + { + "command": "" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "zxcasdqwe" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "foo bar baz" + } + """ + ) + } + + @Test + fun `unknown fields - deserialize should throw MismatchedInputException`() { + assertDeserializedWillThrow( + """ + { + "command": "prepareUi", + "unknown": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "profileName": "foo", + "unknown": "bar" + } + """ + ) + } + + @Test + fun `missing required fields - deserialize fail `() { + assertDeserializedWillThrow( + """ + { + "command": "selectConnection" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "accessKey": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIdC" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIdC", + "url": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIdC", + "region": "bar", + "feature": "baz" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "profileName": "bar" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "profileName": "bar", + "secretKey": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "accessKey": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "openUrl" + } + """ + ) + } + + @Test + fun `Nullable fields in sendUiClickTelemetry should not throw exception`() { + assertDoesNotThrow { + objectMapper.readValue( + """ + { + "command": "sendUiClickTelemetry", + "signInOptionClicked": null + } + """ + ) + } + + assertDoesNotThrow { + objectMapper.readValue( + """ + { + "command": "sendUiClickTelemetry" + + } + """ + ) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt new file mode 100644 index 00000000000..94538bd6f60 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt @@ -0,0 +1,161 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.project.Project +import com.intellij.testFramework.ProjectExtension +import com.intellij.ui.jcef.JBCefBrowserBase +import com.intellij.ui.jcef.JBCefJSQuery +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.q.jetbrains.core.webview.BrowserMessage +import software.amazon.q.jetbrains.core.webview.BrowserState +import software.amazon.q.jetbrains.core.webview.LoginBrowser +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.jetbrains.services.telemetry.MockTelemetryServiceExtension +import kotlin.test.assertNotNull + +class TestLoginBrowser(project: Project) : LoginBrowser(project) { + // test env can't initiate a real jcef and will throw error + override val jcefBrowser: JBCefBrowserBase + get() = mock() + + override fun handleBrowserMessage(message: BrowserMessage?) {} + + override fun prepareBrowser(state: BrowserState) {} + + override fun loadWebView(query: JBCefJSQuery) {} +} + +@Disabled +class LoginBrowserTest { + private lateinit var sut: TestLoginBrowser + private val project: Project + get() = projectExtension.project + + @JvmField + @RegisterExtension + val mockTelemetryService = MockTelemetryServiceExtension() + + companion object { + @JvmField + @RegisterExtension + val projectExtension = ProjectExtension() + } + + @BeforeEach + fun setup() { + sut = TestLoginBrowser(project) + } + + @Test + fun `publish telemetry happy path`() { + val load = """ + { + "metricName": "toolkit_didLoadModule", + "module": "login", + "result": "Succeeded", + "duration": "0" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher()).enqueue(capture()) + val event = firstValue.data.find { it.name == "toolkit_didLoadModule" } + assertNotNull(event) + assertThat(event) + .matches { it.metadata["module"] == "login" } + .matches { it.metadata["result"] == "Succeeded" } + .matches { it.metadata["duration"] == "0.0" } + } + } + + @Test + fun `publish telemetry error path`() { + val load = """ + { + "metricName": "toolkit_didLoadModule", + "module": "login", + "result": "Failed", + "reason": "unexpected error" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher()).enqueue(capture()) + val event = firstValue.data.find { it.name == "toolkit_didLoadModule" } + assertNotNull(event) + assertThat(event) + .matches { it.metadata["module"] == "login" } + .matches { it.metadata["result"] == "Failed" } + .matches { it.metadata["reason"] == "unexpected error" } + } + } + + @Test + fun `missing required field will do nothing`() { + val load = """ + { + "metricName": "toolkit_didLoadModule" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + val load1 = """ + { + "metricName": "toolkit_didLoadModule", + "module": "login" + } + """.trimIndent() + val message1 = BrowserMessage.PublishWebviewTelemetry(load1) + sut.publishTelemetry(message1) + + val load2 = """ + { + "metricName": "toolkit_didLoadModule", + "result": "Failed" + } + """.trimIndent() + val message2 = BrowserMessage.PublishWebviewTelemetry(load2) + sut.publishTelemetry(message2) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher(), times(0)).enqueue(capture()) + } + } + + @Test + fun `metricName doesn't match will do nothing`() { + val load = """ + { + "metricName": "foo", + "module": "login", + "result": "Failed", + "reason": "unexpected error" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher(), times(0)).enqueue(capture()) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt new file mode 100644 index 00000000000..ae014eb2159 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt @@ -0,0 +1,477 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ProjectExtension +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.junit5.TestDisposable +import com.intellij.testFramework.replaceService +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.Mockito.mockConstruction +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.timeout +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.jetbrains.core.MockClientManagerExtension +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileSsoSessionIdentifier +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher +import software.amazon.q.jetbrains.utils.isInstanceOf +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import software.amazon.q.jetbrains.utils.satisfiesKt + +class DefaultToolkitAuthManagerTest { + private class TestTelemetryService( + publisher: TelemetryPublisher = NoOpPublisher(), + batcher: TelemetryBatcher, + ) : TelemetryService(publisher, batcher) + + @JvmField + @RegisterExtension + val mockClientManager = MockClientManagerExtension() + + private lateinit var sut: DefaultToolkitAuthManager + private lateinit var connectionManager: ToolkitConnectionManager + private lateinit var batcher: TelemetryBatcher + private lateinit var telemetryService: TelemetryService + private var isTelemetryEnabledDefault: Boolean = false + + @BeforeEach + fun setUp(@TestDisposable disposable: Disposable) { + mockClientManager.create() + + sut = DefaultToolkitAuthManager() + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, disposable) + + connectionManager = DefaultToolkitConnectionManager(projectRule.project) + projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposable) + + batcher = mock() + telemetryService = spy(TestTelemetryService(batcher = batcher)) + ApplicationManager.getApplication().replaceService(TelemetryService::class.java, telemetryService, disposable) + isTelemetryEnabledDefault = AwsSettings.getInstance().isTelemetryEnabled + } + + @AfterEach + fun tearDown() { + telemetryService.dispose() + AwsSettings.getInstance().isTelemetryEnabled = isTelemetryEnabledDefault + } + + @Test + fun `creates ManagedBearerSsoConnection from ManagedSsoProfile`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + val connection = sut.createConnection(profile) + + assertThat(connection).isInstanceOf() + connection as ManagedBearerSsoConnection + assertThat(connection.sessionName).isEqualTo("") + assertThat(connection.region).isEqualTo(profile.ssoRegion) + assertThat(connection.startUrl).isEqualTo(profile.startUrl) + assertThat(connection.scopes).isEqualTo(profile.scopes) + } + + @Test + fun `creates ManagedBearerSsoConnection from serialized ManagedSsoProfile`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + sut.createConnection(profile) + + assertThat(sut.state?.ssoProfiles).satisfiesKt { profiles -> + assertThat(profiles).isNotNull() + assertThat(profiles).singleElement().isEqualTo(profile) + } + } + + @Test + fun `serializes ManagedSsoProfile from ManagedBearerSsoConnection`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + + sut.loadState( + ToolkitAuthManagerState( + ssoProfiles = listOf(profile) + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("") + assertThat(connection.region).isEqualTo(profile.ssoRegion) + assertThat(connection.startUrl).isEqualTo(profile.startUrl) + assertThat(connection.scopes).isEqualTo(profile.scopes) + } + } + } + + @Test + fun `loadState dedupes profiles`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + + sut.loadState( + ToolkitAuthManagerState( + ssoProfiles = listOf( + profile, + profile, + profile + ) + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("") + assertThat(connection.region).isEqualTo(profile.ssoRegion) + assertThat(connection.startUrl).isEqualTo(profile.startUrl) + assertThat(connection.scopes).isEqualTo(profile.scopes) + } + } + } + + @Test + fun `updates connection list from connection bus`() { + assertThat(sut.listConnections()).isEmpty() + + val scopes = listOf("scope1", "scope2") + val publisher = ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED) + + publisher.ssoSessionAdded( + ProfileSsoSessionIdentifier( + "add", + "startUrl", + "us-east-1", + scopes.toSet() + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("add") + assertThat(connection.region).isEqualTo("us-east-1") + assertThat(connection.startUrl).isEqualTo("startUrl") + assertThat(connection.scopes).isEqualTo(scopes) + } + } + + publisher.ssoSessionModified( + ProfileSsoSessionIdentifier( + "add", + "startUrl2", + "us-east-1", + scopes.toSet() + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("add") + assertThat(connection.region).isEqualTo("us-east-1") + assertThat(connection.startUrl).isEqualTo("startUrl2") + assertThat(connection.scopes).isEqualTo(scopes) + } + } + + publisher.ssoSessionRemoved( + ProfileSsoSessionIdentifier( + "add", + "startUrl2", + "us-east-1", + scopes.toSet() + ) + ) + + assertThat(sut.listConnections()).isEmpty() + } + + @Test + fun `loginSso with an working existing connection`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("scopes") + ) + ) + + loginSso(projectRule.project, "foo", "us-east-1", listOf("scopes")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider).state() + verifyNoMoreInteractions(tokenProvider) + } + } + + @Test + fun `loginSso with an existing connection but expired and refresh token is valid, should refreshToken`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.id).thenReturn("id") + whenever(context.state()).thenReturn(BearerTokenAuthState.NEEDS_REFRESH) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("scopes") + ) + ) + connectionManager.switchConnection(existingConnection) + + loginSso(projectRule.project, "foo", "us-east-1", listOf("scopes")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider).resolveToken() + assertThat(connectionManager.activeConnection()).isEqualTo(existingConnection) + } + } + + @Test + fun `loginSso with an existing connection that token is invalid and there's no refresh token, should re-authenticate`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.NOT_AUTHENTICATED) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("scopes") + ) + ) + connectionManager.switchConnection(existingConnection) + + loginSso(projectRule.project, "foo", "us-east-1", listOf("scopes")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider, timeout(5000)).reauthenticate() + assertThat(connectionManager.activeConnection()).isEqualTo(existingConnection) + } + } + + @Test + fun `loginSso reuses connection if requested scopes are subset of existing`(@TestDisposable disposable: Disposable) { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED) + }.use { + val connectionManager = spy(connectionManager) + projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposable) + + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("existing1", "existing2", "existing3") + ) + ) + + connectionManager.switchConnection(existingConnection) + + loginSso(projectRule.project, "foo", "us-east-1", listOf("existing1")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider).state() + verifyNoMoreInteractions(tokenProvider) + assertThat(connectionManager.activeConnection()).isEqualTo(existingConnection) + verify(connectionManager, atLeastOnce()).switchConnection(existingConnection) + } + } + + @Test + fun `loginSso forces reauth if requested scopes are not complete subset`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("existing1", "existing2", "existing3") + ) + ) + + val newScopes = listOf("existing1", "new1") + loginSso(projectRule.project, "foo", "us-east-1", newScopes) + + assertThat(connectionManager.activeConnection() as AwsBearerTokenConnection).satisfiesKt { connection -> + assertThat(connection.scopes.toSet()).isEqualTo(setOf("existing1", "existing2", "existing3", "new1")) + } + assertThat(sut.listConnections()).singleElement().isInstanceOfSatisfying { connection -> + assertThat(connection).usingRecursiveComparison().isNotEqualTo(existingConnection) + assertThat(connection.scopes.toSet()).isEqualTo(setOf("existing1", "existing2", "existing3", "new1")) + } + } + } + + @Test + fun `loginSso with a new connection`(@TestDisposable disposable: Disposable) { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + doNothing().whenever(context).reauthenticate() + whenever(context.state()).thenReturn(BearerTokenAuthState.NOT_AUTHENTICATED) + }.use { + val connectionManager = spy(connectionManager) + projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposable) + // before + assertThat(sut.listConnections()).hasSize(0) + + loginSso(projectRule.project, "foo", "us-east-1", listOf("scope1", "scope2")) + + // after + assertThat(sut.listConnections()).hasSize(1) + verify(connectionManager, timeout(5000)).switchConnection(any()) + + val expectedConnection = LegacyManagedBearerSsoConnection( + "foo", + "us-east-1", + listOf("scope1", "scope2") + ) + + sut.listConnections()[0].let { conn -> + assertThat(conn.getConnectionSettings()) + .usingRecursiveComparison() + .isEqualTo(expectedConnection.getConnectionSettings()) + assertThat(conn.id).isEqualTo(expectedConnection.id) + assertThat(conn.label).isEqualTo(expectedConnection.label) + } + } + } + + @Test + fun `logoutFromConnection should invalidate the token provider and the connection and invoke callback`(@TestDisposable disposable: Disposable) { + val profile = ManagedSsoProfile("us-east-1", "startUrl000", listOf("scopes")) + val connection = sut.createConnection(profile) as ManagedBearerSsoConnection + connectionManager.switchConnection(connection) + + var providerInvalidatedMessageReceived = 0 + var connectionSwitchedMessageReceived = 0 + var callbackInvoked = 0 + ApplicationManager.getApplication().messageBus.connect(disposable).subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun invalidate(providerId: String) { + if (providerId == "sso;us-east-1;startUrl000") { + providerInvalidatedMessageReceived += 1 + } + } + } + ) + ApplicationManager.getApplication().messageBus.connect(disposable).subscribe( + ToolkitConnectionManagerListener.TOPIC, + object : ToolkitConnectionManagerListener { + override fun activeConnectionChanged(newConnection: ToolkitConnection?) { + connectionSwitchedMessageReceived += 1 + } + } + ) + + logoutFromSsoConnection(projectRule.project, connection) { callbackInvoked += 1 } + assertThat(providerInvalidatedMessageReceived).isEqualTo(1) + assertThat(connectionSwitchedMessageReceived).isEqualTo(1) + assertThat(callbackInvoked).isEqualTo(1) + } + + @Test + fun `loginSso telemetry contains default source ID`() { + AwsSettings.getInstance().isTelemetryEnabled = true + loginSso( + project = projectRule.project, + startUrl = "foo", + region = "us-east-1", + requestedScopes = listOf("scopes") + ) + val metricCaptor = argumentCaptor() + assertThat(metricCaptor.allValues).allSatisfy { event -> + assertThat(event.data.all { it.metadata["credentialSourceId"] == "awsId" }).isTrue() + } + } + + @Test + fun `loginSso telemetry contains no source by default`() { + AwsSettings.getInstance().isTelemetryEnabled = true + loginSso( + project = projectRule.project, + startUrl = "foo", + region = "us-east-1", + requestedScopes = listOf("scopes") + ) + val metricCaptor = argumentCaptor() + assertThat(metricCaptor.allValues).allSatisfy { event -> + assertThat(event.data.all { it.metadata["source"] == null }).isTrue() + } + } + + @Test + fun `loginSso telemetry contains provided source`() { + AwsSettings.getInstance().isTelemetryEnabled = true + loginSso( + project = projectRule.project, + startUrl = "foo", + region = "us-east-1", + requestedScopes = listOf("scopes"), + metadata = ConnectionMetadata("fooSource") + ) + val metricCaptor = argumentCaptor() + assertThat(metricCaptor.allValues).allSatisfy { event -> + assertThat(event.data.all { it.metadata["source"] == "fooSourceId" }).isTrue() + } + } + + @Test + fun `serializing LegacyManagedBearerSsoConnection does not include connectionSettings`() { + val profile = ManagedSsoProfile("us-east-1", "startUrl000", listOf("scopes")) + val connection = sut.createConnection(profile) as LegacyManagedBearerSsoConnection + + assertThat(jacksonObjectMapper().writeValueAsString(connection)).doesNotContain("connectionSettings") + } + + @Test + fun `serializing ProfileSsoManagedBearerSsoConnection does not include connectionSettings`() { + val profile = UserConfigSsoSessionProfile("sessionName", "us-east-1", "startUrl000", listOf("scopes")) + val connection = sut.createConnection(profile) as ProfileSsoManagedBearerSsoConnection + + assertThat(jacksonObjectMapper().writeValueAsString(connection)).doesNotContain("connectionSettings") + } + + private companion object { + @ExtendWith(ProjectExtension::class) + val projectRule = ProjectRule() + } +} diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt new file mode 100644 index 00000000000..e70374e9abb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt @@ -0,0 +1,112 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.testFramework.ProjectExtension +import com.intellij.testFramework.runInEdtAndWait +import io.mockk.every +import io.mockk.junit5.MockKExtension +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.RegisterExtension +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.sso.model.RoleInfo +import software.amazon.q.jetbrains.core.MockClientManagerExtension +import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade +import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.satisfiesKt + +@ExtendWith(MockKExtension::class) +class IdcRolePopupTest { + companion object { + @JvmField + @RegisterExtension + val projectExtension = ProjectExtension() + } + + @JvmField + @RegisterExtension + val mockClientManager = MockClientManagerExtension() + + @JvmField + @RegisterExtension + val mockRegionProvider = MockRegionProviderExtension() + + @Test + fun `validate role selected`() { + val state = IdcRolePopupState() + mockClientManager.create() + + runInEdtAndWait { + val validation = IdcRolePopup(projectExtension.project, aString(), aString(), mockk(), state, mockk()).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).singleElement().satisfiesKt { + assertThat(it.okEnabled).isFalse() + assertThat(it.message).contains(AwsCoreBundle.message("gettingstarted.setup.error.not_selected")) + } + } + } + + @Test + fun `success writes profile to config`() { + val sessionName = aString() + val roleInfo = RoleInfo.builder() + .roleName(aString()) + .accountId(aString()) + .build() + val state = IdcRolePopupState().apply { + this.roleInfo = roleInfo + } + val configFilesFacade = mockk { + every { readAllProfiles() } returns emptyMap() + justRun { appendProfileToConfig(any()) } + } + + mockClientManager.create() + + runInEdtAndWait { + val sut = IdcRolePopup( + projectExtension.project, + region = aString(), + sessionName = sessionName, + tokenProvider = mockk(), + state = state, + configFilesFacade = configFilesFacade + ) + try { + sut.doOkActionWithRoleInfo(roleInfo) + } finally { + sut.close(0) + } + + verify { + configFilesFacade.appendProfileToConfig( + Profile.builder() + .name("$sessionName-${roleInfo.accountId()}-${roleInfo.roleName()}") + .properties( + mapOf( + "sso_session" to sessionName, + "sso_account_id" to roleInfo.accountId(), + "sso_role_name" to roleInfo.roleName() + ) + ) + .build() + ) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt new file mode 100644 index 00000000000..78ab1fede27 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt @@ -0,0 +1,349 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.openapi.ui.TestDialog +import com.intellij.openapi.ui.TestDialogManager +import com.intellij.testFramework.ProjectExtension +import com.intellij.testFramework.runInEdtAndWait +import io.mockk.every +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.any +import org.mockito.kotlin.stub +import org.mockito.kotlin.whenever +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.awssdk.services.sts.model.GetCallerIdentityRequest +import software.amazon.awssdk.services.sts.model.GetCallerIdentityResponse +import software.amazon.awssdk.services.sts.model.StsException +import software.amazon.q.jetbrains.core.MockClientManagerExtension +import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile +import software.amazon.q.jetbrains.core.credentials.authAndUpdateConfig +import software.amazon.q.jetbrains.core.credentials.loginSso +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension +import software.amazon.q.resources.AwsCoreBundle +import software.amazon.q.core.region.Endpoint +import software.amazon.q.core.region.Service +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.satisfiesKt +import software.aws.toolkits.telemetry.FeatureId + +@ExtendWith(MockKExtension::class) +class SetupAuthenticationDialogTest { + companion object { + @JvmField + @RegisterExtension + val projectExtension = ProjectExtension() + } + + @JvmField + @RegisterExtension + val mockClientManager = MockClientManagerExtension() + + @JvmField + @RegisterExtension + val mockRegionProvider = MockRegionProviderExtension() + + @Test + fun `login to IdC tab`() { + mockkStatic(::authAndUpdateConfig) + + val startUrl = aString() + val region = mockRegionProvider.createAwsRegion() + val scopes = listOf(aString(), aString(), aString()) + mockRegionProvider.addService( + "sso", + Service( + endpoints = mapOf(region.id to Endpoint()), + isRegionalized = true, + partitionEndpoint = region.partitionId + ) + ) + + val configFacade = mockk(relaxed = true) + TestDialogManager.setTestDialog(TestDialog.OK) + val state = SetupAuthenticationDialogState().apply { + idcTabState.apply { + this.startUrl = startUrl + this.region = region + } + } + + runInEdtAndWait { + SetupAuthenticationDialog( + projectExtension.project, + scopes = scopes, + state = state, + configFilesFacade = configFacade, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).apply { + try { + doOKAction() + } finally { + close(0) + } + } + } + + verify { + authAndUpdateConfig( + projectExtension.project, + UserConfigSsoSessionProfile("", region.id, startUrl, scopes), + configFacade, + any(), + any(), + any() + ) + } + } + + @Test + fun `login to IdC tab and request role`() { + mockkStatic(::authAndUpdateConfig) + + val startUrl = aString() + val region = mockRegionProvider.createAwsRegion() + val scopes = listOf(aString(), aString(), aString()) + mockRegionProvider.addService( + "sso", + Service( + endpoints = mapOf(region.id to Endpoint()), + isRegionalized = true, + partitionEndpoint = region.partitionId + ) + ) + + val configFacade = mockk(relaxed = true) + TestDialogManager.setTestDialog(TestDialog.OK) + val state = SetupAuthenticationDialogState().apply { + idcTabState.apply { + this.startUrl = startUrl + this.region = region + } + } + + runInEdtAndWait { + SetupAuthenticationDialog( + projectExtension.project, + scopes = scopes, + state = state, + promptForIdcPermissionSet = true, + configFilesFacade = configFacade, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).apply { + try { + doOKAction() + } finally { + close(0) + } + } + } + + verify { + authAndUpdateConfig( + projectExtension.project, + UserConfigSsoSessionProfile("", region.id, startUrl, scopes + "sso:account:access"), + configFacade, + any(), + any(), + any() + ) + } + } + + @Test + fun `login to Builder ID tab`() { + mockkStatic(::loginSso) + every { loginSso(any(), any(), any(), any()) } answers { mockk() } + + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.BUILDER_ID) + } + + runInEdtAndWait { + SetupAuthenticationDialog( + projectExtension.project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).apply { + try { + doOKAction() + } finally { + close(0) + } + } + } + + verify { + loginSso(projectExtension.project, SONO_URL, SONO_REGION, emptyList()) + } + } + + @Test + fun `validate IdC tab`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IDENTITY_CENTER) + } + + runInEdtAndWait { + val validation = SetupAuthenticationDialog( + projectExtension.project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).satisfiesKt { + assertThat(it).hasSize(2) + assertThat(it).allSatisfy { error -> + assertThat(error.message).contains("Must not be empty") + } + } + } + } + + @Test + fun `validate Builder ID tab`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.BUILDER_ID) + } + + runInEdtAndWait { + val validation = SetupAuthenticationDialog( + projectExtension.project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).isEmpty() + } + } + + @Test + fun `validate IAM tab`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IAM_LONG_LIVED) + iamTabState.profileName = "" + } + + runInEdtAndWait { + val validation = SetupAuthenticationDialog( + projectExtension.project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).satisfiesKt { + assertThat(it).hasSize(3) + assertThat(it).allSatisfy { error -> + assertThat(error.message).contains("Must not be empty") + } + } + } + } + + @Test + fun `validate IAM tab fails if credentials are invalid`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IAM_LONG_LIVED) + iamTabState.apply { + profileName = "test" + accessKey = "invalid" + secretKey = "invalid" + } + } + + mockClientManager.create().stub { + whenever(it.getCallerIdentity(any())).thenThrow(StsException.builder().message("Some service exception message").build()) + } + + runInEdtAndWait { + val sut = SetupAuthenticationDialog( + projectExtension.project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ) + val exception = assertThrows { sut.doOKAction() } + assertThat(exception.message).isEqualTo(AwsCoreBundle.message("gettingstarted.setup.iam.profile.invalid_credentials")) + } + } + + @Test + fun `validate IAM tab succeeds if credentials are invalid`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IAM_LONG_LIVED) + iamTabState.apply { + profileName = "test" + accessKey = "validAccess" + secretKey = "validSecret" + } + } + + mockClientManager.create().stub { + whenever(it.getCallerIdentity(any())).thenReturn(GetCallerIdentityResponse.builder().build()) + } + + val configFacade = mockk(relaxed = true) + runInEdtAndWait { + SetupAuthenticationDialog( + projectExtension.project, + state = state, + configFilesFacade = configFacade, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ) + .doOKAction() + } + + verify { + configFacade.appendProfileToCredentials( + Profile.builder() + .name("test") + .properties( + mapOf( + "aws_access_key_id" to "validAccess", + "aws_secret_access_key" to "validSecret" + ) + ) + .build() + ) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/BrowserMessageTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/BrowserMessageTest.kt new file mode 100644 index 00000000000..d9b0287cbca --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/BrowserMessageTest.kt @@ -0,0 +1,332 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.exc.MismatchedInputException +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.openapi.project.Project +import com.intellij.testFramework.HeavyPlatformTestCase +import com.intellij.ui.jcef.JBCefBrowserBase +import com.intellij.ui.jcef.JBCefJSQuery +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.ObjectAssert +import org.junit.jupiter.api.assertDoesNotThrow +import org.mockito.kotlin.mock +import software.amazon.q.jetbrains.core.webview.BrowserMessage +import software.amazon.q.jetbrains.core.webview.BrowserState +import software.amazon.q.jetbrains.core.webview.LoginBrowser + +class NoOpLoginBrowser(project: Project) : LoginBrowser(project) { + override val jcefBrowser: JBCefBrowserBase = mock() + + override fun prepareBrowser(state: BrowserState) {} + + override fun loadWebView(query: JBCefJSQuery) {} + + override fun handleBrowserMessage(message: BrowserMessage?) {} +} + +class BrowserMessageTest : HeavyPlatformTestCase() { + private lateinit var objectMapper: ObjectMapper + + private inline fun assertDeserializedInstanceOf(jsonStr: String): ObjectAssert { + val actual = objectMapper.readValue(jsonStr) + return assertThat(actual).isInstanceOf(T::class.java) + } + + private inline fun assertDeserializedWillThrow(jsonStr: String) { + assertThatThrownBy { + objectMapper.readValue(jsonStr) + }.isInstanceOf(T::class.java) + } + + override fun setUp() { + super.setUp() + objectMapper = NoOpLoginBrowser(project).objectMapper + } + + fun `test exact match, deserialization return correct BrowserMessage subtype`() { + assertDeserializedInstanceOf( + """ + { + "command": "prepareUi" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "toggleBrowser" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "selectConnection", + "connectionId": "foo" + } + """ + ).isEqualTo(BrowserMessage.SelectConnection("foo")) + + assertDeserializedInstanceOf( + """ + { + "command": "loginBuilderId" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "loginIdC", + "url": "foo", + "region": "bar", + "feature": "baz" + } + """ + ).isEqualTo( + BrowserMessage.LoginIdC( + url = "foo", + region = "bar", + feature = "baz" + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "loginIAM", + "profileName": "foo", + "accessKey": "bar", + "secretKey": "baz" + } + """ + ).isEqualTo( + BrowserMessage.LoginIAM( + profileName = "foo", + accessKey = "bar", + secretKey = "baz" + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "cancelLogin" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "signout" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "reauth" + } + """ + ) + + assertDeserializedInstanceOf( + """ + { + "command": "sendUiClickTelemetry" + } + """ + ).isEqualTo( + BrowserMessage.SendUiClickTelemetry( + signInOptionClicked = null + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "webviewTelemetry", + "event": "{ \"metricName\": \"foo\" }" + } + """.trimIndent() + ).isEqualTo( + BrowserMessage.PublishWebviewTelemetry( + event = "{ \"metricName\": \"foo\" }" + ) + ) + + assertDeserializedInstanceOf( + """ + { + "command": "openUrl", + "externalLink": "foo" + } + """ + ).isEqualTo( + BrowserMessage.OpenUrl("foo") + ) + } + + fun `test unrecognizable command - deserialize should throw MismatchedInputException`() { + assertDeserializedWillThrow( + """ + { + "command": "" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "zxcasdqwe" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "foo bar baz" + } + """ + ) + } + + fun `test unknown fields - deserialize should throw MismatchedInputException`() { + assertDeserializedWillThrow( + """ + { + "command": "prepareUi", + "unknown": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "profileName": "foo", + "unknown": "bar" + } + """ + ) + } + + fun `test missing required fields - deserialize fail `() { + assertDeserializedWillThrow( + """ + { + "command": "selectConnection" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "accessKey": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIdC" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIdC", + "url": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIdC", + "region": "bar", + "feature": "baz" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "profileName": "bar" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "profileName": "bar", + "secretKey": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "loginIAM", + "accessKey": "foo" + } + """ + ) + + assertDeserializedWillThrow( + """ + { + "command": "openUrl" + } + """ + ) + } + + fun `test Nullable fields in sendUiClickTelemetry should not throw exception`() { + assertDoesNotThrow { + objectMapper.readValue( + """ + { + "command": "sendUiClickTelemetry", + "signInOptionClicked": null + } + """ + ) + } + + assertDoesNotThrow { + objectMapper.readValue( + """ + { + "command": "sendUiClickTelemetry" + + } + """ + ) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/LoginBrowserTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/LoginBrowserTest.kt new file mode 100644 index 00000000000..ec7c15cd0f1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/LoginBrowserTest.kt @@ -0,0 +1,145 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.project.Project +import com.intellij.testFramework.HeavyPlatformTestCase +import com.intellij.ui.jcef.JBCefBrowserBase +import com.intellij.ui.jcef.JBCefJSQuery +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Disabled +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.q.jetbrains.core.webview.BrowserMessage +import software.amazon.q.jetbrains.core.webview.BrowserState +import software.amazon.q.jetbrains.core.webview.LoginBrowser +import software.aws.toolkits.core.telemetry.MetricEvent +import software.aws.toolkits.jetbrains.services.telemetry.MockTelemetryServiceExtension + +class TestLoginBrowser(project: Project) : LoginBrowser(project) { + // test env can't initiate a real jcef and will throw error + override val jcefBrowser: JBCefBrowserBase + get() = mock() + + override fun handleBrowserMessage(message: BrowserMessage?) {} + + override fun prepareBrowser(state: BrowserState) {} + + override fun loadWebView(query: JBCefJSQuery) {} +} + +@Disabled +class LoginBrowserTest : HeavyPlatformTestCase() { + private lateinit var sut: TestLoginBrowser + private val mockTelemetryService = MockTelemetryServiceExtension() + + override fun setUp() { + super.setUp() + mockTelemetryService.beforeEach(null) + sut = TestLoginBrowser(project) + } + + override fun tearDown() { + try { + mockTelemetryService.afterEach(null) + } finally { + super.tearDown() + } + } + fun `test publish telemetry happy path`() { + val load = """ + { + "metricName": "toolkit_didLoadModule", + "module": "login", + "result": "Succeeded", + "duration": "0" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher()).enqueue(capture()) + val event = requireNotNull(firstValue.data.find { it.name == "toolkit_didLoadModule" }) + assertThat(event) + .matches { it.metadata["module"] == "login" } + .matches { it.metadata["result"] == "Succeeded" } + .matches { it.metadata["duration"] == "0.0" } + } + } + fun `test publish telemetry error path`() { + val load = """ + { + "metricName": "toolkit_didLoadModule", + "module": "login", + "result": "Failed", + "reason": "unexpected error" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher()).enqueue(capture()) + val event = requireNotNull(firstValue.data.find { it.name == "toolkit_didLoadModule" }) + assertThat(event) + .matches { it.metadata["module"] == "login" } + .matches { it.metadata["result"] == "Failed" } + .matches { it.metadata["reason"] == "unexpected error" } + } + } + fun `test missing required field will do nothing`() { + val load = """ + { + "metricName": "toolkit_didLoadModule" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + val load1 = """ + { + "metricName": "toolkit_didLoadModule", + "module": "login" + } + """.trimIndent() + val message1 = BrowserMessage.PublishWebviewTelemetry(load1) + sut.publishTelemetry(message1) + + val load2 = """ + { + "metricName": "toolkit_didLoadModule", + "result": "Failed" + } + """.trimIndent() + val message2 = BrowserMessage.PublishWebviewTelemetry(load2) + sut.publishTelemetry(message2) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher(), times(0)).enqueue(capture()) + } + } + fun `test metricName doesn't match will do nothing`() { + val load = """ + { + "metricName": "foo", + "module": "login", + "result": "Failed", + "reason": "unexpected error" + } + """.trimIndent() + val message = BrowserMessage.PublishWebviewTelemetry(load) + sut.publishTelemetry(message) + + mockTelemetryService.batcher() + argumentCaptor { + verify(mockTelemetryService.batcher(), times(0)).enqueue(capture()) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt new file mode 100644 index 00000000000..9dd15077da1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt @@ -0,0 +1,455 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service +import com.intellij.testFramework.HeavyPlatformTestCase +import com.intellij.testFramework.replaceService +import org.assertj.core.api.Assertions.assertThat +import org.mockito.Mockito.mockConstruction +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.timeout +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileSsoSessionIdentifier +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.aws.toolkits.core.ToolkitClientManager +import software.aws.toolkits.core.telemetry.MetricEvent +import software.aws.toolkits.core.telemetry.TelemetryBatcher +import software.aws.toolkits.core.telemetry.TelemetryPublisher +import software.aws.toolkits.core.utils.delegateMock +import software.aws.toolkits.core.utils.test.aString +import software.aws.toolkits.jetbrains.services.telemetry.NoOpPublisher +import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.aws.toolkits.jetbrains.settings.AwsSettings +import software.aws.toolkits.jetbrains.utils.isInstanceOf +import software.aws.toolkits.jetbrains.utils.isInstanceOfSatisfying +import software.aws.toolkits.jetbrains.utils.satisfiesKt + +class DefaultToolkitAuthManagerTest : HeavyPlatformTestCase() { + private class TestTelemetryService( + publisher: TelemetryPublisher = NoOpPublisher(), + batcher: TelemetryBatcher, + ) : TelemetryService(publisher, batcher) + + private lateinit var mockClientManager: MockClientManager + private lateinit var sut: DefaultToolkitAuthManager + private lateinit var connectionManager: ToolkitConnectionManager + private lateinit var batcher: TelemetryBatcher + private lateinit var telemetryService: TelemetryService + private var isTelemetryEnabledDefault: Boolean = false + + override fun setUp() { + super.setUp() + mockClientManager = service() as MockClientManager + + val ssoOidcClient = delegateMock() + @Suppress("DEPRECATION") + mockClientManager.register(SsoOidcClient::class, ssoOidcClient) + + sut = DefaultToolkitAuthManager() + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, sut, testRootDisposable) + + connectionManager = DefaultToolkitConnectionManager(project) + project.replaceService(ToolkitConnectionManager::class.java, connectionManager, testRootDisposable) + + batcher = mock() + telemetryService = spy(TestTelemetryService(batcher = batcher)) + connectionManager = DefaultToolkitConnectionManager(project) + project.replaceService(ToolkitConnectionManager::class.java, connectionManager, testRootDisposable) + isTelemetryEnabledDefault = AwsSettings.getInstance().isTelemetryEnabled + } + + override fun tearDown() { + try { + telemetryService.dispose() + AwsSettings.getInstance().isTelemetryEnabled = isTelemetryEnabledDefault + } finally { + super.tearDown() + } + } + + fun `test creates ManagedBearerSsoConnection from ManagedSsoProfile`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + val connection = sut.createConnection(profile) + + assertThat(connection).isInstanceOf() + connection as ManagedBearerSsoConnection + assertThat(connection.sessionName).isEqualTo("") + assertThat(connection.region).isEqualTo(profile.ssoRegion) + assertThat(connection.startUrl).isEqualTo(profile.startUrl) + assertThat(connection.scopes).isEqualTo(profile.scopes) + } + + fun `test creates ManagedBearerSsoConnection from serialized ManagedSsoProfile`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + sut.createConnection(profile) + + assertThat(sut.state?.ssoProfiles).satisfiesKt { profiles -> + assertThat(profiles).isNotNull() + assertThat(profiles).singleElement().isEqualTo(profile) + } + } + + fun `test serializes ManagedSsoProfile from ManagedBearerSsoConnection`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + + sut.loadState( + ToolkitAuthManagerState( + ssoProfiles = listOf(profile) + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("") + assertThat(connection.region).isEqualTo(profile.ssoRegion) + assertThat(connection.startUrl).isEqualTo(profile.startUrl) + assertThat(connection.scopes).isEqualTo(profile.scopes) + } + } + } + + fun `test loadState dedupes profiles`() { + val profile = ManagedSsoProfile( + "us-east-1", + aString(), + listOf(aString()) + ) + + sut.loadState( + ToolkitAuthManagerState( + ssoProfiles = listOf( + profile, + profile, + profile + ) + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("") + assertThat(connection.region).isEqualTo(profile.ssoRegion) + assertThat(connection.startUrl).isEqualTo(profile.startUrl) + assertThat(connection.scopes).isEqualTo(profile.scopes) + } + } + } + + fun `test updates connection list from connection bus`() { + assertThat(sut.listConnections()).isEmpty() + + val scopes = listOf("scope1", "scope2") + val publisher = ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED) + + publisher.ssoSessionAdded( + ProfileSsoSessionIdentifier( + "add", + "startUrl", + "us-east-1", + scopes.toSet() + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("add") + assertThat(connection.region).isEqualTo("us-east-1") + assertThat(connection.startUrl).isEqualTo("startUrl") + assertThat(connection.scopes).isEqualTo(scopes) + } + } + + publisher.ssoSessionModified( + ProfileSsoSessionIdentifier( + "add", + "startUrl2", + "us-east-1", + scopes.toSet() + ) + ) + + assertThat(sut.listConnections()).singleElement().satisfiesKt { + assertThat(it).isInstanceOfSatisfying { connection -> + assertThat(connection.sessionName).isEqualTo("add") + assertThat(connection.region).isEqualTo("us-east-1") + assertThat(connection.startUrl).isEqualTo("startUrl2") + assertThat(connection.scopes).isEqualTo(scopes) + } + } + + publisher.ssoSessionRemoved( + ProfileSsoSessionIdentifier( + "add", + "startUrl2", + "us-east-1", + scopes.toSet() + ) + ) + + assertThat(sut.listConnections()).isEmpty() + } + + fun `test loginSso with an working existing connection`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("scopes") + ) + ) + + loginSso(project, "foo", "us-east-1", listOf("scopes")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider).state() + verifyNoMoreInteractions(tokenProvider) + } + } + + fun `test loginSso with an existing connection but expired and refresh token is valid, should refreshToken`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.id).thenReturn("id") + whenever(context.state()).thenReturn(BearerTokenAuthState.NEEDS_REFRESH) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("scopes") + ) + ) + connectionManager.switchConnection(existingConnection) + + loginSso(project, "foo", "us-east-1", listOf("scopes")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider).resolveToken() + assertThat(connectionManager.activeConnection()).isEqualTo(existingConnection) + } + } + + fun `test loginSso with an existing connection that token is invalid and there's no refresh token, should re-authenticate`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.NOT_AUTHENTICATED) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("scopes") + ) + ) + connectionManager.switchConnection(existingConnection) + + loginSso(project, "foo", "us-east-1", listOf("scopes")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider, timeout(5000)).reauthenticate() + assertThat(connectionManager.activeConnection()).isEqualTo(existingConnection) + } + } + + fun `test loginSso reuses connection if requested scopes are subset of existing`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED) + }.use { + val connectionManager = spy(connectionManager) + project.replaceService(ToolkitConnectionManager::class.java, connectionManager, testRootDisposable) + + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("existing1", "existing2", "existing3") + ) + ) + + connectionManager.switchConnection(existingConnection) + + loginSso(project, "foo", "us-east-1", listOf("existing1")) + + val tokenProvider = it.constructed()[0] + verify(tokenProvider).state() + verifyNoMoreInteractions(tokenProvider) + assertThat(connectionManager.activeConnection()).isEqualTo(existingConnection) + verify(connectionManager, atLeastOnce()).switchConnection(existingConnection) + } + } + + fun `test loginSso forces reauth if requested scopes are not complete subset`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + whenever(context.state()).thenReturn(BearerTokenAuthState.AUTHORIZED) + }.use { + val existingConnection = sut.createConnection( + ManagedSsoProfile( + "us-east-1", + "foo", + listOf("existing1", "existing2", "existing3") + ) + ) + + val newScopes = listOf("existing1", "new1") + loginSso(project, "foo", "us-east-1", newScopes) + + assertThat(connectionManager.activeConnection() as AwsBearerTokenConnection).satisfiesKt { connection -> + assertThat(connection.scopes.toSet()).isEqualTo(setOf("existing1", "existing2", "existing3", "new1")) + } + assertThat(sut.listConnections()).singleElement().isInstanceOfSatisfying { connection -> + assertThat(connection).usingRecursiveComparison().isNotEqualTo(existingConnection) + assertThat(connection.scopes.toSet()).isEqualTo(setOf("existing1", "existing2", "existing3", "new1")) + } + } + } + + fun `test loginSso with a new connection`() { + mockConstruction(InteractiveBearerTokenProvider::class.java) { context, _ -> + doNothing().whenever(context).reauthenticate() + whenever(context.state()).thenReturn(BearerTokenAuthState.NOT_AUTHENTICATED) + }.use { + val connectionManager = spy(connectionManager) + project.replaceService(ToolkitConnectionManager::class.java, connectionManager, testRootDisposable) + // before + assertThat(sut.listConnections()).hasSize(0) + + loginSso(project, "foo", "us-east-1", listOf("scope1", "scope2")) + + // after + assertThat(sut.listConnections()).hasSize(1) + verify(connectionManager, timeout(5000)).switchConnection(any()) + + val expectedConnection = LegacyManagedBearerSsoConnection( + "foo", + "us-east-1", + listOf("scope1", "scope2") + ) + + sut.listConnections()[0].let { conn -> + assertThat(conn.getConnectionSettings()) + .usingRecursiveComparison() + .isEqualTo(expectedConnection.getConnectionSettings()) + assertThat(conn.id).isEqualTo(expectedConnection.id) + assertThat(conn.label).isEqualTo(expectedConnection.label) + } + } + } + + fun `test logoutFromConnection should invalidate the token provider and the connection and invoke callback`() { + val profile = ManagedSsoProfile("us-east-1", "startUrl000", listOf("scopes")) + val connection = sut.createConnection(profile) as ManagedBearerSsoConnection + connectionManager.switchConnection(connection) + + var providerInvalidatedMessageReceived = 0 + var connectionSwitchedMessageReceived = 0 + var callbackInvoked = 0 + ApplicationManager.getApplication().messageBus.connect(testRootDisposable).subscribe( + BearerTokenProviderListener.TOPIC, + object : BearerTokenProviderListener { + override fun invalidate(providerId: String) { + if (providerId == "sso;us-east-1;startUrl000") { + providerInvalidatedMessageReceived += 1 + } + } + } + ) + ApplicationManager.getApplication().messageBus.connect(testRootDisposable).subscribe( + ToolkitConnectionManagerListener.TOPIC, + object : ToolkitConnectionManagerListener { + override fun activeConnectionChanged(newConnection: ToolkitConnection?) { + connectionSwitchedMessageReceived += 1 + } + } + ) + + logoutFromSsoConnection(project, connection) { callbackInvoked += 1 } + assertThat(providerInvalidatedMessageReceived).isEqualTo(1) + assertThat(connectionSwitchedMessageReceived).isEqualTo(1) + assertThat(callbackInvoked).isEqualTo(1) + } + + fun `test loginSso telemetry contains default source ID`() { + AwsSettings.getInstance().isTelemetryEnabled = true + loginSso( + project = project, + startUrl = "foo", + region = "us-east-1", + requestedScopes = listOf("scopes") + ) + val metricCaptor = argumentCaptor() + assertThat(metricCaptor.allValues).allSatisfy { event -> + assertThat(event.data.all { it.metadata["credentialSourceId"] == "awsId" }).isTrue() + } + } + + fun `test loginSso telemetry contains no source by default`() { + AwsSettings.getInstance().isTelemetryEnabled = true + loginSso( + project = project, + startUrl = "foo", + region = "us-east-1", + requestedScopes = listOf("scopes") + ) + val metricCaptor = argumentCaptor() + assertThat(metricCaptor.allValues).allSatisfy { event -> + assertThat(event.data.all { it.metadata["source"] == null }).isTrue() + } + } + + fun `test loginSso telemetry contains provided source`() { + AwsSettings.getInstance().isTelemetryEnabled = true + loginSso( + project = project, + startUrl = "foo", + region = "us-east-1", + requestedScopes = listOf("scopes"), + metadata = ConnectionMetadata("fooSource") + ) + val metricCaptor = argumentCaptor() + assertThat(metricCaptor.allValues).allSatisfy { event -> + assertThat(event.data.all { it.metadata["source"] == "fooSourceId" }).isTrue() + } + } + + fun `test serializing LegacyManagedBearerSsoConnection does not include connectionSettings`() { + val profile = ManagedSsoProfile("us-east-1", "startUrl000", listOf("scopes")) + val connection = sut.createConnection(profile) as LegacyManagedBearerSsoConnection + + assertThat(jacksonObjectMapper().writeValueAsString(connection)).doesNotContain("connectionSettings") + } + + fun `test serializing ProfileSsoManagedBearerSsoConnection does not include connectionSettings`() { + val profile = UserConfigSsoSessionProfile("sessionName", "us-east-1", "startUrl000", listOf("scopes")) + val connection = sut.createConnection(profile) as ProfileSsoManagedBearerSsoConnection + + assertThat(jacksonObjectMapper().writeValueAsString(connection)).doesNotContain("connectionSettings") + } +} diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/IdcRolePopupTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/IdcRolePopupTest.kt new file mode 100644 index 00000000000..398b06ba67f --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/IdcRolePopupTest.kt @@ -0,0 +1,100 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.openapi.components.service +import com.intellij.testFramework.HeavyPlatformTestCase +import com.intellij.testFramework.runInEdtAndWait +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.sso.model.RoleInfo +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade +import software.aws.toolkits.core.ToolkitClientManager +import software.aws.toolkits.core.utils.delegateMock +import software.aws.toolkits.core.utils.test.aString +import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.aws.toolkits.resources.AwsCoreBundle + +class IdcRolePopupTest : HeavyPlatformTestCase() { + private lateinit var mockClientManager: MockClientManager + + override fun setUp() { + super.setUp() + mockClientManager = service() as MockClientManager + + @Suppress("DEPRECATION") + mockClientManager.register(SsoClient::class, delegateMock()) + } + + fun `test validate role selected`() { + val state = IdcRolePopupState() + + runInEdtAndWait { + val validation = IdcRolePopup(project, aString(), aString(), mockk(), state, mockk()).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).singleElement().satisfiesKt { + assertThat(it.okEnabled).isFalse() + assertThat(it.message).contains(AwsCoreBundle.message("gettingstarted.setup.error.not_selected")) + } + } + } + + fun `test success writes profile to config`() { + val sessionName = aString() + val roleInfo = RoleInfo.builder() + .roleName(aString()) + .accountId(aString()) + .build() + val state = IdcRolePopupState().apply { + this.roleInfo = roleInfo + } + val configFilesFacade = mockk { + every { readAllProfiles() } returns emptyMap() + justRun { appendProfileToConfig(any()) } + } + + runInEdtAndWait { + val sut = IdcRolePopup( + project, + region = aString(), + sessionName = sessionName, + tokenProvider = mockk(), + state = state, + configFilesFacade = configFilesFacade + ) + try { + sut.doOkActionWithRoleInfo(roleInfo) + } finally { + sut.close(0) + } + + verify { + configFilesFacade.appendProfileToConfig( + Profile.builder() + .name("$sessionName-${roleInfo.accountId()}-${roleInfo.roleName()}") + .properties( + mapOf( + "sso_session" to sessionName, + "sso_account_id" to roleInfo.accountId(), + "sso_role_name" to roleInfo.roleName() + ) + ) + .build() + ) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt new file mode 100644 index 00000000000..1832d2a0055 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt @@ -0,0 +1,340 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import com.intellij.openapi.components.service +import com.intellij.openapi.ui.TestDialog +import com.intellij.openapi.ui.TestDialogManager +import com.intellij.testFramework.HeavyPlatformTestCase +import com.intellij.testFramework.runInEdtAndWait +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.assertThrows +import org.mockito.kotlin.any +import org.mockito.kotlin.stub +import org.mockito.kotlin.whenever +import software.amazon.awssdk.profiles.Profile +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.awssdk.services.sts.model.GetCallerIdentityRequest +import software.amazon.awssdk.services.sts.model.GetCallerIdentityResponse +import software.amazon.awssdk.services.sts.model.StsException +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile +import software.amazon.q.jetbrains.core.credentials.authAndUpdateConfig +import software.amazon.q.jetbrains.core.credentials.loginSso +import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension +import software.aws.toolkits.core.ToolkitClientManager +import software.aws.toolkits.core.region.Endpoint +import software.aws.toolkits.core.region.Service +import software.aws.toolkits.core.utils.delegateMock +import software.aws.toolkits.core.utils.test.aString +import software.aws.toolkits.jetbrains.utils.satisfiesKt +import software.aws.toolkits.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.FeatureId + +class SetupAuthenticationDialogTest : HeavyPlatformTestCase() { + private lateinit var mockClientManager: MockClientManager + private val mockRegionProvider = MockRegionProviderExtension() + + override fun setUp() { + super.setUp() + mockClientManager = service() as MockClientManager + } + + fun `test login to IdC tab`() { + mockkStatic(::authAndUpdateConfig) + + val startUrl = aString() + val region = mockRegionProvider.createAwsRegion() + val scopes = listOf(aString(), aString(), aString()) + mockRegionProvider.addService( + "sso", + Service( + endpoints = mapOf(region.id to Endpoint()), + isRegionalized = true, + partitionEndpoint = region.partitionId + ) + ) + + val configFacade = mockk(relaxed = true) + TestDialogManager.setTestDialog(TestDialog.OK) + val state = SetupAuthenticationDialogState().apply { + idcTabState.apply { + this.startUrl = startUrl + this.region = region + } + } + + runInEdtAndWait { + SetupAuthenticationDialog( + project, + scopes = scopes, + state = state, + configFilesFacade = configFacade, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).apply { + try { + doOKAction() + } finally { + close(0) + } + } + } + + verify { + authAndUpdateConfig( + project, + UserConfigSsoSessionProfile("", region.id, startUrl, scopes), + configFacade, + any(), + any(), + any() + ) + } + } + + fun `test login to IdC tab and request role`() { + mockkStatic(::authAndUpdateConfig) + + val startUrl = aString() + val region = mockRegionProvider.createAwsRegion() + val scopes = listOf(aString(), aString(), aString()) + mockRegionProvider.addService( + "sso", + Service( + endpoints = mapOf(region.id to Endpoint()), + isRegionalized = true, + partitionEndpoint = region.partitionId + ) + ) + + val configFacade = mockk(relaxed = true) + TestDialogManager.setTestDialog(TestDialog.OK) + val state = SetupAuthenticationDialogState().apply { + idcTabState.apply { + this.startUrl = startUrl + this.region = region + } + } + + runInEdtAndWait { + SetupAuthenticationDialog( + project, + scopes = scopes, + state = state, + promptForIdcPermissionSet = true, + configFilesFacade = configFacade, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).apply { + try { + doOKAction() + } finally { + close(0) + } + } + } + + verify { + authAndUpdateConfig( + project, + UserConfigSsoSessionProfile("", region.id, startUrl, scopes + "sso:account:access"), + configFacade, + any(), + any(), + any() + ) + } + } + + fun `test login to Builder ID tab`() { + mockkStatic(::loginSso) + every { loginSso(any(), any(), any(), any()) } answers { mockk() } + + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.BUILDER_ID) + } + + runInEdtAndWait { + SetupAuthenticationDialog( + project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).apply { + try { + doOKAction() + } finally { + close(0) + } + } + } + + verify { + loginSso(project, SONO_URL, SONO_REGION, emptyList()) + } + } + + fun `test validate IdC tab`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IDENTITY_CENTER) + } + + runInEdtAndWait { + val validation = SetupAuthenticationDialog( + project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).satisfiesKt { + assertThat(it).hasSize(2) + assertThat(it).allSatisfy { error -> + assertThat(error.message).contains("Must not be empty") + } + } + } + } + + fun `test validate Builder ID tab`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.BUILDER_ID) + } + + runInEdtAndWait { + val validation = SetupAuthenticationDialog( + project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).isEmpty() + } + } + + fun `test validate IAM tab`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IAM_LONG_LIVED) + iamTabState.profileName = "" + } + + runInEdtAndWait { + val validation = SetupAuthenticationDialog( + project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ).run { + try { + performValidateAll() + } finally { + close(0) + } + } + + assertThat(validation).satisfiesKt { + assertThat(it).hasSize(3) + assertThat(it).allSatisfy { error -> + assertThat(error.message).contains("Must not be empty") + } + } + } + } + + // TODO: Fix StsClient mock exception throwing in 2025.3 migration - this test expects an exception but mock doesn't throw + fun `test validate IAM tab fails if credentials are invalid`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IAM_LONG_LIVED) + iamTabState.apply { + profileName = "test" + accessKey = "invalid" + secretKey = "invalid" + } + } + + val stsClient = delegateMock() + @Suppress("DEPRECATION") + mockClientManager.register(StsClient::class, stsClient) + stsClient.stub { + whenever(it.getCallerIdentity(any())).thenThrow(StsException.builder().message("Some service exception message").build()) + } + + runInEdtAndWait { + val sut = SetupAuthenticationDialog( + project, + state = state, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ) + val exception = assertThrows { sut.doOKAction() } + assertThat(exception.message).isEqualTo(AwsCoreBundle.message("gettingstarted.setup.iam.profile.invalid_credentials")) + } + } + + fun `test validate IAM tab succeeds if credentials are invalid`() { + val state = SetupAuthenticationDialogState().apply { + selectedTab.set(SetupAuthenticationTabs.IAM_LONG_LIVED) + iamTabState.apply { + profileName = "test" + accessKey = "validAccess" + secretKey = "validSecret" + } + } + + val stsClient = delegateMock() + @Suppress("DEPRECATION") + mockClientManager.register(StsClient::class, stsClient) + stsClient.stub { + whenever(it.getCallerIdentity(any())).thenReturn(GetCallerIdentityResponse.builder().build()) + } + + val configFacade = mockk(relaxed = true) + runInEdtAndWait { + SetupAuthenticationDialog( + project, + state = state, + configFilesFacade = configFacade, + sourceOfEntry = SourceOfEntry.UNKNOWN, + featureId = FeatureId.Unknown + ) + .doOKAction() + } + + verify { + configFacade.appendProfileToCredentials( + Profile.builder() + .name("test") + .properties( + mapOf( + "aws_access_key_id" to "validAccess", + "aws_secret_access_key" to "validSecret" + ) + ) + .build() + ) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst-resources/exampleNotification2.json b/plugins/core-q/jetbrains-community/tst-resources/exampleNotification2.json new file mode 100644 index 00000000000..344f45191c7 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-resources/exampleNotification2.json @@ -0,0 +1,124 @@ +{ + "schema": { + "version": "2.0" +}, + "notifications": [ + { + + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "condition": { + "compute": { + "type": { + "or": [ + { + "==": "ec2" + }, + { + "==": "desktop" + } + ] + }, + "architecture": { + "!=": "x64" + } + }, + "os": { + "type": { + "anyOf": [ + "Darwin", + "Linux" + ] + }, + "version": { + "<=": "23.0.1.0" + } + + }, + "ide": { + "type": { + "noneOf": [ + "PyCharm", + "IDEA" + ] + }, + "version": { + "and": [ + { + ">=": "1.0" + }, + { + "<": "2.0" + } + ] + } + }, + "extensions": [ + { + "id": "aws.toolkit", + "version": { + "!=": "1.3334" + } + }, + { + "id": "amazon.q", + "version": { + "!=": "3.37.0" + } + } + ] + + , + "authx": [{ + "feature" : "q", + "type": { + "anyOf": [ + "IamIdentityCenter", + "AwsBuilderId" + ] + }, + "region": { + "==": "us-east-1" + }, + "connectionState": { + "!=": "Connected" + }, + "ssoScopes": { + "noneOf": [ + "codewhisperer:scope1", + "sso:account:access" + ] + } + } ] + }, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + }, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + }, + { + "type": "ShowUrl", + "content": { + "en-US": { + "title": "Click me!", + "url": "http://nowhere" + } + } + } + ] + } + ] +} diff --git a/plugins/core-q/jetbrains-community/tst-resources/olderNotification.json b/plugins/core-q/jetbrains-community/tst-resources/olderNotification.json new file mode 100644 index 00000000000..9ceee415d37 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst-resources/olderNotification.json @@ -0,0 +1,115 @@ +{ + "schema": { + "version": "2.0" + }, + "notifications": [ + { + + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "condition": { + "compute": { + "type": { + "or": [ + { + "==": "ec2" + }, + { + "==": "desktop" + } + ] + }, + "architecture": { + "!=": "x64" + } + }, + "os": { + "type": { + "anyOf": [ + "Darwin", + "Linux" + ] + }, + "version": { + "<=": "23.0.1.0" + } + + }, + "ide": { + "type": { + "noneOf": [ + "PyCharm", + "IDEA" + ] + }, + "version": { + "and": [ + { + ">=": "1.0" + }, + { + "<": "2.0" + } + ] + } + }, + "extension": { + "type": { + "==": "AWS Toolkit for JetBrains" + }, + "version": { + "<": "1.47.0.0" + } + }, + "authx": { + "type": { + "anyOf": [ + "IamIdentityCenter", + "AwsBuilderId" + ] + }, + "region": { + "==": "us-east-1" + }, + "connectionState": { + "!=": "Connected" + }, + "ssoScopes": { + "noneOf": [ + "codewhisperer:scope1", + "sso:account:access" + ] + } + } + }, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + }, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + }, + { + "type": "ShowUrl", + "content": { + "en-US": { + "title": "Click me!", + "url": "http://nowhere" + } + } + } + ] + } + ] +} diff --git a/plugins/core-q/jetbrains-community/tst-resources/selfSigned.jks b/plugins/core-q/jetbrains-community/tst-resources/selfSigned.jks new file mode 100644 index 0000000000000000000000000000000000000000..c863d1a97e277479916e490984b310c432409e5e GIT binary patch literal 2295 zcmchYc{J4j8pr25Gq#zGC2Rc16k{u2J4H#F--t1YFia@fGqx}hW5~p0y;QakS<8|& zq{vlVSthb&3H|&STlVGR`gP9z-OlO%`#I8;82XF9>=24o&5MX?}8DW>fV;kb6?O znIifO#`(0AAD?LRag}FjOR6C!w6C^|DwFR=?zqZ3yjAG2s!Hi68BtH1-ENe?qSj<| zZF)Pd-)H5=OXavQ&Ytd=HQ`H};Y-#quW-E(Z_kq_gD7f@MZJf`gC2c@Zsc(|p3)VJ>I(ElT)|xj?+prK2zY% zl*qHp`2*%l#kN{!fI?FLSDL@L`G4>2=){4LU6LH?;md{S(r zTT7xoYA;n|e&KON`}I0iwGBs|Hi0gm0r;*21&=p>8g24Y)Qh{C&v;3>)jalli`I^Z zHa_fZ*@M>Pv5edO`*&xCk*AgK67&4o#o;9%VS=b)!aIzpUW!egZTTg-(eo1T$SuL; z9ZxCx`%K&+?^%4vV4XZx7xU2AkIU2}|OdV?)87 zG%mVZnA0n}drLz%S~>H7zwd0T>EuQK7GT;nN z5}+f#1C*+3Tx02z^J*pR_TqNp32#L7rSBL9e$nl(n7I39ILflG*4pS)bR@M*J!FLO z`vuVJWuXcWP)os$EkopN{OwckZLTZm1jM<;79t{gC6Bdty5pK z>*Z&6uTIs_)#Y9#*6a!409-V{St|lV_*0pGAJH>aDB*Aj6|}2_=O<M0R6KdhX(^j19%`H4M0F?008Oo(xEY9qQa{=5X=p+dK+=4J7H+qO@N3-it&9+ zkzSztme;1`>_jjm*N$wgd#2Fd;Kw{{qB8Wt?7(o7A=4JQbuUe}jHqsO6@$M?%XoQ` zU&i&JWESy?DGT$!_xM-(R>tM(sn21{b(6WB_3u2Q#mmlh>knjJ8Soo=np&E6Y}*GD zd8P#I1rdDR!yI4H5uc>!DKl;Y#11V7i#Z_{(KSetw`iJkcqXbt&Y@pekdpK^k6SN$ zkbAl#&VtFP>n$u&OO6sXd}SK!LjCj@^VlNiOJJrVEKMJAI~aqMS)qm0EHYCz9kw4% z;^fP;rips>h6x5DNIBJ6C=7rAxdado{&qOY-%xNNxPXUHh{iv(0Zd2hM*`yH(DN?i zPx7GRUu8bHG
Ij&x(^f^ht18Jh?761SM literal 0 HcmV?d00001 diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt new file mode 100644 index 00000000000..946148fa227 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt @@ -0,0 +1,449 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.any +import com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.anyUrl +import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.junit.WireMockRule +import com.github.tomakehurst.wiremock.matching.ContainsPattern +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.use +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.ExtensionTestUtil +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.RuleChain +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder +import software.amazon.awssdk.core.SdkClient +import software.amazon.awssdk.core.client.builder.SdkAsyncClientBuilder +import software.amazon.awssdk.core.client.builder.SdkSyncClientBuilder +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption +import software.amazon.awssdk.core.client.config.SdkClientOption +import software.amazon.awssdk.core.signer.Signer +import software.amazon.awssdk.http.SdkHttpClient +import software.amazon.awssdk.http.async.SdkAsyncHttpClient +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.lambda.LambdaClient +import software.amazon.q.jetbrains.core.AwsClientManager.Companion.CUSTOMIZER_EP +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.MockAwsConnectionManager.ProjectAccountSettingsManagerRule +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitBearerTokenProviderDelegate +import software.amazon.q.core.region.Endpoint +import software.amazon.q.core.region.Service +import software.aws.toolkits.core.region.anAwsRegion +import software.aws.toolkits.core.utils.test.aString +import java.net.URI +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.jvm.isAccessible + +class AwsClientManagerTest { + private val projectRule = ProjectRule() + private val disposableRule = DisposableRule() + private val temporaryDirectory = TemporaryFolder() + private val regionProvider = MockRegionProviderRule() + private val credentialManager = MockCredentialManagerRule() + private val projectSettingsRule = ProjectAccountSettingsManagerRule(projectRule) + + @Rule + @JvmField + val wireMockRule = WireMockRule(WireMockConfiguration.wireMockConfig().dynamicPort()) + + @Rule + @JvmField + val ruleChain = RuleChain( + projectRule, + temporaryDirectory, + credentialManager, + regionProvider, + projectSettingsRule, + disposableRule + ) + + @Test + fun canGetAnInstanceOfAClient() { + val sut = getClientManager() + val client = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + assertThat(client.serviceName()).isEqualTo("dummyClient") + } + + @Test + fun clientsAreCached() { + val sut = getClientManager() + val credProvider = credentialManager.createCredentialProvider() + val region = regionProvider.createAwsRegion() + + val fooClient = sut.getClient(credProvider, region) + val barClient = sut.getClient(credProvider, region) + + assertThat(fooClient).isSameAs(barClient) + } + + @Test + fun oldClientsAreRemovedWhenCredentialsAreRemoved() { + val sut = getClientManager() + + val credentialsIdentifier = credentialManager.addCredentials("profile:admin") + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, regionProvider.defaultRegion()) + + sut.getClient(credentialProvider, anAwsRegion()) + + assertThat(sut.cachedClients().keys).anySatisfy { + assertThat(it.providerId).isEqualTo("profile:admin") + } + + ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerRemoved(credentialsIdentifier) + + assertThat(sut.cachedClients().keys).noneSatisfy { + assertThat(it.providerId).isEqualTo("profile:admin") + } + } + + @Test + fun clientsAreClosedWhenParentIsDisposed() { + val sut = getClientManager() + val client = Disposer.newDisposable().use { parent -> + Disposer.register(parent, sut) + + sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()).also { + assertThat(it.closed).isFalse() + } + } + + assertThat(client.closed).isTrue + assertThat(sut.cachedClients()).isEmpty() + } + + @Test + fun clientsAreClosedWhenCredentialProviderIsRemoved() { + val sut = getClientManager() + val credentialProviderId = credentialManager.addCredentials() + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialProviderId, anAwsRegion()) + val client = sut.getClient(credentialProvider, regionProvider.createAwsRegion()) + + assertThat(client.closed).isFalse + credentialManager.removeCredentials(credentialProviderId) + assertThat(client.closed).isTrue + + assertThat(sut.cachedClients()).isEmpty() + } + + @Test + fun `http client is shared across sync clients`() { + val sut = getClientManager() + val dummy = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val secondDummy = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + + assertThat(dummy.httpClient.delegate).isSameAs(secondDummy.httpClient.delegate) + } + + @Test + fun `async http clients is not shared across sync http clients`() { + val sut = getClientManager() + val dummy = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val asyncDummy = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + + assertThat(dummy.httpClient.delegate).isNotSameAs(asyncDummy.httpClient) + } + + @Test + fun `http client is not shared across async clients`() { + val sut = getClientManager() + val dummy = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val secondDummy = sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + + assertThat(dummy.httpClient).isNotSameAs(secondDummy.httpClient) + } + + @Test + fun clientWithoutBuilderFailsDescriptively() { + val sut = getClientManager() + + assertThatThrownBy { sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessageContaining("builder()") + } + + @Test + fun clientInterfaceWithoutNameFieldFailsDescriptively() { + val sut = getClientManager() + + assertThatThrownBy { sut.getClient(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) } + .isInstanceOf(NoSuchFieldException::class.java) + .hasMessageContaining("SERVICE_METADATA_ID") + } + + @Test + fun `tokenProvider can be passed to bearer clients`() { + val sut = getClientManager() + val client = sut.getClient(TokenConnectionSettings(mockTokenProvider(), regionProvider.createAwsRegion())) + + assertThat(client.withTokenProvider).isTrue() + } + + @Test + fun `no error thrown when tokenProvider is passed to incompatible client`() { + val sut = getClientManager() + sut.getClient(TokenConnectionSettings(mockTokenProvider(), regionProvider.createAwsRegion())) + } + + @Test + fun clientsAreScopedToRegion() { + val sut = getClientManager() + val credProvider = credentialManager.createCredentialProvider() + + val firstRegion = sut.getClient(credProvider, regionProvider.createAwsRegion()) + val secondRegion = sut.getClient(credProvider, regionProvider.createAwsRegion()) + + assertThat(secondRegion).isNotSameAs(firstRegion) + } + + @Test + fun globalServicesCanBeGivenAnyRegion() { + val sut = getClientManager() + regionProvider.addService( + "DummyService", + Service( + endpoints = mapOf("global" to Endpoint()), + isRegionalized = false, + partitionEndpoint = "global" + ) + ) + val credProvider = credentialManager.createCredentialProvider() + + val first = sut.getClient(credProvider, regionProvider.createAwsRegion(partitionId = "test")) + val second = sut.getClient(credProvider, regionProvider.createAwsRegion(partitionId = "test")) + + assertThat(first.serviceName()).isEqualTo("dummyClient") + assertThat(second).isSameAs(first) + } + + @Test + fun clientCustomizationIsAppliedToManagedClients() { + wireMockRule.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200))) + + val aConnection = ConnectionSettings(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val customizer = ToolkitClientCustomizer { credentialProvider, _, region, builder, _ -> + assertThat(credentialProvider).isEqualTo(aConnection.credentials) + assertThat(region).isEqualTo(aConnection.region.id) + + builder.endpointOverride(URI.create(wireMockRule.baseUrl())) + } + ExtensionTestUtil.maskExtensions(CUSTOMIZER_EP, listOf(customizer), disposableRule.disposable) + + getClientManager().getClient(LambdaClient::class, aConnection).use { + it.listFunctions() + } + + wireMockRule.verify(anyRequestedFor(anyUrl())) + } + + @Test + fun clientCustomizationIsAppliedToUnmanagedClients() { + wireMockRule.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200))) + + val aConnection = ConnectionSettings(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val customizer = ToolkitClientCustomizer { credentialProvider, _, region, builder, _ -> + assertThat(credentialProvider).isEqualTo(aConnection.credentials) + assertThat(region).isEqualTo(aConnection.region.id) + + builder.endpointOverride(URI.create(wireMockRule.baseUrl())) + } + ExtensionTestUtil.maskExtensions(CUSTOMIZER_EP, listOf(customizer), disposableRule.disposable) + + getClientManager().createUnmanagedClient(aConnection.credentials, Region.of(aConnection.region.id)).use { + it.listFunctions() + } + + wireMockRule.verify(anyRequestedFor(anyUrl())) + } + + @Test + fun userAgentIsPassedForManagedClients() { + wireMockRule.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200))) + + val aConnection = ConnectionSettings(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val customizer = ToolkitClientCustomizer { _, _, _, builder, _ -> + builder.endpointOverride(URI.create(wireMockRule.baseUrl())) + } + ExtensionTestUtil.maskExtensions(CUSTOMIZER_EP, listOf(customizer), disposableRule.disposable) + + getClientManager().getClient(aConnection).use { + it.listFunctions() + } + + wireMockRule.verify(anyRequestedFor(urlPathMatching("(.*)/functions/")).withHeader("User-Agent", ContainsPattern("AWS-Toolkit-For-JetBrains/"))) + } + + @Test + fun userAgentIsPassedForUnmanagedClients() { + wireMockRule.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200))) + + val aConnection = ConnectionSettings(credentialManager.createCredentialProvider(), regionProvider.createAwsRegion()) + val customizer = ToolkitClientCustomizer { _, _, _, builder, _ -> + builder.endpointOverride(URI.create(wireMockRule.baseUrl())) + } + ExtensionTestUtil.maskExtensions(CUSTOMIZER_EP, listOf(customizer), disposableRule.disposable) + + getClientManager().createUnmanagedClient(aConnection.credentials, Region.of(aConnection.region.id)).use { + it.listFunctions() + } + + wireMockRule.verify(anyRequestedFor(urlPathMatching("(.*)/functions/")).withHeader("User-Agent", ContainsPattern("AWS-Toolkit-For-JetBrains/"))) + } + + private fun mockTokenProvider() = mock().let { + whenever(it.id).thenReturn(aString()) + + ToolkitBearerTokenProvider(it) + } + + // Test against real version so bypass ServiceManager for the client manager + private fun getClientManager() = AwsClientManager() + + class DummyServiceAsyncClient(val httpClient: SdkAsyncHttpClient) : TestClient() { + companion object { + @Suppress("unused") + @JvmStatic + fun builder() = DummyServiceAsyncClientBuilder() + } + } + + class DummyServiceClient(val httpClient: SdkHttpClient) : TestClient() { + companion object { + @Suppress("unused") + @JvmStatic + fun builder() = DummyServiceClientBuilder() + } + } + + class DummyServiceAsyncClientBuilder : TestAsyncClientBuilder() { + override fun serviceName(): String = "DummyService" + + override fun signingName(): String = serviceName() + + override fun buildClient() = DummyServiceAsyncClient(asyncClientConfiguration().option(SdkClientOption.ASYNC_HTTP_CLIENT)) + } + + class DummyServiceClientBuilder : TestSyncClientBuilder() { + override fun serviceName(): String = "DummyService" + + override fun signingName(): String = serviceName() + + override fun buildClient() = DummyServiceClient(syncClientConfiguration().option(SdkClientOption.SYNC_HTTP_CLIENT)) + } + + class DummyBearerServiceClient(val httpClient: SdkHttpClient, val withTokenProvider: Boolean = false) : TestClient() { + companion object { + @Suppress("unused") + @JvmStatic + fun builder() = DummyBearerServiceClientBuilder() + } + } + + class DummyBearerServiceClientBuilder : TestClientBuilder() { + private var tokenProviderMethodInvoked = false + + fun tokenProvider(ignored: SdkTokenProvider): DummyBearerServiceClientBuilder { + tokenProviderMethodInvoked = true + + return this + } + + override fun serviceName(): String = "DummyBearerService" + + override fun signingName(): String = serviceName() + + override fun buildClient() = DummyBearerServiceClient(syncClientConfiguration().option(SdkClientOption.SYNC_HTTP_CLIENT), tokenProviderMethodInvoked) + } + + class SecondDummyServiceClient(val httpClient: SdkHttpClient) : TestClient() { + companion object { + @Suppress("unused") + @JvmStatic + fun builder() = SecondDummyServiceClientBuilder() + } + } + + class SecondDummyServiceClientBuilder : + TestSyncClientBuilder() { + override fun serviceName(): String = "SecondDummyService" + + override fun signingName(): String = serviceName() + + override fun buildClient() = SecondDummyServiceClient(syncClientConfiguration().option(SdkClientOption.SYNC_HTTP_CLIENT)) + } + + class InvalidServiceClient : TestClient() { + override fun close() {} + + override fun serviceName() = "invalidClient" + } + + class NoServiceNameClient : SdkClient { + override fun close() {} + + override fun serviceName() = "invalidClient" + } + + abstract class TestClient : SdkClient, AutoCloseable { + var closed = false + + override fun serviceName() = "dummyClient" + + override fun close() { + closed = true + } + + companion object { + @Suppress("unused", "MayBeConstant") + @JvmField + val SERVICE_METADATA_ID = "DummyService" + } + } + + abstract class TestAsyncClientBuilder : TestClientBuilder(), SdkAsyncClientBuilder + where B : AwsClientBuilder, B : SdkAsyncClientBuilder + + abstract class TestSyncClientBuilder : TestClientBuilder(), SdkSyncClientBuilder + where B : AwsClientBuilder, B : SdkSyncClientBuilder + + abstract class TestClientBuilder, C> : AwsDefaultClientBuilder() { + init { + overrideConfiguration { + it.advancedOptions(mapOf(SdkAdvancedClientOption.SIGNER to Signer { _, _ -> throw NotImplementedError() })) + } + } + + override fun serviceEndpointPrefix() = "dummyClient" + } + + private val SdkHttpClient.delegate: SdkHttpClient + get() { + val delegateProperty = this::class.declaredMemberProperties.find { it.name == "delegate" } + ?: throw IllegalArgumentException( + "Expected instance of software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.NonManagedSdkHttpClient" + ) + delegateProperty.isAccessible = true + return delegateProperty.call(this) as SdkHttpClient + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt new file mode 100644 index 00000000000..58844e253eb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt @@ -0,0 +1,563 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.RuleChain +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.aws.toolkits.core.utils.test.retryableAssert +import software.amazon.q.jetbrains.utils.hasCauseWithMessage +import software.amazon.q.jetbrains.utils.hasException +import software.amazon.q.jetbrains.utils.hasValue +import software.amazon.q.jetbrains.utils.value +import software.amazon.q.jetbrains.utils.wait +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CountDownLatch +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicInteger + +@ExperimentalCoroutinesApi +class AwsResourceCacheTest { + private val projectRule = ProjectRule() + private val credentialsManager = MockCredentialManagerRule() + private val regionProvider = MockRegionProviderRule() + + // If we don't control the order manually, regionProvider can run before projectRule which causes a NPE + @Rule + @JvmField + val ruleChain = RuleChain( + projectRule, + credentialsManager, + regionProvider + ) + + private val mockClock = mock() + private val mockResource = mock>() + + private lateinit var sut: AwsResourceCache + + private lateinit var cred1Identifier: CredentialIdentifier + private lateinit var cred1Provider: ToolkitCredentialsProvider + private lateinit var cred2Identifier: CredentialIdentifier + private lateinit var cred2Provider: ToolkitCredentialsProvider + private lateinit var connectionSettings: ConnectionSettings + + @Before + fun setUp() { + cred1Identifier = credentialsManager.addCredentials("Cred1") + cred1Provider = credentialsManager.getAwsCredentialProvider(cred1Identifier, regionProvider.defaultRegion()) + + cred2Identifier = credentialsManager.addCredentials("Cred2") + cred2Provider = credentialsManager.getAwsCredentialProvider(cred2Identifier, regionProvider.defaultRegion()) + + connectionSettings = ConnectionSettings(credentialsManager.createCredentialProvider(), regionProvider.createAwsRegion()) + + if (this::sut.isInitialized) { + (sut as DefaultAwsResourceCache).dispose() + } + sut = DefaultAwsResourceCache(mockClock, 1000, Duration.ofMinutes(1)) + + reset(mockClock, mockResource) + whenever(mockResource.expiry()).thenReturn(DEFAULT_EXPIRY) + whenever(mockResource.id).thenReturn("mock") + whenever(mockClock.instant()).thenReturn(Instant.now()) + } + + @Test + fun basicCachingWorks() { + whenever(mockResource.fetch(any())).thenReturn("hello") + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + + verifyResourceCalled(times = 1) + } + + @Test + fun expirationWorks() { + whenever(mockResource.fetch(any())).thenReturn("hello") + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + whenever(mockClock.instant()).thenReturn(Instant.now().plus(DEFAULT_EXPIRY)) + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + + verifyResourceCalled(times = 2) + } + + @Test + fun exceptionsAreBubbledWhenNoEntry() { + doAnswer { throw Throwable("Bang!") }.whenever(mockResource).fetch(any()) + assertThatThrownBy { sut.getResource(mockResource, connectionSettings).value }.hasCauseWithMessage("Bang!") + } + + @Test + fun exceptionsAreLoggedButStaleEntryReturnedByDefault() { + whenever(mockResource.fetch(any())).thenReturn("hello").doThrow(RuntimeException("BOOM")) + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + whenever(mockClock.instant()).thenReturn(Instant.now().plus(DEFAULT_EXPIRY)) + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + } + + @Test + fun exceptionsAreBubbledWhenExistingEntryExpiredAndUseStaleIsFalse() { + whenever(mockResource.fetch(any())).thenReturn("hello").doThrow(RuntimeException("BOOM")) + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + whenever(mockClock.instant()).thenReturn(Instant.now().plus(DEFAULT_EXPIRY)) + assertThat(sut.getResource(mockResource, connectionSettings, useStale = false)).hasException + } + + @Test + fun cacheEntriesAreSeparatedByRegionAndCredentials() { + whenever(mockResource.fetch(any())).thenAnswer { + val (cred, region) = it.getArgument(0) + "${region.id}-${cred.id}" + } + + assertThat(sut.getResource(mockResource, region = US_WEST_1, credentialProvider = cred1Provider)).hasValue("us-west-1-Cred1") + assertThat(sut.getResource(mockResource, region = US_WEST_2, credentialProvider = cred1Provider)).hasValue("us-west-2-Cred1") + assertThat(sut.getResource(mockResource, region = US_WEST_1, credentialProvider = cred2Provider)).hasValue("us-west-1-Cred2") + assertThat(sut.getResource(mockResource, region = US_WEST_2, credentialProvider = cred2Provider)).hasValue("us-west-2-Cred2") + assertThat(sut.getResource(mockResource, region = US_WEST_2, credentialProvider = cred2Provider)).hasValue("us-west-2-Cred2") + + verifyResourceCalled(times = 4) + } + + @Test + fun cacheCanBeCleared() { + whenever(mockResource.fetch(any())).thenReturn("hello").thenReturn("goodbye") + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + + runBlocking { sut.clear() } + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("goodbye") + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("goodbye") + + verifyResourceCalled(times = 2) + } + + @Test + fun cacheCanBeClearedByKey() { + whenever(mockResource.fetch(any())).thenReturn("hello").thenReturn("goodbye") + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + + sut.clear(mockResource, connectionSettings) + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("goodbye") + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("goodbye") + + verifyResourceCalled(times = 2) + } + + @Test + fun cacheCanBeClearedByKeyAndConnection() { + val incrementer = AtomicInteger(0) + whenever(mockResource.fetch(any())).thenAnswer { + val (cred, region) = it.getArgument(0) + "${region.id}-${cred.id}-${incrementer.getAndIncrement()}" + } + + val usw1Cred1 = sut.getResource(mockResource, US_WEST_1, cred1Provider).value + val usw1Cred2 = sut.getResource(mockResource, US_WEST_1, cred2Provider).value + val usw2Cred1 = sut.getResource(mockResource, US_WEST_2, cred1Provider).value + val usw2Cred2 = sut.getResource(mockResource, US_WEST_2, cred2Provider).value + + sut.clear(mockResource, ConnectionSettings(cred1Provider, US_WEST_1)) + + assertThat(sut.getResource(mockResource, US_WEST_1, cred1Provider)).wait().isCompletedWithValueMatching { it != usw1Cred1 } + assertThat(sut.getResource(mockResource, US_WEST_1, cred2Provider)).hasValue(usw1Cred2) + assertThat(sut.getResource(mockResource, US_WEST_2, cred1Provider)).hasValue(usw2Cred1) + assertThat(sut.getResource(mockResource, US_WEST_2, cred2Provider)).hasValue(usw2Cred2) + verifyResourceCalled(times = 5) + } + + @Test + fun canForceCacheRefresh() { + whenever(mockResource.fetch(any())).thenReturn("hello").thenReturn("goodbye") + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + assertThat(sut.getResource(mockResource, connectionSettings, forceFetch = true)).hasValue("goodbye") + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("goodbye") + + verifyResourceCalled(times = 2) + } + + @Test + fun expirationOccursOnExpiryTime() { + assertExpectedExpiryFunctions({ minusMillis(1) }, shouldExpire = false) // before expiry + assertExpectedExpiryFunctions({ this }, shouldExpire = true) // on expiry + assertExpectedExpiryFunctions({ plusMillis(1) }, shouldExpire = true) // after expiry + } + + @Test + fun viewsCanBeCreatedOnTopOfOtherCachedItems() { + whenever(mockResource.fetch(any())).thenReturn("hello") + val viewResource = Resource.view(mockResource) { toList() } + + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + assertThat(sut.getResource(viewResource, connectionSettings)).hasValue(listOf('h', 'e', 'l', 'l', 'o')) + verifyResourceCalled(times = 1) + } + + @Test + fun mapFilterAndFindExtensionsToEasilyCreateViews() { + whenever(mockResource.fetch(any())).thenReturn("hello") + val viewResource = Resource.view(mockResource) { toList() } + + val filteredAndMapped = viewResource.filter { it != 'l' }.map { it.uppercaseChar() } + assertThat(sut.getResource(filteredAndMapped, connectionSettings)).hasValue(listOf('H', 'E', 'O')) + + val find = viewResource.find { it == 'l' } + assertThat(sut.getResource(find, connectionSettings)).hasValue('l') + } + + @Test + fun clearingViewsClearTheUnderlyingCachedResource() { + whenever(mockResource.fetch(any())).thenReturn("hello") + val viewResource = Resource.view(mockResource) { toList() } + sut.getResource(viewResource, connectionSettings).value + sut.clear(viewResource, connectionSettings) + sut.getResource(viewResource, connectionSettings).value + + verifyResourceCalled(times = 2) + } + + @Test + fun viewsCanBeRegionAware() { + whenever(mockResource.fetch(any())).thenReturn("hello") + val viewResource = Resource.View(mockResource) { _, region -> region } + + assertThat(sut.getResource(viewResource, connectionSettings)).hasValue(connectionSettings.region) + } + + @Test + fun cacheIsRegularlyPrunedToEnsureItDoesntGrowTooLarge() { + val localSut = DefaultAwsResourceCache(mockClock, 5, Duration.ofMillis(50)) + + val now = Instant.now() + whenever(mockClock.instant()).thenReturn(now) + + localSut.getResource(StringResource("1"), connectionSettings).value + + whenever(mockClock.instant()).thenReturn(now.plusMillis(10)) + + localSut.getResource(StringResource("2"), connectionSettings).value + localSut.getResource(StringResource("3"), connectionSettings).value + localSut.getResource(StringResource("4"), connectionSettings).value + localSut.getResource(StringResource("5"), connectionSettings).value + localSut.getResource(StringResource("6"), connectionSettings).value + + retryableAssert { + assertThat(localSut.getResourceIfPresent(StringResource("1"), connectionSettings)).isNull() + } + } + + @Test + fun pruningConsidersCollectionEntriesBasedOnTheirSize() { + val localSut = DefaultAwsResourceCache(mockClock, 5, Duration.ofMillis(50)) + + val listResource = DummyResource("list", listOf("a", "b", "c", "d")) + val now = Instant.now() + whenever(mockClock.instant()).thenReturn(now) + + localSut.getResource(listResource, connectionSettings).value + + whenever(mockClock.instant()).thenReturn(now.plusMillis(10)) + + localSut.getResource(StringResource("1"), connectionSettings).value + localSut.getResource(StringResource("2"), connectionSettings).value + + retryableAssert { + assertThat(localSut.getResourceIfPresent(listResource, connectionSettings)).isNull() + assertThat(localSut.getResourceIfPresent(StringResource("1"), connectionSettings)).isNotEmpty() + assertThat(localSut.getResourceIfPresent(StringResource("2"), connectionSettings)).isNotEmpty() + } + } + + @Test + fun multipleCallsInDifferentThreadsStillOnlyCallTheUnderlyingResourceOnce() { + whenever(mockResource.fetch(any())).thenReturn("hello") + val concurrency = 200 + + val latch = CountDownLatch(1) + val executor = Executors.newFixedThreadPool(concurrency) + try { + val futures = (1 until concurrency).map { + val future = CompletableFuture() + executor.submit { + latch.await() + sut.getResource(mockResource, connectionSettings).whenComplete { result, error -> + when { + result != null -> future.complete(result) + error != null -> future.completeExceptionally(error) + } + } + } + future + }.toTypedArray() + latch.countDown() + CompletableFuture.allOf(*futures).get(10, TimeUnit.SECONDS) + } finally { + executor.shutdown() + } + + verifyResourceCalled(times = 1) + } + + @Test + fun multipleCallsWhileFetchPendingCallTheUnderlyingResourceOnce() { + val latch = CountDownLatch(1) + whenever(mockResource.fetch(any())).thenAnswer { + // don't allow the task to finish until all futures have been created + latch.await() + return@thenAnswer "hello" + } + + val futures = buildList> { + repeat(20) { + // simulate multiple calls to the same resource + add(sut.getResource(mockResource, connectionSettings).toCompletableFuture()) + } + }.toTypedArray() + latch.countDown() + + // all futures should complete + CompletableFuture.allOf(*futures).value + + // and we should have reused the same task for all of the requests + verifyResourceCalled(times = 1) + } + + @Test + fun cachingShouldBeBasedOnResourceId() { + val first = StringResource("first") + val anotherFirst = StringResource("first") + + sut.getResource(first, connectionSettings).value + sut.getResource(anotherFirst, connectionSettings).value + + assertThat(first.callCount).hasValue(1) + assertThat(anotherFirst.callCount).hasValue(0) + } + + @Test + fun whenACredentialIdIsRemovedItsEntriesAreRemovedFromTheCache() { + whenever(mockResource.fetch(any())).thenReturn("hello") + getAllRegionAndCredPermutations() + + ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerRemoved(cred1Identifier) + + getAllRegionAndCredPermutations() + + verify(mockResource, times(2)).fetch(ConnectionSettings(cred1Provider, US_WEST_1)) + verify(mockResource, times(2)).fetch(ConnectionSettings(cred1Provider, US_WEST_2)) + verify(mockResource, times(1)).fetch(ConnectionSettings(cred2Provider, US_WEST_1)) + verify(mockResource, times(1)).fetch(ConnectionSettings(cred2Provider, US_WEST_2)) + } + + @Test + fun whenACredentialIdIsModifiedItsEntriesAreRemovedFromTheCache() { + whenever(mockResource.fetch(any())).thenReturn("hello") + getAllRegionAndCredPermutations() + + ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerModified(cred1Identifier) + + getAllRegionAndCredPermutations() + + verify(mockResource, times(2)).fetch(ConnectionSettings(cred1Provider, US_WEST_1)) + verify(mockResource, times(2)).fetch(ConnectionSettings(cred1Provider, US_WEST_2)) + verify(mockResource, times(1)).fetch(ConnectionSettings(cred2Provider, US_WEST_1)) + verify(mockResource, times(1)).fetch(ConnectionSettings(cred2Provider, US_WEST_2)) + } + + @Test + fun cacheExposesBlockingApi() { + whenever(mockResource.fetch(any())).thenReturn("hello") + assertThat(sut.getResourceNow(mockResource, connectionSettings)).isEqualTo("hello") + } + + @Test + fun cacheExposesBlockingApiWithRegionAndCred() { + whenever(mockResource.fetch(any())).thenReturn("hello") + assertThat(sut.getResourceNow(mockResource, US_WEST_1, cred1Provider)).isEqualTo("hello") + verify(mockResource).fetch(ConnectionSettings(cred1Provider, US_WEST_1)) + } + + @Test + fun cacheExposesBlockingApiWhereExecutionExceptionIsUnwrapped() { + whenever(mockResource.fetch(any())).thenThrow(RuntimeException("boom")) + assertThatThrownBy { sut.getResourceNow(mockResource, connectionSettings, timeout = Duration.ofSeconds(1)) } + .isInstanceOf(RuntimeException::class.java) + .hasCauseWithMessage("boom") + } + + @Test + fun cacheExposesBlockingApiWithTimeout() { + whenever(mockResource.fetch(any())).thenAnswer { + Thread.sleep(50) + "hello" + } + assertThatThrownBy { sut.getResourceNow(mockResource, connectionSettings, timeout = Duration.ofMillis(5)) }.isInstanceOf(TimeoutException::class.java) + } + + @Test + fun canConditionallyFetchOnlyIfAvailableInCache() { + whenever(mockResource.fetch(any())).thenReturn("hello") + + assertThat(sut.getResourceIfPresent(mockResource, US_WEST_1, cred1Provider)).isNull() + sut.getResource(mockResource, US_WEST_1, cred1Provider).value + assertThat(sut.getResourceIfPresent(mockResource, US_WEST_1, cred1Provider)).isEqualTo("hello") + } + + @Test + fun canConditionallyFetchOnlyIfAvailableInCacheAndRespectExpiry() { + whenever(mockResource.fetch(any())).thenReturn("hello") + + val now = Instant.now() + whenever(mockClock.instant()).thenReturn(now) + sut.getResource(mockResource, US_WEST_1, cred1Provider).value + + whenever(mockClock.instant()).thenReturn(now.plus(DEFAULT_EXPIRY).plusMillis(1)) + assertThat(sut.getResourceIfPresent(mockResource, US_WEST_1, cred1Provider, useStale = false)).isNull() + } + + @Test + fun canConditionallyFetchViewOnlyIfAvailableInCache() { + whenever(mockResource.fetch(any())).thenReturn("hello") + val viewResource = Resource.view(mockResource) { reversed() } + + assertThat(sut.getResourceIfPresent(viewResource, US_WEST_1, cred1Provider)).isNull() + sut.getResource(viewResource, US_WEST_1, cred1Provider).value + assertThat(sut.getResourceIfPresent(viewResource, US_WEST_1, cred1Provider)).isEqualTo("olleh") + } + + @Test + fun canConditionallyFetchOnlyIfAvailableWithoutExplicitCredentialsRegion() { + whenever(mockResource.fetch(any())).thenReturn("hello") + sut.getResource(mockResource, connectionSettings).value + + assertThat(sut.getResourceIfPresent(mockResource, connectionSettings)).isEqualTo("hello") + } + + @Test + fun concurrentlyRunningExceptionalResourcesGetTheSameException() { + val latch = CountDownLatch(1) + whenever(mockResource.fetch(any())).then { + latch.await() + // exception gets thrown fast enough where the second fetchIfNeeded check occurs after the first call throws + runTest { + delay(500) + } + throw RuntimeException("Boom") + } + + val first = sut.getResource(mockResource, connectionSettings) + val second = sut.getResource(mockResource, connectionSettings) + latch.countDown() + assertThatThrownBy { first.value }.hasCauseWithMessage("Boom") + assertThatThrownBy { second.value }.hasCauseWithMessage("Boom") + verifyResourceCalled(1, mockResource) + } + + @Test + fun canRecoverFromACachedException() { + whenever(mockResource.fetch(any())).thenThrow(RuntimeException("Boom")).thenReturn("Success!") + assertThrows { sut.getResource(mockResource, connectionSettings).value } + assertThat(sut.getResource(mockResource, connectionSettings).value).isEqualTo("Success!") + } + + @Test + fun retriesReturnTheMostRecentException() { + whenever(mockResource.fetch(any())).thenThrow(RuntimeException("Boom"), RuntimeException("Ouch")) + assertThatThrownBy { sut.getResource(mockResource, connectionSettings).value }.hasCauseWithMessage("Boom") + assertThatThrownBy { sut.getResource(mockResource, connectionSettings).value }.hasCauseWithMessage("Ouch") + } + + @Test + fun exceptionalEntriesAreRemovedFromTheCache() { + whenever(mockResource.fetch(any())).thenThrow(RuntimeException("Boom")) + assertThrows { sut.getResource(mockResource, connectionSettings).value } + + with(sut as DefaultAwsResourceCache) { + doRunCacheMaintenance() + assertThat(hasCacheEntry(mockResource.id)).isFalse + } + } + + private fun getAllRegionAndCredPermutations() { + sut.getResource(mockResource, US_WEST_1, cred1Provider).value + sut.getResource(mockResource, US_WEST_2, cred1Provider).value + sut.getResource(mockResource, US_WEST_1, cred2Provider).value + sut.getResource(mockResource, US_WEST_2, cred2Provider).value + } + + private fun assertExpectedExpiryFunctions(expiryFunction: Instant.() -> Instant, shouldExpire: Boolean) { + whenever(mockResource.fetch(any())).thenReturn("hello", "goodbye") + whenever(mockResource.expiry()).thenReturn(Duration.ofSeconds(1)) + val now = Instant.now() + val expiry = now.plus(Duration.ofSeconds(1)) + whenever(mockClock.instant()).thenReturn(now) + assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + + whenever(mockClock.instant()).thenReturn(expiryFunction(expiry)) + when (shouldExpire) { + true -> assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("goodbye") + false -> assertThat(sut.getResource(mockResource, connectionSettings)).hasValue("hello") + } + + runBlocking { sut.clear() } + } + + private fun verifyResourceCalled(times: Int, resource: Resource.Cached<*> = mockResource) { + verify(resource, times(times)).fetch(any()) + verify(resource, times(times)).expiry() + verify(resource, atLeastOnce()).id + verifyNoMoreInteractions(resource) + } + + companion object { + private val US_WEST_1 = AwsRegion("us-west-1", "USW1", "aws") + private val US_WEST_2 = AwsRegion("us-west-2", "USW2", "aws") + + private val DEFAULT_EXPIRY = Duration.ofMinutes(10) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt new file mode 100644 index 00000000000..65f189b82b2 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt @@ -0,0 +1,133 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.any +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig +import com.github.tomakehurst.wiremock.junit.WireMockRule +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.ApplicationRule +import com.intellij.util.net.ssl.CertificateManager +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import software.amazon.awssdk.http.HttpExecuteRequest +import software.amazon.awssdk.http.SdkHttpFullRequest +import software.amazon.awssdk.http.SdkHttpMethod +import software.aws.toolkits.core.rules.EnvironmentVariableHelper +import software.aws.toolkits.core.rules.SystemPropertyHelper +import java.net.URI + +class AwsSdkClientTest { + @Rule + @JvmField + val application = ApplicationRule() + + @Rule + @JvmField + val wireMock = createSelfSignedServer() + + @Rule + @JvmField + val sysProps = SystemPropertyHelper() + + @Rule + @JvmField + val environmentVariableHelper = EnvironmentVariableHelper() + + @Before + fun setUp() { + stubFor(any(urlPathEqualTo("/")).willReturn(aResponse().withStatus(200))) + } + + @After + fun tearDown() { + CertificateManager.getInstance().customTrustManager.removeCertificate("selfsign") + } + + @Test + fun testCertGetsTrusted() { + val trustManager = CertificateManager.getInstance().customTrustManager + val initialSize = trustManager.certificates.size + + val request = mockSdkRequest("https://localhost:" + wireMock.httpsPort()) + + val awsSdkClient = AwsSdkClient() + val response = try { + awsSdkClient.sharedSdkClient().prepareRequest( + HttpExecuteRequest.builder().request(request).build() + ).call() + } finally { + Disposer.dispose(awsSdkClient) + } + + assertThat(response.httpResponse().isSuccessful).isTrue() + + assertThat(trustManager.certificates).hasSize(initialSize + 1) + assertThat(trustManager.containsCertificate("selfsign")).isTrue() + } + + @Test + fun systemPropertyProxyConfigurationIgnored() { + System.setProperty("http.proxyHost", "foo.com") + System.setProperty("http.proxyPort", Integer.toString(8888)) + + val request = mockSdkRequest("https://localhost:" + wireMock.httpsPort()) + + val awsSdkClient = AwsSdkClient() + val response = try { + awsSdkClient.sharedSdkClient().prepareRequest( + HttpExecuteRequest.builder().request(request).build() + ).call() + } finally { + Disposer.dispose(awsSdkClient) + } + + assertThat(response.httpResponse().isSuccessful).isTrue() + } + + @Test + fun environmentVariableProxyConfigurationIgnored() { + environmentVariableHelper.set("HTTP_PROXY", "https://foo.com:8888") + + val request = mockSdkRequest("https://localhost:" + wireMock.httpsPort()) + + val awsSdkClient = AwsSdkClient() + val response = try { + awsSdkClient.sharedSdkClient().prepareRequest( + HttpExecuteRequest.builder().request(request).build() + ).call() + } finally { + Disposer.dispose(awsSdkClient) + } + + assertThat(response.httpResponse().isSuccessful).isTrue() + } + + private fun mockSdkRequest(uriString: String): SdkHttpFullRequest? { + val uri = URI.create(uriString) + return SdkHttpFullRequest.builder() + .uri(uri) + .method(SdkHttpMethod.GET) + .build() + } + + private fun createSelfSignedServer(): WireMockRule { + val selfSignedJks = AwsSdkClientTest::class.java.getResource("/selfSigned.jks") + return WireMockRule( + wireMockConfig() + .httpDisabled(true) + .dynamicHttpsPort() + .keystorePath(selfSignedJks.toString()) + .keystorePassword("changeit") + .keyManagerPassword("changeit") + .keystoreType("jks") + ) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/CoroutineUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/CoroutineUtilsTest.kt new file mode 100644 index 00000000000..68ea1e540c5 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/CoroutineUtilsTest.kt @@ -0,0 +1,46 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.coroutines + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.runInEdtAndWait +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test + +class CoroutineUtilsTest { + @Rule + @JvmField + val application = ApplicationRule() + + @Rule + @JvmField + val disposableRule = DisposableRule() + + @Test + fun `getCoroutineUiContext context runs on UI thread`() { + runBlocking { + assertThat(ApplicationManager.getApplication().isDispatchThread).isFalse + withContext(getCoroutineUiContext()) { + assertThat(ApplicationManager.getApplication().isDispatchThread).isTrue + } + } + } + + @Test + fun `getCoroutineBgContext context runs not on UI thread`() { + runInEdtAndWait { + assertThat(ApplicationManager.getApplication().isDispatchThread).isTrue + runBlocking { + withContext(getCoroutineBgContext()) { + assertThat(ApplicationManager.getApplication().isDispatchThread).isFalse + } + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/ScopeTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/ScopeTest.kt new file mode 100644 index 00000000000..2f47de1e778 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/ScopeTest.kt @@ -0,0 +1,201 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.coroutines + +import com.intellij.ide.highlighter.ProjectFileType +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ComponentManager +import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.TestApplicationManager +import com.intellij.testFramework.createTestOpenProjectOptions +import com.intellij.testFramework.replaceService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import migration.software.amazon.q.jetbrains.core.coroutines.PluginCoroutineScopeTracker +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.rules.TestName +import software.amazon.q.jetbrains.utils.isInstanceOf +import java.time.Duration +import java.util.concurrent.CancellationException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +class ScopeTest { + @Rule + @JvmField + val tempDir = TemporaryFolder() + + @Rule + @JvmField + val projectRule = ProjectRule() + + @Rule + @JvmField + val disposableRule = DisposableRule() + + @Rule + @JvmField + val testName = TestName() + + @Test + fun `plugin being uploaded cancels application scope`() { + val fakePluginScope = createFakePluginScope() + + assertScopeIsCanceled(applicationCoroutineScope()) { + Disposer.dispose(fakePluginScope) + } + } + + @Test + fun `plugin being uploaded cancels project scope`() { + val fakePluginScope = createFakePluginScope(projectRule.project) + + assertScopeIsCanceled(projectCoroutineScope(projectRule.project)) { + Disposer.dispose(fakePluginScope) + } + } + + @Test + fun `plugin being uploaded cancels disposable scope`() { + val fakePluginScope = createFakePluginScope() + + // Use fake disposable so we dont accidentally trigger false positive, nor disposable leak detector + val fakeDisposable = Disposable { } + assertScopeIsCanceled(disposableCoroutineScope(fakeDisposable)) { + Disposer.dispose(fakePluginScope) + } + } + + @Test + @Ignore("Disposing the application leads to the AppExecutorUtil.getAppExecutorService being shutdown and no way to restart and thus fails all future tests") + fun `application being disposed cancels application scope`() { + assertScopeIsCanceled(applicationCoroutineScope()) { + TestApplicationManager.getInstance().dispose() + } + } + + @Test + fun `project being disposed cancels project scope`() { + val projectFile = tempDir.newFile("${testName.methodName}${ProjectFileType.DOT_DEFAULT_EXTENSION}").toPath() + val options = createTestOpenProjectOptions(runPostStartUpActivities = false) + val project = ProjectManagerEx.getInstanceEx().openProject(projectFile, options)!! + + assertScopeIsCanceled(projectCoroutineScope(project)) { + PlatformTestUtil.forceCloseProjectWithoutSaving(project) + } + } + + @Test + fun `disposable being disposed cancels disposable scope`() { + val disposable = Disposer.newDisposable() + + assertScopeIsCanceled(disposableCoroutineScope(disposable)) { + Disposer.dispose(disposable) + } + } + + @Test + fun `applicationCoroutineScope launches on background thread`() { + assertScopeIsCorrectThread(applicationCoroutineScope()) + } + + @Test + fun `projectCoroutineScope launches on background thread`() { + assertScopeIsCorrectThread(projectCoroutineScope(projectRule.project)) + } + + @Test + fun `disposableCoroutineScope launches on background thread`() { + assertScopeIsCorrectThread(disposableCoroutineScope(disposableRule.disposable)) + } + + @Test + fun `application and project trackers are different`() { + val projectFile = tempDir.newFile("${testName.methodName}${ProjectFileType.DOT_DEFAULT_EXTENSION}").toPath() + val options = createTestOpenProjectOptions(runPostStartUpActivities = false) + val project2 = ProjectManagerEx.getInstanceEx().openProject(projectFile, options)!! + + try { + assertThat( + listOf( + PluginCoroutineScopeTracker.getInstance(), + PluginCoroutineScopeTracker.getInstance(projectRule.project), + PluginCoroutineScopeTracker.getInstance(project2) + ) + ).doesNotHaveDuplicates() + } finally { + PlatformTestUtil.forceCloseProjectWithoutSaving(project2) + } + } + + @Test + fun `disposableCoroutineScope can't take a project`() { + assertThatThrownBy { disposableCoroutineScope(projectRule.project) }.isInstanceOf() + } + + @Test + fun `disposableCoroutineScope can't take an application`() { + assertThatThrownBy { disposableCoroutineScope(ApplicationManager.getApplication()) }.isInstanceOf() + } + + private fun createFakePluginScope(componentManager: ComponentManager = ApplicationManager.getApplication()): Disposable { + // We can't unload the real plugin in tests, so make another instance of the service and replace it for the tests + val tracker = PluginCoroutineScopeTracker() + componentManager.replaceService(PluginCoroutineScopeTracker::class.java, tracker, disposableRule.disposable) + return tracker + } + + private fun assertScopeIsCorrectThread(scope: CoroutineScope) { + val ran = AtomicBoolean(false) + runBlocking(scope.coroutineContext) { + assertThat(ApplicationManager.getApplication().isDispatchThread).isFalse + ran.set(true) + } + assertThat(ran).isTrue + } + + private fun assertScopeIsCanceled(scope: CoroutineScope, cancellationTask: () -> Unit) { + val testTarget = TestTarget(scope) + val future = testTarget.computeAsync().asCompletableFuture() + assertThat(testTarget.computationStarted.await(10, TimeUnit.SECONDS)).isTrue + + cancellationTask() + + testTarget.cancelFired.countDown() + + assertThat(future).failsWithin(Duration.ofSeconds(10)).withThrowableOfType(CancellationException::class.java) + assertThat(testTarget.bgTaskDone.get()).isFalse + } + + @Suppress("BlockingMethodInNonBlockingContext") + private class TestTarget(private val scope: CoroutineScope) { + val computationStarted = CountDownLatch(1) + val cancelFired = CountDownLatch(1) + val bgTaskDone = AtomicBoolean(false) + + fun computeAsync() = scope.async { + computationStarted.countDown() + cancelFired.await() + doTask() + } + + suspend fun doTask() = withContext(getCoroutineBgContext()) { + bgTaskDone.set(true) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesActionTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesActionTest.kt new file mode 100644 index 00000000000..6f981658c10 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesActionTest.kt @@ -0,0 +1,209 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileTypes.FileTypes +import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx +import com.intellij.openapi.ui.TestDialog +import com.intellij.openapi.ui.TestDialogManager +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.TestActionEvent +import com.intellij.testFramework.runInEdtAndWait +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import java.io.File + +class CreateOrUpdateCredentialProfilesActionTest { + + @Rule + @JvmField + val folderRule = TemporaryFolder() + + @Rule + @JvmField + val projectRule = ProjectRule() + + private lateinit var fileEditorManager: FileEditorManager + private lateinit var localFileSystem: LocalFileSystem + + @Before + fun setUp() { + fileEditorManager = FileEditorManager.getInstance(projectRule.project) + localFileSystem = LocalFileSystem.getInstance() + } + + @After + fun cleanUp() { + runInEdtAndWait { + fileEditorManager.openFiles.forEach { fileEditorManager.closeFile(it) } + } + } + + @Test + fun confirmConfigFileCreated_bothFilesDoNotExist() { + val configFile = File(folderRule.newFolder(), "config") + val credFile = File(folderRule.newFolder(), "credentials") + + val writer = mock { + on { configPath }.thenReturn(configFile.toPath()) + on { credentialsPath }.thenReturn(credFile.toPath()) + on { createConfigFile() }.doAnswer { configFile.writeText("hello") } + } + + val sut = CreateOrUpdateCredentialProfilesAction(writer) + TestDialogManager.setTestDialog(TestDialog.OK) + + sut.actionPerformed(TestActionEvent { projectRule.project }) + + verify(writer).createConfigFile() + + assertThat(fileEditorManager.openFiles).hasOnlyOneElementSatisfying { assertThat(it.name).isEqualTo("config") } + } + + @Test + fun bothFilesOpened_bothFilesExists() { + val configFile = folderRule.newFile("config") + val credFile = folderRule.newFile("credentials") + val writer = mock { + on { configPath }.thenReturn(configFile.toPath()) + on { credentialsPath }.thenReturn(credFile.toPath()) + } + + // IDE interprets blank files with no extension as binary + configFile.writeText("config") + credFile.writeText("cred") + + val sut = CreateOrUpdateCredentialProfilesAction(writer) + TestDialogManager.setTestDialog(TestDialog.OK) + + sut.actionPerformed(TestActionEvent { projectRule.project }) + + verify(writer, atLeastOnce()).configPath + verify(writer, atLeastOnce()).credentialsPath + verifyNoMoreInteractions(writer) + + assertThat(fileEditorManager.openFiles).hasSize(2) + .anySatisfy { assertThat(it.name).isEqualTo("config") } + .anySatisfy { assertThat(it.name).isEqualTo("credentials") } + } + + @Test + fun configFileOpened_onlyConfigExists() { + val configFile = folderRule.newFile("config") + val credFile = folderRule.newFile("credentials") + credFile.delete() + val writer = mock { + on { configPath }.thenReturn(configFile.toPath()) + on { credentialsPath }.thenReturn(credFile.toPath()) + } + + configFile.writeText("config") + + val sut = CreateOrUpdateCredentialProfilesAction(writer) + TestDialogManager.setTestDialog(TestDialog.OK) + + sut.actionPerformed(TestActionEvent { projectRule.project }) + + verify(writer, atLeastOnce()).configPath + verify(writer, atLeastOnce()).credentialsPath + verifyNoMoreInteractions(writer) + + assertThat(fileEditorManager.openFiles).hasOnlyOneElementSatisfying { assertThat(it.name).isEqualTo("config") } + } + + @Test + fun credentialFileOpened_onlyCredentialsExists() { + val configFile = folderRule.newFile("config") + configFile.delete() + val credFile = folderRule.newFile("credentials") + val writer = mock { + on { configPath }.thenReturn(configFile.toPath()) + on { credentialsPath }.thenReturn(credFile.toPath()) + } + + credFile.writeText("cred") + + val sut = CreateOrUpdateCredentialProfilesAction(writer) + TestDialogManager.setTestDialog(TestDialog.OK) + + sut.actionPerformed(TestActionEvent { projectRule.project }) + + verify(writer, atLeastOnce()).configPath + verify(writer, atLeastOnce()).credentialsPath + verifyNoMoreInteractions(writer) + + assertThat(fileEditorManager.openFiles).hasOnlyOneElementSatisfying { assertThat(it.name).isEqualTo("credentials") } + } + + @Test + fun emptyFileCanBeOpenedAsPlainText() { + val configFile = folderRule.newFile("config") + val credFile = folderRule.newFile("credentials") + configFile.delete() + val writer = mock { + on { configPath }.thenReturn(configFile.toPath()) + on { credentialsPath }.thenReturn(credFile.toPath()) + } + + // Mark the file as unknown for the purpose of the test. This is needed because some + // other extensions can have weird file type association patterns (like Docker having + // *. (?)) which makes this test fail because it is not file type unknown + localFileSystem.refreshAndFindFileByIoFile(credFile) + runInEdtAndWait { + ApplicationManager.getApplication().runWriteAction { + FileTypeManagerEx.getInstanceEx().associatePattern( + FileTypes.UNKNOWN, + "credentials" + ) + } + } + + val sut = CreateOrUpdateCredentialProfilesAction(writer) + TestDialogManager.setTestDialog(TestDialog.OK) + + sut.actionPerformed(TestActionEvent { projectRule.project }) + + verify(writer, atLeastOnce()).configPath + verify(writer, atLeastOnce()).credentialsPath + verifyNoMoreInteractions(writer) + + assertThat(fileEditorManager.openFiles).hasOnlyOneElementSatisfying { + assertThat(it.name).isEqualTo("credentials") + // FIX_WHEN_MIN_IS_212: assert that type is `FileTypes.PLAIN_TEXT` or `DetectedByContentFileType` + assertThat(it.fileType).isNotNull() + assertThat(it.fileType).isNotEqualTo(FileTypes.UNKNOWN) + } + } + + @Test + fun negativeConfirmationDoesNotCreateFile() { + val configFile = folderRule.newFile("config") + val credFile = folderRule.newFile("credentials") + val writer = mock { + on { configPath }.thenReturn(configFile.toPath()) + on { credentialsPath }.thenReturn(credFile.toPath()) + } + + val sut = CreateOrUpdateCredentialProfilesAction(writer) + TestDialogManager.setTestDialog(TestDialog.NO) + + sut.actionPerformed(TestActionEvent { projectRule.project }) + + verify(writer, atLeastOnce()).configPath + verify(writer, atLeastOnce()).credentialsPath + verifyNoMoreInteractions(writer) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt new file mode 100644 index 00000000000..7ea4bc711aa --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt @@ -0,0 +1,316 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.ExtensionTestUtil +import com.intellij.testFramework.runInEdtAndWait +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Rule +import org.junit.Test +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.core.region.getDefaultRegion +import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread +import software.amazon.q.jetbrains.utils.computeOnEdt +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialIdentifierBase +import software.amazon.q.core.credentials.CredentialProviderFactory +import software.amazon.q.core.credentials.CredentialProviderNotFoundException +import software.amazon.q.core.credentials.CredentialSourceId +import software.amazon.q.core.credentials.CredentialsChangeEvent +import software.amazon.q.core.credentials.CredentialsChangeListener +import software.amazon.q.core.region.AwsRegion +import software.aws.toolkits.core.region.anAwsRegion +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.isInstanceOf +import kotlin.test.assertNotNull + +class CredentialManagerTest { + @Rule + @JvmField + val disposableRule = DisposableRule() + + @Rule + @JvmField + val application = ApplicationRule() + + @Rule + @JvmField + val regionProvider = MockRegionProviderRule() + + @Test + fun testCredentialsCanLoadFromExtensions() { + val region = getDefaultRegion() + + addFactories( + createTestCredentialFactory( + "testFactory1", + listOf("testFoo1", "testBar1") + ), + createTestCredentialFactory( + "testFactory2", + listOf("testFoo2", "testBar2") + ) + ) + + val credentialManager = DefaultCredentialManager() + + assertThat(credentialManager.getCredentialIdentifiers().map { it.id }).contains("testFoo1", "testFoo2", "testBar1", "testBar2") + + val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo2") + assertNotNull(credentialsIdentifier) + + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, region) + + assertThat(credentialProvider.resolveCredentials()).isInstanceOf() + } + + @Test + fun testCredentialsAreScopedToPartition() { + val partition1 = anAwsRegion(partitionId = "part1") + val partition1Region2 = anAwsRegion(partitionId = "part1") + val partition2 = anAwsRegion(partitionId = "part2") + + addFactories(createTestCredentialFactory("testFactory1", listOf("testFoo1"))) + + val credentialManager = DefaultCredentialManager() + + val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1") + assertNotNull(credentialsIdentifier) + + val partition1Credentials = credentialManager.getAwsCredentialProvider(credentialsIdentifier, partition1).resolveCredentials() + val partition1Region2Credentials = credentialManager.getAwsCredentialProvider(credentialsIdentifier, partition1Region2).resolveCredentials() + val partition2Credentials = credentialManager.getAwsCredentialProvider(credentialsIdentifier, partition2).resolveCredentials() + + assertThat(partition1Credentials).isEqualTo(partition1Region2Credentials).isNotEqualTo(partition2Credentials) + } + + @Test + fun testCredentialUpdatingDoesNotBreakExisting() { + val region = getDefaultRegion() + val originalCredentials = randomCredentialProvider() + val credentialFactory = createTestCredentialFactory("testFactory1").apply { + addCredentialProvider("testFoo1", originalCredentials) + } + + addFactories(credentialFactory) + + val credentialManager = DefaultCredentialManager() + + val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1") + assertNotNull(credentialsIdentifier) + + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, region) + + assertThat(credentialProvider.resolveCredentials()).isEqualTo(originalCredentials.resolveCredentials()) + + val updatedCredentials = randomCredentialProvider() + credentialFactory.updateCredentials("testFoo1", region, updatedCredentials) + + // Existing references are good + assertThat(credentialProvider.resolveCredentials()).isEqualTo(updatedCredentials.resolveCredentials()) + + // New ones are good too + assertThat( + credentialManager.getAwsCredentialProvider( + credentialsIdentifier, + region + ).resolveCredentials() + ).isEqualTo(updatedCredentials.resolveCredentials()) + } + + @Test + fun testRemovedCredentialsCeaseWorkingAfter() { + val region = getDefaultRegion() + val credentialFactory = createTestCredentialFactory("testFactory1", listOf("testFoo1")) + + addFactories(credentialFactory) + + val credentialManager = DefaultCredentialManager() + + val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1") + assertNotNull(credentialsIdentifier) + + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, region) + assertThat(credentialProvider.resolveCredentials()).isInstanceOf() + + credentialFactory.removeCredentials("testFoo1") + + // Existing references throw + assertThatThrownBy { credentialProvider.resolveCredentials() }.isInstanceOf(CredentialProviderNotFoundException::class.java) + + // New ones fail too + assertThatThrownBy { + credentialManager.getAwsCredentialProvider( + credentialsIdentifier, + region + ).resolveCredentials() + }.isInstanceOf() + + assertThat(credentialManager.getCredentialIdentifierById("testFoo1")).isNull() + } + + @Test + fun testUpdatedCredentialIdentifierIsApplied() { + val region = getDefaultRegion() + val credentialFactory = createTestCredentialFactory("testFactory1", listOf("testFoo1")) + + addFactories(credentialFactory) + + val credentialManager = DefaultCredentialManager() + + assertThat(credentialManager.getCredentialIdentifierById("testFoo1")?.defaultRegionId).isEqualTo(region.id) + + val newRegion = regionProvider.addRegion(AwsRegion("test", "test", "test")) + credentialFactory.updateCredentials("testFoo1", newRegion) + + assertThat(credentialManager.getCredentialIdentifierById("testFoo1")?.defaultRegionId).isEqualTo(newRegion.id) + } + + @Test + fun resolvingCredentialsRunsInBackground() { + val credentialFactory = createTestCredentialFactory("testFactory1").apply { + addCredentialProvider("testFoo1") { + assertIsNonDispatchThread() + computeOnEdt { + ApplicationManager.getApplication().assertIsDispatchThread() + + AwsBasicCredentials.create(aString(), aString()) + } + } + } + + addFactories(credentialFactory) + + val credentialManager = DefaultCredentialManager() + val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1") + assertNotNull(credentialsIdentifier) + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, getDefaultRegion()) + + runInEdtAndWait { + credentialProvider.resolveCredentials() + } + } + + @Test + fun processCancellationBubblesOut() { + val credentialFactory = createTestCredentialFactory("testFactory1").apply { + addCredentialProvider("testFoo1") { + throw ProcessCanceledException() + } + } + + addFactories(credentialFactory) + + val credentialManager = DefaultCredentialManager() + val credentialsIdentifier = credentialManager.getCredentialIdentifierById("testFoo1") + assertNotNull(credentialsIdentifier) + val credentialProvider = credentialManager.getAwsCredentialProvider(credentialsIdentifier, getDefaultRegion()) + + assertThatThrownBy { + credentialProvider.resolveCredentials() + }.isInstanceOf() + } + + private fun addFactories(vararg factories: CredentialProviderFactory) { + ExtensionTestUtil.maskExtensions(DefaultCredentialManager.EP_NAME, factories.toList(), disposableRule.disposable) + } + + private fun createTestCredentialFactory(id: String, initialProviderIds: List = emptyList()) = TestCredentialProviderFactory(id).apply { + initialProviderIds.forEach(this::addCredentialProvider) + } + + private class TestCredentialProviderFactory(override val id: String) : CredentialProviderFactory { + private val initialProviders = mutableMapOf() + private val credentialsMapping = mutableMapOf() + private lateinit var callback: CredentialsChangeListener + + override val credentialSourceId = CredentialSourceId.SharedCredentials + + override fun setUp(credentialLoadCallback: CredentialsChangeListener) { + callback = credentialLoadCallback + + credentialsMapping.putAll(initialProviders) + + callback( + CredentialsChangeEvent( + initialProviders.values.toList(), + emptyList(), + emptyList() + ) + ) + + initialProviders.clear() + } + + fun addCredentialProvider( + credentialId: String, + awsCredentialsProvider: AwsCredentialsProvider? = null, + ) { + val identifier = TestCredentialProviderIdentifier(credentialId, id, getDefaultRegion().id, awsCredentialsProvider) + if (!::callback.isInitialized) { + initialProviders[credentialId] = identifier + return + } + + credentialsMapping[credentialId] = identifier + + callback( + CredentialsChangeEvent( + listOf(identifier), + emptyList(), + emptyList() + ) + ) + } + + override fun createAwsCredentialProvider(providerId: CredentialIdentifier, region: AwsRegion): AwsCredentialsProvider = + (providerId as TestCredentialProviderIdentifier).provider ?: StaticCredentialsProvider.create(AwsBasicCredentials.create(aString(), aString())) + + fun updateCredentials(providerId: String, region: AwsRegion, awsCredentialsProvider: AwsCredentialsProvider = randomCredentialProvider()) { + val identifier = TestCredentialProviderIdentifier(providerId, id, region.id, awsCredentialsProvider) + + credentialsMapping[providerId] = identifier + + callback( + CredentialsChangeEvent( + emptyList(), + listOf(identifier), + emptyList() + ) + ) + } + + fun removeCredentials(providerId: String) { + callback( + CredentialsChangeEvent( + emptyList(), + emptyList(), + listOf(credentialsMapping.remove(providerId)!!) + ) + ) + } + } + + private class TestCredentialProviderIdentifier( + override val id: String, + override val factoryId: String, + override val defaultRegionId: String, + val provider: AwsCredentialsProvider?, + ) : CredentialIdentifierBase(null) { + override val displayName: String = "$factoryId:$id" + } + + private companion object { + private fun randomCredentialProvider() = StaticCredentialsProvider.create(AwsBasicCredentials.create(aString(), aString())) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt new file mode 100644 index 00000000000..2fb6201a2d3 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt @@ -0,0 +1,188 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.notification.Notification +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.runInEdtAndWait +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.core.credentials.aCredentialsIdentifier +import software.aws.toolkits.core.region.anAwsRegion +import software.amazon.q.jetbrains.settings.AwsSettingsRule +import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule + +class CredentialsRegionHandlerTest { + + @Rule + @JvmField + val projectRule = ProjectRule() + + @Rule + @JvmField + val regionProviderRule = MockRegionProviderRule() + + @Rule + @JvmField + val disposableRule = DisposableRule() + + @Rule + @JvmField + val notificationListener = NotificationListenerRule(projectRule, disposableRule.disposable) + + private lateinit var sut: DefaultCredentialsRegionHandler + + @Rule + @JvmField + val settingsRule = AwsSettingsRule() + + @Before + fun setup() { + sut = DefaultCredentialsRegionHandler(projectRule.project) + AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Always + } + + @Test + fun `Credential with no default region returns selected region`() { + val identifier = aCredentialsIdentifier(defaultRegionId = null) + val region = anAwsRegion() + + assertThat(sut.determineSelectedRegion(identifier, region)).isEqualTo(region) + } + + @Test + fun `When selected region is null always use credential region`() { + val defaultRegion = regionProviderRule.createAwsRegion() + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + assertThat(sut.determineSelectedRegion(identifier, selectedRegion = null)).isEqualTo(defaultRegion) + } + + @Test + fun `Always use credential region if its partition is different from the selected region`() { + val defaultRegion = regionProviderRule.createAwsRegion() + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + assertThat(sut.determineSelectedRegion(identifier, selectedRegion = regionProviderRule.createAwsRegion())).isEqualTo(defaultRegion) + } + + @Test + fun `Always use credential region if setting is set to Always`() { + AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Always + val defaultRegion = regionProviderRule.createAwsRegion() + val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId) + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + assertThat(sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)).isEqualTo(defaultRegion) + } + + @Test + fun `Do not use credential region if setting is set to Never`() { + AwsSettings.getInstance().useDefaultCredentialRegion = UseAwsCredentialRegion.Never + val defaultRegion = regionProviderRule.createAwsRegion() + val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId) + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + assertThat(sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)).isEqualTo(selectedRegion) + } + + @Test + fun `Do not use credential region if setting is set to Never, even if the partition is different`() { + settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Never + val defaultRegion = regionProviderRule.createAwsRegion() + val selectedRegion = regionProviderRule.createAwsRegion() + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + assertThat(sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion)).isEqualTo(selectedRegion) + } + + @Test + fun `Do not use credential region if setting is set to Never, even if the region is null`() { + settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Never + val defaultRegion = regionProviderRule.createAwsRegion() + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + assertThat(sut.determineSelectedRegion(identifier, selectedRegion = null)).isNull() + } + + @Test + fun `Prompt appears when setting is set to prompt, selected region remains active`() { + settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt + + val defaultRegion = regionProviderRule.createAwsRegion() + val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId) + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + val newSelected = sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion) + + assertThat(newSelected).isEqualTo(selectedRegion) + val notification = getOnlyNotification() + assertThat(notification.actions).hasSize(3) + } + + @Test + fun `Prompt only appears when region is different than default`() { + settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt + + val defaultRegion = regionProviderRule.createAwsRegion() + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + val newSelected = sut.determineSelectedRegion(identifier, selectedRegion = defaultRegion) + + assertThat(newSelected).isEqualTo(defaultRegion) + assertThat(notificationListener.notifications.filter { it.title == AwsCoreBundle.message("aws.notification.title") }).isEmpty() + } + + @Test + fun `Selecting Never at the prompt sets setting to Never`() { + settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt + + val defaultRegion = regionProviderRule.createAwsRegion() + val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId) + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion) + + val notification = getOnlyNotification() + + runInEdtAndWait { + Notification.fire(notification, notification.actions.first { it.templateText == "Never" }, null) + } + + assertThat(AwsSettings.getInstance().useDefaultCredentialRegion).isEqualTo(UseAwsCredentialRegion.Never) + } + + @Test + fun `Selecting Always at the prompt sets setting to Always`() { + settingsRule.settings.useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt + + val defaultRegion = regionProviderRule.createAwsRegion() + val selectedRegion = regionProviderRule.createAwsRegion(partitionId = defaultRegion.partitionId) + val identifier = aCredentialsIdentifier(defaultRegionId = defaultRegion.id) + + sut.determineSelectedRegion(identifier, selectedRegion = selectedRegion) + + val notification = getOnlyNotification() + + runInEdtAndWait { + Notification.fire(notification, notification.actions.first { it.templateText == "Always" }, null) + } + + assertThat(AwsSettings.getInstance().useDefaultCredentialRegion).isEqualTo(UseAwsCredentialRegion.Always) + } + + private fun getOnlyNotification(): Notification { + val credentialNotifications = notificationListener.notifications.filter { it.title == AwsCoreBundle.message("aws.notification.title") } + assertThat(credentialNotifications).hasSize(1) + + return credentialNotifications.first() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt new file mode 100644 index 00000000000..b09b1e77250 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt @@ -0,0 +1,529 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.application.ApplicationManager +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.mock +import software.amazon.q.jetbrains.core.MockResourceCacheRule +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager.Companion.selectedPartition +import software.amazon.q.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.core.region.getDefaultRegion +import software.amazon.q.jetbrains.services.sts.StsResources +import software.amazon.q.core.credentials.CredentialIdentifier +import software.aws.toolkits.core.credentials.aCredentialsIdentifier +import software.amazon.q.core.region.AwsRegion +import software.aws.toolkits.core.rules.EnvironmentVariableHelper +import software.aws.toolkits.core.rules.SystemPropertyHelper +import software.aws.toolkits.core.utils.test.notNull +import software.amazon.q.jetbrains.utils.deserializeState +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.serializeState +import java.nio.file.Files +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +class DefaultAwsConnectionManagerTest { + @Rule + @JvmField + val projectRule = HeavyJavaCodeInsightTestFixtureRule() + + @Rule + @JvmField + val environmentVariableHelper = EnvironmentVariableHelper() + + @Rule + @JvmField + val mockCredentialManager = MockCredentialManagerRule() + + @Rule + @JvmField + val regionProviderRule = MockRegionProviderRule() + + @JvmField + @Rule + val resourceCache = MockResourceCacheRule() + + @Rule + @JvmField + val systemPropertyHelper = SystemPropertyHelper() + + private lateinit var manager: DefaultAwsConnectionManager + + @Before + fun setUp() { + // Isolate our tests + System.getProperties().setProperty("aws.configFile", Files.createTempFile("dummy", null).toAbsolutePath().toString()) + System.getProperties().setProperty("aws.sharedCredentialsFile", Files.createTempFile("dummy", null).toAbsolutePath().toString()) + System.getProperties().remove("aws.region") + environmentVariableHelper.remove("AWS_REGION") + manager = DefaultAwsConnectionManager(projectRule.project) + } + + @After + fun tearDown() { + mockCredentialManager.reset() + } + + @Test + fun `Starts with no active credentials`() { + assertThat(manager.isValidConnectionSettings()).isFalse + assertThat(manager.recentlyUsedCredentials()).isEmpty() + } + + @Test + fun `On load, automatically selects default profile if present and no other active credentials`() { + val credentials = mockCredentialManager.addCredentials(DEFAULT_PROFILE_ID) + markConnectionSettingsAsValid(credentials, AwsRegionProvider.getInstance().defaultRegion()) + + manager.noStateLoaded() + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.selectedCredentialIdentifier).notNull.satisfiesKt { + assertThat(it.id).isEqualTo(credentials.id) + } + } + + @Test + fun `On load, default region of credential is used if there is no other active region`() { + val region = AwsRegion("us-west-2", "Oregon", "AWS") + val credentials = mockCredentialManager.addCredentials(id = "Mock", region = region) + markConnectionSettingsAsValid(credentials, regionProviderRule.defaultRegion()) + regionProviderRule.addRegion(region) + + deserializeState( + """ + + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.selectedRegion).notNull.satisfiesKt { + assertThat(it.id).isEqualTo("us-west-2") + } + } + + @Test + fun `Activated credential are validated and added to the recently used list`() { + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + + assertThat(manager.recentlyUsedCredentials()).isEmpty() + + val credentials = mockCredentialManager.addCredentials("Mock1") + val credentials2 = mockCredentialManager.addCredentials("Mock2") + + markConnectionSettingsAsValid(credentials, AwsRegionProvider.getInstance().defaultRegion()) + markConnectionSettingsAsValid(credentials2, AwsRegionProvider.getInstance().defaultRegion()) + + changeCredentialProvider(credentials) + + assertThat(manager.isValidConnectionSettings()).isTrue + assertThat(manager.connectionSettings()?.credentials?.id).isEqualTo(credentials.id) + + assertThat(manager.recentlyUsedCredentials()).element(0).isEqualTo(credentials) + + changeCredentialProvider(credentials2) + + assertThat(manager.isValidConnectionSettings()).isTrue + assertThat(manager.connectionSettings()?.credentials?.id).isEqualTo(credentials2.id) + + assertThat(manager.recentlyUsedCredentials()).element(0).isEqualTo(credentials2) + assertThat(manager.recentlyUsedCredentials()).element(1).isEqualTo(credentials) + } + + @Test + fun `Activated regions are validated and added to the recently used list`() { + val mockRegion1 = regionProviderRule.addRegion(AwsRegion("MockRegion-1", "MockRegion-1", "aws")) + val mockRegion2 = regionProviderRule.addRegion(AwsRegion("MockRegion-2", "MockRegion-2", "aws")) + + assertThat(manager.recentlyUsedRegions()).isEmpty() + + changeRegion(mockRegion1) + + assertThat(manager.selectedRegion).isEqualTo(mockRegion1) + assertThat(manager.recentlyUsedRegions()).element(0).isEqualTo(mockRegion1) + + changeRegion(mockRegion2) + + assertThat(manager.selectedRegion).isEqualTo(mockRegion2) + assertThat(manager.recentlyUsedRegions()).element(0).isEqualTo(mockRegion2) + assertThat(manager.recentlyUsedRegions()).element(1).isEqualTo(mockRegion1) + } + + @Test + fun `Activating a region fires a state change notification`() { + val project = projectRule.project + + val gotNotification = AtomicBoolean(false) + val latch = CountDownLatch(1) + + val busConnection = project.messageBus.connect() + busConnection.subscribe( + AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED, + object : ConnectionSettingsStateChangeNotifier { + override fun settingsStateChanged(newState: ConnectionState) { + gotNotification.set(true) + latch.countDown() + } + } + ) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + + assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue + assertThat(gotNotification).isTrue + } + + @Test + fun `Activating a credential fires a state change notification`() { + val project = projectRule.project + + val gotNotification = AtomicBoolean(false) + val latch = CountDownLatch(1) + + val busConnection = project.messageBus.connect() + busConnection.subscribe( + AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED, + object : ConnectionSettingsStateChangeNotifier { + override fun settingsStateChanged(newState: ConnectionState) { + gotNotification.set(true) + latch.countDown() + } + } + ) + + changeCredentialProvider(mockCredentialManager.addCredentials("Mock")) + + assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue + assertThat(gotNotification).isTrue + } + + @Test + fun `Active region is persisted`() { + manager.changeRegion(AwsRegion.GLOBAL) + assertThat(serializeState("AccountState", manager)).isEqualToIgnoringWhitespace( + """ + + + + """ + ) + } + + @Test + fun `Active credential is persisted`() { + val credentials = mockCredentialManager.addCredentials("Mock") + markConnectionSettingsAsValid(credentials, manager.activeRegion) + changeCredentialProvider(credentials) + + assertThat(serializeState("AccountState", manager)).isEqualToIgnoringWhitespace( + """ + + + + """ + ) + } + + @Test + fun `Active credential can be restored from persistence`() { + val credentials = mockCredentialManager.addCredentials("Mock") + markConnectionSettingsAsValid(credentials, regionProviderRule.defaultRegion()) + + deserializeState( + """ + + + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.selectedCredentialIdentifier).isEqualTo(credentials) + assertThat(manager.recentlyUsedCredentials()).element(0).isEqualTo(credentials) + } + + @Test + fun `Active region can be restored from persistence`() { + deserializeState( + """ + + + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + val region = regionProviderRule.defaultRegion() + assertThat(manager.selectedRegion).isEqualTo(region) + assertThat(manager.selectedPartition?.id).isEqualTo(region.partitionId) + assertThat(manager.recentlyUsedRegions()).element(0).isEqualTo(region) + } + + @Test + fun `Attempting to restore a region that no longer exists is handled gracefully`() { + deserializeState( + """ + + + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.connectionSettings()?.region).isNull() + assertThat(manager.recentlyUsedRegions()).isEmpty() + } + + @Test + fun `Attempting to restore a credential that no longer exists is handled gracefully`() { + deserializeState( + """ + + + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.isValidConnectionSettings()).isFalse + assertThat(manager.recentlyUsedCredentials()).isEmpty() + assertThat(manager.connectionSettings()).isNull() + } + + @Test + fun `Credentials are validated when restored from persistence`() { + val mockCredentials = mockCredentialManager.addCredentials("Mock") + + markConnectionSettingsAsInvalid(mockCredentials, regionProviderRule.defaultRegion()) + + deserializeState( + """ + + + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.isValidConnectionSettings()).isFalse + } + + @Test + fun `On load, default credential is selected if no other credential is active`() { + val credentials = mockCredentialManager.addCredentials(DEFAULT_PROFILE_ID) + markConnectionSettingsAsValid(credentials, regionProviderRule.defaultRegion()) + + deserializeState( + """ + + """, + manager, + ) + + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.isValidConnectionSettings()).isTrue + assertThat(manager.connectionSettings()?.credentials?.id).isEqualTo(DEFAULT_PROFILE_ID) + + assertThat(manager.recentlyUsedCredentials()).hasSize(1) + assertThat(manager.recentlyUsedCredentials().first().id).isEqualTo(DEFAULT_PROFILE_ID) + } + + @Test + fun `Removal of the active credential falls back to 'no credential selected' state`() { + val someOtherCredential = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) } + val adminCredentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) } + + markConnectionSettingsAsValid(someOtherCredential, AwsRegionProvider.getInstance().defaultRegion()) + markConnectionSettingsAsValid(adminCredentials, AwsRegionProvider.getInstance().defaultRegion()) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + changeCredentialProvider(adminCredentials) + + assertThat(manager.isValidConnectionSettings()).isTrue + + assertThat(manager.selectedCredentialIdentifier?.id).isEqualTo(adminCredentials.id) + + ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerRemoved(adminCredentials) + + assertThat(manager.isValidConnectionSettings()).isFalse + assertThat(manager.selectedCredentialIdentifier).isNull() + assertThat(manager.connectionSettings()).isNull() + } + + @Test + fun `Refreshing state triggers connection to be re-validated`() { + val defaultCredentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) } + + markConnectionSettingsAsInvalid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion()) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + changeCredentialProvider(defaultCredentials) + + assertThat(manager.isValidConnectionSettings()).isFalse + + markConnectionSettingsAsValid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion()) + manager.refreshConnectionState() + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.isValidConnectionSettings()).isTrue + } + + @Test + fun `A change to the selected credential triggers a refresh if the current state is invalid`() { + val defaultCredentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) } + + markConnectionSettingsAsInvalid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion()) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + changeCredentialProvider(defaultCredentials) + + assertThat(manager.isValidConnectionSettings()).isFalse + + markConnectionSettingsAsValid(defaultCredentials, AwsRegionProvider.getInstance().defaultRegion()) + ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerModified(defaultCredentials) + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.isValidConnectionSettings()).isTrue + } + + @Test + fun `A change to the selected credential triggers a refresh if the current state is valid`() { + val credentials = aCredentialsIdentifier().also { mockCredentialManager.addCredentials(it.id) } + + markConnectionSettingsAsValid(credentials, AwsRegionProvider.getInstance().defaultRegion()) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + changeCredentialProvider(credentials) + + assertThat(manager.isValidConnectionSettings()).isTrue + + markConnectionSettingsAsInvalid(credentials, AwsRegionProvider.getInstance().defaultRegion()) + ApplicationManager.getApplication().messageBus.syncPublisher(CredentialManager.CREDENTIALS_CHANGED).providerModified(credentials) + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.isValidConnectionSettings()).isFalse + } + + @Test + fun `credential validation error can be handled`() { + val credentialsIdentifier = object : PostValidateInteractiveCredential, CredentialIdentifier by aCredentialsIdentifier() { + override fun handleValidationException(e: Exception) = ConnectionState.RequiresUserAction( + object : InteractiveCredential, CredentialIdentifier by this { + override val userActionDisplayMessage = "" + override val userAction: AnAction = mock() + override fun userActionRequired() = true + } + ) + }.also { mockCredentialManager.addCredentials(it.id) } + + markConnectionSettingsAsInvalid(credentialsIdentifier, AwsRegionProvider.getInstance().defaultRegion()) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + changeCredentialProvider(credentialsIdentifier) + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.connectionState).isInstanceOf(ConnectionState.RequiresUserAction::class.java) + } + + @Test + fun `credential validation error returns invalid if not handled`() { + val credentialsIdentifier = object : PostValidateInteractiveCredential, CredentialIdentifier by aCredentialsIdentifier() { + override fun handleValidationException(e: Exception) = null + }.also { mockCredentialManager.addCredentials(it.id) } + + markConnectionSettingsAsInvalid(credentialsIdentifier, AwsRegionProvider.getInstance().defaultRegion()) + + changeRegion(AwsRegionProvider.getInstance().defaultRegion()) + changeCredentialProvider(credentialsIdentifier) + manager.waitUntilConnectionStateIsStable() + + assertThat(manager.connectionState).isInstanceOf(ConnectionState.InvalidConnection::class.java) + } + + private fun markConnectionSettingsAsValid(credentialsIdentifier: CredentialIdentifier, region: AwsRegion) { + resourceCache.addEntry(StsResources.ACCOUNT, region.id, credentialsIdentifier.id, "1111222233333") + } + + private fun markConnectionSettingsAsInvalid(credentialsIdentifier: CredentialIdentifier, region: AwsRegion) { + resourceCache.addEntry( + StsResources.ACCOUNT, + region.id, + credentialsIdentifier.id, + CompletableFuture.failedFuture(IllegalStateException("Invalid AWS credentials $credentialsIdentifier")) + ) + } + + private fun changeCredentialProvider(credentialsProvider: CredentialIdentifier) { + manager.changeCredentialProvider(credentialsProvider) + + manager.waitUntilConnectionStateIsStable() + } + + private fun changeRegion(region: AwsRegion) { + manager.changeRegion(region) + + manager.waitUntilConnectionStateIsStable() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt new file mode 100644 index 00000000000..522184f439c --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt @@ -0,0 +1,608 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Assume +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import software.amazon.awssdk.profiles.Profile +import software.amazon.q.core.utils.createParentDirectories +import software.amazon.q.core.utils.writeText +import software.aws.toolkits.core.utils.test.assertPosixPermissions +import software.amazon.q.jetbrains.utils.satisfiesKt +import java.nio.file.Paths + +class DefaultConfigFilesFacadeTest { + + @Rule + @JvmField + val folderRule = TemporaryFolder() + + @Test + fun canCreateCredentialsFileTemplateWithAppropriatePermissions() { + val baseFolder = folderRule.newFolder() + val file = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = file) + sut.createConfigFile() + + assertThat(file).exists().hasContent(DefaultConfigFilesFacade.TEMPLATE) + + assumeNoException { + assertPosixPermissions(file, "rw-------") + assertPosixPermissions(file.parent, "rwxr-xr-x") + } + } + + @Test + fun existingFolderPermissionsAreNotModified() { + val baseFolder = folderRule.newFolder() + baseFolder.setExecutable(true, false) + baseFolder.setWritable(true, false) + baseFolder.setReadable(true, false) + val file = Paths.get(baseFolder.absolutePath, "credentials") + + val sut = DefaultConfigFilesFacade(configPath = file) + sut.createConfigFile() + + assumeNoException { + assertPosixPermissions(file, "rw-------") + assertPosixPermissions(file.parent, "rwxrwxrwx") + } + } + + @Test + fun `readAllProfiles reads across both config and credentials`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [profile profileName1] + key1=config + key2=config + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + creds.writeText( + """ + [profileName2] + key1=credentials + key2=credentials + """.trimIndent() + ) + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + + assertThat(sut.readAllProfiles()) + .hasSize(2) + } + + @Test + fun `readAllProfiles prefers credentials over config`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [profile profileName] + key1=config + key2=config + key3=config + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + creds.writeText( + """ + [profileName] + key1=credentials + key2=credentials + """.trimIndent() + ) + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + + assertThat(sut.readAllProfiles()) + .hasSize(1) + .satisfiesKt { + val entry = it.entries.first() + assertThat(entry.key).isEqualTo("profileName") + assertThat(entry.value).isEqualTo( + Profile.builder() + .name("profileName") + .properties( + mapOf( + "key1" to "credentials", + "key2" to "credentials", + "key3" to "config" + ) + ) + .build() + ) + } + } + + @Test + fun `append profile to config`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + config.createParentDirectories() + config.writeText("# should not be deleted") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + + sut.appendProfileToConfig( + Profile.builder() + .name("profileName") + .properties( + mapOf( + "key1" to "value1", + "key2" to "value2" + ) + ) + .build() + ) + + assertThat(config).hasContent( + """ + # should not be deleted + [profile profileName] + key1=value1 + key2=value2 + """.trimIndent() + ) + assertThat(creds).doesNotExist() + } + + @Test + fun `append profile to credentials`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + creds.createParentDirectories() + creds.writeText("# should not be deleted") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + + sut.appendProfileToCredentials( + Profile.builder() + .name("profileName") + .properties( + mapOf( + "key1" to "value1", + "key2" to "value2" + ) + ) + .build() + ) + + assertThat(config).doesNotExist() + assertThat(creds).hasContent( + """ + # should not be deleted + [profileName] + key1=value1 + key2=value2 + """.trimIndent() + ) + } + + @Test + fun `append section to config`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText("# should not be deleted") + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + + sut.appendSectionToConfig( + "section", + Profile.builder() + .name("sectionName") + .properties( + mapOf( + "key1" to "value1", + "key2" to "value2" + ) + ) + .build() + ) + + assertThat(config).hasContent( + """ + # should not be deleted + [section sectionName] + key1=value1 + key2=value2 + """.trimIndent() + ) + assertThat(creds).doesNotExist() + } + + @Test + fun `update section in config -- single section in config`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session sectionName] + key1=value1 + key2=value2 + """.trimIndent() + ) + val sut = DefaultConfigFilesFacade(configPath = config) + + sut.updateSectionInConfig( + "sso-session", + Profile.builder() + .name("sectionName") + .properties( + mapOf( + "key1" to "newValue1", + "key3" to "value3", + "key4" to "value4" + ) + ) + .build() + ) + + assertThat(config).hasContent( + """ + [sso-session sectionName] + key1=newValue1 + key2=value2 + key3=value3 + key4=value4 + """.trimIndent() + ) + } + + @Test + fun `update section in config -- multiple sections in config`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [profile preceding] + a=b + [sso-session sectionName] + key1=value1 + key2=value2[ + [somethinginvalid + [profile profile] + key=value + """.trimIndent() + ) + val sut = DefaultConfigFilesFacade(configPath = config) + + sut.updateSectionInConfig( + "sso-session", + Profile.builder() + .name("sectionName") + .properties( + mapOf( + "key1" to "newValue1", + "key2" to "value2[", + "key3" to "value3", + "key4" to "value4" + ) + ) + .build() + ) + + assertThat(config).hasContent( + """ + [profile preceding] + a=b + [sso-session sectionName] + key1=newValue1 + key2=value2[ + key3=value3 + key4=value4 + [somethinginvalid + [profile profile] + key=value + """.trimIndent() + ) + } + + @Test + fun `delete session from config on sign out - only sso-session`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session2] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [sso-session session3] + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + sut.deleteSsoConnectionFromConfig("session2") + assertThat(config).hasContent( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + } + + @Test + fun `delete session from config on sign out`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session2] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session2-123-admin] + sso_session=session2 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + sut.deleteSsoConnectionFromConfig("session2") + assertThat(config).hasContent( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + } + + @Test + fun `delete session from config on sign out - profile name is different from session name`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session2] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile othername-with-same-session] + sso_session=session2 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + sut.deleteSsoConnectionFromConfig("session2") + assertThat(config).hasContent( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + } + + @Test + fun `delete session from config on sign out - multiple profiles with same prefix`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session2] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session2-123-admin] + aws_access_key=abjcbd + aws_secret_access_key=123 + [profile session2-123-admin] + sso_session=session2 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + sut.deleteSsoConnectionFromConfig("session2") + assertThat(config).hasContent( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [profile session2-123-admin] + aws_access_key=abjcbd + aws_secret_access_key=123 + [sso-session session3] + """.trimIndent() + ) + } + + @Test + fun `delete session from config on sign out - multiple profiles in the same session`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [sso-session session2] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session2-123-admin] + aws_access_key=abjcbd + aws_secret_access_key=123 + [profile session2-123-admin] + sso_session=session2 + sso_account_id=123 + sso_role_name= admin + [profile session2-345-admin] + sso_session=session2 + sso_account_id=345 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + sut.deleteSsoConnectionFromConfig("session2") + assertThat(config).hasContent( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [profile session2-123-admin] + aws_access_key=abjcbd + aws_secret_access_key=123 + [sso-session session3] + """.trimIndent() + ) + } + + @Test + fun `delete session from config on sign out - multiple profiles in the same session with profile before sso-session`() { + val baseFolder = folderRule.newFolder() + val config = Paths.get(baseFolder.absolutePath, ".aws", "config") + config.createParentDirectories() + config.writeText( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [profile session2-345-admin] + sso_session=session2 + sso_account_id=345 + sso_role_name= admin + [sso-session session2] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session2-123-admin] + aws_access_key=abjcbd + aws_secret_access_key=123 + [profile session2-123-admin] + sso_session=session2 + sso_account_id=123 + sso_role_name= admin + [sso-session session3] + """.trimIndent() + ) + val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") + val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) + sut.deleteSsoConnectionFromConfig("session2") + assertThat(config).hasContent( + """ + [sso-session session1] + sso_start_url=https://start + sso_region=us-west-2 + sso_registration_scopes=scope1, scope2 + [profile session1-123-admin] + sso_session=session1 + sso_account_id=123 + sso_role_name= admin + [profile session2-123-admin] + aws_access_key=abjcbd + aws_secret_access_key=123 + [sso-session session3] + """.trimIndent() + ) + } + + private inline fun assumeNoException(block: () -> Unit) { + try { + block() + } catch (e: Exception) { + if (e is T) { + Assume.assumeNoException(e) + } else { + throw e + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt new file mode 100644 index 00000000000..68b794c8b81 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt @@ -0,0 +1,145 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.replaceService +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager +import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.isInstanceOf + +class DefaultToolkitConnectionManagerTest { + @JvmField + @Rule + val projectRule = ProjectRule() + + @JvmField + @Rule + val credManager = MockCredentialManagerRule() + + @JvmField + @Rule + val authManager = MockToolkitAuthManagerRule() + + @JvmField + @Rule + val disposableRule = DisposableRule() + + @JvmField + @Rule + val mockClientManager = MockClientManagerRule() + + private lateinit var sut: DefaultToolkitConnectionManager + + @Before + fun setUp() { + mockClientManager.create() + sut = DefaultToolkitConnectionManager(projectRule.project) + } + + @Test + fun `active connection is null if no connection or credentials`() { + credManager.clear() + assertThat(sut.activeConnection()).isNull() + } + + @Test + fun `active connection defaults to credentials`() { + assertThat(sut.activeConnection()).isInstanceOf() + } + + @Test + fun `loads connection from state`() { + credManager.clear() + assertThat(sut.activeConnection()).isEqualTo(null) + + val connection = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf(aString()))) + sut.loadState(ToolkitConnectionManagerState(connection.id)) + + assertThat(sut.activeConnection()).isEqualTo(connection) + } + + @Test + fun `loads a us-east-1 connection from state that does not contain the region string`() { + credManager.clear() + assertThat(sut.activeConnection()).isEqualTo(null) + + val connection = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf(aString()))) + sut.loadState(ToolkitConnectionManagerState("sso;https://view.awsapps.com/start")) + + assertThat(sut.activeConnection()).isEqualTo(connection) + } + + @Test + fun `loads null connection from state which has an invalid format`() { + credManager.clear() + assertThat(sut.activeConnection()).isEqualTo(null) + + sut.loadState(ToolkitConnectionManagerState("An invalid active connection id")) + + assertThat(sut.activeConnection()).isEqualTo(null) + } + + @Test + fun `switch connection to null will fall back to IAM credential if applicable`() { + val bearerConnection = LegacyManagedBearerSsoConnection(aString(), "us-east-1", listOf(aString())) + configureSut(sut, bearerConnection) + + sut.switchConnection(null) + + assertThat(sut.activeConnection()).isInstanceOf() + } + + @Test + fun `switch connection to null will fall back to the first SSO connection in the list if IAM credential is not available`() { + credManager.clear() + val conneciton1 = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf(aString()))) + authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf(aString()))) + authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf(aString()))) + configureSut(sut, conneciton1) + + sut.switchConnection(null) + + assertThat(sut.activeConnection()) + .isEqualTo(ToolkitAuthManager.getInstance().listConnections()[0]) + .isInstanceOf() + } + + @Test + fun `activeConnectionForFeature falls back to default if not pinned`() { + credManager.clear() + configureSut(sut, null) + val pinningMock = mock() + val feature = mock { + on { it.supportsConnectionType(any()) } doReturn true + on { it.featureId } doReturn "test" + } + + ApplicationManager.getApplication().replaceService(ConnectionPinningManager::class.java, pinningMock, disposableRule.disposable) + assertThat(sut.activeConnectionForFeature(feature)).isNull() + + val connection = authManager.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf(aString()))) + assertThat(sut.activeConnectionForFeature(feature)).isEqualTo(connection) + } + + private fun configureSut(sut: ToolkitConnectionManager, conneciton1: ToolkitConnection?) { + val clazz = sut::class.java + clazz.getDeclaredField("connection").apply { + this.trySetAccessible() + this.set(sut, conneciton1) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt new file mode 100644 index 00000000000..4ceb247cf93 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt @@ -0,0 +1,89 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.TestActionEvent +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import software.amazon.q.jetbrains.core.MockResourceCacheRule +import software.amazon.q.jetbrains.core.dummyResource +import software.aws.toolkits.core.credentials.aToolkitCredentialsProvider +import software.aws.toolkits.core.region.anAwsRegion +import software.aws.toolkits.core.utils.test.aString +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class RefreshConnectionActionTest { + @Rule + @JvmField + val projectRule = ProjectRule() + + @JvmField + @Rule + val resourceCache = MockResourceCacheRule() + + private val sut = RefreshConnectionAction() + + @Test + fun refreshActionClearsCacheAndUpdatesConnectionState() { + resourceCache.addEntry(projectRule.project, dummyResource(), aString()) + + val latch = CountDownLatch(1) + val states = ConcurrentHashMap.newKeySet() + projectRule.project.messageBus.connect() + .subscribe( + AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED, + object : ConnectionSettingsStateChangeNotifier { + override fun settingsStateChanged(newState: ConnectionState) { + states.add(newState) + latch.countDown() + } + } + ) + + sut.actionPerformed(testAction()) + + assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue + + assertThat(resourceCache.size()).isZero() + assertThat(states).hasAtLeastOneElementOfType(ConnectionState.ValidatingConnection::class.java) + } + + @Test + fun actionIsEnabledIfConnectionStateIsInvalid() { + checkExpectedActionState(ConnectionState.InvalidConnection(RuntimeException("Boom")), shouldBeEnabled = true) + } + + @Test + fun actionIsEnabledIfConnectionStateIsValid() { + checkExpectedActionState(ConnectionState.ValidConnection(aToolkitCredentialsProvider(), anAwsRegion()), shouldBeEnabled = true) + } + + @Test + fun actionIsDisabledIfConnectionStateIsIncomplete() { + checkExpectedActionState(ConnectionState.IncompleteConfiguration(null, null), shouldBeEnabled = false) + } + + @Test + fun actionIsDisabledIfConnectionStateIsNotTerminal() { + checkExpectedActionState(ConnectionState.InitializingToolkit, shouldBeEnabled = false) + } + + private fun checkExpectedActionState(connectionState: ConnectionState, shouldBeEnabled: Boolean) { + val mockProjectAccountSettingsManager = MockAwsConnectionManager.getInstance(projectRule.project) + + mockProjectAccountSettingsManager.setConnectionState(connectionState) + + val actionEvent = testAction() + sut.update(actionEvent) + + assertThat(actionEvent.presentation.isEnabled).isEqualTo(shouldBeEnabled) + } + + private fun testAction() = TestActionEvent(DataContext { projectRule.project }) +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerTest.kt new file mode 100644 index 00000000000..db6c680d35c --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerTest.kt @@ -0,0 +1,201 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.pinning + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.replaceService +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.AwsCredentialConnection +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitConnection + +class ConnectionPinningManagerTest { + + @Rule + @JvmField + val disposableRule = DisposableRule() + + @Rule + @JvmField + val applicationRule = ApplicationRule() + + private lateinit var sut: DefaultConnectionPinningManager + + @Before + fun setUp() { + sut = spy(DefaultConnectionPinningManager()) + } + + @Test + fun `switching connection to unsupported feature always pins initial connection`() { + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = connection.id == "oldConn" + } + + val oldConnection = mock { + on { id } doReturn "oldConn" + } + + val newConnection = mock { + on { id } doReturn "newConn" + } + + val mockAuthManager = mock { + on { it.getConnection("oldConn") }.thenReturn(oldConnection) + on { it.getConnection("newConn") }.thenReturn(newConnection) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockAuthManager, disposableRule.disposable) + + sut.pinFeatures(oldConnection, newConnection, listOf(feature)) + + assertThat(sut.getPinnedConnection(feature)).isEqualTo(oldConnection) + } + + @Test + fun `add new supported connection will pin the feature`() { + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = true + } + + val newConnection = mock { + on { id } doReturn "connId" + } + + val mockAuthManager = mock { + on { it.getConnection("connId") }.thenReturn(newConnection) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockAuthManager, disposableRule.disposable) + + sut.pinFeatures(null, newConnection, listOf(feature)) + + assertThat(sut.getPinnedConnection(feature)).isEqualTo(newConnection) + } + + @Test + fun `pins to old if new connection does not support feature`() { + val oldConnection = mock { + on { id } doReturn "oldConn" + } + + val newConnection = mock { + on { id } doReturn "newConn" + } + + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = connection.id == "oldConn" + } + + val mockAuthManager = mock { + on { it.getConnection("oldConn") }.thenReturn(oldConnection) + on { it.getConnection("newConn") }.thenReturn(newConnection) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockAuthManager, disposableRule.disposable) + + sut.pinFeatures(oldConnection, newConnection, listOf(feature)) + + assertThat(sut.getPinnedConnection(feature)).isEqualTo(oldConnection) + } + + @Test + fun `switching connection from unsupported feature pins connection to new connection`() { + val oldConnectionId = "connId" + + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = + connection is AwsBearerTokenConnection + } + + val oldConnection = mock { + on { id } doReturn oldConnectionId + } + + val newConnection = mock { + on { id } doReturn "newId" + } + val mockAuthManager = mock { + on { it.getConnection(oldConnectionId) }.thenReturn(oldConnection) + on { it.getConnection("newId") }.thenReturn(newConnection) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockAuthManager, disposableRule.disposable) + + sut.pinFeatures(oldConnection, newConnection, listOf(feature)) + + assertThat(sut.getPinnedConnection(feature)).isEqualTo(newConnection) + } + + @Test + fun `pinned connection returns null if connection no longer exists in auth manager`() { + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = true + } + val connection = mock { + on { id } doReturn "connId" + } + + sut.setPinnedConnection(feature, connection) + + assertThat(sut.getPinnedConnection(feature)).isNull() + } + + @Test + fun `pinned connection returns null if connection is not valid for feature`() { + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = false + } + val connection = mock { + on { id } doReturn "connId" + } + val mockAuthManager = mock { + whenever(it.getConnection(any())).thenReturn(connection) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockAuthManager, disposableRule.disposable) + + sut.setPinnedConnection(feature, connection) + + assertThat(sut.getPinnedConnection(feature)).isNull() + } + + @Test + fun `respects pinned feature`() { + val feature = object : FeatureWithPinnedConnection { + override val featureId = "mockId" + override val featureName = "mockFeature" + override fun supportsConnectionType(connection: ToolkitConnection) = true + } + val connection = mock { + on { id } doReturn "connId" + } + val mockAuthManager = mock { + whenever(it.getConnection(any())).thenReturn(connection) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockAuthManager, disposableRule.disposable) + + sut.setPinnedConnection(feature, connection) + + assertThat(sut.getPinnedConnection(feature)).isEqualTo(connection) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt new file mode 100644 index 00000000000..0eb4a9bea05 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt @@ -0,0 +1,102 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Rule +import org.junit.Test +import software.amazon.q.jetbrains.core.credentials.profiles.Ec2MetadataConfigProvider.getEc2MedataEndpoint +import software.aws.toolkits.core.rules.EnvironmentVariableHelper +import software.aws.toolkits.core.rules.SystemPropertyHelper +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.isInstanceOf + +class Ec2MetadataConfigProviderTest { + @Rule + @JvmField + val sysProps = SystemPropertyHelper() + + @Rule + @JvmField + val envVars = EnvironmentVariableHelper() + + @Test + fun `endpoint can be overridden with system property`() { + val endpoint = aString() + System.setProperty("aws.ec2MetadataServiceEndpoint", endpoint) + + assertThat(profile().getEc2MedataEndpoint()).isEqualTo(endpoint) + } + + @Test + fun `endpoint can be overridden with env var`() { + val endpoint = aString() + envVars["AWS_EC2_METADATA_SERVICE_ENDPOINT"] = endpoint + + assertThat(profile().getEc2MedataEndpoint()).isEqualTo(endpoint) + } + + @Test + fun `endpoint can be overridden with profile`() { + val endpoint = aString() + val profile = profile { + put("ec2_metadata_service_endpoint", endpoint) + } + + assertThat(profile.getEc2MedataEndpoint()).isEqualTo(endpoint) + } + + @Test + fun `endpoint defaults to ipv4 endpoint if nothing is specified`() { + assertThat(profile().getEc2MedataEndpoint()).isEqualTo("http://169.254.169.254") + } + + @Test + fun `endpoint defaults to default endpoint if ipv6 is specified`() { + val profile = profile { + put("ec2_metadata_service_endpoint_mode", "ipv6") + } + + assertThat(profile.getEc2MedataEndpoint()).isEqualTo("http://[fd00:ec2::254]") + } + + @Test + fun `mode is case insensitive`() { + val profile = profile { + put("ec2_metadata_service_endpoint_mode", "ipv6") + } + + assertThat(profile.getEc2MedataEndpoint()).isEqualTo("http://[fd00:ec2::254]") + + val profile2 = profile { + put("ec2_metadata_service_endpoint_mode", "iPv6") + } + + assertThat(profile2.getEc2MedataEndpoint()).isEqualTo("http://[fd00:ec2::254]") + } + + @Test + fun `mode can be overridden with system property`() { + System.setProperty("aws.ec2MetadataServiceEndpointMode", "ipv6") + + assertThat(profile().getEc2MedataEndpoint()).isEqualTo("http://[fd00:ec2::254]") + } + + @Test + fun `mode can be overridden with env var`() { + envVars["AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"] = "ipv6" + + assertThat(profile().getEc2MedataEndpoint()).isEqualTo("http://[fd00:ec2::254]") + } + + @Test + fun `invalid mode fails`() { + val profile = profile { + put("ec2_metadata_service_endpoint_mode", "badMode") + } + + assertThatThrownBy { profile.getEc2MedataEndpoint() }.isInstanceOf() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt new file mode 100644 index 00000000000..8db74777281 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt @@ -0,0 +1,192 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.openapi.ui.TestDialogManager +import com.intellij.openapi.ui.TestInputDialog +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.RuleChain +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.profiles.ProfileProperty +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.awssdk.services.sts.model.AssumeRoleRequest +import software.amazon.awssdk.services.sts.model.AssumeRoleResponse +import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.aws.toolkits.core.region.anAwsRegion +import software.aws.toolkits.core.utils.test.aString +import java.time.Duration +import java.time.Instant + +class ProfileAssumeRoleProviderTest { + private val application = ApplicationRule() + private val clientManager = MockClientManagerRule() + + @Rule + @JvmField + val ruleChain = RuleChain(application, clientManager) + + private val mfaToken = "SomeToken" + private lateinit var parentProvider: AwsCredentialsProvider + private lateinit var stsClient: StsClient + + @Before + fun setup() { + parentProvider = mock(extraInterfaces = arrayOf(SdkAutoCloseable::class)) + + stsClient = clientManager.create().stub { + on { assumeRole(any()) } doReturn AssumeRoleResponse.builder() + .credentials { c -> + c.accessKeyId(aString()) + c.secretAccessKey(aString()) + c.sessionToken(aString()) + c.expiration(Instant.now().plus(Duration.ofHours(1))) + }.build() + } + + TestDialogManager.setTestInputDialog { mfaToken } + } + + @After + fun tearDown() { + TestDialogManager.setTestInputDialog(TestInputDialog.DEFAULT) + } + + @Test + fun `role_arn gets passed`() { + val role = aString() + val profile = profile { + put(ProfileProperty.ROLE_ARN, role) + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.roleArn()).isEqualTo(role) + } + } + + @Test + fun `duration_seconds gets respected if provided`() { + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + put(ProfileProperty.DURATION_SECONDS, "12345") + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.durationSeconds()).isEqualTo(12345) + } + } + + @Test + fun `duration_seconds uses default if not provided`() { + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + put(ProfileProperty.DURATION_SECONDS, "abc") + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.durationSeconds()).isEqualTo(3600) + } + } + + @Test + fun `duration_seconds uses default if invalid format`() { + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.durationSeconds()).isEqualTo(3600) + } + } + + @Test + fun `MFA is prompted if keys are specified`() { + val mfaSerial = aString() + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + put(ProfileProperty.MFA_SERIAL, mfaSerial) + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.tokenCode()).isEqualTo(mfaToken) + } + } + + @Test + fun `external ID is respected if provided`() { + val id = aString() + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + put(ProfileProperty.EXTERNAL_ID, id) + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.externalId()).isEqualTo(id) + } + } + + @Test + fun `role session name is respected if provided`() { + val name = aString() + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + put(ProfileProperty.ROLE_SESSION_NAME, name) + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).resolveCredentials() + + argumentCaptor { + verify(stsClient).assumeRole(capture()) + + assertThat(firstValue.roleSessionName()).isEqualTo(name) + } + } + + @Test + fun `calling close shuts down parent provider and client`() { + val profile = profile { + put(ProfileProperty.ROLE_ARN, aString()) + } + + ProfileAssumeRoleProvider(parentProvider, anAwsRegion(), profile).close() + + verify(stsClient).close() + verify(parentProvider as SdkAutoCloseable).close() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt new file mode 100644 index 00000000000..a6c67bf3bef --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt @@ -0,0 +1,1202 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.ui.TestDialogManager +import com.intellij.openapi.ui.TestInputDialog +import com.intellij.openapi.util.io.FileUtil +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.RuleChain +import com.intellij.testFramework.replaceService +import org.assertj.core.api.Assertions.STRING +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Condition +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.assertThrows +import org.junit.rules.TemporaryFolder +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.stub +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider +import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkToken +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.sso.model.GetRoleCredentialsRequest +import software.amazon.awssdk.services.sso.model.GetRoleCredentialsResponse +import software.amazon.awssdk.services.sso.model.RoleCredentials +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection +import software.amazon.q.jetbrains.core.credentials.InteractiveCredential +import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager +import software.amazon.q.jetbrains.core.credentials.ToolkitCredentialProcessProvider +import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.DeviceGrantAccessTokenCacheKey +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAccessTokenCacheKey +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.SsoCache +import software.amazon.q.jetbrains.core.region.getDefaultRegion +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialsChangeEvent +import software.amazon.q.core.credentials.CredentialsChangeListener +import software.amazon.q.core.credentials.SsoSessionIdentifier +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.aws.toolkits.core.rules.SystemPropertyHelper +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.isInstanceOf +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule +import java.io.File +import java.time.Instant +import java.util.Optional +import java.util.function.Function + +class ProfileCredentialProviderFactoryTest { + private val temporaryFolder = TemporaryFolder() + private val systemPropertyHelper = SystemPropertyHelper() + private val disposableRule = DisposableRule() + private val notificationListener = NotificationListenerRule(disposableRule.disposable) + private val clientManager = MockClientManagerRule() + + @Rule + @JvmField + val ruleChain = RuleChain( + temporaryFolder, + ApplicationRule(), + systemPropertyHelper, + notificationListener, + disposableRule, + clientManager + ) + + private lateinit var profileFile: File + + private val mockProfileWatcher = MockProfileWatcher() + private val profileLoadCallback = mock() + private val credentialChangeEvent = argumentCaptor() + + @Before + fun setUp() { + reset(profileLoadCallback) + + val awsFolder = temporaryFolder.newFolder(".aws") + profileFile = File(awsFolder, "config") + + System.getProperties().setProperty("aws.configFile", profileFile.absolutePath) + System.getProperties().setProperty("aws.sharedCredentialsFile", File(awsFolder, "credentials").absolutePath) + + profileLoadCallback.stub { + on { profileLoadCallback.invoke(credentialChangeEvent.capture()) }.thenReturn(Unit) + } + + ApplicationManager.getApplication() + .replaceService(ProfileWatcher::class.java, mockProfileWatcher, disposableRule.disposable) + + TestDialogManager.setTestInputDialog { MFA_TOKEN } + } + + @After + fun tearDown() { + TestDialogManager.setTestInputDialog(TestInputDialog.DEFAULT) + mockProfileWatcher.reset() + } + + @Test + fun `empty profiles report nothing found`() { + createProviderFactory() + + argumentCaptor { + verify(profileLoadCallback).invoke(capture()) + + assertThat(firstValue.added).isEmpty() + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + } + } + + @Test + fun `loading supported profiles are reported as added`() { + writeProfileFile( + """ + [profile noRegion] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + + [profile regionalized] + aws_access_key_id=RegionAccessKey + aws_secret_access_key=RegionSecretKey + region=us-west-2 + """.trimIndent() + ) + + createProviderFactory() + + argumentCaptor { + verify(profileLoadCallback).invoke(capture()) + + assertThat(firstValue.added).hasSize(2) + .has(profileName("noRegion")) + .has(profileName("regionalized", defaultRegion = "us-west-2")) + + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + } + } + + @Test + fun `failing to load a profile shows a notification`() { + writeProfileFile( + """ + [profile bar] + aws_access_key_id + """.trimIndent() + ) + + createProviderFactory() + + assertThat(notificationListener.notifications) + .extracting(Function { t -> t.content }) + .singleElement(STRING) + .contains("Expected an '=' sign defining a property on line 2") + } + + @Test + fun `an error does not prevent loading of other profiles and is reported`() { + writeProfileFile( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + external_id=externalId + source_profile=doNotExist + + [profile another_profile] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + """.trimIndent() + ) + + createProviderFactory() + + argumentCaptor { + verify(profileLoadCallback).invoke(capture()) + + assertThat(firstValue.added).hasSize(1).has(profileName("another_profile")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + } + + assertThat(notificationListener.notifications) + .extracting(Function { t -> t.content }) + .singleElement(STRING) + .contains("2 profiles found. Failed to load 1 profile.") + } + + @Test + fun `modifying a profile gets reported as a modification`() { + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey2 + aws_secret_access_key=FooSecretKey2 + aws_session_token=FooSessionToken2 + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(1).has(profileName("foo")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).hasSize(1).has(profileName("foo")) + assertThat(secondValue.removed).isEmpty() + } + } + + @Test + fun `deleting a profile gets reported as a deletion`() { + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile("") + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(1).has(profileName("foo")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).isEmpty() + assertThat(secondValue.removed).hasSize(1).has(profileName("foo")) + } + } + + @Test + fun `adding a profile gets reported as an addition`() { + writeProfileFile("") + + createProviderFactory() + + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).isEmpty() + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).hasSize(1).has(profileName("foo")) + assertThat(secondValue.modified).isEmpty() + assertThat(secondValue.removed).isEmpty() + } + } + + @Test + fun `modifying a sso-session gets reported as a modification`() { + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example.com + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example2.com + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.ssoAdded).hasSize(1).has(ssoSessionName("foo")) + assertThat(firstValue.ssoModified).isEmpty() + assertThat(firstValue.ssoRemoved).isEmpty() + + assertThat(secondValue.ssoAdded).isEmpty() + assertThat(secondValue.ssoModified).hasSize(1).has(ssoSessionName("foo")) + assertThat(secondValue.ssoRemoved).isEmpty() + } + } + + @Test + fun `deleting a sso-session gets reported as a deletion`() { + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example.com + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile("") + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.ssoAdded).hasSize(1).has(ssoSessionName("foo")) + assertThat(firstValue.ssoModified).isEmpty() + assertThat(firstValue.ssoRemoved).isEmpty() + + assertThat(secondValue.ssoAdded).isEmpty() + assertThat(secondValue.ssoModified).isEmpty() + assertThat(secondValue.ssoRemoved).hasSize(1).has(ssoSessionName("foo")) + } + } + + @Test + fun `adding a sso-session gets reported as an addition`() { + writeProfileFile("") + + createProviderFactory() + + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example.com + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.ssoAdded).isEmpty() + assertThat(firstValue.ssoModified).isEmpty() + assertThat(firstValue.ssoRemoved).isEmpty() + + assertThat(secondValue.ssoAdded).hasSize(1).has(ssoSessionName("foo")) + assertThat(secondValue.ssoModified).isEmpty() + assertThat(secondValue.ssoRemoved).isEmpty() + } + } + + @Test + fun `modifying a parent credential provider reports modification for it and its children`() { + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + + [profile bar] + source_profile=foo + role_arn=SomeArn + + [profile baz] + source_profile=bar + role_arn=SomeArn + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + + [profile bar] + source_profile=foo + role_arn=DifferentArn + + [profile baz] + source_profile=bar + role_arn=SomeArn + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(3).has(profileName("foo")).has(profileName("bar")) + .has(profileName("baz")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).hasSize(2).has(profileName("bar")).has(profileName("baz")) + assertThat(secondValue.removed).isEmpty() + } + } + + @Test + fun `removing a parent credential provider reports removal for it and its children`() { + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + + [profile bar] + source_profile=foo + role_arn=SomeArn + + [profile baz] + source_profile=bar + role_arn=SomeArn + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + + [profile baz] + source_profile=bar + role_arn=SomeArn + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(3).has(profileName("foo")).has(profileName("bar")) + .has(profileName("baz")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).isEmpty() + assertThat(secondValue.removed).hasSize(2).has(profileName("bar")).has(profileName("baz")) + } + } + + @Test + fun `modifying a sso-session reports modification for it and its children`() { + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example.com + + [profile bar] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = bar + + [profile baz] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = baz + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example2.com + + [profile bar] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = bar + + [profile baz] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = baz + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(2).has(profileName("bar")).has(profileName("baz")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + assertThat(firstValue.ssoAdded).hasSize(1).has(ssoSessionName("foo")) + assertThat(firstValue.ssoModified).isEmpty() + assertThat(firstValue.ssoRemoved).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).hasSize(2).has(profileName("bar")).has(profileName("baz")) + assertThat(secondValue.removed).isEmpty() + assertThat(secondValue.ssoAdded).isEmpty() + assertThat(secondValue.ssoModified).hasSize(1).has(ssoSessionName("foo")) + assertThat(secondValue.ssoRemoved).isEmpty() + } + } + + @Test + fun `removing a sso-session reports removal for it and its children`() { + writeProfileFile( + """ + [sso-session foo] + sso_region = us-east-1 + sso_start_url = https://example.com + + [profile bar] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = bar + + [profile baz] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = baz + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [profile bar] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = bar + + [profile baz] + sso_session = foo + sso_account_id = 123456789011 + sso_role_name = baz + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(2).has(profileName("bar")).has(profileName("baz")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + assertThat(firstValue.ssoAdded).hasSize(1).has(ssoSessionName("foo")) + assertThat(firstValue.ssoModified).isEmpty() + assertThat(firstValue.ssoRemoved).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).isEmpty() + assertThat(secondValue.removed).hasSize(2).has(profileName("bar")).has(profileName("baz")) + assertThat(secondValue.ssoAdded).isEmpty() + assertThat(secondValue.ssoModified).isEmpty() + assertThat(secondValue.ssoRemoved).hasSize(1).has(ssoSessionName("foo")) + } + } + + @Test + fun `only modified profiles are reported`() { + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + + [profile bar] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + """.trimIndent() + ) + + createProviderFactory() + + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey2 + aws_secret_access_key=FooSecretKey2 + + [profile bar] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + """.trimIndent() + ) + + mockProfileWatcher.triggerListeners() + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(2).has(profileName("foo")).has(profileName("bar")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).hasSize(1).has(profileName("foo")) + assertThat(secondValue.removed).isEmpty() + } + } + + @Test + fun `a deleted profile throws error when trying to be retrieved`() { + writeProfileFile( + """ + [profile foo] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + """.trimIndent() + ) + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("foo") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider.resolveCredentials()).isInstanceOfSatisfying(AwsSessionCredentials::class.java) { + assertThat(it.accessKeyId()).isEqualTo("FooAccessKey") + assertThat(it.secretAccessKey()).isEqualTo("FooSecretKey") + assertThat(it.sessionToken()).isEqualTo("FooSessionToken") + } + + FileUtil.delete(profileFile) + + mockProfileWatcher.triggerListeners() + + assertThatThrownBy { + providerFactory.createProvider(validProfile) + }.isInstanceOf(IllegalStateException::class.java) + + argumentCaptor { + verify(profileLoadCallback, times(2)).invoke(capture()) + + assertThat(firstValue.added).hasSize(1).has(profileName("foo")) + assertThat(firstValue.modified).isEmpty() + assertThat(firstValue.removed).isEmpty() + + assertThat(secondValue.added).isEmpty() + assertThat(secondValue.modified).isEmpty() + assertThat(secondValue.removed).hasSize(1).has(profileName("foo")) + } + } + + @Test + fun `static credentials profile creates a provider`() { + writeProfileFile( + """ + [profile static] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + """.trimIndent() + ) + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("static") + val credentialsProvider = providerFactory.createProvider(validProfile).resolveCredentials() + + assertThat(credentialsProvider).isInstanceOfSatisfying { + assertThat(it.accessKeyId()).isEqualTo("BarAccessKey") + assertThat(it.secretAccessKey()).isEqualTo("BarSecretKey") + } + } + + @Test + fun `static session credential profile creates a provider`() { + writeProfileFile( + """ + [profile staticSession] + aws_access_key_id=FooAccessKey + aws_secret_access_key=FooSecretKey + aws_session_token=FooSessionToken + """.trimIndent() + ) + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("staticSession") + val credentialsProvider = providerFactory.createProvider(validProfile).resolveCredentials() + + assertThat(credentialsProvider).isInstanceOfSatisfying { + assertThat(it.accessKeyId()).isEqualTo("FooAccessKey") + assertThat(it.secretAccessKey()).isEqualTo("FooSecretKey") + assertThat(it.sessionToken()).isEqualTo("FooSessionToken") + } + } + + @Test + fun `assume role profile with a source_profile creates a provider`() { + writeProfileFile( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + external_id=externalId + source_profile=source_profile + + [profile source_profile] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + """.trimIndent() + ) + + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("role") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider).isInstanceOfSatisfying { + assertThat(it.parentProvider).isInstanceOf() + } + } + + @Test + fun `assume role profile with a credential_source of ec2 creates a provider`() { + writeProfileFile( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + credential_source=Ec2InstanceMetadata + """.trimIndent() + ) + + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("role") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider).isInstanceOfSatisfying { + assertThat(it.parentProvider).isInstanceOf() + } + } + + @Test + fun `assume role profile with a credential_source of ecs creates a provider`() { + writeProfileFile( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + credential_source=EcsContainer + """.trimIndent() + ) + + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("role") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider).isInstanceOfSatisfying { + assertThat(it.parentProvider).isInstanceOf() + } + } + + @Test + fun `assume role profile with a credential_source of env vars creates a provider`() { + writeProfileFile( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + credential_source=Environment + """.trimIndent() + ) + + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("role") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider).isInstanceOfSatisfying { + assertThat(it.parentProvider).isInstanceOf() + } + } + + @Test + fun `sso profile creates a provider`() { + writeProfileFile( + """ + [profile sso] + sso_start_url=ValidUrl + sso_region=us-east-2 + sso_account_id=111222333444 + sso_role_name=RoleName + """.trimIndent() + ) + + clientManager.create() + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("sso") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider).isInstanceOf() + } + + @Test + fun `credential process profile creates a provider`() { + writeProfileFile( + """ + [profile credProcess] + credential_process=echo + """.trimIndent() + ) + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("credProcess") + val credentialsProvider = providerFactory.createProvider(validProfile) + + assertThat(credentialsProvider).isInstanceOf() + } + + @Test + fun `MFA profiles always require user action`() { + writeProfileFile( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + external_id=externalId + mfa_serial=someSerialArn + source_profile=source_profile + + [profile source_profile] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + """.trimIndent() + ) + + createProviderFactory() + + assertThat((findCredentialIdentifier("role") as InteractiveCredential).userActionRequired()).isTrue + } + + @Test + fun `sso profiles only need user action if the token is invalid`() { + writeProfileFile( + """ + [profile valid] + sso_start_url=ValidUrl + sso_region=us-east-2 + sso_account_id=111222333444 + sso_role_name=RoleName + + [profile expired] + sso_start_url=ExpiredUrl + sso_region=us-east-2 + sso_account_id=111222333444 + sso_role_name=RoleName + + [profile validChain] + source_profile = valid + role_arn = AssumedRoleArn + """.trimIndent() + ) + + val ssoCache = mock { + on { loadAccessToken(any()) }.thenAnswer { invocation -> + val arg = invocation.arguments[0] + when (arg) { + is DeviceGrantAccessTokenCacheKey -> { + when (arg.startUrl) { + "ValidUrl" -> mock() + else -> null + } + } + is PKCEAccessTokenCacheKey -> { + when (arg.issuerUrl) { + "ValidUrl" -> mock() + else -> null + } + } + else -> null + } + } + } + + createProviderFactory(ssoCache) + + assertThat((findCredentialIdentifier("valid") as InteractiveCredential).userActionRequired()).isFalse + assertThat((findCredentialIdentifier("expired") as InteractiveCredential).userActionRequired()).isTrue + assertThat((findCredentialIdentifier("validChain") as InteractiveCredential).userActionRequired()).isFalse + } + + private fun writeProfileFile(content: String) { + FileUtil.createIfDoesntExist(profileFile) + FileUtil.writeToFile(profileFile, content) + } + + private fun profileName( + expectedProfileName: String, + defaultRegion: String? = null, + ): Condition> = + object : Condition>(expectedProfileName) { + override fun matches(value: Iterable): Boolean = value.any { + it.id == "profile:$expectedProfileName" && defaultRegion?.let { dr -> it.defaultRegionId == dr } ?: true + } + } + + private fun ssoSessionName( + expectedProfileName: String, + ): Condition> = + object : Condition>(expectedProfileName) { + override fun matches(value: Iterable): Boolean = value.any { + it.id == "sso-session:$expectedProfileName" + } + } + + private fun createProviderFactory(ssoCache: SsoCache = mock()): ProfileCredentialProviderFactory { + val factory = ProfileCredentialProviderFactory(ssoCache) + factory.setUp(profileLoadCallback) + + return factory + } + + private fun findCredentialIdentifier(profileName: String) = credentialChangeEvent.allValues.flatMap { it.added }.first { it.id == "profile:$profileName" } + + private fun ProfileCredentialProviderFactory.createProvider(validProfile: CredentialIdentifier) = + this.createAwsCredentialProvider( + validProfile, + getDefaultRegion(), + ) + + private class MockProfileWatcher : ProfileWatcher { + private val listeners = mutableListOf<() -> Unit>() + + override fun addListener(listener: () -> Unit) { + listeners.add(listener) + } + + fun reset() { + listeners.clear() + } + + fun triggerListeners() { + listeners.forEach { it() } + } + } + + @Test + fun `sso-session profile creates a provider with the appropriate properties`() { + writeProfileFile( + """ + [profile sso] + sso_session = my-sso + sso_account_id=111222333444 + sso_role_name=RoleName + + [sso-session my-sso] + sso_region=us-east-2 + sso_start_url=ValidUrl + sso_registration_scopes = scope1,scope2,scope3 + """.trimIndent() + ) + + // mock out enough of the flow to validate the values we care about + val accessToken = aString() + val mockBearerProvider = mock { + whenever(it.resolveToken()).thenReturn(object : SdkToken { + override fun token() = accessToken + override fun expirationTime() = Optional.empty() + }) + } + val connectionSettingsMock = mock { + whenever(it.tokenProvider).thenReturn(mockBearerProvider) + } + val connectionMock = mock { + whenever(it.getConnectionSettings()).thenReturn(connectionSettingsMock) + } + val authManager = mock { + whenever(it.getOrCreateSsoConnection(any())).thenReturn(connectionMock) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, authManager, disposableRule.disposable) + val ssoClientMock = clientManager.create().also { + whenever(it.getRoleCredentials(any())).thenReturn( + GetRoleCredentialsResponse.builder() + .roleCredentials( + RoleCredentials.builder() + .accessKeyId("access") + .secretAccessKey("secret") + .sessionToken("token") + .expiration(Instant.MAX.epochSecond) + .build() + ) + .build() + ) + } + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("sso") + + val credentialsProvider = providerFactory.createProvider(validProfile) + assertThat(credentialsProvider).isInstanceOf() + + try { + credentialsProvider.resolveCredentials() + } catch (_: Exception) {} + + argumentCaptor { + verify(authManager).getOrCreateSsoConnection(capture()) + assertThat(firstValue.startUrl).isEqualTo("ValidUrl") + assertThat(firstValue.ssoRegion).isEqualTo("us-east-2") + assertThat(firstValue.scopes).containsExactly("scope1", "scope2", "scope3") + } + + argumentCaptor { + verify(ssoClientMock).getRoleCredentials(capture()) + assertThat(firstValue.accessToken()).isEqualTo(accessToken) + assertThat(firstValue.roleName()).isEqualTo("RoleName") + assertThat(firstValue.accountId()).isEqualTo("111222333444") + } + } + + @Test + fun `sso-session profile can get role credentials with no scope`() { + writeProfileFile( + """ + [profile sso] + sso_session = my-sso + sso_account_id=111222333444 + sso_role_name=RoleName + + [sso-session my-sso] + sso_region=us-east-2 + sso_start_url=ValidUrl + """.trimIndent() + ) + + // mock out enough of the flow to validate the values we care about + val accessToken = aString() + val mockBearerProvider = mock { + whenever(it.resolveToken()).thenReturn(object : SdkToken { + override fun token() = accessToken + override fun expirationTime() = Optional.empty() + }) + } + val connectionSettingsMock = mock { + whenever(it.tokenProvider).thenReturn(mockBearerProvider) + } + val connectionMock = mock { + whenever(it.getConnectionSettings()).thenReturn(connectionSettingsMock) + } + val authManager = mock { + whenever(it.getOrCreateSsoConnection(any())).thenReturn(connectionMock) + } + ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, authManager, disposableRule.disposable) + val ssoClientMock = clientManager.create().also { + whenever(it.getRoleCredentials(any())).thenReturn( + GetRoleCredentialsResponse.builder() + .roleCredentials( + RoleCredentials.builder() + .accessKeyId("access") + .secretAccessKey("secret") + .sessionToken("token") + .expiration(Instant.MAX.epochSecond) + .build() + ) + .build() + ) + } + clientManager.create() + + val providerFactory = createProviderFactory() + val validProfile = findCredentialIdentifier("sso") + + val credentialsProvider = providerFactory.createProvider(validProfile) + assertThat(credentialsProvider).isInstanceOf() + + try { + credentialsProvider.resolveCredentials() + } catch (_: Exception) {} + + argumentCaptor { + verify(authManager).getOrCreateSsoConnection(capture()) + assertThat(firstValue.startUrl).isEqualTo("ValidUrl") + assertThat(firstValue.ssoRegion).isEqualTo("us-east-2") + assertThat(firstValue.scopes).containsExactly(IDENTITY_CENTER_ROLE_ACCESS_SCOPE) + } + + argumentCaptor { + verify(ssoClientMock).getRoleCredentials(capture()) + assertThat(firstValue.accessToken()).isEqualTo(accessToken) + assertThat(firstValue.roleName()).isEqualTo("RoleName") + assertThat(firstValue.accountId()).isEqualTo("111222333444") + } + } + + @Test + fun `ignores profile referencing invalid sso-session profile`() { + writeProfileFile( + """ + [profile sso] + sso_session = my-sso + sso_account_id=111222333444 + sso_role_name=RoleName + + [sso-session my-sso] + sso_start_url=ValidUrl + sso_registration_scopes = sso:validAcc:validAccess,sso:validAcc + """.trimIndent() + ) + + clientManager.create() + clientManager.create() + + assertThrows { findCredentialIdentifier("sso") } + } + + @Test + fun `ignores profile referencing missing sso-session section`() { + writeProfileFile( + """ + [profile sso] + sso_session = my-sso + sso_account_id=111222333444 + sso_role_name=RoleName + """.trimIndent() + ) + + clientManager.create() + clientManager.create() + + assertThrows { findCredentialIdentifier("sso") } + } + + private companion object { + const val MFA_TOKEN = "MfaToken" + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt new file mode 100644 index 00000000000..d8dde94be49 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt @@ -0,0 +1,60 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.testFramework.ApplicationExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.mock +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.q.jetbrains.core.MockClientManagerExtension +import software.amazon.q.jetbrains.core.credentials.sso.DiskCache +import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException + +@ExtendWith(ApplicationExtension::class) +class ProfileCredentialsIdentifierSsoTest { + private val sut = ProfileCredentialsIdentifierSso("", "", "", null) + + @JvmField + @RegisterExtension + val mockClientManager = MockClientManagerExtension() + + @Test + fun `handles SsoOidcException`() { + val exception = SsoOidcException.builder().message("message").build() + + assertThat(sut.handleValidationException(exception)).isNotNull() + } + + @Test + fun `handles nested SsoOidcException`() { + val root = SsoOidcException.builder().message("message").build() + // Exception(Exception(Exception(...))) + val exception = (1..1000).fold(root as Exception) { acc, _ -> Exception(acc) } + + assertThat(sut.handleValidationException(exception)).isNotNull() + } + + @Test + fun `handles exception from uninitialized token provider`() { + val cache = mock() + mockClientManager.create() + + // IllegalStateException instead of more general base Exception so we know if the type changes + val exception = assertThrows { + InteractiveBearerTokenProvider("", "us-east-1", listOf("scopes"), cache = cache, id = "test").resolveToken() + } + assertThat(sut.handleValidationException(exception)).isNotNull() + } + + @Test + fun `ignores arbitrary exception`() { + assertThat(sut.handleValidationException(RuntimeException())).isNull() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt new file mode 100644 index 00000000000..0d153eed6d1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt @@ -0,0 +1,335 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.core.rules.SystemPropertyHelper +import java.io.File + +class ProfileReaderTest { + @Rule + @JvmField + val temporaryFolder = TemporaryFolder() + + @Rule + @JvmField + val systemPropertyHelper = SystemPropertyHelper() + + private lateinit var configFile: File + private lateinit var credentialsFile: File + + @Before + fun setUp() { + configFile = temporaryFolder.newFile("config") + credentialsFile = temporaryFolder.newFile("credentials") + + System.getProperties().setProperty("aws.configFile", configFile.absolutePath) + System.getProperties().setProperty("aws.sharedCredentialsFile", credentialsFile.absolutePath) + } + + @Test + fun `source_profile points to a profile that does not exist`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + role_session_name=testSession + source_profile=source_profile + external_id=externalId + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("role" to AwsCoreBundle.message("credentials.profile.source_profile_not_found", "role", "source_profile")) + } + + @Test + fun `source_profile chain can't go in a circle`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + source_profile=source_profile + + [profile source_profile] + role_arn=arn2 + source_profile=source_profile2 + + [profile source_profile2] + role_arn=arn3 + source_profile=source_profile3 + + [profile source_profile3] + role_arn=arn4 + source_profile=source_profile + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains( + "role" to AwsCoreBundle.message( + "credentials.profile.circular_profiles", + "role", + "role->source_profile->source_profile2->source_profile3->source_profile" + ) + ) + } + + @Test + fun `source_profile can't reference itself`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + source_profile=role + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("role" to AwsCoreBundle.message("credentials.profile.circular_profiles", "role", "role->role")) + } + + @Test + fun `assume a role requires either a source_profile or credential_source`() { + configFile.writeText( + """ + [profile role] + role_arn = arn:aws:iam::xxx:role/ + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("role" to AwsCoreBundle.message("credentials.profile.assume_role.missing_source", "role")) + } + + @Test + fun `assume a role can't specify both a source_profile or credential_source`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + source_profile=source_profile + credential_source=EcsContainer + + [profile source_profile] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).containsKey("source_profile") + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("role" to AwsCoreBundle.message("credentials.profile.assume_role.duplicate_source", "role")) + } + + @Test + fun `credential_source with invalid type is invalid`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + credential_source=Foo + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("role" to AwsCoreBundle.message("credentials.profile.assume_role.invalid_credential_source", "role")) + } + + @Test + fun `assume a role with an invalid profile bubbles error`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + source_profile=source_profile + + [profile source_profile] + role_arn=arn2 + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("role" to AwsCoreBundle.message("credentials.profile.assume_role.missing_source", "source_profile")) + .contains("source_profile" to AwsCoreBundle.message("credentials.profile.assume_role.missing_source", "source_profile")) + } + + @Test + fun `valid assume role with source_profile returns valid`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + source_profile=source_profile + + [profile source_profile] + aws_access_key_id=BarAccessKey + aws_secret_access_key=BarSecretKey + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).hasSize(2) + assertThat(invalidProfiles).isEmpty() + } + + @Test + fun `valid assume role with credential_source returns valid`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + credential_source=EcsContainer + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).hasSize(1) + assertThat(invalidProfiles).isEmpty() + } + + @Test + fun `valid assume role with credential_source in a chain returns valid`() { + configFile.writeText( + """ + [profile role] + role_arn=arn1 + source_profile=source_profile + + [profile source_profile] + role_arn=arn2 + source_profile=source_profile2 + + [profile source_profile2] + role_arn=arn3 + source_profile=source_profile3 + + [profile source_profile3] + role_arn=arn4 + credential_source=EcsContainer + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).hasSize(4) + assertThat(invalidProfiles).isEmpty() + } + + @Test + fun `can read sso-session`() { + configFile.writeText( + """ + [sso-session validSession] + sso_start_url=https://example.com + sso_region=us-fake-1 + + [sso-session missingStartUrl] + sso_region=us-fake-2 + + [sso-session missingRegion] + sso_start_url=https://example2.com + """.trimIndent() + ) + + val result = validateAndGetProfiles() + assertThat(result.validSsoSessions).hasSize(1).allSatisfy { profileName, profile -> + assertThat(profileName).isEqualTo("validSession") + assertThat(profile.name()).isEqualTo(profileName) + assertThat(profile.properties()).containsExactlyInAnyOrderEntriesOf( + mapOf( + "sso_start_url" to "https://example.com", + "sso_region" to "us-fake-1" + ) + ) + } + assertThat(result.invalidSsoSessions).hasSize(2) + } + + @Test + fun `can read sso-session based profile`() { + configFile.writeText( + """ + [sso-session validSession] + sso_start_url=https://example.com + sso_region=us-fake-1 + + [profile ssoProfile] + sso_session=validSession + sso_account_id=111122223333 + sso_role_name=SampleRole2 + """.trimIndent() + ) + + val result = validateAndGetProfiles() + assertThat(result.validProfiles).hasSize(1).allSatisfy { profileName, profile -> + assertThat(profileName).isEqualTo("ssoProfile") + assertThat(profile.name()).isEqualTo(profileName) + assertThat(profile.properties()).containsExactlyInAnyOrderEntriesOf( + mapOf( + "sso_session" to "validSession", + "sso_account_id" to "111122223333", + "sso_role_name" to "SampleRole2" + ) + ) + } + assertThat(result.invalidProfiles).isEmpty() + assertThat(result.validSsoSessions).hasSize(1) + assertThat(result.invalidSsoSessions).isEmpty() + } + + @Test + fun `sso-session based profile referencing missing sso-session is invalid`() { + configFile.writeText( + """ + [profile ssoProfile] + sso_session=missingSession + sso_account_id=111122223333 + sso_role_name=SampleRole2 + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("ssoProfile" to AwsCoreBundle.message("credentials.ssoSession.validation_error", "ssoProfile", "missingSession")) + } + + @Test + fun `sso-session based profile referencing invalid sso-session is invalid`() { + configFile.writeText( + """ + [sso-session invalidSession] + sso_region=us-fake-1 + + [profile ssoProfile] + sso_session=invalidSession + sso_account_id=111122223333 + sso_role_name=SampleRole2 + """.trimIndent() + ) + + val (validProfiles, invalidProfiles) = validateAndGetProfiles() + assertThat(validProfiles).isEmpty() + assertThat(invalidProfiles.map { it.key to it.value.message }) + .contains("ssoProfile" to AwsCoreBundle.message("credentials.profile.missing_property", "invalidSession", "sso_start_url")) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt new file mode 100644 index 00000000000..fb5a1698e92 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt @@ -0,0 +1,12 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import software.amazon.awssdk.profiles.Profile +import software.aws.toolkits.core.utils.test.aString + +fun profile(name: String = aString(), properties: MutableMap.() -> Unit = {}): Profile = Profile.builder() + .name(name) + .properties(mutableMapOf().apply { properties(this) }) + .build() diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt new file mode 100644 index 00000000000..529bf12d6eb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt @@ -0,0 +1,177 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.profiles + +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Ref +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl +import com.intellij.openapi.vfs.newvfs.ManagingFS +import com.intellij.openapi.vfs.newvfs.RefreshQueue +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import org.assertj.core.api.Assertions.assertThat +import org.junit.AssumptionViolatedException +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.opentest4j.AssertionFailedError +import software.amazon.q.jetbrains.utils.spinUntil +import software.aws.toolkits.core.rules.SystemPropertyHelper +import java.io.File +import java.io.IOException +import java.time.Duration +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class ProfileWatcherTest { + companion object { + @ClassRule + @JvmField + val disposableRule = DisposableRule() + } + + @Rule + @JvmField + val application = ApplicationRule() + + @Rule + @JvmField + val temporaryFolder = TemporaryFolder() + + @Rule + @JvmField + val systemPropertyHelper = SystemPropertyHelper() + + private lateinit var awsFolder: File + private lateinit var profileFile: File + private lateinit var credentialsFile: File + + @Before + fun setUp() { + awsFolder = File(temporaryFolder.root, ".aws") + profileFile = File(awsFolder, "config") + credentialsFile = File(awsFolder, "credentials") + + System.getProperties().setProperty("aws.configFile", profileFile.absolutePath) + System.getProperties().setProperty("aws.sharedCredentialsFile", credentialsFile.absolutePath) + + try { + assertFileChange { + profileFile.parentFile.mkdirs() + profileFile.writeText("Test") + profileFile.parentFile.deleteRecursively() + } + } catch (e: Throwable) { + if (e is AssertionFailedError) { + // suppress + } else if (e.cause is IOException) { + throw AssumptionViolatedException("native file watcher is not executable; possibly an issue with intellij-platform-gradle-plugin", e) + } else { + throw e + } + } + } + + @Test + fun `watcher is notified on creation`() { + profileFile.parentFile.mkdirs() + + assertFileChange { + profileFile.writeText("Test") + } + } + + @Test + fun `watcher is notified on edit`() { + profileFile.parentFile.mkdirs() + profileFile.writeText("Test") + + assertThat(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(profileFile)).isNotNull + + assertFileChange { + profileFile.writeText("Test2") + } + } + + @Test + fun `watcher is notified on deletion`() { + profileFile.parentFile.mkdirs() + profileFile.writeText("Test") + + assertThat(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(profileFile)).isNotNull + + assertFileChange { + profileFile.delete() + } + + assertThat(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(profileFile)).isNull() + } + + /** + * These tests are complicated and reaching into some low level systems inside of the IDE to replicate how it works due stuff is disabled in unit test mode. + * + * First, FileWatcher (fsnotify[.exe]) that notifies the IDE that files are dirty is not ran in unit test mode. We start/stop it manually so that we can + * validate external edits to the profile file is handled. + * + * Second, the system that marks VFS files dirty from the FileWatcher is only configured to run if FileWatcher is running in the constructor. + * See com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl constructor. Due to that, we schedule a manual VFS refresh to recheck all the files + * marked dirty. + */ + private fun assertFileChange(block: () -> Unit) { + val fileWatcher = (LocalFileSystem.getInstance() as LocalFileSystemImpl).fileWatcher + Disposer.register( + disposableRule.disposable + ) { + fileWatcher.shutdown() + + spinUntil(Duration.ofSeconds(10)) { + !fileWatcher.isOperational + } + } + + val watcherTriggered = CountDownLatch(1) + fileWatcher.startup { + // Contains due to /private/ vs / + if (it.contains(awsFolder.absolutePath)) { + watcherTriggered.countDown() + } + } + + spinUntil(Duration.ofSeconds(10)) { + fileWatcher.isOperational + } + + val sut = DefaultProfileWatcher() + Disposer.register(disposableRule.disposable, sut) + + spinUntil(Duration.ofSeconds(10)) { + !fileWatcher.isSettingRoots + } + + val updateCalled = Ref.create(false) + sut.addListener { updateCalled.set(true) } + + block() + + // Wait for fsnotify to see the change + assertThat(watcherTriggered.await(5, TimeUnit.SECONDS)).describedAs("FileWatcher is triggered").isTrue + + val refreshComplete = CountDownLatch(1) + RefreshQueue.getInstance().refresh(true, true, { refreshComplete.countDown() }, *ManagingFS.getInstance().localRoots) + + // Wait for refresh to complete + refreshComplete.await() + + // The update is triggered asynchronously, so this does not mean it's done, especially when the system is under high load + // Since we are compiling ultimate/rider while running this test it can fail easily. 2000 is since we are dealing with the filesystem + // so, pick a high arbitrary value + if (updateCalled.get() == false) { + Thread.sleep(2000) + } + assertThat(updateCalled.get()).describedAs("ProfileWatcher is triggered").isTrue + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/AccessTokenTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/AccessTokenTest.kt new file mode 100644 index 00000000000..1ceca77d080 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/AccessTokenTest.kt @@ -0,0 +1,38 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import java.time.Instant + +class AccessTokenTest { + @Test + fun `DeviceAuthorizationGrantToken#toString has redacted values`() { + val sut = DeviceAuthorizationGrantToken( + startUrl = "clearText", + region = "clearText", + accessToken = "hiddenText", + refreshToken = "hiddenText", + expiresAt = Instant.EPOCH, + createdAt = Instant.EPOCH, + ) + + assertThat(sut.toString()).doesNotContain("hiddenText") + } + + @Test + fun `PKCEAuthorizationGrantToken#toString has redacted values`() { + val sut = PKCEAuthorizationGrantToken( + issuerUrl = "clearText", + region = "clearText", + accessToken = "hiddenText", + refreshToken = "hiddenText", + expiresAt = Instant.EPOCH, + createdAt = Instant.EPOCH, + ) + + assertThat(sut.toString()).doesNotContain("hiddenText") + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistrationTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistrationTest.kt new file mode 100644 index 00000000000..ae857a4badc --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistrationTest.kt @@ -0,0 +1,39 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import java.time.Instant + +class ClientRegistrationTest { + @Test + fun `DeviceAuthorizationClientRegistration#toString has redacted values`() { + val sut = DeviceAuthorizationClientRegistration( + clientId = "clearText", + clientSecret = "hiddenText", + expiresAt = Instant.EPOCH, + scopes = listOf("clearText"), + ) + + assertThat(sut.toString()).doesNotContain("hiddenText") + } + + @Test + fun `PKCEClientRegistration#toString has redacted values`() { + val sut = PKCEClientRegistration( + clientId = "clearText", + clientSecret = "hiddenText", + expiresAt = Instant.EPOCH, + scopes = listOf("clearText"), + issuerUrl = "clearText", + region = "clearText", + clientType = "clearText", + grantTypes = listOf("clearText"), + redirectUris = listOf("clearText") + ) + + assertThat(sut.toString()).doesNotContain("hiddenText") + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt new file mode 100644 index 00000000000..558ba8ba3c0 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt @@ -0,0 +1,717 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.NioFiles +import com.intellij.testFramework.ApplicationExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.DisabledOnOs +import org.junit.jupiter.api.condition.OS +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.io.TempDir +import software.amazon.q.core.utils.readText +import software.aws.toolkits.core.utils.test.assertPosixPermissions +import software.amazon.q.core.utils.writeText +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.PosixFilePermissions +import java.time.Clock +import java.time.Instant +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import kotlin.io.path.setPosixFilePermissions + +class DiskCacheTest { + @ExtendWith(ApplicationExtension::class) + private val now = Instant.now() + private val clock = Clock.fixed(now, ZoneOffset.UTC) + + private val ssoUrl = "https://123456.awsapps.com/start" + private val ssoRegion = "us-fake-1" + private val scopes = listOf("scope1", "scope2") + + private lateinit var cacheRoot: Path + private lateinit var cacheLocation: Path + private lateinit var sut: DiskCache + + @BeforeEach + fun setUp(@TempDir tempFolder: Path) { + cacheRoot = tempFolder.toAbsolutePath() + cacheLocation = Paths.get(cacheRoot.toString(), "fakehome", ".aws", "sso", "cache") + Files.createDirectories(cacheLocation) + sut = DiskCache(cacheLocation, clock) + } + + @Test + fun nonExistentClientRegistrationReturnsNull() { + assertThat( + sut.loadClientRegistration( + DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ), + "testSource" + ) + ).isNull() + } + + @Test + fun corruptClientRegistrationReturnsNull() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json").writeText("badData") + + assertThat(sut.loadClientRegistration(key, "testSource")).isNull() + } + + @Test + fun expiredClientRegistrationReturnsNull() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json").writeText( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(now.minusSeconds(100))}" + } + """.trimIndent() + ) + + assertThat(sut.loadClientRegistration(key, "testSource")).isNull() + } + + @Test + fun clientRegistrationExpiringSoonIsTreatedAsExpired() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + val expirationTime = now.plus(14, ChronoUnit.MINUTES) + cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json").writeText( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}" + } + """.trimIndent() + ) + + assertThat(sut.loadClientRegistration(key, "testSource")).isNull() + } + + @Test + fun `valid scoped client registration loads correctly`() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json").writeText( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}", + "scopes": ["scope1","scope2"] + } + """.trimIndent() + ) + + assertThat(sut.loadClientRegistration(key, "testSource")) + .usingRecursiveComparison() + .isEqualTo( + DeviceAuthorizationClientRegistration( + "DummyId", + "DummySecret", + expirationTime, + scopes + ) + ) + } + + @Test + fun `scoped client registration saves correctly`() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + val expirationTime = DateTimeFormatter.ISO_INSTANT.parse("2020-04-07T21:31:33Z") + sut.saveClientRegistration( + key, + DeviceAuthorizationClientRegistration( + "DummyId", + "DummySecret", + Instant.from(expirationTime), + scopes + ) + ) + + val clientRegistration = cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json") + if (SystemInfo.isUnix) { + assertPosixPermissions(clientRegistration, "rw-------") + } + assertThat(clientRegistration.readText()) + .isEqualToIgnoringWhitespace( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "2020-04-07T21:31:33Z", + "scopes": ["scope1","scope2"] + } + """.trimIndent() + ) + } + + @Test + fun `PKCE client registration loads correctly`() { + val key = PKCEClientRegistrationCacheKey( + issuerUrl = ssoUrl, + scopes = scopes, + region = ssoRegion, + clientType = "public", + grantTypes = listOf("authorization_code", "refresh_token"), + redirectUris = listOf("http://127.0.0.1/oauth/callback") + ) + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + cacheLocation.resolve("646d06bb78960f8aea2e8efd07c7655f602e9c62.json") + .writeText( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}", + "scopes": [ + "scope1", + "scope2" + ], + "issuerUrl": "$ssoUrl", + "region": "$ssoRegion", + "clientType": "public", + "grantTypes": [ + "authorization_code", + "refresh_token" + ], + "redirectUris": [ + "http://127.0.0.1/oauth/callback" + ] + } + """.trimIndent() + ) + + assertThat(sut.loadClientRegistration(key, "testSource")) + .usingRecursiveComparison() + .isEqualTo( + PKCEClientRegistration( + "DummyId", + "DummySecret", + Instant.from(expirationTime), + scopes, + ssoUrl, + ssoRegion, + "public", + listOf("authorization_code", "refresh_token"), + listOf("http://127.0.0.1/oauth/callback") + ) + ) + } + + @Test + fun `PKCE client registration saves correctly`() { + val key = PKCEClientRegistrationCacheKey( + issuerUrl = ssoUrl, + scopes = scopes, + region = ssoRegion, + clientType = "public", + grantTypes = listOf("authorization_code", "refresh_token"), + redirectUris = listOf("http://127.0.0.1/oauth/callback") + ) + val expirationTime = DateTimeFormatter.ISO_INSTANT.parse("2020-04-07T21:31:33Z") + sut.saveClientRegistration( + key, + PKCEClientRegistration( + "DummyId", + "DummySecret", + Instant.from(expirationTime), + scopes, + ssoUrl, + ssoRegion, + "public", + listOf("authorization_code", "refresh_token"), + listOf("http://127.0.0.1/oauth/callback") + ) + ) + + val clientRegistration = cacheLocation.resolve("646d06bb78960f8aea2e8efd07c7655f602e9c62.json") + if (SystemInfo.isUnix) { + assertPosixPermissions(clientRegistration, "rw-------") + } + assertThat(clientRegistration.readText()) + .isEqualToIgnoringWhitespace( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "2020-04-07T21:31:33Z", + "scopes": [ + "scope1", + "scope2" + ], + "issuerUrl": "$ssoUrl", + "region": "$ssoRegion", + "clientType": "public", + "grantTypes": [ + "authorization_code", + "refresh_token" + ], + "redirectUris": [ + "http://127.0.0.1/oauth/callback" + ] + } + """.trimIndent() + ) + } + + @Test + fun `PKCE client registration cache key is not dependent on list order`() { + val key1 = PKCEClientRegistrationCacheKey( + issuerUrl = ssoUrl, + scopes = scopes, + region = ssoRegion, + clientType = "public", + grantTypes = listOf("refresh_token", "authorization_code"), + redirectUris = listOf("http://127.0.0.1/oauth/callback", "callback2") + ) + val key2 = PKCEClientRegistrationCacheKey( + issuerUrl = ssoUrl, + scopes = scopes, + region = ssoRegion, + clientType = "public", + grantTypes = listOf("authorization_code", "refresh_token"), + redirectUris = listOf("callback2", "http://127.0.0.1/oauth/callback") + ) + sut.saveClientRegistration( + key1, + PKCEClientRegistration( + "DummyId", + "DummySecret", + Instant.EPOCH, + scopes, + ssoUrl, + ssoRegion, + "public", + listOf("authorization_code", "refresh_token"), + listOf("http://127.0.0.1/oauth/callback", "callback2") + ) + ) + + assertThat(sut.loadClientRegistration(key1, "testSource")) + .usingRecursiveComparison() + .isEqualTo( + sut.loadClientRegistration(key2, "testSource") + ) + } + + @Test + fun `invalidate scoped client registration deletes the file`() { + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + val cacheFile = cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json") + cacheFile.writeText( + """ + { + "clientId": "DummyId", + "clientSecret": "DummySecret", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}" + } + """.trimIndent() + ) + + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + + assertThat(sut.loadClientRegistration(key, "testSource")).isNotNull() + + sut.invalidateClientRegistration(key) + + assertThat(sut.loadClientRegistration(key, "testSource")).isNull() + assertThat(cacheFile).doesNotExist() + } + + @Test + fun nonExistentAccessTokenReturnsNull() { + assertThat(sut.loadAccessToken(DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")))).isNull() + } + + @Test + fun corruptAccessTokenReturnsNull() { + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + + assertThat(sut.loadAccessToken(key)).isNull() + } + + @Test + fun `expired access token is not loaded`() { + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json").writeText( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(now.minusSeconds(100))}", + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key)).isNull() + } + + @Test + fun `expired access token is loaded if it has a refresh token`() { + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json").writeText( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "refreshToken": "ARefreshToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(now.minusSeconds(100))}" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key)).isNotNull() + } + + @Test + fun accessTokenExpiringSoonIsTreatedAsExpired() { + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + val expirationTime = now.plus(14, ChronoUnit.MINUTES) + cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json").writeText( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key)).isNull() + } + + @Test + fun validAccessTokenFromCliReturnsCorrectly() { + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json").writeText( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "expiresAt": "2999-06-10T00:50:40UTC" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key)) + .usingRecursiveComparison() + .isEqualTo( + DeviceAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "DummyAccessToken", + expiresAt = ZonedDateTime.of(2999, 6, 10, 0, 50, 40, 0, ZoneOffset.UTC).toInstant() + ) + ) + } + + @Test + fun `scoped access token saves correctly`() { + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + val expirationTime = DateTimeFormatter.ISO_INSTANT.parse("2020-04-07T21:31:33Z") + sut.saveAccessToken( + key, + DeviceAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "DummyAccessToken", + "RefreshToken", + Instant.from(expirationTime) + ) + ) + + val accessTokenCache = cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json") + if (SystemInfo.isUnix) { + assertPosixPermissions(accessTokenCache, "rw-------") + } + + assertThat(accessTokenCache.readText()) + .isEqualToIgnoringWhitespace( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "refreshToken": "RefreshToken", + "expiresAt": "2020-04-07T21:31:33Z", + "createdAt":"1970-01-01T00:00:00Z" + } + """.trimIndent() + ) + } + + @Test + fun `invalidate scoped access token deletes file`() { + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + val key = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + val cacheFile = cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json") + + cacheFile.writeText( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}", + "createdAt":"1970-01-01T00:00:00Z" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key)).isNotNull() + + sut.invalidateAccessToken(key) + + assertThat(sut.loadAccessToken(key)).isNull() + assertThat(cacheFile).doesNotExist() + } + + @Test + fun `scope order does not matter for scoped access token cache`() { + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + val cacheFile = cacheLocation.resolve("72286fb950f12c77c840239851fd64ac60275c5c.json") + val key1 = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope1", "scope2")) + val key2 = DeviceGrantAccessTokenCacheKey("connectionId", ssoUrl, listOf("scope2", "scope1")) + + cacheFile.writeText( + """ + { + "startUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}", + "createdAt":"1970-01-01T00:00:00Z" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key1)).isNotNull() + assertThat(sut.loadAccessToken(key2)).isNotNull() + } + + @Test + @DisabledOnOs(OS.WINDOWS) + fun `handles error saving client registration when user home is not writable`() { + Files.newDirectoryStream(cacheRoot).forEach { NioFiles.deleteRecursively(it) } + cacheRoot.resolve("fakehome").apply { + Files.createDirectory(this) + setPosixFilePermissions(emptySet()) + } + cacheRoot.setPosixFilePermissions(PosixFilePermissions.fromString("r-xr-xr-x")) + + sut.saveClientRegistration( + DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ), + DeviceAuthorizationClientRegistration( + "DummyId", + "DummySecret", + Instant.now() + ) + ) + + val registration = cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json") + assertThat(cacheLocation).isEqualTo(Paths.get(cacheRoot.toString(), "fakehome", ".aws", "sso", "cache")) + assertPosixPermissions(cacheRoot, "rwxr-xr-x") + assertPosixPermissions(cacheRoot.resolve("fakehome"), "rwx------") + assertPosixPermissions(cacheRoot.resolve("fakehome").resolve(".aws"), "rwxr-xr-x") + assertPosixPermissions(cacheRoot.resolve("fakehome").resolve(".aws").resolve("sso"), "rwxr-xr-x") + assertPosixPermissions(cacheRoot.resolve("fakehome").resolve(".aws").resolve("sso").resolve("cache"), "rwxr-xr-x") + assertPosixPermissions(registration, "rw-------") + } + + @Test + @DisabledOnOs(OS.WINDOWS) + fun `handles error saving client registration when client registration is not writable`() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + val registration = cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json") + sut.saveClientRegistration( + key, + DeviceAuthorizationClientRegistration( + "DummyId", + "DummySecret", + Instant.now() + ) + ) + + registration.setPosixFilePermissions(emptySet()) + assertPosixPermissions(registration, "---------") + + sut.saveClientRegistration( + key, + DeviceAuthorizationClientRegistration( + "DummyId", + "DummySecret", + Instant.now() + ) + ) + + assertPosixPermissions(registration, "rw-------") + } + + @Test + @DisabledOnOs(OS.WINDOWS) + fun `handles reading client registration when file is owned but not readable`() { + val key = DeviceAuthorizationClientRegistrationCacheKey( + startUrl = ssoUrl, + scopes = scopes, + region = ssoRegion + ) + sut.saveClientRegistration( + key, + DeviceAuthorizationClientRegistration( + "DummyId", + "DummySecret", + Instant.MAX + ) + ) + val registration = cacheLocation.resolve("223224b6f0b4702c1a984be8284fe2c9d9718759.json") + registration.setPosixFilePermissions(emptySet()) + assertPosixPermissions(registration, "---------") + + assertThat(sut.loadClientRegistration(key, "testSource")).isNotNull() + + assertPosixPermissions(registration, "rw-------") + } + + @Test + fun `PKCE access token saves correctly`() { + val key = PKCEAccessTokenCacheKey(ssoUrl, "us-fake-1", listOf("scope1", "scope2")) + val expirationTime = DateTimeFormatter.ISO_INSTANT.parse("2020-04-07T21:31:33Z") + sut.saveAccessToken( + key, + PKCEAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "DummyAccessToken", + "RefreshToken", + expiresAt = Instant.from(expirationTime), + createdAt = Instant.EPOCH + ) + ) + + val accessTokenCache = cacheLocation.resolve("e5df41d83e4b011b7b6eedf9cc051db6989a3bca.json") + if (SystemInfo.isUnix) { + assertPosixPermissions(accessTokenCache, "rw-------") + } + + assertThat(accessTokenCache.readText()) + .isEqualToIgnoringWhitespace( + """ + { + "issuerUrl": "https://123456.awsapps.com/start", + "region": "us-fake-1", + "accessToken": "DummyAccessToken", + "refreshToken": "RefreshToken", + "expiresAt": "2020-04-07T21:31:33Z", + "createdAt": "1970-01-01T00:00:00Z" + } + """.trimIndent() + ) + } + + @Test + fun `PKCE access token returns correctly`() { + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + val key = PKCEAccessTokenCacheKey(ssoUrl, ssoRegion, listOf("scope1", "scope2")) + cacheLocation.resolve("e5df41d83e4b011b7b6eedf9cc051db6989a3bca.json").writeText( + """ + { + "issuerUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "refreshToken": "RefreshToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}", + "createdAt": "1970-01-01T00:00:00Z" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key)) + .usingRecursiveComparison() + .isEqualTo( + PKCEAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "DummyAccessToken", + "RefreshToken", + expiresAt = Instant.from(expirationTime), + createdAt = Instant.EPOCH + ) + ) + } + + @Test + fun `PKCE access token cache key not dependent on scope order`() { + val expirationTime = now.plus(20, ChronoUnit.MINUTES) + val key1 = PKCEAccessTokenCacheKey(ssoUrl, ssoRegion, listOf("scope1", "scope2")) + val key2 = PKCEAccessTokenCacheKey(ssoUrl, ssoRegion, listOf("scope2", "scope1")) + cacheLocation.resolve("e5df41d83e4b011b7b6eedf9cc051db6989a3bca.json").writeText( + """ + { + "issuerUrl": "$ssoUrl", + "region": "$ssoRegion", + "accessToken": "DummyAccessToken", + "refreshToken": "RefreshToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expirationTime)}", + "createdAt": "1970-01-01T00:00:00Z" + } + """.trimIndent() + ) + + assertThat(sut.loadAccessToken(key1)) + .usingRecursiveComparison() + .isEqualTo(sut.loadAccessToken(key2)) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt new file mode 100644 index 00000000000..799b64d722d --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt @@ -0,0 +1,561 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.registry.Registry +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.RuleChain +import com.intellij.testFramework.replaceService +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.kotlin.KStubbing +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.awssdk.awscore.exception.AwsServiceException +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.services.ssooidc.model.AuthorizationPendingException +import software.amazon.awssdk.services.ssooidc.model.CreateTokenRequest +import software.amazon.awssdk.services.ssooidc.model.CreateTokenResponse +import software.amazon.awssdk.services.ssooidc.model.InvalidClientException +import software.amazon.awssdk.services.ssooidc.model.InvalidRequestException +import software.amazon.awssdk.services.ssooidc.model.RegisterClientRequest +import software.amazon.awssdk.services.ssooidc.model.RegisterClientResponse +import software.amazon.awssdk.services.ssooidc.model.SlowDownException +import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.awssdk.services.ssooidc.model.StartDeviceAuthorizationRequest +import software.amazon.awssdk.services.ssooidc.model.StartDeviceAuthorizationResponse +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService +import software.aws.toolkits.core.region.aRegionId +import software.aws.toolkits.core.utils.delegateMock +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.rules.RegistryRule +import software.amazon.q.jetbrains.utils.rules.SsoLoginCallbackProviderRule +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.time.ZoneOffset +import java.time.temporal.ChronoUnit + +class SsoAccessTokenProviderTest { + private val clock = Clock.fixed(Instant.now().truncatedTo(ChronoUnit.MILLIS), ZoneOffset.UTC) + + private val ssoUrl = aString() + private val ssoRegion = aRegionId() + private val clientId = aString() + private val clientSecret = aString() + + private lateinit var ssoOidcClient: SsoOidcClient + private lateinit var sut: SsoAccessTokenProvider + private lateinit var ssoCache: SsoCache + + private val applicationRule = ApplicationRule() + private val disposableRule = DisposableRule() + private val ssoCallbackRule = SsoLoginCallbackProviderRule() + private val registryRule = RegistryRule("aws.dev.useDAG", true) + + @JvmField + @Rule + val ruleChain = RuleChain(applicationRule, registryRule, ssoCallbackRule, disposableRule) + + @Before + fun setUp() { + ssoOidcClient = delegateMock() + ssoCache = mock() + + sut = SsoAccessTokenProvider(ssoUrl, ssoRegion, ssoCache, ssoOidcClient, scopes = listOf(IDENTITY_CENTER_ROLE_ACCESS_SCOPE), clock = clock) + } + + @Test + fun getAccessTokenWithAccessTokenCache() { + val accessToken = DeviceAuthorizationGrantToken(ssoUrl, ssoRegion, "dummyToken", expiresAt = clock.instant()) + ssoCache.stub { + on( + ssoCache.loadAccessToken(argThat { startUrl == ssoUrl }) + ).thenReturn( + accessToken + ) + } + + val accessTokenActual = runBlocking { sut.accessToken() } + assertThat(accessTokenActual) + .usingRecursiveComparison() + .isEqualTo(accessToken) + + verify(ssoCache).loadAccessToken(any()) + } + + @Test + fun `get device access token with client registration cache`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + setupCacheStub(expirationClientRegistration) + + ssoOidcClient.stub { + stubStartDeviceAuthorization() + stubCreateToken() + } + + val accessToken = runBlocking { sut.accessToken() } + assertThat(accessToken).usingRecursiveComparison() + .isEqualTo( + DeviceAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "accessToken", + expiresAt = clock.instant().plusSeconds(180), + createdAt = clock.instant() + + ) + ) + + verify(ssoOidcClient).startDeviceAuthorization(any()) + verify(ssoOidcClient).createToken(any()) + verify(ssoCache).loadAccessToken(argThat { startUrl == ssoUrl }) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + verify(ssoCache).saveAccessToken(argThat { startUrl == ssoUrl }, eq(accessToken)) + } + + @Test + fun `get device access token without caches`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + setupCacheStub(returnValue = null) + + ssoOidcClient.stub { + on( + ssoOidcClient.registerClient( + RegisterClientRequest.builder() + .clientType("public") + .clientName("AWS IDE Plugins for JetBrains") + .scopes(listOf(IDENTITY_CENTER_ROLE_ACCESS_SCOPE)) + .build() + ) + ).thenReturn( + RegisterClientResponse.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .clientSecretExpiresAt(expirationClientRegistration.toEpochMilli()) + .build() + ) + + stubStartDeviceAuthorization() + stubCreateToken() + } + + val accessToken = runBlocking { sut.accessToken() } + assertThat(accessToken).usingRecursiveComparison() + .isEqualTo( + DeviceAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "accessToken", + expiresAt = clock.instant().plusSeconds(180), + createdAt = clock.instant() + ) + ) + + verify(ssoOidcClient).registerClient(any()) + verify(ssoOidcClient).startDeviceAuthorization(any()) + verify(ssoOidcClient).createToken(any()) + verify(ssoCache).loadAccessToken(argThat { startUrl == ssoUrl }) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + verify(ssoCache).saveClientRegistration(argThat { region == ssoRegion }, any()) + verify(ssoCache).saveAccessToken(argThat { startUrl == ssoUrl }, eq(accessToken)) + } + + @Test + fun `initiates authorizatation_grant registration when requested in a commercial region`() { + setPkceTrue() + val sut = SsoAccessTokenProvider(ssoUrl, "us-east-1", ssoCache, ssoOidcClient, scopes = listOf("dummy:scope"), clock = clock) + setupCacheStub(returnValue = null) + + val oauth = mock() + ApplicationManager.getApplication().replaceService(ToolkitOAuthService::class.java, oauth, disposableRule.disposable) + + ssoOidcClient.stub { + on( + ssoOidcClient.registerClient(any()) + ).thenReturn( + RegisterClientResponse.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .clientSecretExpiresAt(clock.instant().plusSeconds(180).toEpochMilli()) + .build() + ) + } + + // flow is not completely stubbed out + assertThrows { sut.accessToken() } + + verify(ssoCache).saveClientRegistration(any(), any()) + } + + @Test + fun `initiates device code registration when requested in a non-commercial region`() { + setPkceTrue() + val sut = SsoAccessTokenProvider(ssoUrl, "us-gov-east-1", ssoCache, ssoOidcClient, scopes = listOf("dummy:scope"), clock = clock) + setupCacheStub(returnValue = null) + + val oauth = mock() + ApplicationManager.getApplication().replaceService(ToolkitOAuthService::class.java, oauth, disposableRule.disposable) + + ssoOidcClient.stub { + on( + ssoOidcClient.registerClient(any()) + ).thenReturn( + RegisterClientResponse.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .clientSecretExpiresAt(clock.instant().plusSeconds(180).toEpochMilli()) + .build() + ) + } + + // flow is not completely stubbed out + assertThrows { sut.accessToken() } + + verify(ssoCache).saveClientRegistration(any(), any()) + } + + @Test + fun `get device access token without caches and multiple polls`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + + setupCacheStub(expirationClientRegistration) + + ssoOidcClient.stub { + stubStartDeviceAuthorization(interval = 1) + on( + ssoOidcClient.createToken(createTokenRequest()) + ).thenThrow( + AuthorizationPendingException.builder().build() + ).thenReturn( + createTokenResponse() + ) + } + + val startTime = Instant.now() + val accessToken = runBlocking { sut.accessToken() } + val callDuration = Duration.between(startTime, Instant.now()) + val creationTime = clock.instant() + + assertThat(accessToken).usingRecursiveComparison() + .isEqualTo( + DeviceAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "accessToken", + expiresAt = clock.instant().plusSeconds(180), + createdAt = creationTime + ) + ) + + assertThat(callDuration.seconds).isGreaterThanOrEqualTo(1).isLessThan(2) + + verify(ssoOidcClient).startDeviceAuthorization(any()) + verify(ssoOidcClient, times(2)).createToken(any()) + verify(ssoCache).loadAccessToken(argThat { startUrl == ssoUrl }) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + verify(ssoCache).saveAccessToken(argThat { startUrl == ssoUrl }, eq(accessToken)) + } + + @Test + fun `refresh access token updates caches`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + setupCacheStub(expirationClientRegistration) + + val accessToken = DeviceAuthorizationGrantToken(ssoUrl, ssoRegion, "dummyToken", "refreshToken", clock.instant()) + ssoCache.stub { + on( + ssoCache.loadAccessToken(argThat { startUrl == ssoUrl }) + ).thenReturn( + accessToken + ) + } + + ssoOidcClient.stub { + on( + ssoOidcClient.createToken(refreshTokenRequest()) + ).thenReturn( + refreshTokenResponse() + ) + } + + val refreshedToken = runBlocking { sut.refreshToken(sut.accessToken()) } + + verify(ssoCache).loadAccessToken(argThat { startUrl == ssoUrl }) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + verify(ssoOidcClient).createToken(any()) + verify(ssoCache).saveAccessToken(argThat { startUrl == ssoUrl }, eq(refreshedToken)) + } + + @Test + fun `refresh access token error handling does not fail if AWS error details are missing`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + setupCacheStub(expirationClientRegistration) + + val accessToken = DeviceAuthorizationGrantToken(ssoUrl, ssoRegion, "dummyToken", "refreshToken", clock.instant()) + ssoCache.stub { + on( + ssoCache.loadAccessToken(argThat { startUrl == ssoUrl }) + ).thenReturn( + accessToken + ) + } + + ssoOidcClient.stub { + on( + ssoOidcClient.createToken(refreshTokenRequest()) + ) + .thenThrow(AwsServiceException.builder().build()) + } + + assertThatThrownBy { runBlocking { sut.refreshToken(sut.accessToken()) } } + .isInstanceOf(AwsServiceException::class.java) + } + + @Test + fun `PKCE refresh access token saves PKCE token`() { + setPkceTrue() + + val expirationClientRegistration = clock.instant().plusSeconds(120) + setupCacheStub(expirationClientRegistration) + + val accessToken = PKCEAuthorizationGrantToken(ssoUrl, ssoRegion, "dummyToken", "refreshToken", clock.instant(), clock.instant()) + ssoCache.stub { + on( + ssoCache.loadAccessToken(any()) + ).thenReturn( + accessToken + ) + + on( + ssoCache.loadClientRegistration(any(), any()) + ).thenReturn( + PKCEClientRegistration( + clientType = "public", + redirectUris = listOf("uri"), + grantTypes = listOf("grant"), + issuerUrl = ssoUrl, + region = ssoRegion, + scopes = listOf("dummy:scope"), + clientId = clientId, + clientSecret = clientSecret, + expiresAt = clock.instant() + ) + ) + } + + ssoOidcClient.stub { + on( + ssoOidcClient.createToken(refreshTokenRequest()) + ).thenReturn( + refreshTokenResponse() + ) + } + + val refreshedToken = runBlocking { sut.refreshToken(sut.accessToken()) } + + verify(ssoCache).loadAccessToken(any()) + verify(ssoCache).loadClientRegistration(any(), any()) + verify(ssoOidcClient).createToken(any()) + verify(ssoCache).saveAccessToken(any(), eq(refreshedToken)) + } + + @Test + fun `exception stops device code polling`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + + setupCacheStub(expirationClientRegistration) + + ssoOidcClient.stub { + stubStartDeviceAuthorization() + stubCreateToken(throws = true) + } + + assertThatThrownBy { runBlocking { sut.accessToken() } }.isInstanceOf(InvalidRequestException::class.java) + + verify(ssoOidcClient).startDeviceAuthorization(any()) + verify(ssoOidcClient).createToken(any()) + verify(ssoCache).loadAccessToken(argThat { startUrl == ssoUrl }) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + } + + @Test + fun `backoff time is respected during device code polling`() { + val expirationClientRegistration = clock.instant().plusSeconds(120) + setupCacheStub(expirationClientRegistration) + + ssoOidcClient.stub { + stubStartDeviceAuthorization(interval = 1) + + on( + ssoOidcClient.createToken(createTokenRequest()) + ).thenThrow( + SlowDownException.builder().build() + ).thenReturn( + createTokenResponse() + ) + } + + val startTime = Instant.now() + val accessToken = runBlocking { sut.accessToken() } + val callDuration = Duration.between(startTime, Instant.now()) + + assertThat(accessToken).usingRecursiveComparison() + .isEqualTo( + DeviceAuthorizationGrantToken( + ssoUrl, + ssoRegion, + "accessToken", + expiresAt = clock.instant().plusSeconds(180), + createdAt = clock.instant() + ) + ) + + assertThat(callDuration).isGreaterThan(Duration.ofSeconds(5)) + + verify(ssoCache).saveAccessToken(argThat { startUrl == ssoUrl }, eq(accessToken)) + + verify(ssoOidcClient).startDeviceAuthorization(any()) + verify(ssoOidcClient, times(2)).createToken(any()) + verify(ssoCache).loadAccessToken(argThat { startUrl == ssoUrl }) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + verify(ssoCache).saveAccessToken(argThat { startUrl == ssoUrl }, eq(accessToken)) + } + + @Test + fun failToGetClientRegistrationLeadsToError() { + setupCacheStub(returnValue = null) + + ssoOidcClient.stub { + on( + ssoOidcClient.registerClient(any()) + ).thenThrow( + SsoOidcException.builder().build() + ) + } + + assertThatThrownBy { runBlocking { sut.accessToken() } }.isInstanceOf(SsoOidcException::class.java) + + verify(ssoOidcClient).registerClient(any()) + verify(ssoCache).loadAccessToken(any()) + verify(ssoCache).loadClientRegistration(argThat { region == ssoRegion }, any()) + } + + @Test + fun `invalid device code registration clears the cache`() { + setupCacheStub(Instant.now(clock)) + + ssoOidcClient.stub { + on( + ssoOidcClient.startDeviceAuthorization(any()) + ).thenThrow( + InvalidClientException.builder().build() + ) + } + + assertThatThrownBy { runBlocking { sut.accessToken() } }.isInstanceOf(InvalidClientException::class.java) + + verify(ssoCache, times(2)).invalidateClientRegistration(argThat { region == ssoRegion }) + } + + @Test + fun invalidateClearsTheCache() { + sut.invalidate() + + verify(ssoCache).invalidateAccessToken(ssoUrl) + } + + private fun setupCacheStub(expirationClientRegistration: Instant) { + setupCacheStub(DeviceAuthorizationClientRegistration(clientId, clientSecret, expirationClientRegistration)) + } + + private fun setupCacheStub(returnValue: ClientRegistration?) { + ssoCache.stub { + on( + ssoCache.loadAccessToken(any()) + ).thenReturn( + null + ) + + on( + ssoCache.loadClientRegistration(argThat { region == ssoRegion }, any()) + ).thenReturn( + returnValue + ) + } + } + + private fun KStubbing.stubStartDeviceAuthorization(interval: Int? = null) { + on( + ssoOidcClient.startDeviceAuthorization( + StartDeviceAuthorizationRequest.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .startUrl(ssoUrl) + .build() + ) + ).thenReturn( + StartDeviceAuthorizationResponse.builder() + .expiresIn(120) + .deviceCode("dummyCode") + .userCode("dummyUserCode") + .verificationUri("someUrl") + .verificationUriComplete("someUrlComplete") + .apply { if (interval != null) interval(interval) } + .build() + ) + } + + private fun KStubbing.stubCreateToken(throws: Boolean = false) { + on( + ssoOidcClient.createToken(createTokenRequest()) + ).apply { + if (throws) { + thenThrow(InvalidRequestException.builder().build()) + } else { + thenReturn(createTokenResponse()) + } + } + } + + private fun createTokenRequest(): CreateTokenRequest = CreateTokenRequest.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .deviceCode("dummyCode") + .grantType("urn:ietf:params:oauth:grant-type:device_code") + .build() + + private fun createTokenResponse(): CreateTokenResponse = CreateTokenResponse.builder() + .accessToken("accessToken") + .expiresIn(180) + .build() + + private fun refreshTokenRequest(): CreateTokenRequest = CreateTokenRequest.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .refreshToken("refreshToken") + .grantType("refresh_token") + .build() + + private fun refreshTokenResponse(): CreateTokenResponse = CreateTokenResponse.builder() + .accessToken("accessToken2") + .refreshToken("refreshToken2") + .expiresIn(180) + .build() + + private fun setPkceTrue() = Registry.get("aws.dev.useDAG").setValue(false) +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt new file mode 100644 index 00000000000..bfa6109aecb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt @@ -0,0 +1,122 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.intellij.testFramework.ApplicationRule +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.services.sso.SsoClient +import software.amazon.awssdk.services.sso.model.GetRoleCredentialsRequest +import software.amazon.awssdk.services.sso.model.GetRoleCredentialsResponse +import software.amazon.awssdk.services.sso.model.RoleCredentials +import software.amazon.awssdk.services.sso.model.UnauthorizedException +import software.aws.toolkits.core.utils.delegateMock +import software.aws.toolkits.core.utils.test.aString +import java.time.Instant +import java.time.temporal.ChronoUnit + +class SsoCredentialProviderTest { + private val accessTokenId = aString() + private val accountId = aString() + private val roleName = aString() + private val accessKey = aString() + private val secretKey = aString() + private val sessionToken = aString() + private val credentials = AwsSessionCredentials.create(accessKey, secretKey, sessionToken) + private val accessToken = DeviceAuthorizationGrantToken(aString(), aString(), accessTokenId, expiresAt = Instant.now().plusSeconds(10)) + + private lateinit var ssoClient: SsoClient + private lateinit var ssoAccessTokenProvider: SsoAccessTokenProvider + private lateinit var sut: SsoCredentialProvider + + @JvmField + @Rule + val applicationRule = ApplicationRule() + + @Before + fun setUp() { + ssoClient = delegateMock() + ssoAccessTokenProvider = mock { + onBlocking { + it.resolveToken() + }.thenReturn( + accessToken + ) + } + sut = SsoCredentialProvider(accountId, roleName, ssoClient, ssoAccessTokenProvider) + } + + @Test + fun cachingDoesNotApplyToExpiredSession() { + createSsoResponse(Instant.now().minusSeconds(5000)) + + assertThat(sut.resolveCredentials()).usingRecursiveComparison().isEqualTo(credentials) + + // Resolve again + assertThat(sut.resolveCredentials()).usingRecursiveComparison().isEqualTo(credentials) + + verify(ssoClient, times(2)).getRoleCredentials(any()) + } + + @Test + fun cachingDoesApplyToExpiredSession() { + createSsoResponse(Instant.now().plus(2, ChronoUnit.HOURS)) + + assertThat(sut.resolveCredentials()).usingRecursiveComparison().isEqualTo(credentials) + + // Resolve again + assertThat(sut.resolveCredentials()).usingRecursiveComparison().isEqualTo(credentials) + + verify(ssoClient).getRoleCredentials(any()) + } + + @Test + fun rejectedAccessTokenInvalidatesIt() { + ssoClient.stub { + on( + ssoClient.getRoleCredentials(any()) + ).thenThrow( + UnauthorizedException.builder().build() + ) + } + + assertThatThrownBy { sut.resolveCredentials() }.isNotNull() + + verify(ssoAccessTokenProvider).invalidate() + } + + private fun createSsoResponse(expirationTime: Instant) { + ssoClient.stub { + on( + ssoClient.getRoleCredentials( + GetRoleCredentialsRequest.builder() + .accessToken(accessTokenId) + .accountId(accountId) + .roleName(roleName) + .build() + ) + ).thenReturn( + GetRoleCredentialsResponse.builder() + .roleCredentials( + RoleCredentials.builder() + .accessKeyId(accessKey) + .secretAccessKey(secretKey) + .sessionToken(sessionToken) + .expiration(expirationTime.toEpochMilli()) + .build() + ) + .build() + ) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderTest.kt new file mode 100644 index 00000000000..a491c23a693 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderTest.kt @@ -0,0 +1,36 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import software.amazon.q.jetbrains.core.credentials.sso.AccessToken +import java.time.Instant + +class BearerTokenProviderTest { + private val sut = { token: AccessToken? -> BearerTokenProvider.state(token) } + + @Test + fun `state is NOT_AUTHENTICATED if there is no token`() { + assertThat(sut(null)).isEqualTo(BearerTokenAuthState.NOT_AUTHENTICATED) + } + + @Test + fun `state is NOT_AUTHENTICATED if expired token doesn't have refresh token`() { + val token = anAccessToken(null, Instant.now().minusSeconds(10)) + assertThat(sut(token)).isEqualTo(BearerTokenAuthState.NOT_AUTHENTICATED) + } + + @Test + fun `state is NEEDS_REFRESH if expired token has refresh token`() { + val token = anAccessToken(expiresAt = Instant.now().minusSeconds(10)) + assertThat(sut(token)).isEqualTo(BearerTokenAuthState.NEEDS_REFRESH) + } + + @Test + fun `state is AUTHORIZED if token is currently valid`() { + val token = anAccessToken(null, Instant.now().plusSeconds(10)) + assertThat(sut(token)).isEqualTo(BearerTokenAuthState.AUTHORIZED) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt new file mode 100644 index 00000000000..9d481606132 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt @@ -0,0 +1,17 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken +import software.aws.toolkits.core.region.aRegionId +import software.aws.toolkits.core.utils.test.aString +import java.time.Instant + +fun anAccessToken(refreshToken: String? = aString(), expiresAt: Instant) = DeviceAuthorizationGrantToken( + startUrl = aString(), + region = aRegionId(), + accessToken = aString(), + refreshToken = refreshToken, + expiresAt = expiresAt +) diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt new file mode 100644 index 00000000000..018e379f025 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt @@ -0,0 +1,355 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.RuleChain +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.spy +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.core.exception.SdkException +import software.amazon.awssdk.core.interceptor.Context +import software.amazon.awssdk.core.interceptor.ExecutionAttributes +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor +import software.amazon.awssdk.core.internal.InternalCoreExecutionAttribute +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.awssdk.services.ssooidc.model.AccessDeniedException +import software.amazon.awssdk.services.ssooidc.model.CreateTokenRequest +import software.amazon.awssdk.services.ssooidc.model.CreateTokenResponse +import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.AccessTokenCacheKey +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationClientRegistration +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationClientRegistrationCacheKey +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken +import software.amazon.q.jetbrains.core.credentials.sso.DeviceGrantAccessTokenCacheKey +import software.amazon.q.jetbrains.core.credentials.sso.DiskCache +import software.amazon.q.jetbrains.core.credentials.sso.PKCEAccessTokenCacheKey +import software.aws.toolkits.core.region.aRegionId +import software.aws.toolkits.core.utils.test.aString +import java.time.Clock +import java.time.Instant +import java.time.temporal.ChronoUnit + +class InteractiveBearerTokenProviderTest { + val applicationRule = ApplicationRule() + val mockClientManager = MockClientManagerRule() + + @Rule + @JvmField + val ruleChain = RuleChain( + applicationRule, + mockClientManager + ) + + @Rule + @JvmField + val disposableRule = DisposableRule() + + private lateinit var oidcClient: SsoOidcClient + private val diskCache = mock() + private val startUrl = aString() + private val region = aRegionId() + private val scopes = listOf("scope1", "scope2") + + @Before + fun setUp() { + oidcClient = mockClientManager.create() + } + + @After + fun tearDown() { + oidcClient.close() + } + + @Test + fun `oidcClient does not retry on InvalidGrantException failure`() { + fun verifyRetryAttempts(configuration: ClientOverrideConfiguration.Builder) { + configuration.addExecutionInterceptor( + object : ExecutionInterceptor { + override fun onExecutionFailure(context: Context.FailedExecution?, executionAttributes: ExecutionAttributes?) { + super.onExecutionFailure(context, executionAttributes) + + assertThat(executionAttributes?.getAttribute(InternalCoreExecutionAttribute.EXECUTION_ATTEMPT)).isEqualTo(1) + } + } + ) + } + fun buildUnmanagedSsoOidcClientForTests(region: String): SsoOidcClient = + AwsClientManager.getInstance() + .createUnmanagedClient( + AnonymousCredentialsProvider.create(), + Region.of(region), + clientCustomizer = { _, _, _, _, configuration -> + verifyRetryAttempts(ssoOidcClientConfigurationBuilder(configuration)) + } + ) + + MockClientManager.useRealImplementations(disposableRule.disposable) + oidcClient.close() + oidcClient = spy(buildUnmanagedSsoOidcClientForTests("us-east-1")) + val registerClientResponse = oidcClient.registerClient { + it.clientType("public") + it.scopes(scopes) + it.clientName("test") + } + val deviceAuthorizationResponse = oidcClient.startDeviceAuthorization { + it.clientId(registerClientResponse.clientId()) + it.clientSecret(registerClientResponse.clientSecret()) + it.startUrl(SONO_URL) + } + assertThrows { + oidcClient.createToken { + it.clientId(registerClientResponse.clientId()) + it.clientSecret(registerClientResponse.clientSecret()) + it.deviceCode(deviceAuthorizationResponse.deviceCode() + "invalid") + it.grantType("urn:ietf:params:oauth:grant-type:device_code") + } + } + } + + @Test + fun `oidcClient has detailed error message on InvalidGrantException failure`() { + fun buildUnmanagedSsoOidcClientForTests(region: String): SsoOidcClient = + AwsClientManager.getInstance() + .createUnmanagedClient( + AnonymousCredentialsProvider.create(), + Region.of(region), + clientCustomizer = { _, _, _, _, configuration -> + ssoOidcClientConfigurationBuilder(configuration) + } + ) + + MockClientManager.useRealImplementations(disposableRule.disposable) + oidcClient.close() + oidcClient = spy(buildUnmanagedSsoOidcClientForTests("us-east-1")) + val exception = assertThrows { + oidcClient.createToken { + it.clientId("test") + it.clientSecret("test") + it.deviceCode("invalid for test") + it.grantType("urn:ietf:params:oauth:grant-type:device_code") + } + } + + assertThat(exception.message) + .isEqualTo("invalid_grant: Invalid device code provided (Service: SsoOidc, Status Code: 400, Request ID: ${exception.requestId()})") + } + + @Test + fun `resolveToken doesn't refresh if token was retrieved recently`() { + stubClientRegistration() + whenever(diskCache.loadAccessToken(any())).thenReturn( + DeviceAuthorizationGrantToken( + startUrl = startUrl, + region = region, + accessToken = "accessToken", + refreshToken = "refreshToken", + expiresAt = Instant.now().plus(1, ChronoUnit.HOURS) + ) + ) + val sut = buildSut() + sut.resolveToken() + } + + @Test + fun `resolveToken attempts to refresh token on first invoke if expired`() { + stubClientRegistration() + stubAccessToken() + whenever(diskCache.loadAccessToken(any())).thenReturn( + DeviceAuthorizationGrantToken( + startUrl = startUrl, + region = region, + accessToken = "accessToken", + refreshToken = "refreshToken", + expiresAt = Instant.now() + ) + ) + val sut = buildSut() + sut.resolveToken() + + verify(oidcClient).createToken(any()) + } + + @Test + fun `resolveToken refreshes on subsequent invokes if expired`() { + val mockClock = mock() + whenever(mockClock.instant()).thenReturn(Instant.now()) + stubClientRegistration() + stubAccessToken() + whenever(diskCache.loadAccessToken(any())).thenReturn( + DeviceAuthorizationGrantToken( + startUrl = startUrl, + region = region, + accessToken = "accessToken", + refreshToken = "refreshToken", + expiresAt = Instant.now().plus(1, ChronoUnit.HOURS) + ) + ) + val sut = buildSut(mockClock) + // current token should be valid + assertThat(sut.resolveToken().accessToken).isEqualTo("accessToken") + verify(oidcClient, times(0)).createToken(any()) + + // then if we advance the clock it should refresh + whenever(mockClock.instant()).thenReturn(Instant.now().plus(100, ChronoUnit.DAYS)) + assertThat(sut.resolveToken().accessToken).isEqualTo("access1") + verify(oidcClient, times(1)).createToken(any()) + } + + @Test + fun `resolveToken throws if reauthentication is needed`() { + stubClientRegistration() + stubAccessToken() + reset(oidcClient) + whenever(oidcClient.createToken(any())).thenThrow(AccessDeniedException.create("denied", null)) + + val sut = buildSut() + assertThrows { sut.resolveToken() } + } + + @Test + fun `invalidate notifies listeners of update`() { + val mockListener = mock() + val conn = ApplicationManager.getApplication().messageBus.connect() + conn.subscribe(BearerTokenProviderListener.TOPIC, mockListener) + + stubClientRegistration() + stubAccessToken() + val sut = buildSut() + sut.invalidate() + + verify(mockListener).onProviderChange(sut.id) + } + + @Test + fun `invalidate clears correctly`() { + stubClientRegistration() + stubAccessToken() + val sut = buildSut() + whenever(diskCache.loadAccessToken(any())).thenReturn(null) + sut.invalidate() + + // initial load + // invalidate attempts to reload token from disk + verify(diskCache, times(2)).loadAccessToken(any()) + verify(diskCache).loadAccessToken(any()) + verify(diskCache).invalidateClientRegistration(region) + verify(diskCache).invalidateAccessToken(startUrl) + + // clears out on-disk token + verify(diskCache, times(2)).invalidateAccessToken( + argThat { + when (this) { + is DeviceGrantAccessTokenCacheKey -> startUrl == this.startUrl && scopes == this.scopes + is PKCEAccessTokenCacheKey -> startUrl == this.issuerUrl && scopes == this.scopes + } + } + ) + + // nothing else + verifyNoMoreInteractions(diskCache) + + // should not have a token now + assertThat(sut.currentToken()?.accessToken).isNull() + assertThrows { sut.resolveToken() } + } + + @Test + fun `reauthenticate updates current token`() { + stubClientRegistration() + stubAccessToken() + val sut = buildSut() + + assertThat(sut.resolveToken().accessToken).isEqualTo("access1") + + // and now instead of trying to stub out the entire OIDC device flow, abuse the fact that we short-circuit and read from disk if available + reset(diskCache) + whenever(diskCache.loadAccessToken(any())).thenReturn( + DeviceAuthorizationGrantToken( + startUrl = startUrl, + region = region, + accessToken = "access1234", + refreshToken = "refresh1234", + expiresAt = Instant.MAX + ) + ) + sut.reauthenticate() + + assertThat(sut.resolveToken().accessToken).isEqualTo("access1234") + } + + @Test + fun `reauthenticate notifies listeners of update`() { + val mockListener = mock() + val conn = ApplicationManager.getApplication().messageBus.connect() + conn.subscribe(BearerTokenProviderListener.TOPIC, mockListener) + + stubClientRegistration() + stubAccessToken() + val sut = buildSut() + sut.reauthenticate() + + // once for invalidate, once after the token has been retrieved + verify(mockListener, times(2)).onProviderChange(sut.id) + } + + private fun buildSut(clock: Clock = Clock.systemUTC()) = InteractiveBearerTokenProvider( + startUrl = startUrl, + region = region, + scopes = scopes, + cache = diskCache, + id = "test", + clock = clock, + ) + + private fun stubClientRegistration() { + whenever(diskCache.loadClientRegistration(any(), any())).thenReturn( + DeviceAuthorizationClientRegistration( + "", + "", + Instant.MAX + ) + ) + } + + private fun stubAccessToken() { + whenever(diskCache.loadAccessToken(any())).thenReturn( + DeviceAuthorizationGrantToken( + startUrl = startUrl, + region = region, + accessToken = "accessToken", + refreshToken = "refreshToken", + expiresAt = Instant.now().minus(100, ChronoUnit.DAYS), + ) + ) + whenever(oidcClient.createToken(any())).thenReturn( + CreateTokenResponse.builder() + .refreshToken("refresh1") + .accessToken("access1") + .expiresIn(1800) + .build() + ) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt new file mode 100644 index 00000000000..9ea76912984 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt @@ -0,0 +1,115 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso.bearer + +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.RuleChain +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.BeforeClass +import org.junit.ClassRule +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.mockito.kotlin.verifyNoInteractions +import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.aws.toolkits.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.utils.createParentDirectories +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.toHexString +import software.amazon.q.core.utils.writeText +import java.nio.file.Path +import java.security.MessageDigest +import java.time.Instant +import java.time.format.DateTimeFormatter + +class ProfileSdkTokenProviderWrapperTest { + val applicationRule = ApplicationRule() + val mockClientManager = MockClientManagerRule() + + @Rule + @JvmField + val ruleChain = RuleChain( + applicationRule, + mockClientManager + ) + + private lateinit var oidcClient: SsoOidcClient + + companion object { + lateinit var testHomeDir: Path + + @ClassRule + @JvmField + val envVarManager = EnvironmentVariableHelper() + + @ClassRule + @JvmField + val tempDirRule = TemporaryFolder() + + @BeforeClass + @JvmStatic + fun beforeClass() { + testHomeDir = tempDirRule.newFolder().toPath().toAbsolutePath() + envVarManager.set("HOME", testHomeDir.toString()) + envVarManager.set("USERPROFILE", testHomeDir.toString()) + } + } + + @Before + fun setUp() { + oidcClient = mockClientManager.create() + } + + @Test + @Ignore("is flaky") + fun `currentToken retrieves from expected location`() { + val session = aString() + val sut = ProfileSdkTokenProviderWrapper(sessionName = session, region = "us-east-1") + writeToken(session, expiry = Instant.now().plusSeconds(9000)) + + assertThat(sut.currentToken()).isNotNull() + } + + @Test + fun `retrieving nonexistent current token doesn't make SDK calls`() { + val session = aString() + val sut = ProfileSdkTokenProviderWrapper(sessionName = session, region = "us-east-1") + + assertThat(sut.currentToken()).isNull() + verifyNoInteractions(oidcClient) + } + + @Test + fun `retrieving expired current token doesn't make SDK calls`() { + val session = aString() + val sut = ProfileSdkTokenProviderWrapper(sessionName = session, region = "us-east-1") + writeToken(session, Instant.now().minusSeconds(9000)) + + assertThat(sut.currentToken()).isNotNull() + verifyNoInteractions(oidcClient) + } + + private fun writeToken(session: String, expiry: Instant = Instant.now()) { + val tokenPath = testHomeDir + .resolve(Path.of(".aws", "sso", "cache")) + .resolve(MessageDigest.getInstance("SHA-1").digest(session.toByteArray()).toHexString() + ".json") + tokenPath.createParentDirectories() + tokenPath.writeText( + // doesn't match what SDK actually uses, but we only care about a subset of fields at the moment + // language=JSON + """ + { + "accessToken": "anAccessToken", + "expiresAt": "${DateTimeFormatter.ISO_INSTANT.format(expiry)}", + "refreshToken": "aRefreshToken", + "region": "aRegion", + "startUrl": "aStartUrl" + } + """.trimIndent() + ) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/gettingstarted/SourceOfEntryTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/gettingstarted/SourceOfEntryTest.kt new file mode 100644 index 00000000000..8b00a2c2f85 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/gettingstarted/SourceOfEntryTest.kt @@ -0,0 +1,20 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.gettingstarted + +import org.assertj.core.api.Assertions.assertThat +import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry +import kotlin.test.Test + +class SourceOfEntryTest { + @Test + fun `Camel case is returned if string contains an underscore`() { + assertThat(SourceOfEntry.RESOURCE_EXPLORER.toString()).isEqualTo("resourceExplorer") + } + + @Test + fun `Camel case is not returned if string doesn't contain an underscore`() { + assertThat(SourceOfEntry.EXPLORER.toString()).isEqualTo("explorer") + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationDismissalStateTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationDismissalStateTest.kt new file mode 100644 index 00000000000..14330230332 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationDismissalStateTest.kt @@ -0,0 +1,84 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.Instant +import java.time.temporal.ChronoUnit + +class NotificationDismissalStateTest { + private lateinit var state: NotificationDismissalState + + @BeforeEach + fun setUp() { + state = NotificationDismissalState() + } + + @Test + fun `notifications less than 2 months old are not removed`() { + val recentNotification = DismissedNotification( + id = "recent-notification", + dismissedAt = Instant.now().minus(30, ChronoUnit.DAYS).toEpochMilli().toString() + ) + + state.loadState(NotificationDismissalConfiguration(mutableSetOf(recentNotification))) + + val persistedState = state.getState() + + assertThat(persistedState.dismissedNotifications) + .singleElement() + .extracting(DismissedNotification::id) + .isEqualTo("recent-notification") + + assertThat(state.isDismissed("recent-notification")).isTrue() + } + + @Test + fun `notifications older than 2 months are removed`() { + val oldNotification = DismissedNotification( + id = "old-notification", + dismissedAt = Instant.now().minus(61, ChronoUnit.DAYS).toEpochMilli().toString() + ) + + state.loadState(NotificationDismissalConfiguration(mutableSetOf(oldNotification))) + + val persistedState = state.getState() + + assertThat(persistedState.dismissedNotifications).isEmpty() + assertThat(state.isDismissed("old-notification")).isFalse() + } + + @Test + fun `mixed age notifications are handled correctly`() { + val recentNotification = DismissedNotification( + id = "recent-notification", + dismissedAt = Instant.now().minus(30, ChronoUnit.DAYS).toEpochMilli().toString() + ) + val oldNotification = DismissedNotification( + id = "old-notification", + dismissedAt = Instant.now().minus(61, ChronoUnit.DAYS).toEpochMilli().toString() + ) + + state.loadState( + NotificationDismissalConfiguration( + mutableSetOf(recentNotification, oldNotification) + ) + ) + + val persistedState = state.getState() + + assertThat(persistedState.dismissedNotifications).hasSize(1) + assertThat(state.isDismissed("recent-notification")).isTrue() + assertThat(state.isDismissed("old-notification")).isFalse() + } + + @Test + fun `dismissing new notification retains it`() { + state.dismissNotification("new-notification") + + assertThat(state.isDismissed("new-notification")).isTrue() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt new file mode 100644 index 00000000000..cf60a49d436 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -0,0 +1,156 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.ProjectRule +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.inputStream +import java.io.InputStream +import java.nio.file.Paths +import java.util.stream.Stream + +@ExtendWith(ApplicationExtension::class) +class NotificationFormatUtilsTest { + @Rule + @JvmField + val projectRule = ProjectRule() + + private lateinit var mockSystemDetails: SystemDetails + private lateinit var mockSystemDetailsWithNoPlugin: SystemDetails + private lateinit var exampleNotification: InputStream + + @BeforeEach + fun setUp() { + mockSystemDetails = SystemDetails( + computeType = "Local", + computeArchitecture = "x86_64", + osType = "Linux", + osVersion = "5.4.0", + ideType = "IC", + ideVersion = "2023.1", + pluginVersions = mapOf( + "aws.toolkit" to "1.0", + "amazon.q" to "2.0" + ) + ) + + mockSystemDetailsWithNoPlugin = SystemDetails( + computeType = "Local", + computeArchitecture = "x86_64", + osType = "Linux", + osVersion = "5.4.0", + ideType = "IC", + ideVersion = "2023.1", + pluginVersions = mapOf( + "aws.toolkit" to "1.0", + ) + ) + + exampleNotification = javaClass.getResource("/exampleNotification2.json")?.let { + Paths.get(it.toURI()).takeIf { f -> f.exists() } + }?.inputStream() ?: throw RuntimeException("Test not found") + + mockkStatic("software.amazon.q.jetbrains.core.notifications.RulesEngineKt") + every { getCurrentSystemAndConnectionDetails() } returns mockSystemDetails + every { getConnectionDetailsForFeature(projectRule.project, BearerTokenFeatureSet.Q) } returns FeatureAuthDetails( + "Idc", + "us-west-2", + "Connected" + ) + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `test System Details`() { + val result = getCurrentSystemAndConnectionDetails() + assertThat(mockSystemDetails).isEqualTo(result) + } + + @Test + fun `check Json Validity which has all the required fields`() { + assertDoesNotThrow { + mapper.readValue(exampleNotification) + } + } + + @Test + fun `No schema version associated with the notification file throws an exception`() { + assertThrows { + mapper.readValue(exampleNotificationWithoutSchema) + } + } + + @Test + fun `No notifications present with the version file does not throw an exception`() { + assertDoesNotThrow { + mapper.readValue(exampleNotificationWithoutNotification) + } + } + + @Test + fun `If plugin is not present, notification is not shown`() { + every { getCurrentSystemAndConnectionDetails() } returns mockSystemDetailsWithNoPlugin + val shouldShow = RulesEngine.displayNotification(projectRule.project, pluginNotPresentData) + assertThat(shouldShow).isFalse + } + + @ParameterizedTest + @MethodSource("validNotifications") + fun `The notification is shown`(notification: String, expectedData: NotificationData) { + val notificationData = mapper.readValue(notification) + assertThat(notificationData).isEqualTo(expectedData) + val shouldShow = RulesEngine.displayNotification(projectRule.project, notificationData) + assertThat(shouldShow).isTrue + } + + @ParameterizedTest + @MethodSource("invalidNotifications") + fun `The notification is not shown`(notification: String, expectedData: NotificationData) { + val notificationData = mapper.readValue(notification) + assertThat(notificationData).isEqualTo(expectedData) + val shouldShow = RulesEngine.displayNotification(projectRule.project, notificationData) + assertThat(shouldShow).isFalse + } + + companion object { + @JvmStatic + fun validNotifications(): Stream = Stream.of( + Arguments.of(notificationWithConditionsOrActions, notificationWithConditionsOrActionsData), + Arguments.of(notificationWithoutConditionsOrActions, notificationsWithoutConditionsOrActionsData), + Arguments.of(notificationWithValidConnection, notificationWithValidConnectionData) + ) + + @JvmStatic + fun invalidNotifications(): Stream = Stream.of( + Arguments.of(validComputeInvalidOs, validOsInvalidComputeData), + Arguments.of(invalidExtensionVersion, invalidExtensionVersionData), + Arguments.of(invalidIdeTypeAndVersion, invalidIdeTypeAndVersionData) + ) + + private val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt new file mode 100644 index 00000000000..ebafb1d48a1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt @@ -0,0 +1,388 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +val validComputeInvalidOs = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "compute": { + "type": {"==": "Local"} +}, +"os": { + "type": {"==": "Windows"} +} +}, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + } + ], + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val validOsInvalidComputeData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = ComputeType(type = NotificationExpression.ComparisonCondition("Local"), architecture = null), + os = SystemType(type = NotificationExpression.ComparisonCondition("Windows"), version = null), + ide = null, + extension = null, + authx = null + ), + actions = listOf( + NotificationFollowupActions( + type = "ShowMarketplace", + content = NotificationFollowupActionsContent( + NotificationActionDescription( + title = "Go to market", + url = null + ) + ) + ) + ), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + +val invalidExtensionVersion = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "extension": [ + { + "id": "aws.toolkit", + "version": { + "!=": "1.3334" + } + }, + { + "id": "amazon.q", + "version": { + ">": "3.37.0" + } + } + ] +}, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val invalidExtensionVersionData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = null, + extension = listOf( + ExtensionType( + id = "aws.toolkit", + version = NotificationExpression.NotEqualsCondition("1.3334") + ), + ExtensionType( + id = "amazon.q", + version = NotificationExpression.GreaterThanCondition("3.37.0") + ) + ), + authx = null + + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + +val exampleNotificationWithoutSchema = """ + { + "notifications": [ + { + "id": "notification-001", + "title": "Test Notification", + "description": "This is a test notification", + "type": "INFO", + + "rules": { + "computeType": "Local", + "osType": "Linux", + "ideType": "IC", + "pluginVersion": { + "aws.toolkit": "1.0" + } + } + } + ] + } +""".trimIndent() + +val exampleNotificationWithoutNotification = """ + { + "schema": { + "version": "2.0" +} + + } +""".trimIndent() + +val notificationWithoutConditionsOrActions = """ + { + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + } + } + + +""".trimIndent() + +val notificationsWithoutConditionsOrActionsData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = null, + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + +val notificationWithConditionsOrActions = """ + { + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "condition": { + "compute": { + "type": { + + "==": "Local" + + } + } + }, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + } + ], + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + } + } + +""".trimIndent() + +val notificationWithConditionsOrActionsData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = ComputeType(type = NotificationExpression.ComparisonCondition("Local"), architecture = null), + os = null, + ide = null, + extension = null, + authx = null + ), + actions = listOf( + NotificationFollowupActions( + type = "ShowMarketplace", + content = NotificationFollowupActionsContent( + NotificationActionDescription( + title = "Go to market", + url = null + ) + ) + ) + ), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + +val notificationWithValidConnection = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "authx": [{ + "feature" : "q", + "type": { + "anyOf": [ + "Idc", + "BuilderId" + ] + }, + "region": { + "==": "us-west-2" + }, + "connectionState": { + "==": "Connected" + } + } ] +}, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val notificationWithValidConnectionData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = null, + extension = null, + authx = listOf( + AuthxType( + feature = "q", + type = NotificationExpression.AnyOfCondition(listOf("Idc", "BuilderId")), + region = NotificationExpression.ComparisonCondition("us-west-2"), + connectionState = NotificationExpression.ComparisonCondition("Connected"), + ssoScopes = null + ) + ) + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + +val invalidIdeTypeAndVersion = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "ide": { + "type": {"noneOf": ["IC","IU","RD"]}, + "version": {"!=": "1.3334"} +} +}, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val invalidIdeTypeAndVersionData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = SystemType( + type = NotificationExpression.NoneOfCondition(listOf("IC", "IU", "RD")), + version = NotificationExpression.NotEqualsCondition("1.3334") + ), + extension = null, + authx = null + + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + +val pluginNotPresentData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = null, + extension = mutableListOf( + ExtensionType( + "amazon.q", + version = NotificationExpression.NotEqualsCondition("1.3334") + ) + ), + authx = null + + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationManagerTest.kt new file mode 100644 index 00000000000..0439f78526f --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationManagerTest.kt @@ -0,0 +1,45 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.ProjectRule +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extension +import org.junit.jupiter.api.extension.RegisterExtension + +@ExtendWith(ApplicationExtension::class) +class NotificationManagerTest { + + val projectRule = ProjectRule() + + @JvmField + @RegisterExtension + val testExtension = object : Extension { + fun getProject() = projectRule.project + } + + @Test + fun `If no follow-up actions, expand action is present`() { + val sut = NotificationManager.createActions(projectRule.project, listOf(), "Dummy Test Action", "Dummy title") + assertThat(sut).isNotNull + assertThat(sut).hasSize(1) + assertThat(sut.first().title).isEqualTo("More...") + } + + @Test + fun `Show Url action shows the option to learn more`() { + val followupActions = NotificationFollowupActions( + "UpdateExtension", + NotificationFollowupActionsContent(NotificationActionDescription("title", null)) + ) + val sut = NotificationManager.createActions(projectRule.project, listOf(followupActions), "Dummy Test Action", "Dummy title") + assertThat(sut).isNotNull + assertThat(sut).hasSize(2) + assertThat(sut.first().title).isEqualTo("Update") + assertThat(sut[1].title).isEqualTo("More...") + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt new file mode 100644 index 00000000000..5b3f9e69351 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt @@ -0,0 +1,83 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.testFramework.ApplicationExtension +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider +import software.amazon.q.core.utils.RemoteResourceResolver +import software.amazon.q.core.utils.UpdateCheckResult +import java.nio.file.Path +import java.util.concurrent.CompletableFuture + +@ExtendWith(ApplicationExtension::class) +class NotificationPollingServiceTest { + private lateinit var sut: NotificationPollingService + private lateinit var mockResolver: RemoteResourceResolver + private lateinit var mockProvider: RemoteResourceResolverProvider + private lateinit var observer: () -> Unit + private val testPath = Path.of("/test/path") + + @BeforeEach + fun setUp() { + sut = NotificationPollingService() + + mockResolver = mockk { + every { resolve(any()) } returns CompletableFuture.completedFuture(testPath) + } + + mockProvider = mockk { + every { get() } returns mockResolver + } + + val providerField = NotificationPollingService::class.java + .getDeclaredField("resourceResolver") + providerField.isAccessible = true + providerField.set(sut, mockProvider) + + // Create mock observers + observer = mockk<() -> Unit>() + every { observer.invoke() } just Runs + + val observersField = NotificationPollingService::class.java + .getDeclaredField("observers") + .apply { isAccessible = true } + + observersField.set(sut, mutableListOf(observer)) + } + + @AfterEach + fun tearDown() { + sut.dispose() + } + + @Test + fun `test pollForNotifications when ETag matches - no new notifications`() { + every { mockResolver.checkForUpdates(any(), any()) } returns UpdateCheckResult.NoUpdates + sut.startPolling() + verify(exactly = 0) { observer.invoke() } + } + + @Test + fun `test pollForNotifications when ETag matches on startup - notify observers`() { + every { mockResolver.checkForUpdates(any(), any()) } returns UpdateCheckResult.FirstPollCheck + sut.startPolling() + verify(exactly = 1) { observer.invoke() } + } + + @Test + fun `test pollForNotifications when ETag different - notify observers`() { + every { mockResolver.checkForUpdates(any(), any()) } returns UpdateCheckResult.HasUpdates + sut.startPolling() + verify(exactly = 1) { observer.invoke() } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationResourceResolverTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationResourceResolverTest.kt new file mode 100644 index 00000000000..7d0455c3de8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationResourceResolverTest.kt @@ -0,0 +1,78 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.testFramework.ApplicationExtension +import io.mockk.every +import io.mockk.mockk +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.io.TempDir +import software.amazon.q.core.utils.DefaultRemoteResourceResolver +import software.amazon.q.core.utils.UpdateCheckResult +import software.amazon.q.core.utils.UrlFetcher +import java.nio.file.Path +import java.util.concurrent.Callable +import java.util.concurrent.CompletableFuture + +@ExtendWith(ApplicationExtension::class) +class NotificationResourceResolverTest { + private lateinit var urlFetcher: UrlFetcher + private lateinit var sut: DefaultRemoteResourceResolver + + @TempDir + lateinit var tempDir: Path + + @BeforeEach + fun setUp() { + urlFetcher = mockk() + sut = DefaultRemoteResourceResolver( + urlFetcher = urlFetcher, + cacheBasePath = tempDir, + executor = { callable: Callable -> CompletableFuture.completedFuture(callable.call()) } + ) + } + + @Test + fun `first poll with no ETag changes returns FirstPollCheck`() { + NotificationEtagState.getInstance().etag = "same-etag" + val expectedETag = "same-etag" + every { urlFetcher.getETag(any()) } returns expectedETag + + val result = sut.checkForUpdates("http://notification.test", NotificationEtagState.getInstance()) + assertThat(result).isEqualTo(UpdateCheckResult.FirstPollCheck) + } + + @Test + fun `ETag changes returns HasUpdates`() { + NotificationEtagState.getInstance().etag = "old-etag" + val expectedETag = "new-etag" + every { urlFetcher.getETag(any()) } returns expectedETag + + val result = sut.checkForUpdates("http://notification.test", NotificationEtagState.getInstance()) + assertThat(result).isEqualTo(UpdateCheckResult.HasUpdates) + } + + @Test + fun `no ETag changes returns NoUpdates after first poll`() { + NotificationEtagState.getInstance().etag = "same-etag" + val expectedETag = "same-etag" + every { urlFetcher.getETag(any()) } returns expectedETag + + // sets isFirstPoll to false + val firstResult = sut.checkForUpdates("http://notification.test", NotificationEtagState.getInstance()) + assertThat(firstResult).isEqualTo(UpdateCheckResult.FirstPollCheck) + + val secondResult = sut.checkForUpdates("http://notification.test", NotificationEtagState.getInstance()) + assertThat(secondResult).isEqualTo(UpdateCheckResult.NoUpdates) + } + + @Test + fun `getLocalResourcePath returns null for non-existent file`() { + val result = sut.getLocalResourcePath("non-existent-file") + assertThat(result).isNull() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBaseTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBaseTest.kt new file mode 100644 index 00000000000..160143d626e --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBaseTest.kt @@ -0,0 +1,161 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.notifications + +import com.intellij.openapi.project.Project +import com.intellij.testFramework.ApplicationExtension +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.spyk +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import java.util.concurrent.atomic.AtomicBoolean + +@ExtendWith(ApplicationExtension::class) +class ProcessNotificationsBaseTest { + private lateinit var sut: ProcessNotificationsBase + private lateinit var project: Project + private lateinit var dismissalState: NotificationDismissalState + + @BeforeEach + fun setUp() { + project = mockk() + dismissalState = spyk(NotificationDismissalState()) + + mockkObject(NotificationDismissalState) + every { NotificationDismissalState.getInstance() } returns dismissalState + + sut = spyk( + objToCopy = ProcessNotificationsBase(project) + ) + } + + @Test + fun `startup notifications are only processed on first poll`() { + resetIsStartup() + val startupNotification = createNotification("startup-1", NotificationScheduleType.STARTUP) + every { sut["getNotificationsFromFile"]() } returns createNotificationsList(startupNotification) + every { dismissalState.isDismissed(any()) } returns false + + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 1) { sut.processNotification(project, startupNotification) } + + // Second poll + sut.retrieveStartupAndEmergencyNotifications() + + // Verify processNotification wasn't called again + verify(exactly = 1) { sut.processNotification(project, any()) } + } + + @Test + fun `non startup notifications are processed on every poll`() { + val emergencyNotification = createNotification("emergency-1", NotificationScheduleType.EMERGENCY) + every { sut["getNotificationsFromFile"]() } returns createNotificationsList(emergencyNotification) + every { dismissalState.isDismissed(any()) } returns false + + // First poll + sut.retrieveStartupAndEmergencyNotifications() + // Second poll + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 2) { sut.processNotification(project, emergencyNotification) } + } + + @Test + fun `dismissed notifications are not processed`() { + val notification = createNotification("toBeDismissed-1", NotificationScheduleType.EMERGENCY) + every { sut["getNotificationsFromFile"]() } returns createNotificationsList(notification) + + // first poll results in showing/dismissal + sut.retrieveStartupAndEmergencyNotifications() + NotificationDismissalState.getInstance().dismissNotification(notification.id) + + // second poll skips processing + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 1) { sut.processNotification(project, any()) } + } + + @Test + fun `null notifications list is handled gracefully`() { + every { sut["getNotificationsFromFile"]() } returns null + + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 0) { sut.processNotification(project, any()) } + } + + @Test + fun `empty notifications list is handled gracefully`() { + every { sut["getNotificationsFromFile"]() } returns createNotificationsList() + + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 0) { sut.processNotification(project, any()) } + } + + @Test + fun `multiple notifications are processed correctly`() { + val startupNotification = createNotification("startup-1", NotificationScheduleType.STARTUP) + val emergencyNotification = createNotification("emergency-1", NotificationScheduleType.EMERGENCY) + + every { sut["getNotificationsFromFile"]() } returns createNotificationsList( + startupNotification, + emergencyNotification + ) + every { dismissalState.isDismissed(any()) } returns false + + // First poll - both should be processed + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 1) { sut.processNotification(project, startupNotification) } + verify(exactly = 1) { sut.processNotification(project, emergencyNotification) } + + // Second poll - only emergency should be processed + sut.retrieveStartupAndEmergencyNotifications() + + verify(exactly = 1) { sut.processNotification(project, startupNotification) } + verify(exactly = 2) { sut.processNotification(project, emergencyNotification) } + } + + // Helper functions to create test data + private fun createNotification(id: String, type: NotificationScheduleType) = NotificationData( + id = id, + schedule = NotificationSchedule(type = type), + severity = "INFO", + condition = null, + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ), + actions = emptyList() + ) + + private fun createNotificationsList(vararg notifications: NotificationData) = NotificationsList( + schema = Schema("1.0"), + notifications = notifications.toList() + ) + + private fun resetIsStartup() { + val clazz = Class.forName("software.amazon.q.jetbrains.core.notifications.ProcessNotificationsBaseKt") + val field = clazz.getDeclaredField("isStartup") + field.isAccessible = true + + val value = field.get(null) as AtomicBoolean + value.set(true) + } + + @AfterEach + fun tearDown() { + unmockkAll() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt new file mode 100644 index 00000000000..d106937a048 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt @@ -0,0 +1,124 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.plugin + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.updateSettings.impl.PluginDownloader +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.RuleChain +import io.mockk.every +import io.mockk.mockkObject +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import software.amazon.q.jetbrains.AwsToolkit.TOOLKIT_PLUGIN_ID +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.core.utils.tryOrNull + +class PluginUpdateManagerTest { + val applicationRule = ApplicationRule() + val disposableRule = DisposableRule() + + @Rule + @JvmField + val ruleChain = RuleChain(applicationRule, disposableRule) + + private lateinit var sut: PluginUpdateManager + private val testIdeaPluginDescriptorToolkit = getPluginDescriptorForIdAndVersion(TOOLKIT_PLUGIN_ID, "1.84") + private var isAutoUpdateEnabledDefault: Boolean = false + + @Before + fun setup() { + sut = PluginUpdateManager().let { + Disposer.register(disposableRule.disposable, it) + spy(it) + } + + val downloaderSpy = mock() + downloaderSpy.stub { + onGeneric { + id + } doAnswer { testIdeaPluginDescriptorToolkit.pluginId } + onGeneric { + pluginVersion + } doAnswer { testIdeaPluginDescriptorToolkit.version } + onGeneric { + install() + } doAnswer {} + } + + mockkObject(PluginUpdateManager.Companion) + every { + PluginUpdateManager.getUpdateInfo() + } returns listOf(downloaderSpy) + + isAutoUpdateEnabledDefault = AwsSettings.getInstance().isAutoUpdateEnabled + } + + @After + fun teardown() { + tryOrNull { + AwsSettings.getInstance().isAutoUpdateEnabled = isAutoUpdateEnabledDefault + } + } + + @Test + fun `test getUpdate() should return null if aws toolkit download is not found`() { + val testPluginDescriptor = getPluginDescriptorForIdAndVersion("test", "1.0") + assertThat(PluginUpdateManager.getUpdate(testPluginDescriptor)).isNull() + } + + @Test + fun `test getUpdate() should return null if current version is same or newer`() { + var testPluginDescriptorCurrentVersion = getPluginDescriptorForIdAndVersion(TOOLKIT_PLUGIN_ID, "1.84") + assertThat(PluginUpdateManager.getUpdate(testPluginDescriptorCurrentVersion)).isNull() + testPluginDescriptorCurrentVersion = getPluginDescriptorForIdAndVersion(TOOLKIT_PLUGIN_ID, "1.85") + assertThat(PluginUpdateManager.getUpdate(testPluginDescriptorCurrentVersion)).isNull() + } + + @Test + fun `test getUpdate() should return toolkit if current version is older`() { + val testPluginDescriptorCurrentVersion = getPluginDescriptorForIdAndVersion(TOOLKIT_PLUGIN_ID, "1.83") + val update = PluginUpdateManager.getUpdate(testPluginDescriptorCurrentVersion) + assertThat(update).isNotNull + assertThat(update?.pluginVersion).isEqualTo("1.84") + assertThat(update?.id.toString()).isEqualTo(TOOLKIT_PLUGIN_ID) + } + + @Test + fun `test auto update feature respects user setting`() { + AwsSettings.getInstance().isAutoUpdateEnabled = false + sut.scheduleAutoUpdate() + runInEdt { + verify(sut, never()).checkForUpdates(any(), any()) + } + + AwsSettings.getInstance().isAutoUpdateEnabled = true + sut.scheduleAutoUpdate() + runInEdt { + verify(sut).checkForUpdates(any(), any()) + } + } + + private fun getPluginDescriptorForIdAndVersion(id: String, version: String): IdeaPluginDescriptor { + val mockDescriptor = mock() + whenever(mockDescriptor.version).thenReturn(version) + whenever(mockDescriptor.pluginId).thenReturn(PluginId.getId(id)) + return mockDescriptor + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt new file mode 100644 index 00000000000..0f897c0d3a1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt @@ -0,0 +1,190 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.testFramework.ApplicationRule +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials +import software.amazon.awssdk.services.cognitoidentity.CognitoIdentityClient +import software.amazon.awssdk.services.cognitoidentity.model.Credentials +import software.amazon.awssdk.services.cognitoidentity.model.GetCredentialsForIdentityRequest +import software.amazon.awssdk.services.cognitoidentity.model.GetCredentialsForIdentityResponse +import software.amazon.awssdk.services.cognitoidentity.model.GetIdRequest +import software.amazon.awssdk.services.cognitoidentity.model.GetIdResponse +import software.amazon.q.core.telemetry.CachedIdentityStorage +import software.aws.toolkits.core.utils.delegateMock +import java.time.Instant +import java.time.temporal.ChronoUnit + +class AwsCognitoCredentialsProviderTest { + @Rule + @JvmField + val application = ApplicationRule() + + private val cognitoClient = delegateMock() + private val storage = mock() + private val getCredentialsRequestCaptor = argumentCaptor() + private val getIdRequestCaptor = argumentCaptor() + + @Test + fun testGetCredentials() { + val getCredentialsResult = GetCredentialsForIdentityResponse.builder() + .credentials(CREDENTIALS) + .build() + + cognitoClient.stub { + on { getId(getIdRequestCaptor.capture()) }.thenReturn(GET_ID_RESULT) + on { getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) }.thenReturn(getCredentialsResult) + } + + val provider = AwsCognitoCredentialsProvider(IDENTITY_POOL_ID, cognitoClient) + val awsCredentials = provider.resolveCredentials() as AwsSessionCredentials + + assertThat(getIdRequestCaptor.firstValue.identityPoolId()).isEqualTo(IDENTITY_POOL_ID) + assertThat(getCredentialsRequestCaptor.firstValue.identityId()).isEqualTo(IDENTITY_ID) + + assertThat(awsCredentials.accessKeyId()).isEqualTo(ACCESS_KEY) + assertThat(awsCredentials.secretAccessKey()).isEqualTo(SECRET_KEY) + assertThat(awsCredentials.sessionToken()).isEqualTo(SESSION_TOKEN) + } + + @Test + fun testGetCredentialsNotExpired() { + val notExpiredCredentials = Credentials.builder() + .accessKeyId(ACCESS_KEY) + .secretKey(SECRET_KEY) + .sessionToken(SESSION_TOKEN) + .expiration(Instant.now().plus(1, ChronoUnit.HOURS)) + .build() + + val getCredentialsResult = GetCredentialsForIdentityResponse.builder() + .credentials(notExpiredCredentials) + .build() + + cognitoClient.stub { + on { getId(getIdRequestCaptor.capture()) }.thenReturn(GET_ID_RESULT) + on { getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) }.thenReturn(getCredentialsResult) + } + + val provider = AwsCognitoCredentialsProvider(IDENTITY_POOL_ID, cognitoClient) + + provider.resolveCredentials() + provider.resolveCredentials() // Try to get them again to check for a refresh + + verify(cognitoClient).getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) + } + + @Test + fun testGetCredentialsExpired() { + val expiredCredentials = Credentials.builder() + .accessKeyId(ACCESS_KEY) + .secretKey(SECRET_KEY) + .sessionToken(SESSION_TOKEN) + .expiration(Instant.now().minus(1, ChronoUnit.HOURS)) + .build() + + val getCredentialsResult = GetCredentialsForIdentityResponse.builder() + .credentials(expiredCredentials) + .build() + + cognitoClient.stub { + on { getId(getIdRequestCaptor.capture()) }.thenReturn(GET_ID_RESULT) + on { getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) }.thenReturn(getCredentialsResult) + } + + val provider = AwsCognitoCredentialsProvider(IDENTITY_POOL_ID, cognitoClient) + + provider.resolveCredentials() + provider.resolveCredentials() + + verify(cognitoClient, times(1)).getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) + } + + @Test + fun testGetCredentialsWithEmptyCache() { + val getCredentialsResult = GetCredentialsForIdentityResponse.builder() + .credentials(CREDENTIALS) + .build() + + storage.stub { + on { loadIdentity(any()) }.thenReturn(null) + } + cognitoClient.stub { + on { getId(getIdRequestCaptor.capture()) }.thenReturn(GET_ID_RESULT) + on { getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) }.thenReturn(getCredentialsResult) + } + + val provider = AwsCognitoCredentialsProvider(IDENTITY_POOL_ID, cognitoClient, storage) + + val awsCredentials = provider.resolveCredentials() as AwsSessionCredentials + + verify(storage).loadIdentity(IDENTITY_POOL_ID) + verify(cognitoClient).getId(getIdRequestCaptor.capture()) + verify(cognitoClient).getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) + verify(storage).storeIdentity(IDENTITY_POOL_ID, IDENTITY_ID) + + assertThat(getIdRequestCaptor.firstValue.identityPoolId()).isEqualTo(IDENTITY_POOL_ID) + assertThat(getCredentialsRequestCaptor.firstValue.identityId()).isEqualTo(IDENTITY_ID) + + assertThat(awsCredentials.accessKeyId()).isEqualTo(ACCESS_KEY) + assertThat(awsCredentials.secretAccessKey()).isEqualTo(SECRET_KEY) + assertThat(awsCredentials.sessionToken()).isEqualTo(SESSION_TOKEN) + } + + @Test + fun testGetCredentialsWithValidCache() { + val getCredentialsResult = GetCredentialsForIdentityResponse.builder() + .credentials(CREDENTIALS) + .build() + + storage.stub { + on { loadIdentity(any()) }.thenReturn(IDENTITY_ID) + } + cognitoClient.stub { + on { getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) }.thenReturn(getCredentialsResult) + } + + val provider = AwsCognitoCredentialsProvider(IDENTITY_POOL_ID, cognitoClient, storage) + val awsCredentials = provider.resolveCredentials() as AwsSessionCredentials + + verify(storage).loadIdentity(IDENTITY_POOL_ID) + verify(cognitoClient).getCredentialsForIdentity(getCredentialsRequestCaptor.capture()) + + assertThat(getCredentialsRequestCaptor.firstValue.identityId()).isEqualTo(IDENTITY_ID) + + assertThat(awsCredentials.accessKeyId()).isEqualTo(ACCESS_KEY) + assertThat(awsCredentials.secretAccessKey()).isEqualTo(SECRET_KEY) + assertThat(awsCredentials.sessionToken()).isEqualTo(SESSION_TOKEN) + } + + @Test + fun closeShutsDownTheClient() { + AwsCognitoCredentialsProvider(IDENTITY_POOL_ID, cognitoClient, storage).close() + + verify(cognitoClient).close() + } + + companion object { + private const val IDENTITY_POOL_ID = "IdentityPoolID" + private const val IDENTITY_ID = "IdentityID" + private const val ACCESS_KEY = "AccessKey" + private const val SECRET_KEY = "SecretKey" + private const val SESSION_TOKEN = "SessionToken" + private val GET_ID_RESULT = GetIdResponse.builder().identityId(IDENTITY_ID).build() + private val CREDENTIALS = Credentials.builder() + .accessKeyId(ACCESS_KEY) + .secretKey(SECRET_KEY) + .sessionToken(SESSION_TOKEN) + .expiration(Instant.now()) + .build() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt new file mode 100644 index 00000000000..5c3ecf525de --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt @@ -0,0 +1,275 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.testFramework.ProjectRule +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.awssdk.services.toolkittelemetry.ToolkitTelemetryClient +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.awssdk.services.toolkittelemetry.model.MetadataEntry +import software.amazon.awssdk.services.toolkittelemetry.model.PostFeedbackRequest +import software.amazon.awssdk.services.toolkittelemetry.model.PostMetricsRequest +import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment +import software.amazon.q.core.telemetry.DefaultMetricEvent +import software.aws.toolkits.core.utils.delegateMock + +class DefaultTelemetryPublisherTest { + @Rule + @JvmField + val projectRule = ProjectRule() + + @Test + fun testPublishWithNamespace() { + val mockPostMetricsRequestCaptor = argumentCaptor() + + val mockTelemetryClient = delegateMock() + val publisher = DefaultTelemetryPublisher( + clientProvider = { mockTelemetryClient }, + clientMetadataProvider = { product, version -> defaultMetadata } + ) + + runBlocking { + publisher.publish( + listOf( + DefaultMetricEvent.builder() + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("foobar") { this.count() } + .build(), + DefaultMetricEvent.builder() + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("spam") { this.count() } + .build() + ) + ) + } + + verify(mockTelemetryClient, times(1)).postMetrics(mockPostMetricsRequestCaptor.capture()) + val postMetricsRequest = mockPostMetricsRequestCaptor.firstValue + + assertThat(postMetricsRequest.awsProduct()).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + assertThat(postMetricsRequest.awsProductVersion()).isEqualTo("1.0") + assertThat(postMetricsRequest.clientID()).isEqualTo("foo") + assertThat(postMetricsRequest.parentProduct()).isEqualTo("JetBrains") + assertThat(postMetricsRequest.parentProductVersion()).isEqualTo("191") + assertThat(postMetricsRequest.os()).isEqualTo("mac") + assertThat(postMetricsRequest.osVersion()).isEqualTo("1.0") + assertThat(postMetricsRequest.metricData()).hasSize(2) + + postMetricsRequest.metricData()[0].let { + assertThat(it.metricName()).isEqualTo("foobar") + assertThat(it.metadata()).contains( + MetadataEntry.builder() + .key("awsAccount") + .value("111111111111") + .build(), + MetadataEntry.builder() + .key("awsRegion") + .value("us-west-2") + .build() + ) + } + + postMetricsRequest.metricData()[1].let { + assertThat(it.metricName()).isEqualTo("spam") + assertThat(it.metadata()).contains( + MetadataEntry.builder() + .key("awsAccount") + .value("111111111111") + .build(), + MetadataEntry.builder() + .key("awsRegion") + .value("us-west-2") + .build() + ) + } + } + + @Test + fun testPublishWithoutNamespace() { + val mockPostMetricsRequestCaptor = argumentCaptor() + + val mockTelemetryClient = delegateMock() + val publisher = DefaultTelemetryPublisher( + clientProvider = { mockTelemetryClient }, + clientMetadataProvider = { product, version -> defaultMetadata } + ) + + runBlocking { + publisher.publish( + listOf( + DefaultMetricEvent.builder() + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("foobar") { this.count() } + .build(), + DefaultMetricEvent.builder() + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("spam") { this.count() } + .build() + ) + ) + } + + verify(mockTelemetryClient, times(1)).postMetrics(mockPostMetricsRequestCaptor.capture()) + val postMetricsRequest = mockPostMetricsRequestCaptor.firstValue + + assertThat(postMetricsRequest.awsProduct()).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + assertThat(postMetricsRequest.awsProductVersion()).isEqualTo("1.0") + assertThat(postMetricsRequest.clientID()).isEqualTo("foo") + assertThat(postMetricsRequest.parentProduct()).isEqualTo("JetBrains") + assertThat(postMetricsRequest.parentProductVersion()).isEqualTo("191") + assertThat(postMetricsRequest.os()).isEqualTo("mac") + assertThat(postMetricsRequest.osVersion()).isEqualTo("1.0") + assertThat(postMetricsRequest.metricData()).hasSize(2) + + postMetricsRequest.metricData()[0].let { + assertThat(it.metricName()).isEqualTo("foobar") + assertThat(it.metadata()).contains( + MetadataEntry.builder() + .key("awsAccount") + .value("111111111111") + .build(), + MetadataEntry.builder() + .key("awsRegion") + .value("us-west-2") + .build() + ) + } + + postMetricsRequest.metricData()[1].let { + assertThat(it.metricName()).isEqualTo("spam") + assertThat(it.metadata()).contains( + MetadataEntry.builder() + .key("awsAccount") + .value("111111111111") + .build(), + MetadataEntry.builder() + .key("awsRegion") + .value("us-west-2") + .build() + ) + } + } + + @Test + fun testPublishMultipleProductsAndVersions() { + val mockPostMetricsRequestCaptor = argumentCaptor() + + val mockTelemetryClient = delegateMock() + val publisher = DefaultTelemetryPublisher( + clientProvider = { mockTelemetryClient }, + clientMetadataProvider = { product, version -> getClientMetadata(product, version) } + ) + + runBlocking { + publisher.publish( + listOf( + DefaultMetricEvent.builder() + .awsProduct(AWSProduct.AMAZON_Q_FOR_JET_BRAINS) + .awsVersion("1.0") + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("foobar") { this.count() } + .build(), + DefaultMetricEvent.builder() + .awsProduct(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + .awsVersion("2.0") + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("spam") { this.count() } + .build(), + DefaultMetricEvent.builder() + .awsProduct(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + .awsVersion("2.0") + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("baz") { this.count() } + .build(), + DefaultMetricEvent.builder() + .awsProduct(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + .awsVersion("3.0") + .awsAccount("111111111111") + .awsRegion("us-west-2") + .datum("random") { this.count() } + .build() + ) + ) + } + + verify(mockTelemetryClient, times(3)).postMetrics(mockPostMetricsRequestCaptor.capture()) + val firstPostMetricsRequest = mockPostMetricsRequestCaptor.firstValue + + assertThat(firstPostMetricsRequest.awsProduct()).isEqualTo(AWSProduct.AMAZON_Q_FOR_JET_BRAINS) + assertThat(firstPostMetricsRequest.awsProductVersion()).isEqualTo("1.0") + assertThat(firstPostMetricsRequest.metricData()).hasSize(1) + + val secondPostMetricsRequest = mockPostMetricsRequestCaptor.secondValue + assertThat(secondPostMetricsRequest.awsProduct()).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + assertThat(secondPostMetricsRequest.awsProductVersion()).isEqualTo("2.0") + assertThat(secondPostMetricsRequest.metricData()).hasSize(2) + + val thirdPostMetricsRequest = mockPostMetricsRequestCaptor.thirdValue + assertThat(thirdPostMetricsRequest.awsProduct()).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + assertThat(thirdPostMetricsRequest.awsProductVersion()).isEqualTo("3.0") + assertThat(thirdPostMetricsRequest.metricData()).hasSize(1) + } + + @Test + fun testSendFeedback() { + val mockPostFeedbackRequest = argumentCaptor() + + val mockTelemetryClient = delegateMock() + val publisher = DefaultTelemetryPublisher( + clientProvider = { mockTelemetryClient }, + clientMetadataProvider = { product, version -> defaultMetadata } + ) + + val metadata = mapOf("foo" to "bar") + + runBlocking { + publisher.sendFeedback( + Sentiment.POSITIVE, + "fooBar", + metadata + ) + } + + verify(mockTelemetryClient, times(1)).postFeedback(mockPostFeedbackRequest.capture()) + val postFeedbackRequest = mockPostFeedbackRequest.firstValue + + assertThat(postFeedbackRequest.awsProduct()).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + assertThat(postFeedbackRequest.awsProductVersion()).isEqualTo("1.0") + assertThat(postFeedbackRequest.parentProduct()).isEqualTo("JetBrains") + assertThat(postFeedbackRequest.parentProductVersion()).isEqualTo("191") + assertThat(postFeedbackRequest.os()).isEqualTo("mac") + assertThat(postFeedbackRequest.osVersion()).isEqualTo("1.0") + assertThat(postFeedbackRequest.sentiment()).isEqualTo(Sentiment.POSITIVE) + assertThat(postFeedbackRequest.comment()).isEqualTo("fooBar") + assertThat(postFeedbackRequest.metadata()).hasSize(1) + assertThat(postFeedbackRequest.metadata().get(0).key()).isEqualTo("foo") + assertThat(postFeedbackRequest.metadata().get(0).value()).isEqualTo("bar") + } + + private val defaultMetadata = getClientMetadata(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS, "1.0") + + private fun getClientMetadata(product: AWSProduct, version: String): ClientMetadata = + ClientMetadata( + awsProduct = product, + awsVersion = version, + clientId = "foo", + parentProduct = "JetBrains", + parentProductVersion = "191", + os = "mac", + osVersion = "1.0" + ) +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt new file mode 100644 index 00000000000..41e8a8360cb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt @@ -0,0 +1,37 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import software.amazon.q.jetbrains.services.telemetry.OpenedFileTypesMetricsService + +class OpenedFileTypeMetricsTest { + + private lateinit var service: OpenedFileTypesMetricsService + + @BeforeEach + fun setup() { + service = OpenedFileTypesMetricsService() + } + + @AfterEach + fun teardown() { + service.dispose() + } + + @Test + fun `test addToExistingTelemetryBatch with allowed extension`() { + service.addToExistingTelemetryBatch("kt") + assertThat(service.getOpenedFileTypes()).contains(".kt") + } + + @Test + fun `test addToExistingTelemetryBatch with disallowed extension`() { + service.addToExistingTelemetryBatch("mp4") + assertThat(service.getOpenedFileTypes()).isEmpty() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt new file mode 100644 index 00000000000..c3b0a0a8bab --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt @@ -0,0 +1,115 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.ide.plugins.PluginManagerCore +import io.mockk.every +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.q.jetbrains.services.telemetry.PluginResolver + +@ExtendWith(MockKExtension::class) +class PluginResolverTest { + @BeforeEach + fun setup() { + PluginResolver.setThreadLocal(null) + mockkStatic(PluginManagerCore::class) + } + + @Test + fun getsProductForAmazonQPlugin() { + val pluginDescriptor = mockk { + every { pluginId.idString } returns "amazon.q" + } + every { PluginManagerCore.getPluginDescriptorOrPlatformByClassName(any()) } returns pluginDescriptor + + val pluginResolver = PluginResolver.fromCurrentThread() + + assertThat(pluginResolver.product).isEqualTo(AWSProduct.AMAZON_Q_FOR_JET_BRAINS) + } + + @Test + fun getsToolkitProductByDefault() { + val pluginDescriptor = mockk { + every { pluginId.idString } returns "amazon.foo" + } + every { PluginManagerCore.getPluginDescriptorOrPlatformByClassName(any()) } returns pluginDescriptor + + val pluginResolver = PluginResolver.fromCurrentThread() + + assertThat(pluginResolver.product).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + } + + @Test + fun getsResolvedVersion() { + val pluginDescriptor = mockk { + every { version } returns "1.2.3" + } + every { PluginManagerCore.getPluginDescriptorOrPlatformByClassName(any()) } returns pluginDescriptor + + val pluginResolver = PluginResolver.fromCurrentThread() + + assertThat(pluginResolver.version).isEqualTo("1.2.3") + } + + @Test + fun getsUnresolvedVersionAsUnknown() { + val pluginDescriptor = mockk { + every { version } returns null + } + every { PluginManagerCore.getPluginDescriptorOrPlatformByClassName(any()) } returns pluginDescriptor + + val pluginResolver = PluginResolver.fromCurrentThread() + + assertThat(pluginResolver.version).isEqualTo("unknown") + } + + @Test + fun stackTraceResolvesExpectedToolkitClass() { + val mockStackTrace = arrayOf( + StackTraceElement("foo", "mockMethod", "mockFile.kt", 1), + StackTraceElement("software.aws.toolkits.core.foo", "mockMethod", "mockFile.kt", 1), + StackTraceElement("software.aws.toolkits.plugins.amazonq.bar", "mockMethod", "mockFile.kt", 1), + StackTraceElement("bar", "mockMethod", "mockFile.kt", 1) + ) + + val pluginDescriptor = mockk { + every { pluginId.idString } returns "amazon.q" + every { version } returns "1.2.3" + } + val pluginResolver = PluginResolver.fromStackTrace(mockStackTrace) + every { PluginManagerCore.getPluginDescriptorOrPlatformByClassName(any()) } returns pluginDescriptor + + assertThat(pluginResolver.product).isEqualTo(AWSProduct.AMAZON_Q_FOR_JET_BRAINS) + assertThat(pluginResolver.version).isEqualTo("1.2.3") + + verify { + PluginManagerCore.getPluginDescriptorOrPlatformByClassName("software.aws.toolkits.plugins.amazonq.bar") + } + } + + @Test + fun stackTraceNoToolkitClassMatches() { + val mockStackTrace = arrayOf( + StackTraceElement("foo", "mockMethod", "mockFile.kt", 1), + StackTraceElement("bar", "mockMethod", "mockFile.kt", 1) + ) + val pluginResolver = PluginResolver.fromStackTrace(mockStackTrace) + + assertThat(pluginResolver.product).isEqualTo(AWSProduct.AWS_TOOLKIT_FOR_JET_BRAINS) + assertThat(pluginResolver.version).isEqualTo("unknown") + + verify(exactly = 0) { + PluginManagerCore.getPlugin(any()) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt new file mode 100644 index 00000000000..937c5a83913 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt @@ -0,0 +1,289 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.ide.highlighter.ProjectFileType +import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.TemporaryDirectory +import com.intellij.testFramework.createTestOpenProjectOptions +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.q.jetbrains.core.MockResourceCacheRule +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.ConnectionState +import software.amazon.q.jetbrains.core.credentials.MockAwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.MockAwsConnectionManager.ProjectAccountSettingsManagerRule +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.credentials.waitUntilConnectionStateIsStable +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.services.sts.StsResources +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_INVALID +import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_NOT_SET +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class TelemetryServiceTest { + private class TestTelemetryService(publisher: TelemetryPublisher = NoOpPublisher(), batcher: TelemetryBatcher) : TelemetryService(publisher, batcher) + + @Rule + @JvmField + val projectRule = ProjectRule() + + @JvmField + @Rule + val resourceCache = MockResourceCacheRule() + + @JvmField + @Rule + val regionProvider = MockRegionProviderRule() + + @JvmField + @Rule + val credentialManager = MockCredentialManagerRule() + + @JvmField + @Rule + val connectionSettingsManager = ProjectAccountSettingsManagerRule(projectRule) + + @JvmField + @Rule + val disposableRule = DisposableRule() + + @After + fun tearDown() { + AwsSettings.getInstance().isTelemetryEnabled = false + } + + @Test + fun testInitialTelemetrySetting() { + AwsSettings.getInstance().isTelemetryEnabled = true + + val changeCountDown = CountDownLatch(1) + val changeCaptor = argumentCaptor() + val onChangeEventCaptor = argumentCaptor<(Boolean) -> Unit>() + + val batcher = mock { + on { onTelemetryEnabledChanged(changeCaptor.capture(), onChangeEventCaptor.capture()) } + .doAnswer { + changeCountDown.countDown() + } + } + + TestTelemetryService(batcher = batcher) + + changeCountDown.await(5, TimeUnit.SECONDS) + assertThat(onChangeEventCaptor.allValues).hasSize(1) + verify(batcher).onTelemetryEnabledChanged(true, onChangeEventCaptor.firstValue) + assertThat(changeCaptor.allValues).hasSize(1) + assertThat(changeCaptor.firstValue).isEqualTo(true) + } + + @Test + fun testTelemetrySettingChanged() { + AwsSettings.getInstance().isTelemetryEnabled = true + + val changeCountDown = CountDownLatch(3) + val changeCaptor = argumentCaptor() + val onChangeEventCaptor = argumentCaptor<(Boolean) -> Unit>() + + val batcher = mock() + + batcher.stub { + on(batcher.onTelemetryEnabledChanged(changeCaptor.capture(), onChangeEventCaptor.capture())) + .doAnswer { + changeCountDown.countDown() + } + } + + val dummyEnabledEvent: (Boolean) -> Unit = { + assertThat(it).isTrue() + } + val dummyDisabledEvent: (Boolean) -> Unit = { + assertThat(it).isFalse() + } + val telemetryService = TestTelemetryService(batcher = batcher) + + telemetryService.setTelemetryEnabled(false, dummyDisabledEvent) + telemetryService.setTelemetryEnabled(true, dummyEnabledEvent) + + changeCountDown.await(5, TimeUnit.SECONDS) + assertThat(onChangeEventCaptor.allValues).hasSize(3) + verify(batcher).onTelemetryEnabledChanged(true, onChangeEventCaptor.firstValue) + verify(batcher).onTelemetryEnabledChanged(true, dummyEnabledEvent) + verify(batcher).onTelemetryEnabledChanged(false, dummyDisabledEvent) + assertThat(changeCaptor.allValues).hasSize(3) + assertThat(changeCaptor.firstValue).isEqualTo(true) + assertThat(changeCaptor.secondValue).isEqualTo(false) + assertThat(changeCaptor.thirdValue).isEqualTo(true) + } + + @Test + fun metricEventMetadataIsNotSet() { + connectionSettingsManager.settingsManager.nullifyCredentialProviderAndWait() + + val eventCaptor = argumentCaptor() + + val batcher = mock() + val telemetryService = TestTelemetryService(batcher = batcher) + + telemetryService.record(projectRule.project) { + datum("Foo") + } + telemetryService.dispose() + + verify(batcher).enqueue(eventCaptor.capture()) + + assertMetricEventsContains(eventCaptor.allValues, "Foo", METADATA_NOT_SET, METADATA_NOT_SET) + } + + @Test + fun metricEventMetadataIsSet() { + val credentials = credentialManager.addCredentials("profile:admin") + val mockRegion = regionProvider.createAwsRegion() + + addAccountId(credentials, mockRegion) + + connectionSettingsManager.settingsManager.changeCredentialProvider(credentials) + connectionSettingsManager.settingsManager.changeRegion(mockRegion) + connectionSettingsManager.settingsManager.waitUntilConnectionStateIsStable() + + // assert that connection setting succeeded. This test has been failing sometimes in the assert stage that there is nothing + assertThat(connectionSettingsManager.settingsManager.connectionState).isInstanceOf(ConnectionState.ValidConnection::class.java) + + val eventCaptor = argumentCaptor() + val batcher = mock() + val telemetryService = TestTelemetryService(batcher = batcher) + + telemetryService.record(projectRule.project) { + datum("Foo") + } + telemetryService.dispose() + + verify(batcher).enqueue(eventCaptor.capture()) + assertMetricEventsContains(eventCaptor.allValues, "Foo", "1111222233333", mockRegion.id) + } + + @Test + fun metricEventMetadataIsOverridden() { + val accountSettings = MockAwsConnectionManager.getInstance(projectRule.project) + val credentials = credentialManager.addCredentials("profile:admin") + + addAccountId(credentials, accountSettings.activeRegion) + accountSettings.changeCredentialProvider(credentials) + + val mockRegion = AwsRegion("foo-region", "foo-region", "aws") + regionProvider.addRegion(mockRegion) + accountSettings.changeRegion(mockRegion) + + val eventCaptor = argumentCaptor() + + val batcher = mock() + val telemetryService = TestTelemetryService(batcher = batcher) + + telemetryService.record( + MetricEventMetadata( + awsAccount = "123456789012", + awsRegion = "bar-region" + ) + ) { + datum("Foo") + } + telemetryService.dispose() + + verify(batcher).enqueue(eventCaptor.capture()) + assertMetricEventsContains(eventCaptor.allValues, "Foo", "123456789012", "bar-region") + } + + @Test + fun telemetryCanBeSendOnAfterProjectClosed() { + // Create a temp project that we own the life cycle for + val projectFile = TemporaryDirectory.generateTemporaryPath("project_telemetryCanBeSendOnAfterProjectClosed${ProjectFileType.DOT_DEFAULT_EXTENSION}") + val options = createTestOpenProjectOptions(runPostStartUpActivities = false) + val project = ProjectManagerEx.getInstanceEx().openProject(projectFile, options)!! + try { + val credentials = credentialManager.addCredentials("profile:admin") + val mockRegion = regionProvider.createAwsRegion() + + addAccountId(credentials, mockRegion) + + val connectionSettingsManager = AwsConnectionManager.getInstance(project) + + connectionSettingsManager.changeCredentialProvider(credentials) + connectionSettingsManager.changeRegion(mockRegion) + connectionSettingsManager.waitUntilConnectionStateIsStable() + + val batcher = mock() + val telemetryService = TestTelemetryService(batcher = batcher) + + telemetryService.record(project) { + datum("Foo") + } + + PlatformTestUtil.forceCloseProjectWithoutSaving(project) + + telemetryService.record(project) { + datum("Bar") + } + + telemetryService.dispose() + + argumentCaptor().apply { + verify(batcher, times(2)).enqueue(capture()) + + assertMetricEventsContains(allValues, "Foo", "1111222233333", mockRegion.id) + assertMetricEventsContains(allValues, "Bar", METADATA_INVALID, METADATA_INVALID) + } + } finally { + // Make sure we closed it if test failed + if (project.isOpen) { + PlatformTestUtil.forceCloseProjectWithoutSaving(project) + } + } + } + + @Test + fun disposeClosesThePublisher() { + val mockPublisher = mock() + val mockBatcher = mock() + + val telemetryService = TestTelemetryService(mockPublisher, mockBatcher) + telemetryService.dispose() + + verify(mockBatcher).shutdown() + verify(mockPublisher).close() + } + + private fun addAccountId(credentialsIdentifier: CredentialIdentifier, region: AwsRegion) { + resourceCache.addEntry(StsResources.ACCOUNT, region.id, credentialsIdentifier.id, "1111222233333") + } + + private fun assertMetricEventsContains(events: Collection, eventName: String, awsAccount: String, awsRegion: String) { + assertThat(events).filteredOn { event -> + event.data.any { it.name == eventName } + }.anySatisfy { element -> + assertThat(element).isInstanceOfSatisfying { + assertThat(it.awsAccount).isEqualTo(awsAccount) + assertThat(it.awsRegion).isEqualTo(awsRegion) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt new file mode 100644 index 00000000000..b5cd0989df8 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt @@ -0,0 +1,67 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import software.amazon.q.jetbrains.services.telemetry.scrubNames +import java.util.stream.Stream + +class TelemetryUtilsTest { + + @ParameterizedTest + @MethodSource("scrubNamesTestCases") + fun testScrubNames(input: String, expected: String) { + val fakeUser = "jdoe123" + assertThat(expected).isEqualTo(scrubNames(input, fakeUser)) + } + + companion object { + @JvmStatic + fun scrubNamesTestCases(): Stream = Stream.of( + Arguments.of("", ""), + Arguments.of("a ./ b", "a ./ b"), + Arguments.of("a ../ b", "a ../ b"), + Arguments.of("a /.. b", "a /.. b"), + Arguments.of("a //..// b", "a //..// b"), + Arguments.of("a / b", "a / b"), + Arguments.of("a ~/ b", "a ~/ b"), + Arguments.of("a //// b", "a //// b"), + Arguments.of("a .. b", "a .. b"), + Arguments.of("a . b", "a . b"), + Arguments.of(" lots of space ", "lots of space"), + Arguments.of( + "Failed to save c:/fooß/aïböcß/aób∑c/∑ö/ππ¨p/ö/a/bar123öabc/baz.txt no permissions (error!)", + "Failed to save c:/xß/xï/xó/x∑/xπ/xö/x/xö/x.txt no permissions (error!)" + ), + Arguments.of( + "user: jdoe123 file: C:/Users/user1/.aws/sso/cache/abc123.json (regex: /foo/)", + "user: x file: C:/Users/x/.aws/sso/cache/x.json (regex: /x/)" + ), + Arguments.of("/Users/user1/foo.jso", "/Users/x/x.jso"), + Arguments.of("/Users/user1/foo.js", "/Users/x/x.js"), + Arguments.of("/Users/user1/noFileExtension", "/Users/x/x"), + Arguments.of("/Users/user1/minExtLength.a", "/Users/x/x.a"), + Arguments.of("/Users/user1/extIsNum.123456", "/Users/x/x.123456"), + Arguments.of("/Users/user1/foo.looooooooongextension", "/Users/x/x.looooooooongextension"), + Arguments.of("/Users/user1/multipleExts.ext1.ext2.ext3", "/Users/x/x.ext3"), + Arguments.of("c:\\fooß\\bar\\baz.txt", "c:/xß/x/x.txt"), + Arguments.of("unc path: \\\\server$\\pipename\\etc END", "unc path: //x$/x/x END"), + Arguments.of( + "c:\\Users\\user1\\.aws\\sso\\cache\\abc123.json jdoe123 abc", + "c:/Users/x/.aws/sso/cache/x.json x abc" + ), + Arguments.of("unix /home/jdoe123/.aws/config failed", "unix /home/x/.aws/config failed"), + Arguments.of("unix ~jdoe123/.aws/config failed", "unix ~x/.aws/config failed"), + Arguments.of("unix ../../.aws/config failed", "unix ../../.aws/config failed"), + Arguments.of("unix ~/.aws/config failed", "unix ~/.aws/config failed"), + Arguments.of( + "/Users/user1/.aws/sso/cache/abc123.json no space", + "/Users/x/.aws/sso/cache/x.json no space" + ) + ) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt new file mode 100644 index 00000000000..2c1342a2c42 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt @@ -0,0 +1,364 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry.otel + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ApplicationExtension +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.TraceId +import io.opentelemetry.context.Context +import io.opentelemetry.extension.kotlin.asContextElement +import io.opentelemetry.sdk.common.CompletableResultCode +import io.opentelemetry.sdk.trace.ReadWriteSpan +import io.opentelemetry.sdk.trace.ReadableSpan +import io.opentelemetry.sdk.trace.SpanProcessor +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.AfterAllCallback +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.RegisterExtension +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.jetbrains.utils.spinUntil +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.utils.satisfiesKt +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Telemetry +import java.time.Duration +import java.time.Instant +import java.util.concurrent.TimeUnit +import java.util.stream.Stream + +@ExtendWith(ApplicationExtension::class) +class OtelBaseTest { + private companion object { + @RegisterExtension + val otelExtension = OtelExtension() + + private fun spanEndArgs() = Stream.of( + Arguments.of("end()", { span: Span -> span.end() }), + Arguments.of("end(long, TimeUnit)", { span: Span -> span.end(1, TimeUnit.SECONDS) }), + Arguments.of("end(Instant)", { span: Span -> span.end(Instant.now()) }), + ) + + @JvmStatic + fun `AbstractBaseSpan#end() throws if attributes are missing`() = spanEndArgs() + + @JvmStatic + fun `AbstractBaseSpan#end() does not throw if all required attributes are present`() = spanEndArgs() + } + + @Test + fun `context propagates from parent to child - happy case`() { + spanBuilder("tracer", "parentSpan").use { + spanBuilder("anotherTracer", "childSpan").use {} + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context propagates from parent to child - happy case coroutines`() = runTest { + spanBuilder("tracer", "parentSpan").useWithScope { + spanBuilder("anotherTracer", "childSpan").useWithScope {} + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context propagates from parent to child - with context override`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + spanBuilder("anotherTracer", "childSpan").use {} + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context propagates from parent to child when child overrides context`() { + spanBuilder("tracer", "parentSpan").use { + // parent->child relationship is still maintained because Context.current() will return parent context + spanBuilder("anotherTracer", "childSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use {} + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isNotEqualTo(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + } + } + + @Test + fun `context override does not propagate from parent to child when switching threads`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + ApplicationManager.getApplication().executeOnPooledThread { + spanBuilder("anotherTracer", "childSpan").use {} + }.get(10, TimeUnit.SECONDS) + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isNotEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context override propagates from parent to child when switching threads while preserving thread-local`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + pluginAwareExecuteOnPooledThread { + spanBuilder("anotherTracer", "childSpan").use {} + }.get(10, TimeUnit.SECONDS) + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context override propagates from parent to child when only child is coroutine`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + runTest { + spanBuilder("anotherTracer", "childSpan").useWithScope { + delay(10000) + } + } + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context override propagates from parent to child when only parent is coroutine`() = runTest { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).useWithScope { + spanBuilder("anotherTracer", "childSpan").use {} + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context override propagates from parent to child when both are coroutines`() = runTest { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).useWithScope { + spanBuilder("anotherTracer", "childSpan").useWithScope {} + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context override does not propagate from parent to child coroutines if context is not preserved`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + runBlocking(getCoroutineBgContext()) { + spanBuilder("anotherTracer", "childSpan").use {} + } + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isNotEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context override propagates from parent to child coroutines with manual coroutine context propagation`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + runBlocking(getCoroutineBgContext() + Context.current().asContextElement()) { + spanBuilder("anotherTracer", "childSpan").use {} + } + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.first() + val child = spans.last() + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @Test + fun `context propagates from parent to child when child#end is after parent#end`() { + spanBuilder("tracer", "parentSpan").setParent(Context.current().with(AWS_PRODUCT_CONTEXT_KEY, AWSProduct.AMAZON_Q_FOR_VS_CODE)).use { + pluginAwareExecuteOnPooledThread { + spanBuilder("anotherTracer", "childSpan").use { + Thread.sleep(100) + } + } + } + spinUntil(Duration.ofSeconds(10)) { + otelExtension.completedSpans.size == 2 + } + + assertThat(otelExtension.completedSpans).hasSize(2).satisfiesKt { spans -> + val parent = spans.last() + val child = spans.first() + + assertThat(parent.hasEnded()) + assertThat(child.hasEnded()) + + // child started after parent + assertThat(child.toSpanData().startEpochNanos).isGreaterThanOrEqualTo(parent.toSpanData().startEpochNanos) + // and called end after parent + assertThat(child.toSpanData().endEpochNanos).isGreaterThanOrEqualTo(parent.toSpanData().endEpochNanos) + + assertThat(parent.parentSpanContext.traceId).isEqualTo(TraceId.getInvalid()) + assertThat(child.parentSpanContext.traceId).isEqualTo(parent.spanContext.traceId) + assertThat(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo("Amazon Q For VS Code") + assertThat(child.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)).isEqualTo(parent.getAttribute(PLUGIN_NAME_ATTRIBUTE_KEY)) + } + } + + @ParameterizedTest(name = "{0}") + @MethodSource + fun `AbstractBaseSpan#end() throws if attributes are missing`(ignored: String, block: Span.() -> Unit) { + val span = Telemetry.aws.openUrl.startSpan() + val e = assertThrows { block(span) } + assertThat(e.message).contains("aws_openUrl is missing required fields: result") + } + + @ParameterizedTest(name = "{0}") + @MethodSource + fun `AbstractBaseSpan#end() does not throw if all required attributes are present`(ignored: String, block: Span.() -> Unit) { + val span = Telemetry.aws.openUrl.startSpan() + span.result(MetricResult.Succeeded) + assertDoesNotThrow { block(span) } + } + + private fun spanBuilder(tracer: String, spanName: String) = DefaultSpanBuilder(otelExtension.sdk.sdk.getTracer(tracer).spanBuilder(spanName)) +} + +class OtelExtension : AfterEachCallback, AfterAllCallback { + private val openSpans = mutableSetOf() + private val _completedSpans = mutableListOf() + val completedSpans + get() = _completedSpans.reversed().toList() + + val sdk = OTelService( + listOf( + // should probably be a service loader + object : SpanProcessor { + override fun isStartRequired() = true + override fun isEndRequired() = true + + override fun onStart(parentContext: Context, span: ReadWriteSpan) { + openSpans.add(span) + } + + override fun onEnd(span: ReadableSpan) { + _completedSpans.add(span) + + if (!openSpans.contains(span)) { + LOG.warn(RuntimeException("Span ended without corresponding start")) { span.toString() } + } + openSpans.remove(span) + } + + override fun forceFlush(): CompletableResultCode { + assert(openSpans.isEmpty()) { "Not all open spans were closed: ${openSpans.joinToString(", ")}" } + return CompletableResultCode.ofSuccess() + } + }, + ToolkitTelemetryOTelSpanProcessor() + ) + ) + + override fun afterEach(context: ExtensionContext?) { + reset() + } + + override fun afterAll(context: ExtensionContext?) { + sdk.sdk.shutdown() + } + + fun reset() { + openSpans.clear() + _completedSpans.clear() + } + + companion object { + private val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt new file mode 100644 index 00000000000..a437c49257c --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt @@ -0,0 +1,227 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.junit5.TestDisposable +import com.intellij.testFramework.replaceService +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanId +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.core.telemetry.DefaultMetricEvent +import software.amazon.q.core.telemetry.DefaultMetricEvent.DefaultDatum +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher +import software.amazon.q.jetbrains.utils.satisfiesKt +import software.aws.toolkits.telemetry.CodewhispererAutomatedTriggerType +import software.aws.toolkits.telemetry.CodewhispererCompletionType +import software.aws.toolkits.telemetry.CodewhispererLanguage +import software.aws.toolkits.telemetry.CodewhispererTelemetry +import software.aws.toolkits.telemetry.CodewhispererTriggerType +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Telemetry +import java.time.Instant + +@ExtendWith(ApplicationExtension::class) +class ToolkitTelemetryOTelSpanProcessorTest { + private class TestTelemetryService( + publisher: TelemetryPublisher = NoOpPublisher(), + batcher: TelemetryBatcher, + ) : TelemetryService(publisher, batcher) + + private lateinit var telemetryService: TelemetryService + private lateinit var batcher: TelemetryBatcher + + @BeforeEach + fun setUp(@TestDisposable disposable: Disposable) { + batcher = mock() + telemetryService = spy(TestTelemetryService(batcher = batcher)) + ApplicationManager.getApplication().replaceService(TelemetryService::class.java, telemetryService, disposable) + } + + @Test + fun `OTel emits same payload as old metrics`() { + val otelOnlyFields = setOf("traceId", "metricId", "parentId") + + val createTime = Instant.now() + CodewhispererTelemetry.serviceInvocation( + project = null, + codewhispererAutomatedTriggerType = CodewhispererAutomatedTriggerType.Enter, + codewhispererCompletionType = CodewhispererCompletionType.Line, + codewhispererCursorOffset = 123, + codewhispererCustomizationArn = "codewhispererCustomizationArn", + codewhispererLanguage = CodewhispererLanguage.Python, + codewhispererLineNumber = 0, + codewhispererTriggerType = CodewhispererTriggerType.AutoTrigger, + duration = 0.0, + result = MetricResult.Succeeded, + createTime = createTime, + ) + + Telemetry.codewhisperer.serviceInvocation.setStartTimestamp(createTime).use { + it.codewhispererAutomatedTriggerType(CodewhispererAutomatedTriggerType.Enter) + .codewhispererCompletionType(CodewhispererCompletionType.Line) + .codewhispererCursorOffset(123) + .codewhispererCustomizationArn("codewhispererCustomizationArn") + .codewhispererLanguage(CodewhispererLanguage.Python) + .codewhispererLineNumber(0) + .codewhispererTriggerType(CodewhispererTriggerType.AutoTrigger) + .duration(0.0) + .result(MetricResult.Succeeded) + } + + argumentCaptor { + verify(batcher, times(2)).enqueue(capture()) + + val filteredOtelEvent = secondValue.copy( + data = secondValue.data.map { data -> + (data as DefaultDatum).copy( + metadata = data.metadata.filter { it.key !in otelOnlyFields } + ) + } + ) + assertThat(filteredOtelEvent).isEqualTo(firstValue) + } + } + + @Test + fun `OTel emits trace ids`() { + val span = Telemetry.toolkit.init.startSpan() + span.end() + + argumentCaptor { + verify(batcher, times(1)).enqueue(capture()) + + val traceId = span.spanContext.traceId + val metricId = span.spanContext.spanId + assertThat(firstValue.data).singleElement().satisfiesKt { input -> + assertThat(input.metadata).containsEntry("traceId", traceId) + assertThat(input.metadata).containsEntry("metricId", metricId) + } + } + } + + @Test + fun `OTel emits trace ids from parent`() { + lateinit var childSpan: Span + val parentSpan = Telemetry.toolkit.init.use { parent -> + childSpan = Telemetry.toolkit.init.use { child -> + child + } + parent + } + + argumentCaptor { + verify(batcher, times(2)).enqueue(capture()) + + assertThat(firstValue.data).singleElement().satisfiesKt { input -> + assertThat(input.metadata).containsEntry("traceId", childSpan.spanContext.traceId) + assertThat(input.metadata).containsEntry("metricId", childSpan.spanContext.spanId) + assertThat(input.metadata).containsEntry("parentId", parentSpan.spanContext.spanId) + } + + assertThat(secondValue.data).singleElement().satisfiesKt { input -> + assertThat(input.metadata).containsEntry("traceId", parentSpan.spanContext.traceId) + assertThat(input.metadata).containsEntry("metricId", parentSpan.spanContext.spanId) + assertThat(input.metadata).containsEntry("parentId", SpanId.getInvalid()) + } + } + } + + @Test + fun `automatically applies duration on metric without duration`() = runTest { + Telemetry.toolkit.init.useWithScope { + delay(10_000) + } + + argumentCaptor { + verify(batcher, times(1)).enqueue(capture()) + + assertThat(firstValue.data).singleElement().satisfiesKt { input -> + assertThat(input.metadata).containsKey("duration") + } + } + } + + @Test + fun `respects duration on metric if defined`() { + Telemetry.toolkit.init.use { + it.duration(12_345.6) + } + + argumentCaptor { + verify(batcher, times(1)).enqueue(capture()) + + assertThat(firstValue.data).singleElement().satisfiesKt { input -> + assertThat(input.metadata).containsEntry("duration", "12345.6") + } + } + } + + @Test + fun `strips metadata fields that need special handling`() { + Telemetry.toolkit.init.use { + it.passive(true) + it.unit(MetricUnit.COUNT) + it.value(99999) + } + + argumentCaptor { + verify(batcher, times(1)).enqueue(capture()) + + assertThat(firstValue.data).singleElement().satisfiesKt { input -> + assertThat(input.metadata).containsOnlyKeys("duration", "metricId", "parentId", "traceId") + } + } + } + + @Test + fun `attaches fields as metadata`() { + Telemetry.codewhisperer.serviceInvocation.use { + it.codewhispererAutomatedTriggerType(CodewhispererAutomatedTriggerType.Enter) + .codewhispererCompletionType(CodewhispererCompletionType.Line) + .codewhispererCursorOffset(123) + .codewhispererCustomizationArn("codewhispererCustomizationArn") + .codewhispererLanguage(CodewhispererLanguage.Python) + .codewhispererLineNumber(0) + .codewhispererTriggerType(CodewhispererTriggerType.AutoTrigger) + .duration(0.0) + .result(MetricResult.Succeeded) + } + + argumentCaptor { + verify(batcher, times(1)).enqueue(capture()) + + assertThat(firstValue.data).singleElement().satisfiesKt { input -> + // tracing ids tested in different case + assertThat(input.metadata).containsAllEntriesOf( + mapOf( + "codewhispererAutomatedTriggerType" to "Enter", + "codewhispererCompletionType" to "Line", + "codewhispererCursorOffset" to "123", + "codewhispererCustomizationArn" to "codewhispererCustomizationArn", + "codewhispererLanguage" to "python", + "codewhispererLineNumber" to "0", + "codewhispererTriggerType" to "AutoTrigger", + "duration" to "0.0", + "result" to "Succeeded", + ) + ) + } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt new file mode 100644 index 00000000000..472a42110cb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt @@ -0,0 +1,74 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.settings + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.junit5.TestDisposable +import com.intellij.testFramework.replaceService +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.inOrder +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher + +@ExtendWith(ApplicationExtension::class) +class AwsSettingsTest { + private class TestTelemetryService( + publisher: TelemetryPublisher = NoOpPublisher(), + batcher: TelemetryBatcher, + ) : TelemetryService(publisher, batcher) + + private lateinit var telemetryService: TelemetryService + private lateinit var batcher: TelemetryBatcher + private lateinit var awsSettings: DefaultAwsSettings + private lateinit var awsConfiguration: AwsConfiguration + + @BeforeEach + fun setup(@TestDisposable disposable: Disposable) { + batcher = mock() + telemetryService = spy(TestTelemetryService(batcher = batcher)) + awsSettings = spy(DefaultAwsSettings()) + awsConfiguration = spy(AwsConfiguration()) + awsSettings.loadState(awsConfiguration) + ApplicationManager.getApplication().replaceService(TelemetryService::class.java, telemetryService, disposable) + } + + @AfterEach + fun tearDown() { + telemetryService.dispose() + } + + @Test + fun `telemetry event batched before setting isTelemetryEnabled to false`() { + verifyTelemetryEventOrder(false) + } + + @Test + fun `telemetry event batched before setting isTelemetryEnabled to true`() { + verifyTelemetryEventOrder(true) + } + + private fun verifyTelemetryEventOrder(value: Boolean) { + val inOrder = inOrder(telemetryService, batcher, awsConfiguration) + val changeCaptor = argumentCaptor() + val onChangeEventCaptor = argumentCaptor<(Boolean) -> Unit>() + + awsSettings.isTelemetryEnabled = value + + inOrder.verify(telemetryService).setTelemetryEnabled(changeCaptor.capture(), onChangeEventCaptor.capture()) + assertThat(changeCaptor.firstValue).isEqualTo(value) + inOrder.verify(batcher).onTelemetryEnabledChanged(value, onChangeEventCaptor.firstValue) + inOrder.verify(awsConfiguration).isTelemetryEnabled = value + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt new file mode 100644 index 00000000000..3f86712fa79 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt @@ -0,0 +1,85 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.ui + +import com.intellij.testFramework.ApplicationRule +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.spy +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import software.amazon.q.jetbrains.ui.AsyncComboBox +import java.util.concurrent.CountDownLatch +import javax.swing.DefaultComboBoxModel + +class AsyncComboBoxTest { + @Rule + @JvmField + val applicationRule = ApplicationRule() + + @Test + fun `can populate combobox`() { + val spy = spy>() + val sut = AsyncComboBox(comboBoxModel = spy) + sut.proposeModelUpdate { it.addElement("1") } + sut.waitForSelection().get() + + verify(spy).addElement("1") + } + + @Test + fun `returns null selection while loading`() { + val spy = spy>() + val sut = AsyncComboBox(comboBoxModel = spy) + val latch = CountDownLatch(1) + sut.proposeModelUpdate { + latch.await() + it.addElement("1") + } + + assertThat(sut.selectedItem).isNull() + + latch.countDown() + } + + @Test + fun `multiple update proposals results in single execution`() { + val spy = spy>() + val sut = AsyncComboBox(comboBoxModel = spy) + sut.proposeModelUpdate { it.addElement("1") } + sut.proposeModelUpdate { it.addElement("2") } + sut.proposeModelUpdate { it.addElement("3") } + + sut.waitForSelection().get() + + verify(spy).addElement("3") + } + + @Test + fun `long running update proposals preempted by newer ones`() { + val spy = spy>() + val sut = AsyncComboBox(comboBoxModel = spy) + val latch = CountDownLatch(1) + val latch2 = CountDownLatch(1) + sut.proposeModelUpdate { + it.addElement("1") + latch2.countDown() + latch.await() + it.addElement("2") + } + // wait for first update to start running + latch2.await() + sut.proposeModelUpdate { + it.addElement("3") + } + + sut.waitForSelection().get() + latch.countDown() + + verify(spy).addElement("1") + verify(spy).addElement("3") + verify(spy, times(0)).addElement("2") + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt new file mode 100644 index 00000000000..3ef58077aec --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt @@ -0,0 +1,45 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import software.amazon.q.jetbrains.utils.MRUList + +class MRUListTest { + private lateinit var list: MRUList + + @Before + fun setUp() { + list = MRUList(3) + } + + @Test + fun testEvictionOfOldest() { + list.add(FOO) + list.add(BAR) + list.add(BAZ) + list.add(FIZ) + + assertThat(list.elements()).containsExactly(FIZ, BAZ, BAR) + } + + @Test + fun testReAddingMovesUp() { + list.add(FOO) + list.add(BAR) + list.add(BAZ) + list.add(FOO) + + assertThat(list.elements()).containsExactly(FOO, BAZ, BAR) + } + + private companion object { + const val FOO = "Foo" + const val BAR = "Bar" + const val BAZ = "Baz" + const val FIZ = "Fiz" + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/NotificationUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/NotificationUtilsTest.kt new file mode 100644 index 00000000000..08a02fd4061 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/NotificationUtilsTest.kt @@ -0,0 +1,38 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.ProjectRule +import org.assertj.core.api.Assertions.STRING +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule +import java.util.function.Function + +class NotificationUtilsTest { + @Rule + @JvmField + val projectRule = ProjectRule() + + @Rule + @JvmField + val disposableRule = DisposableRule() + + @Rule + @JvmField + val notificationListener = NotificationListenerRule(projectRule, disposableRule.disposable) + + @Test + fun `Notifications show stack traces for exceptions`() { + NullPointerException().notifyError("ooops", project = projectRule.project) + + assertThat(notificationListener.notifications) + .extracting(Function { t -> t.content }) + .singleElement(STRING) + .startsWith("java.lang.NullPointerException") + .contains("NotificationUtilsTest.kt") + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt new file mode 100644 index 00000000000..68dfe6a3f1e --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt @@ -0,0 +1,207 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.json.JsonLanguage +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.runInEdtAndWait +import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language +import org.junit.Rule +import org.junit.Test +import software.amazon.q.jetbrains.utils.applyPatch +import software.amazon.q.jetbrains.utils.formatText +import software.amazon.q.jetbrains.utils.generateUnifiedPatch +import software.amazon.q.jetbrains.utils.toHumanReadable +import software.amazon.q.core.utils.convertMarkdownToHTML +import software.amazon.q.core.utils.extractCodeBlockLanguage + +class TextUtilsTest { + @Rule + @JvmField + val projectRule = ProjectRule() + + private val defaultTestGenResponseLanguage = "plaintext" + + @Test + fun textGetsFormatted() { + @Language("JSON") + val actual = + """ + { + "hello": + "world"} + """.trimIndent() + + @Language("JSON") + val expected = + """ + { + "hello": "world" + } + """.trimIndent() + + lateinit var formatted: String + runInEdtAndWait { + formatted = formatText(projectRule.project, JsonLanguage.INSTANCE, actual) + } + assertThat(formatted).isEqualTo(expected) + } + + @Test + fun canConvertToTitleHumanReadable() { + assertThat("CREATE_COMPLETE".toHumanReadable()).isEqualTo("Create Complete") + assertThat("UPDATE_IN_PROGRESS".toHumanReadable()).isEqualTo("Update In Progress") + } + + @Test + fun canConvertMarkdownToHTML() { + @Language("md") + val input = """ + # heading 1 + ## heading 2 + + ```js + console.log("hello world"); + ``` + """.trimIndent() + + @Language("html") + val expected = """ +

heading 1

+

heading 2

+
console.log("hello world");
+            
+ + """.trimIndent() + + val actual = convertMarkdownToHTML(input) + assertThat(actual).isEqualTo(expected) + } + + @Test + fun canRenderDiffsWithCustomRenderer() { + @Language("md") + val input = """ + ```diff + line 1 + - line 2 + + line 3 + line 4 + ``` + """.trimIndent() + + @Language("html") + val expected = """ +
  line 1
- line 2
+ line 3
  line 4
+ + """.trimIndent() + + val actual = convertMarkdownToHTML(input) + assertThat(actual).isEqualTo(expected) + } + + @Test + fun canApplyPatchSuccessfully() { + val inputPatch = "@@ -1,3 +1,3 @@\n first line\n-second line\n+third line\n forth line" + val inputFilePath = "dummy.py" + val fileContent = "first line\nsecond line\nforth line" + val actual = applyPatch(inputPatch, fileContent, inputFilePath) + val expected = "first line\nthird line\nforth line" + assertThat(actual).isEqualTo(expected) + } + + @Test + fun shouldTryToApplyPatchEvenIfPatchIsIncorrect() { + val inputPatch = "@@ -1,3 +1,3 @@\n first line\n-second line\n+third line\n forth line" + val inputFilePath = "dummy.py" + val fileContent = "first line\nThree line\nforth line" + val actual = applyPatch(inputPatch, fileContent, inputFilePath) + val expected = "first line\nthird line\nThree line\nforth line" + assertThat(actual).isEqualTo(expected) + } + + @Test + fun shouldHaveZeroHunkSizeForIncorrectPatchGenerated() { + val inputPatch = " first line\n-second line\n+third line\n forth line" + val inputFilePath = "dummy.py" + val actual = generateUnifiedPatch(inputPatch, inputFilePath) + assertThat(actual.hunks.size).isEqualTo(0) + } + + @Test + fun shouldHaveHunksForCorrectPatchGenerated() { + val inputPatch = "@@ -1,3 +1,3 @@\n first line\n-second line\n+third line\n forth line" + val inputFilePath = "dummy.py" + val actual = generateUnifiedPatch(inputPatch, inputFilePath) + assertThat(actual.hunks.size).isEqualTo(1) + val hunk = actual.hunks[0] + assertThat(hunk.startLineAfter).isEqualTo(0) + assertThat(hunk.startLineBefore).isEqualTo(0) + assertThat(hunk.endLineAfter).isEqualTo(3) + assertThat(hunk.endLineBefore).isEqualTo(3) + val inputPatchLines = inputPatch.split("\n") + hunk.lines.forEachIndexed { index, patchLine -> assertThat(inputPatchLines[index + 1].substring(1)).isEqualTo(patchLine.text) } + } + + @Test + fun shouldApplyPatchEvenIfNeighboringLinesDiffer() { + val inputPatch = "@@ -1,3 +1,3 @@\n first line\n-second line\n+third line\n forth line" + val inputFilePath = "dummy.py" + val fileContent = "foo\nsecond line\nbar" + val actual = applyPatch(inputPatch, fileContent, inputFilePath) + assertThat(actual).isEqualTo("foo\nthird line\nbar") + } + + @Test + fun shouldApplyPatchEvenIfLineNumbersDiffer() { + val inputPatch = "@@ -1,3 +1,3 @@\n first line\n-second line\n+third line\n forth line" + val inputFilePath = "dummy.py" + val fileContent = "dummy\ndummy\ndummy\ndummy\nfirst line\nsecond line\nforth line" + val actual = applyPatch(inputPatch, fileContent, inputFilePath) + assertThat(actual).isEqualTo("dummy\ndummy\ndummy\ndummy\nfirst line\nthird line\nforth line") + } + + @Test + fun `extractCodeBlockLanguage returns default when no code block is present`() { + val message = "This is a message without a code block" + assertThat(defaultTestGenResponseLanguage).isEqualTo(extractCodeBlockLanguage(message)) + } + + @Test + fun `extractCodeBlockLanguage returns language when code block with language is present`() { + val message = "Here's a code block:\n```kotlin\nval x = 5\n```" + assertThat("kotlin").isEqualTo(extractCodeBlockLanguage(message)) + } + + @Test + fun `extractCodeBlockLanguage returns default when code block has no language specified`() { + val message = "Here's a code block:\n```\nval x = 5\n```" + assertThat(defaultTestGenResponseLanguage).isEqualTo(extractCodeBlockLanguage(message)) + } + + @Test + fun `extractCodeBlockLanguage returns language when multiple code blocks are present`() { + val message = "First block:\n```java\nint x = 5;\n```\nSecond block:\n```python\nx = 5\n```" + assertThat("java").isEqualTo(extractCodeBlockLanguage(message)) + } + + @Test + fun `extractCodeBlockLanguage returns default when code block is not closed`() { + val message = "Incomplete code block:\n```kotlin\nval x = 5" + assertThat("kotlin").isEqualTo(extractCodeBlockLanguage(message)) + } + + @Test + fun `extractCodeBlockLanguage trims whitespace from language`() { + val message = "Code block with spaces:\n``` kotlin \nval x = 5\n```" + assertThat("kotlin").isEqualTo(extractCodeBlockLanguage(message)) + } + + @Test + fun `extractCodeBlockLanguage handles empty message`() { + val message = "" + assertThat(defaultTestGenResponseLanguage).isEqualTo(extractCodeBlockLanguage(message)) + } +} diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/ThreadingUtilsKtTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/ThreadingUtilsKtTest.kt new file mode 100644 index 00000000000..187a1bbc14c --- /dev/null +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/ThreadingUtilsKtTest.kt @@ -0,0 +1,97 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressManager +import com.intellij.testFramework.ApplicationRule +import com.intellij.util.concurrency.AppExecutorUtil +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit4.MockKRule +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Rule +import org.junit.Test +import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct +import software.amazon.q.jetbrains.services.telemetry.PluginResolver +import java.time.Duration +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class ThreadingUtilsKtTest { + @Rule + @JvmField + val application = ApplicationRule() + + @get:Rule + val mockkRule = MockKRule(this) + + @MockK + private lateinit var pluginResolver: PluginResolver + + @Test + fun `computeOnEdt runs on edt`() { + computeOnEdt { + ApplicationManager.getApplication().assertIsDispatchThread() + } + } + + @Test + fun `computeOnEdt bubbles out errors`() { + assertThatThrownBy { + computeOnEdt { + throw IllegalStateException("Dummy error") + } + }.isInstanceOf() + } + + @Test + fun `computeOnEdt respects cancellation`() { + val latch = CountDownLatch(1) + try { + assertThatThrownBy { + val indicator = EmptyProgressIndicator() + ProgressManager.getInstance().runProcess( + { + computeOnEdt { + indicator.cancel() + latch.await() + } + }, + indicator + ) + }.isInstanceOf() + } finally { + latch.countDown() + } + } + + @Test + fun `sleepWithCancellation respects cancellation`() { + val indicator = EmptyProgressIndicator() + AppExecutorUtil.getAppScheduledExecutorService().schedule( + { indicator.cancel() }, + 100, + TimeUnit.MILLISECONDS + ) + assertThatThrownBy { + sleepWithCancellation(Duration.ofHours(3), indicator) + }.isInstanceOf() + } + + @Test + fun `pluginAwareExecuteOnPooledThread inherits plugin resolver`() { + every { pluginResolver.product } returns AWSProduct.AMAZON_Q_FOR_JET_BRAINS + PluginResolver.setThreadLocal(pluginResolver) + + pluginAwareExecuteOnPooledThread { + assertThat(PluginResolver.fromCurrentThread().product).isEqualTo(AWSProduct.AMAZON_Q_FOR_JET_BRAINS) + }.get() + + PluginResolver.setThreadLocal(PluginResolver.fromStackTrace(Thread.currentThread().stackTrace)) + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/com/intellij/testFramework/junit5/impl/TestDisposableExtension.kt b/plugins/core-q/jetbrains-community/tstFixtures/com/intellij/testFramework/junit5/impl/TestDisposableExtension.kt new file mode 100644 index 00000000000..f031ddbb151 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/com/intellij/testFramework/junit5/impl/TestDisposableExtension.kt @@ -0,0 +1,76 @@ +@file:Suppress("all") +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.testFramework.junit5.impl + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.CheckedDisposable +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.junit5.TestDisposable +import org.jetbrains.annotations.TestOnly +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.extension.* +import org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields +import org.junit.platform.commons.util.ReflectionUtils + +/** + * An exact copy of JetBrains' [com.intellij.testFramework.junit5.impl.TestDisposableExtension], + * with the goal of pushing this first on the runtime classpath to resolve NoSuchMethodError from + * the binary method signature change of [ReflectionUtils.makeAccessible] caused by JUnit 5.11.0 in + * https://github.com/junit-team/junit5/commit/abb5ed16be3a9ce552f4a45c11264ded608ae9da + */ +internal class TestDisposableExtension : + BeforeEachCallback, + AfterEachCallback, + ParameterResolver { + + @TestOnly + override fun beforeEach(context: ExtensionContext) { + val instance = context.requiredTestInstance + for (field in findAnnotatedFields(instance.javaClass, TestDisposable::class.java, ReflectionUtils::isNotStatic)) { + ReflectionUtils.makeAccessible(field)[instance] = context.testDisposable() + } + } + + @TestOnly + override fun afterEach(context: ExtensionContext) { + context.testDisposableIfRequested()?.let { disposable -> + Assertions.assertFalse(disposable.isDisposed) + Disposer.dispose(disposable) + } + } + + @TestOnly + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + return parameterContext.parameter.type === Disposable::class.java && parameterContext.isAnnotated(TestDisposable::class.java) + } + + @TestOnly + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { + return extensionContext.testDisposable() + } +} + +private const val testDisposableKey = "test disposable" + +private fun ExtensionContext.testDisposable(): CheckedDisposable { + return getStore(ExtensionContext.Namespace.GLOBAL) + .computeIfAbsent(testDisposableKey) { + Disposer.newCheckedDisposable(uniqueId) + } +} + +private fun ExtensionContext.testDisposableIfRequested(): CheckedDisposable? { + return getStore(ExtensionContext.Namespace.GLOBAL) + .typedGet(testDisposableKey) +} + +internal inline fun ExtensionContext.Store.typedGet(key: String): T { + return get(key, T::class.java) +} + +fun ExtensionContext.Store.computeIfAbsent(key: String, computable: () -> T): T { + @Suppress("UNCHECKED_CAST") + return getOrComputeIfAbsent(key) { + computable() + } as T +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt new file mode 100644 index 00000000000..0dbaaa0e41e --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt @@ -0,0 +1,21 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import software.amazon.q.core.ClientConnectionSettings +import software.aws.toolkits.core.utils.test.aString +import java.util.concurrent.atomic.AtomicInteger + +open class DummyResource(override val id: String, private val value: T) : Resource.Cached() { + val callCount = AtomicInteger(0) + + override fun fetch(connectionSettings: ClientConnectionSettings<*>): T { + callCount.getAndIncrement() + return value + } +} + +class StringResource(id: String) : DummyResource(id, id) + +fun dummyResource(value: String = aString()): Resource.Cached = StringResource(value) diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/Id.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/Id.kt new file mode 100644 index 00000000000..636cdacd6e4 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/Id.kt @@ -0,0 +1,10 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +val Resource<*>.id: String + get() = when (this) { + is Resource.Cached -> this.id + is Resource.View<*, *> -> this.underlying.id + } diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt new file mode 100644 index 00000000000..30b7864b327 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt @@ -0,0 +1,134 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service +import com.intellij.testFramework.common.ThreadLeakTracker +import com.intellij.testFramework.replaceService +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.rules.ExternalResource +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider +import software.amazon.awssdk.core.SdkClient +import software.amazon.awssdk.regions.Region +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.ToolkitClientManager +import software.amazon.q.core.clients.SdkClientProvider +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.region.ToolkitRegionProvider +import software.aws.toolkits.core.utils.delegateMock +import kotlin.reflect.KClass + +class MockClientManager : AwsClientManager() { + private data class Key( + val clazz: KClass, + val regionId: String? = null, + val credProviderId: AwsCredentialsProvider? = null, + ) + + private val mockClients = mutableMapOf() + + @Suppress("UNCHECKED_CAST") + override fun constructAwsClient( + sdkClass: KClass, + credProvider: AwsCredentialsProvider?, + tokenProvider: SdkTokenProvider?, + region: Region, + endpointOverride: String?, + clientCustomizer: ToolkitClientCustomizer?, + ): T = mockClients[Key(sdkClass, region.id(), credProvider)] as? T + ?: mockClients[Key(sdkClass)] as? T + ?: throw IllegalStateException("No mock registered for $sdkClass") + + override fun dispose() { + super.dispose() + mockClients.clear() + } + + // Note: You must pass KClass of the interface, since we do not do instanceof checks, but == on the classes + // This will lead to comparing the anonymous mock proxy to the interface and it will fail + @Deprecated("Do not use, use MockClientManagerRule") + fun register(clazz: KClass, sdkClient: SdkClient) { + mockClients[Key(clazz)] = sdkClient + } + + @Deprecated("Do not use, use MockClientManagerRule") + fun register(clazz: KClass, sdkClient: T, region: AwsRegion, credProvider: ToolkitCredentialsProvider) { + mockClients[Key(clazz, region.id, credProvider)] = sdkClient + } + + companion object { + /** + * Replaces all required test services with the real version for the life of the [Disposable] to allow calls to AWS to succeed + */ + fun useRealImplementations(disposable: Disposable) { + val clientManager = AwsClientManager() + ApplicationManager.getApplication().replaceService(ToolkitClientManager::class.java, clientManager, disposable) + + // Need to use real region provider to know about global services + val regionProvider = AwsRegionProvider() + ApplicationManager.getApplication().replaceService(ToolkitRegionProvider::class.java, regionProvider, disposable) + + // Make a new http client that is scoped to the disposable and replace the global one with it, otherwise the apache connection reaper thread + // is detected as leaking threads and fails the tests + // TODO: We aren't closing cred providers and sdks when they are removed, we need to see what ramifications that has + ThreadLeakTracker.longRunningThreadCreated(disposable, "idle-connection-reaper") + + val httpClient = AwsSdkClient() + ApplicationManager.getApplication().replaceService(SdkClientProvider::class.java, httpClient, disposable) + } + } +} + +// Scoped to this file only, users should be using MockClientManagerRule to enforce cleanup correctly +private fun getMockInstance(): MockClientManager = service() as MockClientManager + +sealed class MockClientManagerBase : ExternalResource() { + private lateinit var mockClientManager: MockClientManager + + override fun before() { + mockClientManager = getMockInstance() + } + + override fun after() { + mockClientManager.dispose() + } + + @PublishedApi + @Deprecated("Do not use, visible for inline") + internal fun manager() = mockClientManager + + inline fun create(): T = delegateMock(verboseLogging = true).also { + @Suppress("DEPRECATION") + manager().register(T::class, it) + } + + inline fun create(region: AwsRegion, credProvider: ToolkitCredentialsProvider): T = delegateMock().also { + @Suppress("DEPRECATION") + manager().register(T::class, it, region, credProvider) + } + + inline fun register(mock: T): T = mock.also { + @Suppress("DEPRECATION") + manager().register(T::class, it) + } +} + +class MockClientManagerRule : MockClientManagerBase() + +class MockClientManagerExtension : MockClientManagerBase(), BeforeEachCallback, AfterEachCallback { + override fun beforeEach(context: ExtensionContext?) { + before() + } + + override fun afterEach(context: ExtensionContext?) { + after() + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt new file mode 100644 index 00000000000..1c8aa0fe590 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt @@ -0,0 +1,207 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.testFramework.ApplicationRule +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.runner.Description +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.credentials.ToolkitAuthenticationProvider +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage +import java.util.concurrent.ConcurrentHashMap + +@Suppress("UNCHECKED_CAST") +class MockResourceCache : AwsResourceCache { + private val map = ConcurrentHashMap() + + override fun getResourceIfPresent( + resource: Resource, + region: AwsRegion, + credentialProvider: ToolkitCredentialsProvider, + useStale: Boolean, + ): T? = when (resource) { + is Resource.View<*, T> -> getResourceIfPresent(resource.underlying, region, credentialProvider)?.let { resource.doMap(it, region) } + is Resource.Cached -> mockResourceIfPresent(resource, region, credentialProvider) + } + + override fun getResource( + resource: Resource, + region: AwsRegion, + credentialProvider: ToolkitCredentialsProvider, + useStale: Boolean, + forceFetch: Boolean, + ): CompletionStage = when (resource) { + is Resource.View<*, T> -> getResource(resource.underlying, region, credentialProvider, useStale, forceFetch).thenApply { + resource.doMap(it as Any, region) + } + is Resource.Cached -> { + mockResource(resource, region, credentialProvider) + } + } + + override fun getResourceIfPresent( + resource: Resource, + region: AwsRegion, + tokenProvider: ToolkitBearerTokenProvider, + useStale: Boolean, + ): T? = when (resource) { + is Resource.View<*, T> -> getResourceIfPresent(resource.underlying, region, tokenProvider)?.let { resource.doMap(it, region) } + is Resource.Cached -> mockResourceIfPresent(resource, region, tokenProvider) + } + + override fun getResource( + resource: Resource, + region: AwsRegion, + tokenProvider: ToolkitBearerTokenProvider, + useStale: Boolean, + forceFetch: Boolean, + ): CompletionStage = when (resource) { + is Resource.View<*, T> -> getResource(resource.underlying, region, tokenProvider, useStale, forceFetch).thenApply { + resource.doMap(it as Any, region) + } + is Resource.Cached -> { + mockResource(resource, region, tokenProvider) + } + } + + private fun mockResourceIfPresent( + resource: Resource.Cached, + region: AwsRegion, + credentials: ToolkitAuthenticationProvider, + ): T? = when (val value = map[CacheKey(resource.id, region.id, credentials.id)]) { + is CompletableFuture<*> -> if (value.isDone) value.get() as T else null + else -> value as? T? + } + + private fun mockResource( + resource: Resource.Cached, + region: AwsRegion, + credentials: ToolkitAuthenticationProvider, + ) = when (val value = map[CacheKey(resource.id, region.id, credentials.id)]) { + is CompletableFuture<*> -> value as CompletionStage + else -> { + val future = CompletableFuture() + ApplicationManager.getApplication().executeOnPooledThread { + value?.also { future.complete(it as T) } + ?: future.completeExceptionally(IllegalStateException("No value found for $resource ${region.id} ${credentials.id} in mock")) + } + future + } + } + + override fun clear(resource: Resource<*>, connectionSettings: ClientConnectionSettings<*>) { + when (resource) { + is Resource.Cached<*> -> map.remove(CacheKey(resource.id, connectionSettings.region.id, connectionSettings.providerId)) + is Resource.View<*, *> -> clear(resource.underlying, connectionSettings) + } + } + + override fun clear(connectionSettings: ClientConnectionSettings<*>) { + map.keys.removeIf { it.credentialsId == connectionSettings.providerId && it.regionId == connectionSettings.region.id } + } + + override suspend fun clear() { + map.clear() + } + + fun entryCount() = map.size + + fun addEntry(resourceId: String, regionId: String, credentialsId: String, value: Any) { + map[CacheKey(resourceId, regionId, credentialsId)] = value + } + + companion object { + @JvmStatic + fun getInstance(): MockResourceCache = service() as MockResourceCache + + private data class CacheKey(val resourceId: String, val regionId: String, val credentialsId: String) + } +} + +class MockResourceCacheRule : ApplicationRule(), MockResourceCacheInterface by MockResourceCacheInterface.delegate() { + public override fun before(description: Description) { + super.before(description) + } + + public override fun after() { + runBlocking { cache.clear() } + } +} + +class MockResourceCacheExtension : BeforeEachCallback, AfterEachCallback, MockResourceCacheInterface by MockResourceCacheInterface.delegate() { + private val rule = MockResourceCacheRule() + + override fun beforeEach(context: ExtensionContext) { + rule.before(Description.EMPTY) + } + + override fun afterEach(context: ExtensionContext) { + rule.after() + } +} + +interface MockResourceCacheInterface { + val cache: MockResourceCache + + fun addEntry(resourceId: String, regionId: String, credentialsId: String, value: Any) { + cache.addEntry(resourceId, regionId, credentialsId, value) + } + + fun addEntry(project: Project, resourceId: String, value: Any) { + val connectionManager = AwsConnectionManager.getInstance(project) + addEntry(resourceId, connectionManager.selectedRegion!!.id, connectionManager.selectedCredentialIdentifier!!.id, value) + } + + fun addEntry(project: Project, resource: Resource.Cached, value: T) { + addEntry(project, resource.id, value as Any) + } + + fun addEntry(project: Project, resourceId: String, value: CompletableFuture) { + val connectionManager = AwsConnectionManager.getInstance(project) + addEntry(resourceId, connectionManager.selectedRegion!!.id, connectionManager.selectedCredentialIdentifier!!.id, value) + } + + fun addEntry(project: Project, resource: Resource.Cached, value: CompletableFuture) { + addEntry(project, resource.id, value) + } + + fun addEntry(project: Project, resourceId: String, throws: Exception) { + addEntry(project, resourceId, CompletableFuture.failedFuture(throws)) + } + + fun addEntry(connectionSettings: ClientConnectionSettings<*>, resource: Resource.Cached, value: T) { + addEntry(resource, connectionSettings.region.id, connectionSettings.providerId, value) + } + + fun addEntry(connectionSettings: ClientConnectionSettings<*>, resource: Resource.Cached, value: CompletableFuture) { + addEntry(resource, connectionSettings.region.id, connectionSettings.providerId, value) + } + + fun addEntry(resource: Resource.Cached, regionId: String, credentialsId: String, value: T) { + addEntry(resource.id, regionId, credentialsId, value as Any) + } + + fun addEntry(resource: Resource.Cached, regionId: String, credentialsId: String, value: CompletableFuture) { + addEntry(resource.id, regionId, credentialsId, value) + } + + fun size() = cache.entryCount() + + companion object { + fun delegate() = object : MockResourceCacheInterface { + override val cache by lazy { MockResourceCache.getInstance() } + } + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt new file mode 100644 index 00000000000..ab446de62a6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt @@ -0,0 +1,125 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.testFramework.ProjectRule +import org.junit.rules.ExternalResource +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.utils.spinUntil +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import java.time.Duration + +class MockAwsConnectionManager(project: Project) : AwsConnectionManager(project) { + init { + reset() + } + + fun reset() { + recentlyUsedRegions.clear() + recentlyUsedProfiles.clear() + val regionProvider = AwsRegionProvider.getInstance() + @Suppress("DEPRECATION") + changeConnectionSettings(DUMMY_PROVIDER_IDENTIFIER, regionProvider.defaultRegion()) + + waitUntilConnectionStateIsStable() + } + + fun changeRegionAndWait(region: AwsRegion) { + changeRegion(region) + waitUntilConnectionStateIsStable() + } + + fun changeCredentialProviderAndWait(identifier: CredentialIdentifier) { + changeCredentialProvider(identifier) + waitUntilConnectionStateIsStable() + } + + fun nullifyCredentialProviderAndWait() { + changeConnectionSettings(null, selectedRegion) + waitUntilConnectionStateIsStable() + } + + fun setConnectionState(state: ConnectionState) { + connectionState = state + } + + fun addRecentRegion(region: AwsRegion) { + recentlyUsedRegions.add(region.id) + } + + fun clearRecentRegions() { + recentlyUsedRegions.clear() + } + + fun addRecentCredentials(identifier: CredentialIdentifier) { + recentlyUsedProfiles.add(identifier.id) + } + + fun clearRecentCredentials() { + recentlyUsedProfiles.clear() + } + + override fun validate(credentialsProvider: ToolkitCredentialsProvider, region: AwsRegion) {} + + companion object { + fun getInstance(project: Project): MockAwsConnectionManager = project.service() as MockAwsConnectionManager + } + + class ProjectAccountSettingsManagerRule private constructor(projectSupplier: () -> Project) : ExternalResource() { + constructor(projectRule: ProjectRule) : this({ projectRule.project }) + constructor(projectRule: CodeInsightTestFixtureRule) : this({ projectRule.project }) + + private val settingsManagerDelegate = lazy { + getInstance(projectSupplier()) + } + + val settingsManager: MockAwsConnectionManager + get() = settingsManagerDelegate.value + + override fun before() { + if (settingsManagerDelegate.isInitialized()) { + settingsManager.reset() + } + } + + override fun after() { + if (settingsManagerDelegate.isInitialized()) { + settingsManager.reset() + } + } + } +} + +fun runUnderRealCredentials(project: Project, block: () -> T): T { + val credentials = DefaultCredentialsProvider.create().resolveCredentials() + + val manager = MockAwsConnectionManager.getInstance(project) + val credentialsManager = MockCredentialsManager.getInstance() + val oldActive = manager.connectionSettings()?.credentials + val realCredentialsProvider = credentialsManager.addCredentials("RealCredentials", credentials) + try { + println("Running using real credentials") + + manager.changeCredentialProviderAndWait(realCredentialsProvider) + + return block.invoke() + } finally { + oldActive?.let { + credentialsManager.getCredentialIdentifierById(oldActive.id)?.let { + manager.changeCredentialProviderAndWait(it) + } + } + credentialsManager.removeCredentials(realCredentialsProvider) + } +} + +fun AwsConnectionManager.waitUntilConnectionStateIsStable() = spinUntil(Duration.ofSeconds(10)) { + connectionState.isTerminal +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt new file mode 100644 index 00000000000..c0bdd936ddc --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt @@ -0,0 +1,183 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.openapi.components.service +import com.intellij.testFramework.ApplicationRule +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.q.jetbrains.core.region.getDefaultRegion +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialIdentifierBase +import software.amazon.q.core.credentials.CredentialProviderFactory +import software.amazon.q.core.credentials.CredentialSourceId +import software.amazon.q.core.credentials.CredentialsChangeListener +import software.amazon.q.core.credentials.SsoSessionIdentifier +import software.amazon.q.core.credentials.ToolkitCredentialsProvider +import software.amazon.q.core.region.AwsRegion +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.rules.ClearableLazy + +@Deprecated("Use MockCredentialManagerRule") +class MockCredentialsManager : CredentialManager() { + init { + reset() + } + + @Suppress("DEPRECATION") + fun reset() { + getCredentialIdentifiers().filterNot { it.id == DUMMY_PROVIDER_IDENTIFIER.id }.forEach { removeProvider(it) } + + addProvider(DUMMY_PROVIDER_IDENTIFIER) + } + + fun addCredentials( + id: String, + credentials: AwsCredentials = AwsBasicCredentials.create("Access", "Secret"), + regionId: String? = null, + ): CredentialIdentifier = addCredentials(id, StaticCredentialsProvider.create(credentials), regionId) + + fun addCredentials( + id: String, + credentials: AwsCredentialsProvider, + regionId: String? = null, + ): MockCredentialIdentifier = MockCredentialIdentifier(id, credentials, regionId).also { + addProvider(it) + } + + fun addCredentials( + credentialIdentifier: CredentialIdentifier, + ): CredentialIdentifier { + addProvider(credentialIdentifier) + return credentialIdentifier + } + + fun addSsoProvider( + ssoSessionIdentifier: SsoSessionIdentifier, + ): SsoSessionIdentifier { + super.addSsoSession(ssoSessionIdentifier) + return ssoSessionIdentifier + } + + fun createCredentialProvider( + id: String = aString(), + credentials: AwsCredentials, + region: AwsRegion, + ): ToolkitCredentialsProvider { + val credentialIdentifier = MockCredentialIdentifier(id, StaticCredentialsProvider.create(credentials), null) + + addProvider(credentialIdentifier) + + return getAwsCredentialProvider(credentialIdentifier, region) + } + + fun removeCredentials(credentialIdentifier: CredentialIdentifier) { + removeProvider(credentialIdentifier) + } + + override fun factoryMapping(): Map = + mapOf(MockCredentialProviderFactory.id to MockCredentialProviderFactory) + + companion object { + @Suppress("DEPRECATION") + fun getInstance(): MockCredentialsManager = service() as MockCredentialsManager + } + + class MockCredentialIdentifier(override val displayName: String, val credentials: AwsCredentialsProvider, override val defaultRegionId: String?) : + CredentialIdentifierBase(null) { + override val id: String = displayName + override val factoryId: String = "mockCredentialProviderFactory" + } + + private object MockCredentialProviderFactory : CredentialProviderFactory { + override val id: String = "mockCredentialProviderFactory" + override val credentialSourceId: CredentialSourceId = CredentialSourceId.SharedCredentials + + override fun setUp(credentialLoadCallback: CredentialsChangeListener) {} + + override fun createAwsCredentialProvider( + providerId: CredentialIdentifier, + region: AwsRegion, + ): ToolkitCredentialsProvider = ToolkitCredentialsProvider(providerId, (providerId as MockCredentialIdentifier).credentials) + } +} + +@Suppress("DEPRECATION") +open class MockCredentialManagerRule : ApplicationRule() { + private val lazyCredentialManager = ClearableLazy { + MockCredentialsManager.getInstance() + } + + private val credentialManager: MockCredentialsManager + get() = lazyCredentialManager.value + + fun addCredentials( + id: String = aString(), + credentials: AwsCredentials = AwsBasicCredentials.create("Access", "Secret"), + region: AwsRegion? = null, + ): CredentialIdentifier = credentialManager.addCredentials(id, credentials, region?.id) + + fun addCredentials( + id: String, + credentials: AwsCredentialsProvider, + regionId: String? = null, + ): MockCredentialsManager.MockCredentialIdentifier = credentialManager.addCredentials(id, credentials, regionId) + + fun addCredentials( + credentialIdentifier: CredentialIdentifier, + ): CredentialIdentifier = credentialManager.addCredentials(credentialIdentifier) + + fun addSsoProvider( + ssoSessionIdentifier: SsoSessionIdentifier, + ): SsoSessionIdentifier = credentialManager.addSsoProvider(ssoSessionIdentifier) + + fun createCredentialProvider( + id: String = aString(), + credentials: AwsCredentials = AwsBasicCredentials.create("Access", "Secret"), + // Do not store this value as we should be able to dynamically change it + region: AwsRegion = getDefaultRegion(), + ): ToolkitCredentialsProvider = credentialManager.createCredentialProvider(id, credentials, region) + + fun getAwsCredentialProvider(providerId: CredentialIdentifier, region: AwsRegion): ToolkitCredentialsProvider = + credentialManager.getAwsCredentialProvider(providerId, region) + + fun removeCredentials(credentialIdentifier: CredentialIdentifier) = credentialManager.removeCredentials(credentialIdentifier) + + override fun after() { + lazyCredentialManager.ifSet { + reset() + lazyCredentialManager.clear() + } + } + + fun clear() { + reset() + credentialManager.removeCredentials(DUMMY_PROVIDER_IDENTIFIER) + } + + fun reset() { + credentialManager.reset() + } +} + +class MockCredentialManagerExtension : MockCredentialManagerRule(), AfterEachCallback { + override fun afterEach(context: ExtensionContext) { + after() + } +} + +@Deprecated( + "DUMMY_PROVIDER_IDENTIFIER should not be used outside of the MockCredentialsManager, if you " + + "need mock credentials set them up in the test instead of relying on the global one" +) +@Suppress("DEPRECATION") +val DUMMY_PROVIDER_IDENTIFIER: CredentialIdentifier = MockCredentialsManager.MockCredentialIdentifier( + "DUMMY_CREDENTIALS", + StaticCredentialsProvider.create(AwsBasicCredentials.create("DummyAccess", "DummySecret")), + null +) diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsRegionHandler.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsRegionHandler.kt new file mode 100644 index 00000000000..016e1fe6078 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsRegionHandler.kt @@ -0,0 +1,11 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.region.AwsRegion + +class MockCredentialsRegionHandler : CredentialsRegionHandler { + override fun determineSelectedRegion(identifier: CredentialIdentifier, selectedRegion: AwsRegion?): AwsRegion? = selectedRegion +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockToolkitAuthManagerRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockToolkitAuthManagerRule.kt new file mode 100644 index 00000000000..03b49b47485 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockToolkitAuthManagerRule.kt @@ -0,0 +1,31 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials + +import com.intellij.testFramework.ApplicationRule +import software.amazon.q.jetbrains.utils.rules.ClearableLazy + +class MockToolkitAuthManagerRule : ApplicationRule() { + private val lazyAuthManager = ClearableLazy { + ToolkitAuthManager.getInstance() + } + + private val authManager + get() = lazyAuthManager.value + + override fun after() { + lazyAuthManager.ifSet { + reset() + lazyAuthManager.clear() + } + } + + fun reset() { + authManager.listConnections().forEach { + authManager.deleteConnection(it) + } + } + + fun createConnection(profile: AuthProfile) = authManager.createConnection(profile) +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/sso/MockSsoLoginCallbackProvider.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/sso/MockSsoLoginCallbackProvider.kt new file mode 100644 index 00000000000..16457f38971 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/sso/MockSsoLoginCallbackProvider.kt @@ -0,0 +1,106 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.credentials.sso + +import com.intellij.openapi.components.service +import org.junit.jupiter.api.Assumptions.assumeTrue +import software.amazon.awssdk.core.SdkBytes +import software.amazon.awssdk.services.lambda.LambdaClient +import software.amazon.awssdk.services.lambda.model.InvocationType +import software.amazon.awssdk.services.lambda.model.LambdaException +import software.amazon.q.core.clients.nullDefaultProfileFile +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.utils.scrubException + +internal class MockSsoLoginCallbackProvider : SsoLoginCallbackProvider { + internal var provider: SsoLoginCallback? = null + private object ErrorSsoLoginCallback : SsoLoginCallback { + override fun tokenPending(authorization: Authorization) { + error("Not implemented") + } + + override fun tokenRetrieved() {} + + override fun tokenRetrievalFailure(e: Exception) {} + } + + override fun getProvider(isAlwaysShowDeviceCode: Boolean, ssoUrl: String): SsoLoginCallback = + provider ?: ErrorSsoLoginCallback + + companion object { + fun getInstance() = service() as MockSsoLoginCallbackProvider + } +} + +object NoOpSsoLoginCallback : SsoLoginCallback { + override fun tokenPending(authorization: Authorization) {} + + override fun tokenRetrieved() {} + + override fun tokenRetrievalFailure(e: Exception) {} +} + +internal class TestSsoPrompt(private val secretName: String) : SsoLoginCallback { + override fun tokenPending(authorization: Authorization) { + val authLambda = System.getenv(authLambdaArn) + assumeTrue(authLambda != null) { + "Skipping test since $authLambdaArn wasn't set".also { + LOG.warn { it } + } + } + + LOG.info { "Invoking login with authorization: $authorization" } + + // language=JSON + val payload = """{ + | "secret": "$secretName", + | "userCode": "${authorization.userCode}", + | "verificationUri": "${authorization.verificationUri}" + |} + """.trimMargin() + + val response = try { + LambdaClient.builder() + .overrideConfiguration { + if (!System.getenv("CI").isNullOrBlank()) { + it.nullDefaultProfileFile() + } + } + .build().use { client -> + client.invoke { + it.functionName(authLambda) + it.payload(SdkBytes.fromUtf8String(payload)) + it.invocationType(InvocationType.REQUEST_RESPONSE) + } + } + } catch (e: LambdaException) { + throw scrubException(e) + } + + LOG.info { + """ + "Auth invocation request ID: ${response.responseMetadata().requestId()}" + "Auth function error: ${response.functionError()} + "Auth invocation response status code: ${response.statusCode()}" + """.trimIndent() + } + } + + override fun tokenRetrieved() { + LOG.info { "Token successfully retrieved" } + } + + override fun tokenRetrievalFailure(e: Exception) { + LOG.error(e) { "Failed to retrieve token" } + } + + private companion object { + const val authLambdaArn = "AUTH_UTIL_LAMBDA_ARN" + + private val LOG = getLogger() + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt new file mode 100644 index 00000000000..3f816763bd9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt @@ -0,0 +1,126 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.core.region + +import com.intellij.openapi.components.service +import com.intellij.testFramework.ApplicationRule +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import software.amazon.awssdk.regions.Region +import software.amazon.q.core.region.AwsPartition +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.region.Service +import software.amazon.q.core.region.ToolkitRegionProvider +import software.aws.toolkits.core.region.aRegionId +import software.aws.toolkits.core.region.anAwsRegion +import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.jetbrains.utils.rules.ClearableLazy + +private class MockRegionProvider : ToolkitRegionProvider() { + private val overrideRegions: MutableMap = mutableMapOf() + private val services: MutableMap = mutableMapOf() + + fun addRegion(region: AwsRegion): AwsRegion { + overrideRegions[region.id] = region + return region + } + + fun addService(serviceName: String, service: Service) { + services[serviceName] = service + } + + fun reset() { + overrideRegions.clear() + services.clear() + } + + override fun partitionData(): Map { + val combinedRegions = regions + overrideRegions + return combinedRegions + .asSequence() + .associate { + it.value.partitionId to PartitionData( + it.value.partitionId, + services, + combinedRegions.filterValues { regions -> regions.partitionId == it.value.partitionId } + ) + } + } + + override fun isServiceGlobal(region: AwsRegion, serviceId: String) = + if (serviceId in services.keys) super.isServiceGlobal(region, serviceId) else false + + override fun isServiceSupported(region: AwsRegion, serviceName: String): Boolean = true + + override fun defaultPartition(): AwsPartition = AWS_CLASSIC + + override fun defaultRegion(): AwsRegion = US_EAST_1 + + companion object { + private val AWS_CLASSIC = AwsPartition("aws", "AWS Classic", listOf(US_EAST_1)) + private val regions = mapOf(US_EAST_1.id to US_EAST_1) + fun getInstance(): MockRegionProvider = service() as MockRegionProvider + } +} + +sealed class MockRegionProviderBase : ApplicationRule() { + private val lazyRegionProvider = ClearableLazy { + MockRegionProvider.getInstance() + } + + private val regionManager: MockRegionProvider + get() = lazyRegionProvider.value + + fun addRegion(region: AwsRegion): AwsRegion = regionManager.addRegion(region) + fun addRegion(sdkRegion: Region): AwsRegion = regionManager.addRegion( + AwsRegion( + id = sdkRegion.id(), + name = sdkRegion.toString(), + partitionId = sdkRegion.metadata().partition().id() + ) + ) + + fun createAwsRegion(id: String = uniqueRegionId(), partitionId: String = aString()): AwsRegion = + anAwsRegion(id = id, partitionId = partitionId).also { regionManager.addRegion(it) } + + private fun uniqueRegionId(): String { + repeat(10) { + val generatedId = aRegionId() + if (regionManager[generatedId] == null) { + return generatedId + } + } + error("Failed to generate a unique region ID") + } + + fun defaultPartition(): AwsPartition = regionManager.defaultPartition() + + fun defaultRegion(): AwsRegion = regionManager.defaultRegion() + + fun addService(serviceName: String, service: Service) = regionManager.addService(serviceName, service) + + override fun after() { + lazyRegionProvider.ifSet { + reset() + lazyRegionProvider.clear() + } + } + + fun reset() { + regionManager.reset() + } +} + +class MockRegionProviderRule : MockRegionProviderBase() + +class MockRegionProviderExtension : MockRegionProviderBase(), AfterEachCallback { + override fun afterEach(context: ExtensionContext?) { + after() + } +} + +// dynamically get the default region from whatever is currently registered +fun getDefaultRegion() = service().defaultRegion() + +val US_EAST_1 = AwsRegion("us-east-1", "US East (N. Virginia)", "aws") diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/services/telemetry/MockTelemetryService.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/services/telemetry/MockTelemetryService.kt new file mode 100644 index 00000000000..02108450eb6 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/services/telemetry/MockTelemetryService.kt @@ -0,0 +1,73 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.services.telemetry + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.replaceService +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.rules.ExternalResource +import org.mockito.kotlin.reset +import org.mockito.kotlin.spy +import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment +import software.amazon.q.core.telemetry.DefaultTelemetryBatcher +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher + +class NoOpTelemetryService : TelemetryService { + constructor(noOpPublisher: NoOpPublisher, batcher: TelemetryBatcher) : super(noOpPublisher, batcher) + constructor() : this(NO_OP_PUBLISHER, DefaultTelemetryBatcher(NO_OP_PUBLISHER)) + + fun batcher() = super.batcher + + companion object { + val NO_OP_PUBLISHER = NoOpPublisher() + } +} + +class NoOpPublisher : TelemetryPublisher { + override suspend fun publish(metricEvents: Collection) {} + + override suspend fun sendFeedback(sentiment: Sentiment, comment: String, metadata: Map) {} + + override fun close() {} +} + +sealed class MockTelemetryServiceBase : ExternalResource() { + protected val publisher: NoOpPublisher by lazy { NoOpTelemetryService.NO_OP_PUBLISHER } + protected val batcher: TelemetryBatcher by lazy { spy(DefaultTelemetryBatcher(publisher)) } + private lateinit var disposableParent: Disposable + + private val mockTelemetryService: NoOpTelemetryService by lazy { NoOpTelemetryService(publisher, batcher) } + + override fun before() { + // hack because @TestDisposable doesn't work here as it's not a test + disposableParent = Disposer.newDisposable() + ApplicationManager.getApplication().replaceService(TelemetryService::class.java, mockTelemetryService, disposableParent) + } + + override fun after() { + reset(batcher()) + Disposer.dispose(disposableParent) + } + + fun telemetryService() = mockTelemetryService + fun batcher() = mockTelemetryService.batcher() +} + +class MockTelemetryServiceRule : MockTelemetryServiceBase() + +class MockTelemetryServiceExtension : MockTelemetryServiceBase(), BeforeEachCallback, AfterEachCallback { + override fun beforeEach(context: ExtensionContext?) { + before() + } + + override fun afterEach(context: ExtensionContext?) { + after() + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt new file mode 100644 index 00000000000..62ef0c67ca9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt @@ -0,0 +1,37 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.settings + +import com.intellij.testFramework.ApplicationRule +import software.amazon.q.jetbrains.settings.AwsSettings +import java.util.UUID + +class MockAwsSettings : AwsSettings { + override var isTelemetryEnabled: Boolean = true + override var promptedForTelemetry: Boolean = false + override var useDefaultCredentialRegion: UseAwsCredentialRegion = UseAwsCredentialRegion.Prompt + override var profilesNotification: ProfilesNotification = ProfilesNotification.Always + override var isAutoUpdateEnabled: Boolean = true + override var isAutoUpdateNotificationEnabled: Boolean = true + override var isAutoUpdateFeatureNotificationShownOnce: Boolean = false + override var isQMigrationNotificationShownOnce: Boolean = false + override val clientId: UUID = UUID.randomUUID() + + internal fun reset() { + isTelemetryEnabled = true + promptedForTelemetry = false + useDefaultCredentialRegion = UseAwsCredentialRegion.Prompt + profilesNotification = ProfilesNotification.Always + } +} + +class AwsSettingsRule : ApplicationRule() { + val settings by lazy { + AwsSettings.getInstance() + } + + override fun after() { + (settings as MockAwsSettings).reset() + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/AssertJMatchers.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/AssertJMatchers.kt new file mode 100644 index 00000000000..7f760a2fde9 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/AssertJMatchers.kt @@ -0,0 +1,58 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import org.assertj.core.api.AbstractAssert +import org.assertj.core.api.AbstractIterableAssert +import org.assertj.core.api.AbstractThrowableAssert +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.CompletableFutureAssert +import java.time.Duration +import java.util.concurrent.CompletionStage +import java.util.concurrent.TimeUnit +import java.util.function.Consumer + +private val TIMEOUT = Duration.ofSeconds(1) + +fun CompletableFutureAssert.wait(): CompletableFutureAssert { + try { + matches { it.get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS) != null } + } catch (e: Exception) { + // suppress + } + return this +} + +fun CompletableFutureAssert.hasValue(value: T) { + wait().isCompletedWithValue(value) +} + +val CompletionStage.value get() = toCompletableFuture().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS) + +val CompletableFutureAssert.hasException get() = this.wait().isCompletedExceptionally + +// https://github.com/assertj/assertj/issues/2357 +@Suppress("UNCHECKED_CAST") +fun > AbstractIterableAssert<*, I, E, *>.allSatisfyKt(requirements: Consumer) = + allSatisfy(requirements) as AbstractIterableAssert<*, I, E, *> + +@Suppress("UNCHECKED_CAST") +fun > AbstractIterableAssert<*, I, E, *>.anySatisfyKt(requirements: Consumer) = + anySatisfy(requirements) as AbstractIterableAssert<*, I, E, *> + +@Suppress("UNCHECKED_CAST") +fun AbstractAssert<*, T>.satisfiesKt(requirements: Consumer) = + satisfies(requirements) as AbstractAssert<*, T> + +fun , ACTUAL : Throwable> AbstractThrowableAssert.hasCauseWithMessage( + message: String, +): AbstractThrowableAssert { + satisfiesKt { parentThrowable -> + assertThat(parentThrowable.cause).isNotNull.hasMessage(message) + } + return this +} + +inline fun AbstractAssert<*, *>.isInstanceOf() = isInstanceOf(T::class.java) +inline fun AbstractAssert<*, *>.isInstanceOfSatisfying(checker: Consumer) = isInstanceOfSatisfying(T::class.java, checker) diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/SerializationUtils.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/SerializationUtils.kt new file mode 100644 index 00000000000..d74e32ee6de --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/SerializationUtils.kt @@ -0,0 +1,29 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import com.intellij.configurationStore.deserializeAndLoadState +import com.intellij.configurationStore.serializeStateInto +import com.intellij.openapi.components.PersistentStateComponent +import org.intellij.lang.annotations.Language +import org.jdom.Element +import org.jdom.input.SAXBuilder +import org.jdom.output.XMLOutputter +import java.io.ByteArrayInputStream + +fun xmlElement(@Language("XML") str: String): Element { + val stream = ByteArrayInputStream(str.toByteArray()) + val builder = SAXBuilder() + return builder.build(stream).rootElement +} + +fun serializeState(rootTag: String, state: PersistentStateComponent<*>): String { + val element = Element(rootTag) + serializeStateInto(state, element) + return XMLOutputter().outputString(element) +} + +fun deserializeState(@Language("XML") str: String, state: PersistentStateComponent<*>) { + deserializeAndLoadState(state, xmlElement(str)) +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/ServiceExceptionUtils.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/ServiceExceptionUtils.kt new file mode 100644 index 00000000000..af27465b888 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/ServiceExceptionUtils.kt @@ -0,0 +1,47 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils + +import software.amazon.awssdk.awscore.exception.AwsServiceException +import software.amazon.awssdk.core.exception.SdkException + +// https://github.com/aws/aws-toolkit-vscode/blob/bf612d8c7477316a5f2e4bd591966b78d449846d/src/test/setupUtil.ts#L139-L150 +val ARN_REGEX = "arn:(aws|aws-cn|aws-us-gov):(?:.*?):(.*?):(.*?):.".toRegex() + +fun scrubException(e: Exception): Exception { + if (e is AwsServiceException && e.awsErrorDetails() != null) { + return e.toBuilder() + .message(scrubArn(e.message)) + .awsErrorDetails(e.awsErrorDetails().toBuilder().errorMessage(scrubArn(e.awsErrorDetails().errorMessage())).build()) + .build().apply { + stackTrace = e.stackTrace + } + } + + if (e is SdkException) { + return e.toBuilder() + .message(scrubArn(e.message)) + .build().apply { + stackTrace = e.stackTrace + } + } + + return e +} + +private fun scrubArn(s: String?) = s?.let { message -> + ARN_REGEX.replace(message) { + val (_partition, region, account) = it.destructured + + var ret = it.value + if (region.isNotBlank()) { + ret = ret.replace(region, "***") + } + if (account.isNotBlank()) { + ret = ret.replace(account, "***") + } + + ret + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/extensions/SsoLoginExtension.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/extensions/SsoLoginExtension.kt new file mode 100644 index 00000000000..4e7183fb0cf --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/extensions/SsoLoginExtension.kt @@ -0,0 +1,46 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.extensions + +import com.intellij.testFramework.DisposableRule +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.credentials.sso.MockSsoLoginCallbackProvider +import software.amazon.q.jetbrains.core.credentials.sso.TestSsoPrompt + +class SsoLoginExtension : DisposableRule(), BeforeEachCallback, AfterEachCallback { + override fun beforeEach(context: ExtensionContext) { + val ssoSecret = getAnnotation(context) ?: return + + MockClientManager.useRealImplementations(disposable) + MockSsoLoginCallbackProvider.getInstance().provider = TestSsoPrompt(ssoSecret) + } + + override fun afterEach(context: ExtensionContext?) { + MockSsoLoginCallbackProvider.getInstance().provider = null + + // todo: is there a better way to do this + after() + } + + private fun getAnnotation(context: ExtensionContext?): String? { + if (context == null || context == context.root) { + return null + } + + return context.element.orElse(null) + ?.annotations + ?.filterIsInstance() + ?.firstOrNull() + ?.secretName + ?: getAnnotation(context.parent.orElse(null)) + } +} + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@ExtendWith(SsoLoginExtension::class) +annotation class SsoLogin(val secretName: String) diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/CodeInsightTestFixtureRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/CodeInsightTestFixtureRule.kt new file mode 100644 index 00000000000..4280e5f58e1 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/CodeInsightTestFixtureRule.kt @@ -0,0 +1,183 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.module.JavaModuleType +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.common.ThreadLeakTracker +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import com.intellij.testFramework.runInEdtAndGet +import com.intellij.testFramework.runInEdtAndWait +import com.intellij.testFramework.writeChild +import org.junit.runner.Description +import org.mockito.Mockito +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn +import java.nio.file.Paths + +/** + * JUnit test Rule that will create a Light [Project] and [CodeInsightTestFixture]. Projects are lazily created and are + * torn down after each test. + * + * If you wish to have just a [Project], you may use Intellij's [com.intellij.testFramework.ProjectRule] + */ +open class CodeInsightTestFixtureRule(protected val testDescription: LightProjectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR) : + ApplicationRule() { + private lateinit var description: Description + protected val lazyFixture = ClearableLazy { + createTestFixture() + } + + protected open fun createTestFixture(): CodeInsightTestFixture { + val fixtureBuilder = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder(testDescription, testName) + val newFixture = IdeaTestFixtureFactory.getFixtureFactory() + .createCodeInsightFixture(fixtureBuilder.fixture, LightTempDirTestFixtureImpl(true)) + newFixture.setUp() + newFixture.testDataPath = testDataPath + return newFixture + } + + override fun before(description: Description) { + super.before(description) + this.description = description + // This timer is cancelled but it still continues running when the test is over since it cancels lazily. This is fine, so suppress the leak + ThreadLeakTracker.longRunningThreadCreated(ApplicationManager.getApplication(), "Debugger Worker launch timer") + ThreadLeakTracker.longRunningThreadCreated(ApplicationManager.getApplication(), "Test worker") + } + + override fun after() { + super.after() + // Hack: Runs often enough that we keep our leaks down. https://github.com/mockito/mockito/pull/1619 + // TODO: Investigate Mockk and remove this + Mockito.framework().clearInlineMocks() + + lazyFixture.ifSet { + try { + fixture.tearDown() + } catch (e: Exception) { + LOG.warn(e) { "Exception during tear-down" } + } + lazyFixture.clear() + } + } + + val project: Project + get() = fixture.project + + val testName: String + get() = PlatformTestUtil.getTestName(description.methodName, true) + + private val testClass: Class<*> + get() = description.testClass + + val module: Module + get() = fixture.module + + open val fixture: CodeInsightTestFixture + get() = lazyFixture.value + + protected val testDataPath: String + get() = Paths.get("testdata", testClass.simpleName, testName).toString() + + private companion object { + val LOG = getLogger() + } +} + +class ClearableLazy(private val initializer: () -> T) { + private var _value: T? = null + private var isSet = false + + val value: T + get() { + synchronized(this) { + if (!isSet) { + _value = initializer() + isSet = true + } + return _value!! + } + } + + fun clear() { + synchronized(this) { + isSet = false + _value = null + } + } + + fun ifSet(function: () -> Unit) { + synchronized(this) { + if (isSet) function() + } + } +} + +internal fun invokeAndWait(action: () -> T): T { + val application = ApplicationManager.getApplication() + + return if (application.isDispatchThread) { + action() + } else { + var ref: T? = null + application.invokeAndWait({ ref = action() }, ModalityState.NON_MODAL) + ref!! + } +} + +fun CodeInsightTestFixture.openFile(relativePath: String, fileText: String): VirtualFile { + val file = this.addFileToProject(relativePath, fileText).virtualFile + runInEdtAndWait { + this.openFileInEditor(file) + } + + return file +} + +fun CodeInsightTestFixture.addModule(moduleName: String): Module { + val root = this.tempDirFixture.findOrCreateDir(moduleName) + val module = PsiTestUtil.addModule(project, JavaModuleType.getModuleType(), moduleName, root) + runInEdtAndWait { + WriteAction.run { + PsiTestUtil.addContentRoot(module, root) + } + } + return module +} + +fun CodeInsightTestFixture.addFileToModule( + module: Module, + relativePath: String, + fileText: String, +): PsiFile = runInEdtAndGet { + val file = try { + val contentRoot = ModuleRootManager.getInstance(module).contentRoots[0] + runWriteAction { + contentRoot.writeChild(FileUtil.toSystemIndependentName(relativePath), fileText) + } + } finally { + PsiManager.getInstance(project).dropPsiCaches() + } + + runReadAction { + PsiManager.getInstance(project).findFile(file)!! + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/JavaCodeInsightTestFixtureRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/JavaCodeInsightTestFixtureRule.kt new file mode 100644 index 00000000000..ecc2b002508 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/JavaCodeInsightTestFixtureRule.kt @@ -0,0 +1,168 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.module.JavaModuleType +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.PsiJavaFile +import com.intellij.testFramework.IdeaTestUtil +import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture +import com.intellij.testFramework.fixtures.JavaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import com.intellij.testFramework.runInEdtAndWait +import org.intellij.lang.annotations.Language +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import java.io.File +import java.nio.file.Paths + +/** + * JUnit test Rule that will create a Light [Project] and [JavaCodeInsightTestFixture]. Projects are lazily created + * and are torn down after each test. + * + * If you wish to have just a [Project], you may use Intellij's [com.intellij.testFramework.ProjectRule] + */ +class JavaCodeInsightTestFixtureRule(testDescription: DefaultLightProjectDescriptor = DefaultLightProjectDescriptor()) : + CodeInsightTestFixtureRule(testDescription) { + + override fun createTestFixture(): CodeInsightTestFixture { + val fixtureBuilder = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder(testDescription, testName) + val newFixture = JavaTestFixtureFactory.getFixtureFactory() + .createCodeInsightFixture(fixtureBuilder.fixture, LightTempDirTestFixtureImpl(true)) + newFixture.setUp() + newFixture.testDataPath = testDataPath + return newFixture + } + + override val fixture: JavaCodeInsightTestFixture + get() = lazyFixture.value as JavaCodeInsightTestFixture +} + +/** + * JUnit test Rule that will create a Heavy [Project] and [JavaCodeInsightTestFixture]. Projects are lazily created + * and are torn down after each test. + * + * If you wish to have just a [Project], you may use Intellij's [com.intellij.testFramework.ProjectRule] + */ +class HeavyJavaCodeInsightTestFixtureRule : CodeInsightTestFixtureRule() { + override fun createTestFixture(): CodeInsightTestFixture { + val fixtureBuilder = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(testName) + val newFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(fixtureBuilder.fixture) + newFixture.setUp() + newFixture.testDataPath = testDataPath + return newFixture + } + + override val fixture: JavaCodeInsightTestFixture + get() = lazyFixture.value as JavaCodeInsightTestFixture +} + +/** + * Add a JDK1.8 module named [moduleName] to the test fixture project with 'src', 'src-resources' and 'tst roots setup. + * @return the created [Module] + */ +fun JavaCodeInsightTestFixture.addModule(moduleName: String): Module { + val root = this.tempDirFixture.findOrCreateDir(moduleName) + val module = PsiTestUtil.addModule(project, JavaModuleType.getModuleType(), moduleName, root) + PsiTestUtil.removeAllRoots(module, IdeaTestUtil.getMockJdk18()) + runInEdtAndWait { + WriteAction.run { + PsiTestUtil.addContentRoot(module, root) + PsiTestUtil.addSourceRoot(module, createChildDirectories(root, "src/main/java"), false) + PsiTestUtil.addSourceRoot( + module, + createChildDirectories(root, "src/main/resources"), + JavaResourceRootType.RESOURCE + ) + PsiTestUtil.addSourceRoot(module, createChildDirectories(root, "tst/main/java"), true) + } + } + return module +} + +private fun createChildDirectories(root: VirtualFile, path: String): VirtualFile { + var parent = root + path.split("/").forEach { + val childDirectory = parent.findChild(it) ?: parent.createChildDirectory(null, it) + parent = childDirectory + } + + return parent +} + +fun JavaCodeInsightTestFixture.openClass(@Language("JAVA") javaClass: String): PsiClass { + val psiClass = this.addClass(javaClass) + runInEdtAndWait { + this.openFileInEditor(psiClass.containingFile.virtualFile) + } + return psiClass +} + +/** + * Add a Java class to the given [module] in the [JavaSourceRootType.SOURCE] root at the in the appropriate directory + * determined by the definition in the [classText] content. + * + * @see JavaCodeInsightTestFixture.addClass + */ +fun JavaCodeInsightTestFixture.addClass(module: Module, @Language("JAVA") classText: String): PsiClass { + val qName = determineQualifiedName(module.project, classText) + val fileName = qName.replace('.', File.separatorChar) + ".java" + val psiFile = addFile(module, JavaSourceRootType.SOURCE, fileName, classText) as PsiJavaFile + return ReadAction.compute { psiFile.classes[0] } +} + +/** + * Add a Java test class to the given [module] in the [JavaSourceRootType.TEST_SOURCE] root at the in the appropriate directory + * determined by the definition in the [classText] content. + * + * @see JavaCodeInsightTestFixture.addClass + */ +fun JavaCodeInsightTestFixture.addTestClass(module: Module, @Language("JAVA") classText: String): PsiClass { + val qName = determineQualifiedName(module.project, classText) + val fileName = qName.replace('.', File.separatorChar) + ".java" + val psiFile = addFile(module, JavaSourceRootType.TEST_SOURCE, fileName, classText) as PsiJavaFile + return ReadAction.compute { psiFile.classes[0] } +} + +/** + * Add a resource file to the given [module] in the [JavaResourceRootType.RESOURCE] root at the path specified in [fileName] + * with [content]. + */ +fun JavaCodeInsightTestFixture.addResourceFile(module: Module, fileName: String, content: String): PsiFile = + addFile(module, JavaResourceRootType.RESOURCE, fileName, content) + +private fun JavaCodeInsightTestFixture.addFile( + module: Module, + type: JpsModuleSourceRootType<*>, + fileName: String, + content: String, +): PsiFile { + val sourceRoot = ModuleRootManager.getInstance(module).getSourceRoots(type).first() + val fullPath = Paths.get(sourceRoot.path, fileName).toString() + val projectRelativePath = FileUtil.getRelativePath(tempDirPath, fullPath, File.separatorChar) + ?: throw RuntimeException("Cannot determine relative path") + return addFileToProject(projectRelativePath.replace('\\', '/'), content) +} + +private fun determineQualifiedName(project: Project, classText: String): String = + ReadAction.compute { + val factory = PsiFileFactory.getInstance(project) + val javaFile = factory.createFileFromText("a.java", JavaFileType.INSTANCE, classText) as PsiJavaFile + javaFile.classes[0].qualifiedName + } ?: throw RuntimeException("Cannot determine fully qualified name") diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/NotificationListenerRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/NotificationListenerRule.kt new file mode 100644 index 00000000000..9919f967705 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/NotificationListenerRule.kt @@ -0,0 +1,50 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.notification.Notification +import com.intellij.notification.Notifications +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ComponentManager +import com.intellij.testFramework.ProjectRule +import org.junit.rules.ExternalResource +import java.util.concurrent.CopyOnWriteArrayList + +class NotificationListenerRule : ExternalResource { + private val messageBusSupplier: () -> ComponentManager + private val disposable: Disposable + + constructor(disposable: Disposable) : super() { + this.messageBusSupplier = { ApplicationManager.getApplication() } + this.disposable = disposable + this.notifications = CopyOnWriteArrayList() + } + + constructor(projectRule: ProjectRule, disposable: Disposable) : super() { + this.messageBusSupplier = { projectRule.project } + this.disposable = disposable + this.notifications = CopyOnWriteArrayList() + } + + constructor(projectRule: CodeInsightTestFixtureRule, disposable: Disposable) : super() { + this.messageBusSupplier = { projectRule.project } + this.disposable = disposable + this.notifications = CopyOnWriteArrayList() + } + + val notifications: CopyOnWriteArrayList + + override fun before() { + messageBusSupplier().messageBus.connect(disposable).subscribe( + Notifications.TOPIC, + object : Notifications { + override fun notify(notification: Notification) { + notifications.add(notification) + } + } + ) + notifications.clear() + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/RegistryRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/RegistryRule.kt new file mode 100644 index 00000000000..5ca3f043acb --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/RegistryRule.kt @@ -0,0 +1,36 @@ +// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.openapi.util.registry.Registry +import com.intellij.testFramework.ApplicationRule +import org.junit.runner.Description + +/** + * Allows a test run to have an experiment enabled, and then restore previous state + */ +class RegistryRule(private val featureId: String, private val desiredEnabledState: Boolean = true) : ApplicationRule() { + + private var originalState: Boolean = false + + override fun before(description: Description) { + super.before(description) + originalState = Registry.`is`(featureId) + if (originalState != desiredEnabledState) { + Registry.get(featureId).setValue(desiredEnabledState) + } + } + + fun setState(state: Boolean) { + if (Registry.get(featureId).asBoolean() != state) { + Registry.get(featureId).setValue(state) + } + } + + override fun after() { + if (Registry.`is`(featureId) != originalState) { + Registry.get(featureId).setValue(originalState) + } + } +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/SsoLoginRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/SsoLoginRule.kt new file mode 100644 index 00000000000..4bba2d1046b --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/SsoLoginRule.kt @@ -0,0 +1,51 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.testFramework.DisposableRule +import org.junit.rules.ExternalResource +import org.junit.runner.Description +import org.junit.runners.model.Statement +import software.amazon.q.jetbrains.core.MockClientManager +import software.amazon.q.jetbrains.core.credentials.sso.MockSsoLoginCallbackProvider +import software.amazon.q.jetbrains.core.credentials.sso.NoOpSsoLoginCallback +import software.amazon.q.jetbrains.core.credentials.sso.SsoLoginCallback +import software.amazon.q.jetbrains.core.credentials.sso.TestSsoPrompt +import software.amazon.q.jetbrains.utils.extensions.SsoLogin + +class SsoLoginRule : DisposableRule() { + override fun apply(base: Statement, description: Description): Statement { + val annotation = description.getAnnotation(SsoLogin::class.java) ?: description.testClass.getAnnotation(SsoLogin::class.java) + return if (annotation == null) { + base + } else { + object : Statement() { + override fun evaluate() { + try { + MockClientManager.useRealImplementations(disposable) + MockSsoLoginCallbackProvider.getInstance().provider = TestSsoPrompt(annotation.secretName) + + base.evaluate() + } finally { + MockSsoLoginCallbackProvider.getInstance().provider = null + } + } + } + } + } +} + +class SsoLoginCallbackProviderRule : ExternalResource() { + override fun before() { + setCallback(NoOpSsoLoginCallback) + } + + fun setCallback(callback: SsoLoginCallback?) { + MockSsoLoginCallbackProvider.getInstance().provider = callback + } + + override fun after() { + setCallback(null) + } +} diff --git a/plugins/core-q/jetbrains-ultimate/build.gradle.kts b/plugins/core-q/jetbrains-ultimate/build.gradle.kts new file mode 100644 index 00000000000..b440209e626 --- /dev/null +++ b/plugins/core-q/jetbrains-ultimate/build.gradle.kts @@ -0,0 +1,16 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import software.aws.toolkits.gradle.intellij.IdeFlavor + +plugins { + id("toolkit-intellij-subplugin") +} + +intellijToolkit { + ideFlavor.set(IdeFlavor.IU) +} + +dependencies { + compileOnly(project(":plugin-core-q:jetbrains-community")) +} diff --git a/plugins/core-q/jetbrains-ultimate/src-242-243/compat/com/intellij/lang/javascript/JavascriptLanguage.kt b/plugins/core-q/jetbrains-ultimate/src-242-243/compat/com/intellij/lang/javascript/JavascriptLanguage.kt new file mode 100644 index 00000000000..629fb0b370f --- /dev/null +++ b/plugins/core-q/jetbrains-ultimate/src-242-243/compat/com/intellij/lang/javascript/JavascriptLanguage.kt @@ -0,0 +1,8 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package compat.com.intellij.lang.javascript + +// inline to avoid loading this through core classpath +inline val JavascriptLanguage + get() = com.intellij.lang.javascript.JavascriptLanguage.INSTANCE diff --git a/plugins/core-q/jetbrains-ultimate/src-251+/compat/com/intellij/lang/javascript/JavascriptLanguage.kt b/plugins/core-q/jetbrains-ultimate/src-251+/compat/com/intellij/lang/javascript/JavascriptLanguage.kt new file mode 100644 index 00000000000..66549f253c0 --- /dev/null +++ b/plugins/core-q/jetbrains-ultimate/src-251+/compat/com/intellij/lang/javascript/JavascriptLanguage.kt @@ -0,0 +1,8 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package compat.com.intellij.lang.javascript + +// inline to avoid loading this through core classpath +inline val JavascriptLanguage + get() = com.intellij.lang.javascript.JavascriptLanguage diff --git a/plugins/core-q/resources/build.gradle.kts b/plugins/core-q/resources/build.gradle.kts new file mode 100644 index 00000000000..658da9abc66 --- /dev/null +++ b/plugins/core-q/resources/build.gradle.kts @@ -0,0 +1,49 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import de.undercouch.gradle.tasks.download.Download +import software.aws.toolkits.gradle.resources.ValidateMessages + +plugins { + id("toolkit-kotlin-conventions") + id("toolkit-testing") + id("de.undercouch.download") +} + +sourceSets { + main { + resources.srcDir(layout.buildDirectory.dir("downloaded-resources")) + } +} + +dependencies { + testImplementation(libs.junit4) + testRuntimeOnly(libs.junit5.jupiterVintage) +} + +tasks.test { + useJUnitPlatform() +} + +val download = tasks.register("downloadResources") { + val resourcesDir = layout.buildDirectory.dir("downloaded-resources/software/aws/toolkits/resources/").get().asFile + dest(resourcesDir) + src(listOf("https://idetoolkits.amazonwebservices.com/endpoints.json")) + onlyIfModified(true) + useETag(true) + doFirst { + mkdir(resourcesDir) + } +} + +tasks.processResources { + dependsOn(download) +} + +val validateLocalizedMessages = tasks.register("validateLocalizedMessages") { + paths.from("resources/software/aws/toolkits/resources/MessagesBundle.properties") +} + +tasks.check { + dependsOn(validateLocalizedMessages) +} diff --git a/plugins/core-q/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core-q/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties new file mode 100644 index 00000000000..895f524fe69 --- /dev/null +++ b/plugins/core-q/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -0,0 +1,2120 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +action.apprunner.service.copyServiceUri.text=Copy Service URL +action.apprunner.service.openServiceUri.text=Open Service URL +action.aws.caws.devtools.actions.clone.text=Clone Repository +action.aws.caws.devtools.actions.copyCloneUrl.text=Copy Clone URL for Command Line +action.aws.caws.devtools.actions.learnMore.text=Learn More about CodeCatalyst +action.aws.caws.devtools.actions.learnMoreReAuth.text=Learn More about CodeCatalyst +action.aws.caws.devtools.actions.login.text=Start +action.aws.caws.devtools.actions.openGateway.text=Open Dev Environments in JetBrains Gateway +action.aws.caws.devtools.actions.reauthenticate.text=Re-authenticate to connect +action.aws.caws.rebuildAction.text=Rebuild Dev Environment +action.aws.caws.updateDevfile.text=Update Devfile... +action.aws.toolkit.caws.logout.text=Sign out +action.aws.toolkit.caws.profile.text=View Profile... +action.aws.toolkit.dynamodb.delete_table.text=Delete Table... +action.aws.toolkit.ecr.repository.pull.text=Pull from Repository... +action.aws.toolkit.ecr.repository.push.text=Push to Repository... +action.aws.toolkit.jetbrains.core.services.cwc.commands.ExplainCodeAction.description=Explains the selected code +action.aws.toolkit.jetbrains.core.services.cwc.commands.ExplainCodeAction.text=Explain Code +action.aws.toolkit.jetbrains.core.services.cwc.commands.FixCodeAction.description=Fixes the selected code +action.aws.toolkit.jetbrains.core.services.cwc.commands.FixCodeAction.text=Fix Code +action.aws.toolkit.jetbrains.core.services.cwc.commands.GenerateUnitTestsAction.description=Generates unit tests for the selected code +action.aws.toolkit.jetbrains.core.services.cwc.commands.GenerateUnitTestsAction.text=Generate Tests +action.aws.toolkit.jetbrains.core.services.cwc.commands.OptimizeCodeAction.description=Optimizes the selected code +action.aws.toolkit.jetbrains.core.services.cwc.commands.OptimizeCodeAction.text=Optimize Code +action.aws.toolkit.jetbrains.core.services.cwc.commands.RefactorCodeAction.description=Refactors the selected code +action.aws.toolkit.jetbrains.core.services.cwc.commands.RefactorCodeAction.text=Refactor Code +action.aws.toolkit.jetbrains.core.services.cwc.commands.SendToPromptAction.description=Sends selected code to chat +action.aws.toolkit.jetbrains.core.services.cwc.commands.SendToPromptAction.text=Send to Prompt +action.aws.toolkit.jetbrains.core.services.cwc.inline.openChat.text=Inline Chat +action.aws.toolkit.open.arn.browser.text=Open ARN in AWS Console +action.aws.toolkit.open.telemetry.viewer.text=View AWS Telemetry +action.aws.toolkit.s3.open.bucket.viewer.prefixed.text=View Bucket with Prefix... +action.aws.toolkit.s3.open.bucket.viewer.text=View Bucket +action.aws.toolkit.toolwindow.explorer.newConnection.text=Setup authentication to begin +action.aws.toolkit.toolwindow.newConnection.text=Add Another Connection... +action.dynamic.open.text=Open Resource... +action.q.openchat.text=Open Chat Panel +amazon.q.settings.title=Amazon Q +amazonqChat.project_context.index_in_progress=By the way, I'm still indexing this project for full context from your workspace. I may have a better response in a few minutes when it's complete if you'd like to try again then. +amazonqChat.stopChatResponse=You stopped your current work, please provide additional examples or ask another question. +amazonqDoc.answer.codeResult=You can accept the changes to your files, or describe any additional changes you'd like me to make. +amazonqDoc.answer.readmeCreated=I've created a README for your code. +amazonqDoc.answer.readmeUpdated=I've updated your README. +amazonqDoc.edit.message=Okay, let's work on your README. Describe the changes you would like to make. For example, you can ask me to:\n- Correct something\n- Expand on something\n- Add a section\n- Remove a section +amazonqDoc.edit.placeholder=Describe documentation changes +amazonqDoc.error.generating=Unable to generate changes. +amazonqDoc.error_text=I'm sorry, I ran into an issue while trying to generate your documentation. Please try again. +amazonqDoc.exception.content_length_error=The folder you selected is in a project or workspace that is too large for me to use as context. To create or update a README for your code, choose a folder in a smaller project or workspace. For information on quotas, see the Amazon Q Developer documentation. +amazonqDoc.exception.no_change_required=I couldn't find any code changes to update in the README. Try another documentation task. +amazonqDoc.exception.prompt_too_vague=I need more information to make changes to your README. Try providing some of the following details:\n- Which sections you want to modify\n- The content you want to add or remove\n- Specific issues that need correcting\n\nFor more information on prompt best practices, see the Amazon Q Developer documentation. +amazonqDoc.exception.prompt_unrelated=These changes don't seem related to documentation. Try describing your changes again, using the following best practices:\n- Changes should relate to how project functionality is reflected in the README\n- Content you refer to should be available in your codebase\n\nFor more information on prompt best practices, see the Amazon Q Developer documentation. +amazonqDoc.exception.readme_too_large=The README in your folder is too large for me to review. Try reducing the size of your README, or choose a folder with a smaller README. For more information on quotas, see the Amazon Q Developer documentation. +amazonqDoc.exception.readme_update_too_large=The updated README is too large. Try reducing the size of your README, or asking for a smaller update. For more information on quotas, see the Amazon Q Developer documentation. +amazonqDoc.exception.workspace_empty=The folder you chose did not contain any source files in a supported language. Choose another folder and try again. For more information on supported languages, see the Amazon Q Developer documentation. +amazonqDoc.inprogress_message.generating=Generating documentation... +amazonqDoc.progress_message.baseline=This might take a few minutes. +amazonqDoc.progress_message.creating=Okay, I'm creating a README for your project. +amazonqDoc.progress_message.generating=Generating documentation +amazonqDoc.progress_message.scanning=Scanning source files +amazonqDoc.progress_message.summarizing=Summarizing source files +amazonqDoc.progress_message.updating=Okay, I'm updating the README. +amazonqDoc.prompt.canceled_source_folder_selection=It looks like you didn't choose a folder. Choose a folder to continue. +amazonqDoc.prompt.choose_folder_to_continue=Choose a folder to continue +amazonqDoc.prompt.create=Create a README +amazonqDoc.prompt.create.confirmation=Create a README for this project? +amazonqDoc.prompt.folder.change=Change folder +amazonqDoc.prompt.folder.proceed=Yes +amazonqDoc.prompt.placeholder=Choose an option to continue +amazonqDoc.prompt.reject.close_session=End session +amazonqDoc.prompt.reject.message=Your changes have been discarded. +amazonqDoc.prompt.reject.new_task=Start a new documentation task +amazonqDoc.prompt.review.accept=Accept +amazonqDoc.prompt.review.changes=Make changes +amazonqDoc.prompt.review.message=Please review and accept the changes. +amazonqDoc.prompt.update=Update an existing README +amazonqDoc.prompt.update.follow_up.edit=Make a specific change +amazonqDoc.prompt.update.follow_up.sync=Update README to reflect code +amazonqDoc.session.create=Create documentation for a specific folder +amazonqDoc.session.sync=Sync documentation +amazonqFeatureDev.chat_message.ask_for_new_task=What new task would you like to work on? +amazonqFeatureDev.chat_message.closed_session=Okay, I've ended this chat session. You can open a new tab to chat or start another workflow. +amazonqFeatureDev.chat_message.devFileInRepository=I noticed that your repository has a `devfile.yaml`. Would you like me to use the devfile to build and test your project as I generate code?\n\nFor more information on using devfiles to improve code generation, see the [Amazon Q Developer documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html). +amazonqFeatureDev.chat_message.generate_dev_file=For future tasks in this project, I can create a devfile to build and test code as I generate it. This can improve the quality of generated code. To allow me to create a devfile, choose **Generate devfile to build code**. +amazonqFeatureDev.chat_message.requesting_changes=Requesting changes ... +amazonqFeatureDev.chat_message.setting_updated=I've updated your settings so I can run code and test commands based on your devfile for this project. You can update this setting under **Amazon Q: Allow Q /dev to run code and test commands**. +amazonqFeatureDev.chat_message.start_code_generation=Okay, I'll generate code for that. This might take a few minutes.\n\nYou can navigate away from this chat, but please keep this tab open. I'll notify you when I'm done. +amazonqFeatureDev.chat_message.start_code_generation_retry=Okay, I'll generate new code. This might take a few minutes.\n\nYou can navigate away from this chat, but please keep this tab open. I'll notify you when I'm done. +amazonqFeatureDev.chat_message.uploading_code=Uploading code... +amazonqFeatureDev.code_generation.error_message=I'm sorry, I ran into an issue while trying to generate your code. Please try again. +amazonqFeatureDev.code_generation.failed_generation=Code generation failed +amazonqFeatureDev.code_generation.generating_code=Generating code ... +amazonqFeatureDev.code_generation.iteration_counts=Would you like me to add this code to your project, or provide feedback for new code? You have {0} out of {1} code generations left. +amazonqFeatureDev.code_generation.iteration_counts_ask_to_add_code=Would you like me to add this code to your project? +amazonqFeatureDev.code_generation.iteration_counts_ask_to_add_code_or_feedback=Would you like me to add this code to your project, or provide feedback for new code? +amazonqFeatureDev.code_generation.iteration_limit.error_text=Sorry, you've reached the quota for number of iterations on code generation. You can insert this code in your files or discuss a new plan. For more information on quotas, see the [Amazon Q Developer documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html#quotas). +amazonqFeatureDev.code_generation.no_file_changes=Unable to generate any file changes +amazonqFeatureDev.code_generation.no_retries.error_message=I'm sorry, I'm having trouble generating your code and can't continue at the moment. Please try again later, and share feedback to help me improve. +amazonqFeatureDev.code_generation.notification_message=Your code suggestions from Amazon Q are ready to review +amazonqFeatureDev.code_generation.notification_open_link=Open chat +amazonqFeatureDev.code_generation.notification_title=Amazon Q Developer Agent for software development +amazonqFeatureDev.code_generation.provide_code_feedback=How can I improve the code for your use case? +amazonqFeatureDev.code_generation.stopped_code_generation=I stopped generating your code. If you want to continue working on this task, provide another description. You have {0} out of {1} code generations left. +amazonqFeatureDev.code_generation.stopped_code_generation_no_iteration_count_display=I stopped generating your code. If you want to continue working on this task, provide another description. +amazonqFeatureDev.code_generation.stopped_code_generation_no_iterations=I stopped generating your code. You don't have more iterations left, however, you can start a new session. +amazonqFeatureDev.code_generation.stopping_code_generation=Stopping code generation... +amazonqFeatureDev.code_generation.updated_code=Okay, I updated your code files. Would you like to work on another task? +amazonqFeatureDev.content_length.error_text=The folder you selected is too large for me to use as context. Please choose a smaller folder to work on. For more information on quotas, see the [Amazon Q Developer documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html#quotas). +amazonqFeatureDev.error_text=Sorry, we encountered a problem when processing your request. +amazonqFeatureDev.example_text=You can use /dev to:\n- Add a new feature or logic\n- Write tests\n- Fix a bug in your project\n- Generate a README for a file, folder, or project\n\nTo learn more, visit the [Amazon Q Developer User Guide](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html) +amazonqFeatureDev.exception.conversation_not_found=I'm sorry, I'm having technical difficulties at the moment. Please try again. +amazonqFeatureDev.exception.export_parsing_error=I'm sorry, I'm having trouble producing your code. Please try again. +amazonqFeatureDev.exception.guardrails=I'm sorry, I'm having trouble generating your code. Please try again. +amazonqFeatureDev.exception.insert_code_failed=Failed to insert code changes +amazonqFeatureDev.exception.monthly_limit_error=You've reached the monthly quota for the Amazon Q agent for software development. You can try again next month. For more information on usage limits, see the [Amazon Q Developer pricing page](https://aws.amazon.com/q/developer/pricing/). +amazonqFeatureDev.exception.no_change_required_exception=I'm sorry, I ran into an issue while trying to generate your code.\n\n- `/dev` can generate code to make a change in your project. Provide a detailed description of the new feature or code changes you want to make, including the specifics of what the code should achieve.\n\n- To ask me to explain, debug, or optimize your code, you can close this chat tab to start a new conversation. +amazonqFeatureDev.exception.open_diff_failed=Failed to open diff +amazonqFeatureDev.exception.prompt_refusal=I'm sorry, I can't generate code for your request. Please make sure your message and code files comply with the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/). +amazonqFeatureDev.exception.request_failed=Request failed +amazonqFeatureDev.exception.retry_request_failed=Retry request failed +amazonqFeatureDev.exception.throttling=I'm sorry, I'm experiencing high demand at the moment and can't generate your code. This attempt won't count toward usage limits. Please try again. +amazonqFeatureDev.exception.upload_code=I'm sorry, I couldn't upload your workspace artifacts to Amazon S3 to help you with this task. You might need to allow access to the S3 bucket. For more information, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security_iam_manage-access-with-policies.html#data-perimeters) or contact your network or organization administrator. +amazonqFeatureDev.exception.upload_url_expiry=I'm sorry, I wasn't able to generate code. A connection timed out or became unavailable. Please try again or check the following:\n\n- Exclude non-essential files in your workspace's `.gitignore`.\n\n- Check that your network connection is stable. +amazonqFeatureDev.follow_instructions_for_authentication=Follow instructions to re-authenticate ... +amazonqFeatureDev.follow_up.accept_for_project=Yes, use my devfile for this project +amazonqFeatureDev.follow_up.close_session=No, thanks +amazonqFeatureDev.follow_up.continue=Continue +amazonqFeatureDev.follow_up.decline_for_project=No, thanks +amazonqFeatureDev.follow_up.generate_dev_file=Generate devfile to build code +amazonqFeatureDev.follow_up.incorrect_source_folder=The folder you chose isn't in your open workspace folder. You can add this folder to your workspace, or choose a folder in your open workspace. +amazonqFeatureDev.follow_up.insert_all_code=Accept all changes +amazonqFeatureDev.follow_up.insert_remaining_code=Accept remaining changes +amazonqFeatureDev.follow_up.modified_source_folder=Changed source root to: {0} +amazonqFeatureDev.follow_up.modify_source_folder=Select files for context +amazonqFeatureDev.follow_up.new_task=Yes, I have another task +amazonqFeatureDev.follow_up.provide_feedback_and_regenerate=Provide feedback & regenerate +amazonqFeatureDev.follow_up.retry=Retry +amazonqFeatureDev.follow_up.send_feedback=Send feedback +amazonqFeatureDev.no_retries.error_text=I'm sorry, I'm having technical difficulties and can't continue at the moment. Please try again later, and share feedback to help me improve. +amazonqFeatureDev.placeholder.additional_improvements=Describe your task or issue in detail +amazonqFeatureDev.placeholder.after_code_generation=Choose an option to proceed +amazonqFeatureDev.placeholder.after_monthly_limit=Chat input is disabled +amazonqFeatureDev.placeholder.closed_session=Open a new chat tab to continue +amazonqFeatureDev.placeholder.context_gathering_complete=Gathering context... +amazonqFeatureDev.placeholder.downloading_and_extracting_lsp_artifacts=Downloading and Extracting LSP Artifacts... +amazonqFeatureDev.placeholder.generating_code=Generating code... +amazonqFeatureDev.placeholder.lsp=LSP +amazonqFeatureDev.placeholder.new_plan=Describe your task or issue in as much detail as possible +amazonqFeatureDev.placeholder.node_runtime_message=Please provide the absolute path of your node js v18+ runtime executable in Settings. Re-open IDE to apply this change. +amazonqFeatureDev.placeholder.node_runtime_path=Node Runtime Path +amazonqFeatureDev.placeholder.provide_code_feedback=Provide feedback or comments +amazonqFeatureDev.placeholder.select_lsp_artifact=Select LSP Artifact +amazonqFeatureDev.placeholder.write_new_prompt=Write a new prompt +apprunner.action.configure=Configure Service +apprunner.action.create.service=Create Service... +apprunner.action.delete.service=Delete Service... +apprunner.action.deploy=Deploy +apprunner.action.deploy.failed=Deployment Failed +apprunner.action.deploy.starting=Starting deployment +apprunner.action.deploy.unableToFindLogStream=Unable to find the log stream for deployment {0} +apprunner.action.pause=Pause... +apprunner.action.pause.confirm=Pause +apprunner.action.resume=Resume... +apprunner.action.resume.confirm=Resume +apprunner.creation.failed=Failed to create App Runner service +apprunner.creation.panel.cpu=CPU: +apprunner.creation.panel.cpu.missing=Select a CPU configuration +apprunner.creation.panel.deployment=Deployment type: +apprunner.creation.panel.deployment.automatic=Automatic +apprunner.creation.panel.deployment.automatic.tooltip=App Runner monitors your registry and deploys a new version of your service for each new revision +apprunner.creation.panel.deployment.manual=Manual +apprunner.creation.panel.deployment.manual.tooltip=Start each deployment yourself using the AWS CLI, Console, or Toolkit +apprunner.creation.panel.environment=Environment Variables: +apprunner.creation.panel.image.access_role=ECR access role: +apprunner.creation.panel.image.access_role.missing=Missing ECR access role for App Runner +apprunner.creation.panel.image.access_role.tooltip=This role gives App Runner permission to access ECR +apprunner.creation.panel.image.uri=Container image URI: +apprunner.creation.panel.image.uri.missing=Enter container image URI +apprunner.creation.panel.memory=Memory: +apprunner.creation.panel.memory.missing=Select memory amount +apprunner.creation.panel.name=Service name: +apprunner.creation.panel.name.missing=Enter a service name +apprunner.creation.panel.port=Port: +apprunner.creation.panel.repository.api=Configure all settings here +apprunner.creation.panel.repository.api.tooltip=Specify all settings for your service on creation +apprunner.creation.panel.repository.branch=Branch: +apprunner.creation.panel.repository.build_command=Build Command: +apprunner.creation.panel.repository.build_command.missing=Enter a build command +apprunner.creation.panel.repository.build_command.tooltip=This command runs in the root directory of your repository when a new code version is deployed. Use it to install dependencies or compile your code. +apprunner.creation.panel.repository.configuration=Configuration: +apprunner.creation.panel.repository.connection=Connection: +apprunner.creation.panel.repository.connection.help=App Runner deploys your source code by installing an app called "AWS Connector for GitHub" in your GitHub account. This connection must be set up through the console. +apprunner.creation.panel.repository.connection.missing=Select a connection +apprunner.creation.panel.repository.file=Provide a configuration file +apprunner.creation.panel.repository.file.tooltip=App Runner will read your configuration from the apprunner.yaml file in the root of your repository +apprunner.creation.panel.repository.runtime=Runtime: +apprunner.creation.panel.repository.runtime.missing=Select a runtime +apprunner.creation.panel.repository.runtime.tooltip=Runtime determines what tools are available in the build and runtime environments +apprunner.creation.panel.repository.url=Repository URL: +apprunner.creation.panel.repository.url.tooltip=The URL of the Git repository (e.g. "https://github.com/aws/aws-toolkit-jetbrains") +apprunner.creation.panel.source=Source: +apprunner.creation.panel.source.ecr=ECR +apprunner.creation.panel.source.ecr_public=ECR public +apprunner.creation.panel.source.repository=Source code repository +apprunner.creation.panel.start_command=Start Command: +apprunner.creation.panel.start_command.image.tooltip=The application’s container runs this command on launch. Leave blank to use the entry point command defined in the container image. +apprunner.creation.panel.start_command.missing=Enter a start command +apprunner.creation.panel.start_command.repo.tooltip=This command runs in the root directory of your service to start the service processes. Use it to start a webserver for your service. +apprunner.creation.started=Started creating App Runner service +apprunner.creation.started.title=App Runner +apprunner.creation.title=Create App Runner Service +apprunner.pause.failed=Failed to pause service {0}!\n{1} +apprunner.pause.succeeded=Pausing service {0} +apprunner.pause.warning=Your service will be unavailable while paused.

You can resume the service once the pause operation is complete.

Click pause to proceed pausing {0} +apprunner.resume.failed=Failed to resume {0}!\n{1} +apprunner.resume.succeeded=Resuming {0} +apprunner.resume.warning=Resume {0}? +apprunner.service.configure.title=Configure Service {0} +apprunner.service.resource_type=App Runner Service +apprunner.view_application_log_streams=View Application Log Streams +apprunner.view_service_log_streams=View Service Log Streams +apprunner.view_service_log_streams.error=Unable to open log group {0} +apprunner.view_service_log_streams.error_not_created=App Runner log group has not been created +aws.codewhispererq.tab.title=Amazon Q +aws.description=Amazon Web Services (AWS) is a secure cloud services platform, offering compute power, database storage, content delivery and other functionality to help businesses scale and grow. +aws.developer.tools.tab.title=CodeCatalyst +aws.getstarted.auth.panel.notSupport_text=Not supported for this feature +aws.getstarted.auth.panel_bullet_iam=IAM Credentials +aws.getstarted.auth.panel_bullets=IAM Identity Center\nAWS Builder ID +aws.getstarted.resource.panel_description=Work with S3, CloudWatch and more. +aws.getstarted.resource.panel_question_text=Don't have an AWS account? +aws.getstarted.resource.panel_title=AWS Explorer +aws.notification.auto_update.feature_intro.ok=OK +aws.notification.auto_update.feature_intro.title=AWS plugins will now auto update +aws.notification.auto_update.settings.title=Manage auto-updates +aws.notification.auto_update.title={0} plugin updated +aws.notification.credentials_missing=Error: AWS credentials not configured +aws.notification.do_not_show_again=Don't show again +aws.notification.sam_cli_not_valid=Error: {0}. Click here to reconfigure AWS SAM. +aws.notification.title=AWS Toolkit +aws.notification.title.amazonq=Amazon Q +aws.notification.title.amazonq.feature_dev=Amazon Q FeatureDev +aws.notification.title.amazonq.test_generation=Amazon Q Test Generation +aws.notification.title.codewhisperer=Amazon Q +aws.onboarding.getstarted.panel.bottom_text=Learn more about authenticating with the Toolkit +aws.onboarding.getstarted.panel.bottom_text_question=Why do these have different authentication requirements? +aws.onboarding.getstarted.panel.builderid_row_comment_text=Personal profile for builders +aws.onboarding.getstarted.panel.button_iam_login=Authenticate with IAM +aws.onboarding.getstarted.panel.comment_link_doc=Documentation +aws.onboarding.getstarted.panel.comment_link_github=Join us on GitHub +aws.onboarding.getstarted.panel.group_title=Select a feature to setup authentication +aws.onboarding.getstarted.panel.iam_row_comment_text=Long-term programmatic access +aws.onboarding.getstarted.panel.idc_row_comment_text=Successor to AWS Single Sign-on +aws.onboarding.getstarted.panel.login_with_iam=Use Professional License +aws.onboarding.getstarted.panel.share_feedback=Share Feedback +aws.onboarding.getstarted.panel.signup_iam_text=Sign up for free +aws.onboarding.getstarted.panel.title=Authenticate with AWS Toolkit +aws.q.lsp.client.diff_message=(Generated by Amazon Q) +aws.q.migration.action.install.text=Install +aws.q.migration.action.manage_plugins.text=Manage plugins +aws.q.migration.action.read_more.text=Read more +aws.q.migration.action.restart.text=Restart +aws.q.migration.existing_users.notify.message=We've auto-installed it for you with all the same features and settings from CodeWhisperer and Amazon Q chat +aws.q.migration.existing_users.notify.title=Amazon Q is now its own plugin +aws.q.migration.failed_to_install.message=Amazon Q failed to install, click below to go to the plugin marketplace and install the plugin manually. +aws.q.migration.new_users.notify.message=Install it to use Amazon Q, a generative AI assistant, with chat and code suggestions. +aws.q.migration.new_users.notify.title=Amazon Q has moved to its own plugin +aws.q.settings.title=Amazon Q +aws.settings.auto_detect=Auto-detected: {0} +aws.settings.auto_update.notification.message=Restart your IDE to apply the update. +aws.settings.auto_update.notification.no=Not now +aws.settings.auto_update.notification.yes=Restart +aws.settings.auto_update.notification_enable.text=Show notification on update success +aws.settings.auto_update.notification_enable.tooltip=If unchecked, updates will still get applied upon the next IDE restart. +aws.settings.auto_update.progress.message=Updating AWS plugins +aws.settings.auto_update.text=Automatically install plugin updates when available +aws.settings.aws_cli_settings=AWS CLI Settings +aws.settings.codewhisperer.automatic_import_adder=Imports recommendation +aws.settings.codewhisperer.automatic_import_adder.tooltip=Amazon Q will add import statements with code suggestions when necessary +aws.settings.codewhisperer.code_review=Code Review +aws.settings.codewhisperer.code_review.description=Specifies a list of code issue identifiers(separated by ";") that Amazon Q should ignore when reviewing your workspace. Each item in the array should be a unique string identifier for a specific code issue. This allows you to suppress notifications for known issues that you've assessed and determined to be false positives or not applicable to your project. Use this setting with caution, as it may cause you to miss important security alerts. +aws.settings.codewhisperer.code_review.title=Ignored Security Issues +aws.settings.codewhisperer.configurable.controlled_by_admin=\ Controlled by your admin +aws.settings.codewhisperer.configurable.opt_out.title=Share Amazon Q content with AWS +aws.settings.codewhisperer.configurable.opt_out.tooltip=When checked, your content processed by Amazon Q may be used for service improvement (except for content processed by the Amazon Q Developer Pro tier). Unchecking this box will cause AWS to delete any of your content used for that purpose. The information used to provide the Amazon Q service to you will not be affected. See the Service Terms for more detail. +aws.settings.codewhisperer.configurable.title=Amazon Q +aws.settings.codewhisperer.feature_development=Feature Development +aws.settings.codewhisperer.feature_development.allow_running_code_and_test_commands=Allow /dev to run code and test commands +aws.settings.codewhisperer.group.data_sharing=Data Sharing +aws.settings.codewhisperer.group.general=General +aws.settings.codewhisperer.group.inline_suggestions=Inline Suggestions +aws.settings.codewhisperer.group.plugin_settings=Plugin Settings +aws.settings.codewhisperer.group.q_chat=Chat +aws.settings.codewhisperer.include_code_with_reference=Include suggestions with code references +aws.settings.codewhisperer.include_code_with_reference.tooltip=When checked, Amazon Q will include suggestions with code references. If you authenticate through IAM Identity Center, this setting is controlled by your Amazon Q Developer Pro administrator. Learn more +aws.settings.codewhisperer.project_context=Workspace index +aws.settings.codewhisperer.project_context.tooltip=When you add @workspace to your questions in Amazon Q chat, Amazon Q will index your workspace files locally to use as context for its response. Extra CPU usage is expected while indexing a workspace. This will not impact Amazon Q features or your IDE, but you may manage CPU usage by setting the number of local threads below. +aws.settings.codewhisperer.project_context_gpu=Workspace index uses GPU +aws.settings.codewhisperer.project_context_gpu.tooltip=Enable GPU to help index your local workspace files. This setting only applies to Linux and Windows. +aws.settings.codewhisperer.project_context_index_max_size=Workspace index max size +aws.settings.codewhisperer.project_context_index_max_size.tooltip=The maximum size of local workspace files to be indexed in MB. +aws.settings.codewhisperer.project_context_index_thread=Workspace index worker threads +aws.settings.codewhisperer.project_context_index_thread.tooltip=Number of worker threads of Amazon Q local index process. Set to 0 to use system default worker threads for balanced performance. Please restart or reload IntelliJ after changing worker threads. +aws.settings.codewhisperer.warning=To use Amazon Q, login with AWS Builder ID or AWS IAM Identity Center. +aws.settings.codewhisperer.workspace_context=Server-side context +aws.settings.codewhisperer.workspace_context.tooltip=Index project files on the server and use as context for higher-quality responses. This feature will activate only if your administrator has opted you in. +aws.settings.dynamic_resources_configurable.clear_all=Clear All +aws.settings.dynamic_resources_configurable.select_all=Select All +aws.settings.dynamic_resources_configurable.suggest_types.dialog.message=Please suggest additional AWS resource types (e.g. AWS::S3::Bucket)\nyou would like to see supported in future releases.\n\n(max length: 2000 chars) +aws.settings.dynamic_resources_configurable.suggest_types.dialog.title=Additional Resource Type Suggestions +aws.settings.dynamic_resources_configurable.suggest_types.prompt=Don't see a resource type you expected? +aws.settings.dynamic_resources_configurable.title=Resources +aws.settings.executables.auto_resolved=(Auto-resolved: {0}) +aws.settings.executables.cannot_determine_version=Cannot determine version of {0}: {1} +aws.settings.executables.executable_invalid=Invalid {0} executable: {1} +aws.settings.executables.find.description=Select path to {0} executable +aws.settings.executables.resolution_exception=Exception occurred attempting to resolve {0} executable: {1} +aws.settings.find.description=Select path to {0} CLI executable +aws.settings.find.title={0} CLI Configuration +aws.settings.global_label=Global Settings +aws.settings.lambda.configurable.title=Lambda +aws.settings.learn_more=Learn more +aws.settings.sam.location=SAM CLI executable: +aws.settings.sam.show_all_gutter_icons=Show gutter icons for all potential AWS Lambda handlers +aws.settings.sam.show_all_gutter_icons_tooltip=When enabled, show gutter icons in source files for all potential Lambda handlers, even if they are not defined in a template or remotely. +aws.settings.serverless_label=Serverless Settings +aws.settings.show.label=Show AWS Settings +aws.settings.telemetry.description=Help us improve our product! Allow us to collect basic usage data. +aws.settings.telemetry.option=Send usage metrics to AWS +aws.settings.telemetry.prompt.message=Usage metrics are collected by default. Click here to adjust this behavior. +aws.settings.telemetry.prompt.title=AWS IDE plugins telemetry +aws.settings.title=AWS Toolkit +aws.settings.toolkit.configurable.title=AWS Toolkit +aws.sso.signing.device.code=Proceed To Browser +aws.sso.signing.device.code.copy=Copy Code +aws.sso.signing.device.code.copy.dialog.text=To proceed, open the login page and confirm that the code matches: +aws.sso.signing.device.waiting=Waiting for browser authorization for code: {0} +aws.terminal.action=Open AWS local terminal +aws.terminal.action.tooltip=Start a local terminal with the current AWS connection settings injected as environment variables +aws.terminal.exception.failed_to_resolve_credentials=Unable to open AWS-aware Terminal, unable to resolve credentials: {0} +aws.terminal.exception.invalid_credentials=Unable to open AWS-aware Terminal, credentials aren't valid: {0} +aws.toolkit.experimental.description=Warning: These features are experimental, may contain bugs or usability problems, and may be
removed from the AWS Toolkit at any time. +aws.toolkit.experimental.enable=Enable +aws.toolkit.experimental.suggestion.description=The experimental feature "{0}" adds the following capability:
{1}.
Do you want to enable it? +aws.toolkit.experimental.suggestion.title=AWS Toolkit Experiment Suggestion +aws.toolkit.experimental.title=Experimental Features +aws.toolkit_deprecation.message=Support for {0} {1} is being deprecated - an upcoming release will require a version based on {2} or newer. +aws.toolkit_deprecation.message.gateway=Upgrade JetBrains Gateway to version {2} or newer to use the next release of the toolkit. Support for {0} {1} is being deprecated. +aws.toolkit_deprecation.title=AWS Toolkit deprecation notice +aws_builder_id.service_name=AWS Builder ID +aws_builder_id.sign_out=Sign out of AWS Builder ID +aws_connection.credentials.label=Credentials: +aws_connection.region.label=Region: +aws_connection.tab.label=AWS Connection +caws.add_repository=Add a repository to this project +caws.add_workspace=Create Dev Environment +caws.alias.instruction.text=Aliases can contain any combination of letters, numbers, dashes and underlines. +caws.backend.error.expired=The IDE backend is expired. Please create a new Dev Environment or update the Dev Environment settings. +caws.backend.error.unknown=Starting the IDE backend failed with exit code {0}. Any available backend logs are available below. +caws.branch_title=Branch: {0} +caws.check_connection=Check Connection and Continue +caws.clone.invalid_pat=Invalid PAT +caws.clone.invalid_pat.help=Cloning may have failed due to an invalid access token. Would you like the toolkit to recreate your access token? +caws.clone_dialog_description=Clone repository from Amazon CodeCatalyst +caws.clone_dialog_directory=Directory: +caws.clone_dialog_repository_loading_error=Failed to load repositories +caws.clone_dialog_title=Clone Repository +caws.compute.size.in.free.tier.comment=Some compute and storage options are not currently available for your space. These require upgrading the billing tier. +caws.configure_workspace=Configure +caws.configure_workspace_failed=Reconfigure Dev Environment Failed +caws.configure_workspace_not_running=Reconfigure Dev Environment is only available for running environments +caws.configure_workspace_tab_save_button=Save and restart +caws.configure_workspace_tab_title=Configure Dev Environment +caws.configure_workspace_title=Configure Dev Environment: {0} in {1} +caws.connected.builder_id=\ AWS Builder ID Connected +caws.connected.identity_center=\ IAM Identity Center Connected {0} +caws.connecting.checking=Checking state of Dev Environment +caws.connecting.in_progress=Connecting to Dev Environment +caws.connecting.waiting_for_environment=Launching Dev Environment (This may take a few minutes) +caws.connection_progress_panel_title=Amazon CodeCatalyst Dev Environment: {0} +caws.copy.url.select_repository=Select a repository to clone +caws.create_workspace=Create Dev Environment +caws.create_workspace_description=Create Dev Environments for your Amazon CodeCatalyst projects +caws.creating_branch=Creating source repository branch +caws.creating_project=Creating Project +caws.creating_workspace=Creating Dev Environment +caws.credential.setup.invalid_credentials_error=Credentials provided are not valid +caws.credential.setup.invalid_credentials_error_title=Invalid Credentials +caws.credential.setup.profile=AWS Profile +caws.credential.setup.requirements_message="You need AWS credentials to use this feature" +caws.credential.setup_access_key=AWS Access Key ID: +caws.credential.setup_access_key_validation_text=Access Key ID must not be empty +caws.credential.setup_default_region=Default Region: +caws.credential.setup_profile_name=Name: +caws.credential.setup_profile_name_validation_text=Profile name must not be empty +caws.credential.setup_secret_access_key=AWS Secret Access Key: +caws.credential.setup_secret_access_key_validation_text=Secret Access Key must not be empty +caws.delete_failed=Delete Dev Environment Failed +caws.delete_workspace=Delete +caws.delete_workspace_warning=Are you sure you wish to delete the Dev Environment? All data will be deleted +caws.delete_workspace_warning_title=Confirm Deletion +caws.devenv.continue.working.after.timeout=Your dev environment has had no activity in the past {0} minutes and will be terminated within 5 minutes. Press OK to continue working +caws.devenv.continue.working.after.timeout.title=Do you want to continue working? +caws.devfile.schema=Devfile Schema +caws.devtoolPanel.fetch.git.url=Fetching Git clone URL for {0} +caws.devtoolPanel.git_url_copied=Clone URL copied to clipboard +caws.devtoolPanel.title=CodeCatalyst +caws.download.thin_client=Downloading IDE thin client +caws.environment.status=Dev Environment is currently: {0} +caws.environment.view_pricing=Learn more about Dev Environment pricing +caws.expired.connection=\ Expired Connection +caws.free.tier.subscription.storage=Other storage options are not currently available for your space. These require upgrading the billing tier. +caws.getstarted.panel.description=Spend more time coding and less
time managing development
environments. +caws.getstarted.panel.link_text=Learn more about CodeCatalyst Spaces +caws.getstarted.panel.login=Connect to CodeCatalyst +caws.getstarted.panel.question.text=Don't have a CodeCatalyst Space? +caws.ide_version_validation_text=IDE version must be selected +caws.information.panel=A thin client on your machine makes development feel local, even when the heavy lifting is done on Amazon CodeCatalyst. +caws.information_panel=Create an on-demand Amazon CodeCatalyst Dev Environment to work on your code in the cloud. +caws.list_workspaces_failed=Failed to list Dev Environments +caws.loading.panel.credential_validation_text=Please select a credential profile from the drop down +caws.login=Sign in +caws.no_repo=No repository +caws.no_spaces=No spaces available +caws.one.branch.per.dev.env.comment=You cannot create more than one Dev Environment for a branch. If the chosen branch already has a Dev Environment, resume the existing Dev Environment. +caws.open.devfile=Open Devfile +caws.open.devfile.failed=Unable to open Devfile +caws.pause_action=Pause +caws.pause_warning=Are you sure you wish to pause the Dev Environment? +caws.pause_warning_title=Confirm Pause +caws.pause_workspace_failed=Pause Dev Environment Failed +caws.project=Project: +caws.project.autocreate.description=Created by AWS Toolkit for JetBrains +caws.rebuild.devfile.failed=Dev Environment rebuild with the provided Devfile failed: {0} +caws.rebuild.devfile.failed_server=Dev Environment returned the following:
{0} +caws.rebuild.failed.title=Rebuild Dev Environment +caws.rebuild.workspace.notification=Dev Environment requires rebuild +caws.reconnect.wait_for_client=Waiting for client to gracefully close +caws.repository=Source repository: +caws.spaces.error_loading=Error loading spaces +caws.spaces.refresh=Refresh spaces +caws.sso_profile_configuration_instruction_message=To configure SSO profile use the AWS CLI +caws.storage.value={0} GiB +caws.title=Amazon CodeCatalyst +caws.update_dev_environment=Update Dev Environment +caws.update_dev_environment.failed=Failed to update Dev Environment +caws.update_devfile=Update Dev Environment with this Devfile? This action will trigger a Dev Environment restart. +caws.update_devfile.failed=Failed to update Devfile +caws.update_devfile_title=Update Dev Environment +caws.updating_devfile=Updating Dev Environment with Devfile... +caws.view.projects_web=View project summary in Amazon CodeCatalyst... +caws.view.workspaces_web=View all Dev Environments for project in Amazon CodeCatalyst... +caws.workspace.backend.title=Amazon CodeCatalyst Dev Environment +caws.workspace.clone.info=The project source repository will be cloned to the Dev Environment directly. +caws.workspace.clone.info_repo=Choose the branch to clone, or create a branch for your work in this Dev Environment. +caws.workspace.clone.ssh_agent=Your SSH agent will be forwarded to the Dev Environment for authentication to the repository. +caws.workspace.connection.failed=Dev Environment connection failed +caws.workspace.creation.failed=Dev Environment creation failed +caws.workspace.details.additional_settings_header=Additional Settings +caws.workspace.details.alias.label=Dev Environment alias: +caws.workspace.details.backend_toolkit_location=Backend Toolkit Location: +caws.workspace.details.branch_existing=Existing Branch: +caws.workspace.details.branch_new=New Branch: +caws.workspace.details.branch_new_validation=New branch name is required +caws.workspace.details.branch_title=Branch: +caws.workspace.details.branch_validation=Branch selection is required +caws.workspace.details.clone_repo=Clone a repository +caws.workspace.details.create_branch_comment=Choose an existing branch or create a new branch. If you choose a third-party source repository, you must work in an existing branch. +caws.workspace.details.create_empty_dev_env=Create an empty Dev Environment +caws.workspace.details.create_from_existing_branch=Existing Branch +caws.workspace.details.create_from_new_branch=New Branch +caws.workspace.details.developer_tool_settings=Toolkit Developer Settings +caws.workspace.details.inactivity_timeout=Timeout: +caws.workspace.details.inactivity_timeout_comment=Dev Environment will be stopped after inactivity timeout. +caws.workspace.details.introduction_message=Create Amazon CodeCatalyst projects to start using Dev Environments. +caws.workspace.details.last_used=Last Used: {0} +caws.workspace.details.no_timeout=No timeout +caws.workspace.details.persistent_storage_comment=Storage cannot be edited from CodeCatalyst or IDE after creating Dev Environment. +caws.workspace.details.persistent_storage_title=Storage: +caws.workspace.details.project.comment=A Dev Environment must be associated with a project +caws.workspace.details.project.required=Project name must be provided +caws.workspace.details.project.title=Project name: +caws.workspace.details.project_specific_title=New Amazon CodeCatalyst Dev Environment for {0} +caws.workspace.details.project_validation=Project selection is required +caws.workspace.details.repository_validation=Repository selection is required +caws.workspace.details.s3_bucket=S3 Staging Bucket: +caws.workspace.details.select_org=Select a space above to continue +caws.workspace.details.title=New Amazon CodeCatalyst Dev Environment +caws.workspace.details.toolkit_location=Toolkit Location: +caws.workspace.details.unlinked_repo_url=Repo URL: +caws.workspace.details.use_bundled_toolkit=Use Bundled Toolkit +caws.workspace.ide_label=IDE: +caws.workspace.incompatible=(incompatible) +caws.workspace.instance_size=Compute: +caws.workspace.labels_label=Label: +caws.workspace.list_panel_search_empty_text=Search Dev Environments +caws.workspace.new=Create Dev Environment +caws.workspace.panel_other_repos=Other repositories +caws.workspace.settings=Dev Environment Configuration +caws.workspace.settings.repository_header=Repository +caws.write_credentials_to_file_progress=Writing credentials to shared credentials file +cloudformation.capabilities=CloudFormation Capabilities: +cloudformation.capabilities.auto_expand=Auto Expand +cloudformation.capabilities.auto_expand.toolTipText=Allows expansion of macros in templates +cloudformation.capabilities.iam=IAM +cloudformation.capabilities.iam.toolTipText=Allows templates that contain IAM resources to be deployed +cloudformation.capabilities.named_iam=Named IAM +cloudformation.capabilities.named_iam.toolTipText=Allows templates that contain IAM resources with custom names to be deployed +cloudformation.capabilities.toolTipText=CloudFormation capabilities represent potentially dangerous actions that must be user acknowledged for CloudFormation to perform. +cloudformation.create_stack.failed=Failed to create stack {0}: {1} +cloudformation.create_stack.failed_validation=Failed to create stack {0} due to validation error +cloudformation.create_stack.timeout=Failed to create stack {0} in {1} seconds. View the latest status of the stack via the AWS Console. +cloudformation.delete_stack.failed=Failed to delete stack {0}: {1} +cloudformation.delete_stack.timeout=Failed to delete stack {0} in {1} seconds. View the latest status of the stack via the AWS Console. +cloudformation.execute_change_set.failed=Failed to execute change set against {0} +cloudformation.execute_change_set.success=Successfully executed change set against {0} +cloudformation.execute_change_set.success.title=Successfully executed change set +cloudformation.invalid_property=Property {0} has invalid value {1} +cloudformation.key_not_found={0} not found on resource {1} +cloudformation.missing_property=Property {0} not found in {1} +cloudformation.service_name=AWS CloudFormation +cloudformation.stack.delete.action=Delete Stack... +cloudformation.stack.filter.show_completed=Show Completed +cloudformation.stack.logical_id=Logical ID +cloudformation.stack.logical_id.copy=Copy Logical ID +cloudformation.stack.outputs.description=Description +cloudformation.stack.outputs.export=Export Name +cloudformation.stack.outputs.export.copy=Copy Export Name +cloudformation.stack.outputs.key=Key +cloudformation.stack.outputs.key.copy=Copy Key +cloudformation.stack.outputs.value=Value +cloudformation.stack.outputs.value.copy=Copy Value +cloudformation.stack.physical_id=Physical ID +cloudformation.stack.physical_id.copy=Copy Physical ID +cloudformation.stack.reason=Status Reason +cloudformation.stack.status=Status +cloudformation.stack.tab_labels.events=Events +cloudformation.stack.tab_labels.outputs=Outputs +cloudformation.stack.tab_labels.resources=Resources +cloudformation.stack.type=Type +cloudformation.stack.view=View Stack Status +cloudformation.template_index.missing_type=Resource type must not be null for indexing +cloudformation.toolwindow.label=CloudFormation +cloudformation.update_stack.failed=Failed to update stack {0}: {1} +cloudformation.update_stack.failed_validation=Failed to update stack {0} due to validation error +cloudformation.update_stack.timeout=Failed to update stack {0} in {1} seconds. View the latest status of the stack via the AWS Console. +cloudformation.yaml.invalid_root_type=Template does not start with a mapping: {0} +cloudformation.yaml.too_many_documents=There should only be 1 YAML document per file: {0} +cloudformation.yaml.too_many_files=Found {0} YAML files but only expected 1 +cloudwatch.logs.compare.start.end.date=Start date must be before end date +cloudwatch.logs.delete_log_group=Delete Log Group +cloudwatch.logs.download=Download Log Stream +cloudwatch.logs.download.description=Select a folder to save the Log Stream to +cloudwatch.logs.exception=CloudWatch Logs Exception +cloudwatch.logs.export=Export Log Stream +cloudwatch.logs.failed_to_load_more=Failed to load more +cloudwatch.logs.failed_to_load_stream=Failed to load log stream {0} +cloudwatch.logs.failed_to_load_streams=Failed to load log streams for log group {0} +cloudwatch.logs.failed_to_save_query=Failed to save query +cloudwatch.logs.filter_loggroup=Filter streams by prefix +cloudwatch.logs.filter_logs=Filter events +cloudwatch.logs.filtered_log_stream_title=Filtered Stream: {0} from {1} to {2} +cloudwatch.logs.last_event_time=Last Event Time +cloudwatch.logs.log_group_does_not_exist=Log group {0} does not exist +cloudwatch.logs.log_group_title=Group: {0} +cloudwatch.logs.log_groups=Log Groups +cloudwatch.logs.log_record=Log Record: {0} +cloudwatch.logs.log_record_field=Log Event Field +cloudwatch.logs.log_record_open_log_stream=View Log Stream +cloudwatch.logs.log_record_value=Log Event Value +cloudwatch.logs.log_stream_does_not_exist=Log stream {0} does not exist +cloudwatch.logs.log_stream_title=Stream: {0} +cloudwatch.logs.log_streams=Log Streams +cloudwatch.logs.no_end_date=End Date must be specified +cloudwatch.logs.no_events=No Events in Log Stream +cloudwatch.logs.no_events_query=No Events matching the query found in Log Stream {0} +cloudwatch.logs.no_log_group=At least one log group must be selected +cloudwatch.logs.no_log_streams=No Log Stream found +cloudwatch.logs.no_query_entered=Query must be specified +cloudwatch.logs.no_relative_time_number=Number must be specified +cloudwatch.logs.no_results_found=No results found +cloudwatch.logs.no_start_date=Start Date must be specified +cloudwatch.logs.no_term_entered=Search Term must be specified +cloudwatch.logs.open=View Log Streams +cloudwatch.logs.open_in_editor=Open in Editor +cloudwatch.logs.open_in_editor_failed=Open in editor failed +cloudwatch.logs.open_query_editor=Open Query Editor +cloudwatch.logs.opening_in_editor=Opening Stream {0} in editor +cloudwatch.logs.query=Query +cloudwatch.logs.query.form.ok_Button=Execute +cloudwatch.logs.query_editor_title=Query Log Groups +cloudwatch.logs.query_name=Query Name +cloudwatch.logs.query_name_missing=Query name is missing +cloudwatch.logs.query_result=Query Results +cloudwatch.logs.query_result_completion_status=Result +cloudwatch.logs.query_result_completion_successful=Query Execution Completed +cloudwatch.logs.query_results_table_error=Failed to load Query Results +cloudwatch.logs.query_saved_successfully=Query has been saved to your account! +cloudwatch.logs.query_tab_title=Query: {0} +cloudwatch.logs.save_action=Save to a File +cloudwatch.logs.save_query=Save Query +cloudwatch.logs.save_query_dialog_name=Enter Query Name +cloudwatch.logs.saved_query_status=Saved Query Status +cloudwatch.logs.saving_to_disk=Saving Stream {0} to disk +cloudwatch.logs.saving_to_disk_failed=Saving Stream {0} failed +cloudwatch.logs.saving_to_disk_succeeded=Finished saving Log Stream {0} to {1} +cloudwatch.logs.select_saved_query_dialog_name=Select Saved Query +cloudwatch.logs.show_logs_around=Show Logs Around +cloudwatch.logs.stream_save_to_file=Save {0} to a file +cloudwatch.logs.stream_too_big=Stream is Too Large +cloudwatch.logs.stream_too_big_message=Log stream {0} is too large to open in the editor +cloudwatch.logs.tail=Tail logs +cloudwatch.logs.time_days=Days +cloudwatch.logs.time_hours=Hours +cloudwatch.logs.time_minutes=Minutes +cloudwatch.logs.time_weeks=Weeks +cloudwatch.logs.toolwindow=CloudWatch Logs +cloudwatch.logs.validation.timerange=Time Range not Selected +cloudwatch.logs.view_log_stream=View Log Stream +cloudwatch.logs.view_log_streams=View Log Streams +cloudwatch.logs.wrap=Wrap logs +code.aws=Amazon CodeCatalyst +code.aws.value_prop_text=Amazon CodeCatalyst: Launch developer environments in the cloud within seconds. +code.aws.workspaces=Amazon CodeCatalyst +code.aws.workspaces.short=Dev Environments +codemodernizer.builderrordialog.description.title=Error occurred when building your project +codemodernizer.chat.form.user_selection.item.choose_module=Choose a module to transform +codemodernizer.chat.form.user_selection.item.choose_one_or_multiple_diffs_option=Choose how to receive proposed changes +codemodernizer.chat.form.user_selection.item.choose_skip_tests_option=Choose to skip unit tests +codemodernizer.chat.form.user_selection.item.choose_sql_metadata_file=Okay, I can convert the embedded SQL code for your Oracle to PostgreSQL transformation. To get started, upload the zipped metadata file from your schema conversion in AWS Data Migration Service (DMS). To retrieve the metadata file:\n1. Open your database migration project in the AWS DMS console.\n2. Open the schema conversion and choose **Convert the embedded SQL in your application**.\n3. Once you complete the conversion, close the project and go to the S3 bucket where your project is stored.\n4. Open the folder and find the project folder ("sct-project").\n5. Download the object inside the project folder. This will be a zip file.\n\nFor more info, refer to the [documentation](https://docs.aws.amazon.com/dms/latest/userguide/schema-conversion-embedded-sql.html). +codemodernizer.chat.form.user_selection.item.choose_target_version=Choose the target code version +codemodernizer.chat.form.user_selection.title=Q - Code transformation +codemodernizer.chat.message.absolute_path_detected=I detected {0} potential absolute file path(s) in your {1} file: **{2}**. Absolute file paths might cause issues when I build your code. Any errors will show up in the build log. +codemodernizer.chat.message.auth_prompt=Follow instructions to re-authenticate. If you experience difficulty, sign out of the Amazon Q plugin and sign in again. +codemodernizer.chat.message.button.cancel=Cancel +codemodernizer.chat.message.button.confirm=Confirm +codemodernizer.chat.message.button.hil_cancel=Cancel +codemodernizer.chat.message.button.hil_submit=Submit +codemodernizer.chat.message.button.open_file=Open file +codemodernizer.chat.message.button.open_transform_hub=Open Transformation Hub +codemodernizer.chat.message.button.select_sql_metadata=Select metadata file +codemodernizer.chat.message.button.stop_transform=Stop transformation +codemodernizer.chat.message.button.view_build=View build progress +codemodernizer.chat.message.button.view_diff=View diff +codemodernizer.chat.message.button.view_failure_build_log=View build log +codemodernizer.chat.message.button.view_summary=View summary +codemodernizer.chat.message.changes_applied=I applied the changes to your project. +codemodernizer.chat.message.choose_objective=I can help you with the following tasks:\n- Upgrade your Java 8, Java 11, and Java 17 codebases to Java 17 or Java 21.\n- Upgrade Java 17 or Java 21 code with up-to-date libraries and other dependencies.\n- Convert embedded SQL code for Oracle to PostgreSQL database migrations in AWS DMS.\n\nWhat would you like to do? You can enter "language upgrade" or "sql conversion". +codemodernizer.chat.message.choose_objective_placeholder=Enter "language upgrade" or "sql conversion" +codemodernizer.chat.message.custom_dependency_upgrades_continue=Ok, I will continue the transformation without additional dependency upgrade information. +codemodernizer.chat.message.custom_dependency_upgrades_invalid=The dependency upgrade file provided is missing required field `{0}`. Check that it is configured properly and try again. For an example of the required dependency upgrade file format, see the [documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#dependency-upgrade-file). +codemodernizer.chat.message.custom_dependency_upgrades_invalid_not_yaml=Provided file is not a YAML/YML file +codemodernizer.chat.message.custom_dependency_upgrades_prompt_jdk_upgrade=Would you like to provide a dependency upgrade file? You can specify first party dependencies and their versions in a YAML file, and I will upgrade them during the JDK upgrade transformation. For an example dependency upgrade file, see the [documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#dependency-upgrade-file). +codemodernizer.chat.message.custom_dependency_upgrades_prompt_library_upgrade=Would you like to provide a dependency upgrade file? You can specify third party dependencies and their versions in a YAML file, and I will only upgrade these dependencies during the library upgrade transformation. For an example dependency upgrade file, see the [documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#dependency-upgrade-file). +codemodernizer.chat.message.custom_dependency_upgrades_valid=The dependency upgrade file looks good. I will use this information to upgrade the dependencies you specified. +codemodernizer.chat.message.download_failed_client_instructions_expired=Your transformation is not available anymore. Your code and transformation summary are deleted 24 hours after the transformation completes. Please try starting the transformation again. +codemodernizer.chat.message.download_failed_invalid_artifact=Sorry, I was unable to find your {0}. Artifacts are deleted after 24 hours. Please try starting the transformation again. +codemodernizer.chat.message.download_failed_other=Sorry, I ran into an issue while trying to download your {0}. Please try again. {1} +codemodernizer.chat.message.download_failed_ssl=Sorry, I couldn''t download your {0} because of an issue with your certificate. Please make sure all your certificates for your proxy client have been set up correctly for your IDE. +codemodernizer.chat.message.download_failed_wildcard=Sorry, I couldn''t download your {0} because of an issue with your proxy client. Please check your IDE proxy settings and remove any wildcard (*) references, then restart your IDE. +codemodernizer.chat.message.enter_jdk_name=Enter the name of the {0} you are using. You can find the name in File > Project Structure > Platform Settings > SDKs. If you do not see the name of your JDK in the SDK settings, add your JDK, and then return to this chat and enter the name here. +codemodernizer.chat.message.enter_jdk_name_error=I could not find "{0}" in File > Project Structure > Platform Settings > SDKs. Please add the target JDK there and try again. +codemodernizer.chat.message.error_request=Request failed +codemodernizer.chat.message.follow_up.new_transformation=Start a new transformation +codemodernizer.chat.message.hil.cannot_resume=I ran into an issue trying to resume your transformation. +codemodernizer.chat.message.hil.continue_after_error=I'll continue upgrading your module. When I'm done, you can review the dependency error in the Transformation summary. +codemodernizer.chat.message.hil.dependency_choose_version=I can replace this dependency with a newer version. Choose which version I should use: +codemodernizer.chat.message.hil.dependency_latest_incremental=\n\nLatest incremental version: {0} +codemodernizer.chat.message.hil.dependency_latest_major=\n\nLatest major version: {0} +codemodernizer.chat.message.hil.dependency_latest_minor=\n\nLatest minor version: {0} +codemodernizer.chat.message.hil.dependency_summary=I found {0} other dependency versions that are more recent than the dependency in your code that''s causing an error ({1}). +codemodernizer.chat.message.hil.error.cancel_dependency_search=I cancelled the local dependency search. +codemodernizer.chat.message.hil.error.cancel_upload=I cancelled the dependency upload. +codemodernizer.chat.message.hil.error.cannot_download_artifact=I ran into an error trying to download the dependency information. +codemodernizer.chat.message.hil.error.cannot_upload=I'm sorry, I wasn't able to upload the dependency you chose. +codemodernizer.chat.message.hil.error.no_other_versions_found=I couldn''t find any other versions of this dependency in your local Maven repository. Try transforming the **{0}** dependency to make it compatible with your target Java version, and then try transforming this module again. +codemodernizer.chat.message.hil.pom_snippet_title=Here is the dependency causing the issue: +codemodernizer.chat.message.hil.resumed=I received your target version dependency. I'll continue transforming your code. You can monitor progress in the Transformation Hub. +codemodernizer.chat.message.hil.searching=I'm searching for other dependency versions available in your Maven repository... +codemodernizer.chat.message.hil.start_message=I was not able to upgrade all dependencies. To resolve it, I'll try to find an updated dependency in your local Maven repository. I'll need additional information from you to continue. +codemodernizer.chat.message.hil.trying_resume=Trying to resume transformation with your selected version. +codemodernizer.chat.message.hil.user_rejected=I'll continue upgrading your module. When I'm done, you can review the dependency error in the Transformation summary. +codemodernizer.chat.message.local_build_begin=I'm building your module. This can take up to 10 minutes, depending on the size of your module. +codemodernizer.chat.message.local_build_failed=Sorry, I couldn't run the Maven clean test-compile command to build your module. +codemodernizer.chat.message.local_build_success=I was able to build your module and will start uploading your code. +codemodernizer.chat.message.result.fail=Sorry, I ran into an issue during the transformation. Please try again. +codemodernizer.chat.message.result.fail_initial_build=I am having trouble building your project in the secure build environment and couldn't complete the transformation. +codemodernizer.chat.message.result.fail_initial_build_no_build_log=I am having trouble building your project in the secure build environment: {0}. +codemodernizer.chat.message.result.fail_with_known_reason=Sorry, I couldn''t complete the transformation. {0} +codemodernizer.chat.message.result.partially_success=I transformed part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation. After successfully transforming to Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. +codemodernizer.chat.message.result.success=I successfully completed your transformation. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the changes I am proposing. After successfully transforming to Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. +codemodernizer.chat.message.result.zip_too_large=Sorry, your project size exceeds the Amazon Q Code Transformation upload limit of 2GB. +codemodernizer.chat.message.resume_ongoing=I'm still transforming your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your module. To monitor progress, go to the Transformation Hub. +codemodernizer.chat.message.skip_tests=I will build generated code in your local environment, not on the server side. For information on how I scan code to reduce security risks associated with building the code in your local environment, see the [Amazon Q Developer documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#java-local-builds).\n\nI will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`. +codemodernizer.chat.message.skip_tests_form.response=Okay, I will {0} when building your module. +codemodernizer.chat.message.skip_tests_form.run_tests=Run unit tests +codemodernizer.chat.message.skip_tests_form.skip=Skip unit tests +codemodernizer.chat.message.sql_metadata_success=I found the following source database, target database, and host based on the schema conversion metadata you provided: +codemodernizer.chat.message.sql_module_schema_prompt=To continue, choose the module and schema for this transformation. +codemodernizer.chat.message.transform_begin=I'm starting to transform your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your module. To monitor progress, go to the Transformation Hub. +codemodernizer.chat.message.transform_cancelled_by_user=I cancelled your transformation. If you want to start another transformation, choose **Start a new transformation**. +codemodernizer.chat.message.transform_failed=I could not complete the transformation. {0} +codemodernizer.chat.message.transform_in_progress=If I run into any issues, I might pause the transformation to get input from you on how to proceed. +codemodernizer.chat.message.transform_stopped_by_user=I stopped your transformation. If you want to start another transformation, choose **Start a new transformation**. +codemodernizer.chat.message.transform_stopping=I'm stopping your transformation... +codemodernizer.chat.message.upload_failed_connection_refused=Sorry, I couldn't upload your project to begin the transformation. Please check your network connectivity or firewall configuration, and then try again. +codemodernizer.chat.message.upload_failed_http_error=Sorry, I couldn''t upload your project to start the transformation. You can investigate the issue with the HTTP Status Code: {0} +codemodernizer.chat.message.upload_failed_other=Sorry, I was unable to upload your project due to an unexpected failure. {0} +codemodernizer.chat.message.upload_failed_ssl_error=Sorry, I was unable to upload your project. This might have been caused by your IDE not trusting the certificate of your HTTP proxy. Ensure all certificates for your proxy client have been configured in your IDE, and then retry transformation. +codemodernizer.chat.message.upload_failed_url_expired=Sorry, I couldn't upload your project to begin the transformation. The Amazon S3 pre-signed URL used to upload your code expired after 30 minutes. This might have been caused by delays introduced by intermediate services in your network infrastructure.\n\nCheck your network configuration for services that might be causing delays. If the issue persists, you might need to allow list the following Amazon S3 bucket: 'amazonq-code-transformation-us-east-1-c6160f047e0.s3.amazonaws.com'. +codemodernizer.chat.message.validation.check_eligible_modules=Checking for eligible modules... +codemodernizer.chat.message.validation.check_passed=I can upgrade your Java module. To start the transformation, I need some information from you. Choose the module you want to upgrade and the target code version to upgrade to. Then, choose **Confirm**.\n\nIf you do not see the module you want to transform, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and language level. In the Modules tab, set the correct module JDK and language level.\n\nAfter successfully transforming to Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this.\n\nI will perform the transformation based on your project's requests, descriptions, and content. To maintain security, avoid including external, unvetted artifacts in your project repository prior to starting the transformation and always validate transformed code for both functionality and security. Do not turn off or close your machine during the transformation because a stable network connection is required. +codemodernizer.chat.message.validation.error.downgrade_attempt=I can't transform a project from Java 21 to Java 17, but I can upgrade Java 21 code with up-to-date libraries and other dependencies. Try again with a supported language upgrade. +codemodernizer.chat.message.validation.error.invalid_sct=It looks like the .sct file you provided isn't valid. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS. +codemodernizer.chat.message.validation.error.invalid_source_db=I can only convert SQL for migrations from an Oracle source database. The provided .sct file indicates another source database for this migration. +codemodernizer.chat.message.validation.error.invalid_target_db=I can only convert SQL for migrations to Aurora PostgreSQL or Amazon RDS for PostgreSQL target databases. The provided .sct file indicates another target database for this migration. +codemodernizer.chat.message.validation.error.missing_sct_file=An .sct file is required for transformation. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS. +codemodernizer.chat.message.validation.error.more_info=For more information, see the [Amazon Q documentation]({0}). +codemodernizer.chat.message.validation.error.no_java_project=Sorry, I could not find an open Java module with embedded Oracle SQL statements. Make sure you have a Java module open that has at least 1 content root. +codemodernizer.chat.message.validation.error.other=I couldn't find a module that I can upgrade. Currently, I support Java 8, Java 11, Java 17, and Java 21 projects built on Maven. Make sure your project is open in the IDE. If you have a Java 8, Java 11, Java 17, or Java 21 module in your workspace, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and the correct language level. In the Modules tab, set the correct module JDK and language level. +codemodernizer.chat.message.validation.error.unsupported_module=I couldn't find a module that I can upgrade. Currently, I support Java 8, Java 11, Java 17, and Java 21 projects built on Maven. Make sure your project is open in the IDE. If you have a Java 8, Java 11, Java 17, or Java 21 in your workspace, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and the correct language level. In the Modules tab, set the correct module JDK and language level. +codemodernizer.chat.message.validation.no_jdk=I couldn't build your project with your current JDK configuration. To update your JDK, go to File and choose Project Structure. In the Projects tab, set the correct project JDK in the SDK field. In the Modules tab, set the correct module JDK in the SDK field. In Maven Runner settings, set the correct JDK in the JRE field. +codemodernizer.chat.prompt.label.dependency_current_version=Current version +codemodernizer.chat.prompt.label.dependency_name=Dependency name +codemodernizer.chat.prompt.label.dependency_selected_version=Target version +codemodernizer.chat.prompt.label.module=Module +codemodernizer.chat.prompt.label.target_version=Target JDK version +codemodernizer.chat.prompt.stop_transform=Stop transformation +codemodernizer.chat.prompt.title.dependency_details=Dependency details +codemodernizer.chat.prompt.title.details=Transformation details +codemodernizer.explorer.show_job_status=Show job status +codemodernizer.explorer.show_job_status_description=View job status +codemodernizer.explorer.show_transformation_plan_title=View your code transformation plan +codemodernizer.explorer.show_transformation_status=Show transformation status +codemodernizer.explorer.show_transformation_status_description=View transformation status +codemodernizer.explorer.show_transformation_summary_title=View your code transformation summary +codemodernizer.explorer.stop_migration_job=Stop Transformation +codemodernizer.file.invalid_pom_version=Amazon Q experienced an issue upgrading this dependency version. Use Amazon Q chat to upgrade the version of this dependency to a version compatible with your target Java version. +codemodernizer.manager.job_ongoing_content=Amazon Q is still transforming your code. To see the current status of the transformation, go to the Transformation Hub. +codemodernizer.manager.job_ongoing_title=Code Transformation ongoing +codemodernizer.migration_plan.body.info.action_column=Action +codemodernizer.migration_plan.body.info.appendix_title=Appendix +codemodernizer.migration_plan.body.info.changed_files_column=Files to be changed +codemodernizer.migration_plan.body.info.current_version_column=Current version +codemodernizer.migration_plan.body.info.dependency_name_column=Dependency +codemodernizer.migration_plan.body.info.dependency_replace_message=Dependencies to be replaced +codemodernizer.migration_plan.body.info.deprecated_code_column=Deprecated code +codemodernizer.migration_plan.body.info.deprecated_code_message=Deprecated code instances to be replaced +codemodernizer.migration_plan.body.info.file_column=File +codemodernizer.migration_plan.body.info.files_changed_message=Files to be changed +codemodernizer.migration_plan.body.info.job_statistic_message={0}: {1} +codemodernizer.migration_plan.body.info.lines_of_code_message=Lines of code in your application +codemodernizer.migration_plan.body.info.target_version_column=Target version +codemodernizer.migration_plan.body.steps_intro_subtitle=Amazon Q will use the proposed changes as guidance during the transformation. The final code updates might differ from this plan. Read more. +codemodernizer.migration_plan.body.steps_intro_title=Planned transformation changes +codemodernizer.migration_plan.body.steps_name={0} +codemodernizer.migration_plan.body.steps_scroll_top=Scroll to top +codemodernizer.migration_plan.header.awsq=Amazon Q reviewed your code and generated a transformation plan. Amazon Q will suggest code changes according to the plan, and you can review the updated code before accepting changes to your files. +codemodernizer.migration_plan.header.billing_text=
{0} lines of code were submitted for transformation. If you reach the quota for lines of code included in your subscription, you will be charged ${1} for each additional line of code. You might be charged up to ${2} for this transformation. To avoid being charged, stop the transformation job before it completes. For more information on pricing and quotas, see Amazon Q Developer pricing.

+codemodernizer.migration_plan.header.description=Plan to transform your module +codemodernizer.migration_plan.header.title=Code Transformation plan by Amazon Q +codemodernizer.migration_plan.substeps.description_completed=Build completed +codemodernizer.migration_plan.substeps.description_failed=Build failed +codemodernizer.migration_plan.substeps.description_stopped=Job is stopped +codemodernizer.migration_summary.header.title=Transformation summary +codemodernizer.notification.info.download.started.content=Downloading the updated code +codemodernizer.notification.info.download.started.title=Download Started +codemodernizer.notification.info.modernize_complete.content=Amazon Q finished the transformation. You can review the diff to see the proposed changes and accept or reject them. The transformation summary has details about the files that were updated. +codemodernizer.notification.info.modernize_complete.title=Transform Complete +codemodernizer.notification.info.modernize_complete.view_summary=View transformation summary +codemodernizer.notification.info.modernize_failed.connection_failed=Amazon Q could not complete the transformation. Try starting the transformation again. {0} +codemodernizer.notification.info.modernize_failed.title=Transformation failed +codemodernizer.notification.info.modernize_failed.unknown_failure_reason=Unknown failure reason +codemodernizer.notification.info.modernize_ongoing.view_status=View status +codemodernizer.notification.info.modernize_partial_complete.content=Amazon Q transformed part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation. +codemodernizer.notification.info.modernize_partial_complete.title=Transformation partially successful! +codemodernizer.notification.info.transformation_resume.content=Amazon Q was unable to resume polling for the job you started before closing the module. +codemodernizer.notification.info.transformation_resume.title=Unable to resume polling for job updates. +codemodernizer.notification.info.transformation_start_stopping.as_no_response=Amazon Q could not stop the transformation. +codemodernizer.notification.info.transformation_start_stopping.content=Amazon Q is stopping your transformation. This might take a few seconds. +codemodernizer.notification.info.transformation_start_stopping.failed_content=Amazon Q could not stop the transformation. +codemodernizer.notification.info.transformation_start_stopping.failed_title=Error stopping transformation +codemodernizer.notification.info.transformation_start_stopping.title=Transformation stopping +codemodernizer.notification.info.transformation_stop.content=You cancelled the transformation. To start a new transformation, return to the Q - Transform chat tab. +codemodernizer.notification.info.transformation_stop.title=Transformation cancelled +codemodernizer.notification.info.view_troubleshooting_guide=View troubleshooting guide +codemodernizer.notification.warn.action.reauthenticate=Reauthenticate +codemodernizer.notification.warn.download_failed_client_instructions_expired=Your transformation is not available anymore. Your code and transformation summary are deleted 24 hours after the transformation completes. Please try starting the transformation again. +codemodernizer.notification.warn.download_failed_expired_credentials.content=Unable to download results as your credentials expired, please reauthenticate to Q and try again. +codemodernizer.notification.warn.download_failed_invalid_artifact=Amazon Q was unable to find your {0}. Artifacts are deleted after 24 hours. Please try starting the transformation again. +codemodernizer.notification.warn.download_failed_other.content=Amazon Q ran into an issue while trying to download your {0}. Please try again. {1} +codemodernizer.notification.warn.download_failed_ssl.content=Please make sure all your certificates for your proxy client have been set up correctly for your IDE. +codemodernizer.notification.warn.download_failed_wildcard.content=Check your IDE proxy settings and remove any wildcard (*) references, and then try viewing the diff again. +codemodernizer.notification.warn.expired_credentials.content=Unable to check transformation status as your credentials expired. Try signing out of the Amazon Q plugin and signing in again. +codemodernizer.notification.warn.expired_credentials.title=Your connection to Q has expired +codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots=None of your open modules are supported for code transformation with Amazon Q. Amazon Q can upgrade Java 8, Java 11, Java 17, and Java 21 projects built on Maven, with content roots configured. +codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in=Amazon Q cannot start the transformation as you are not logged in with Identity Center or Builder ID. Also ensure that you are not using IntelliJ version 232.8660.185 and that you are not developing on a remote host (uncommon). +codemodernizer.notification.warn.invalid_project.description.reason.remote_backend=None of your open modules are supported for code transformation with Amazon Q. Amazon Q cannot transform modules running on a remote host. +codemodernizer.notification.warn.maven_failed.content=Amazon Q could not run the Maven clean test-compile command to build your module. +codemodernizer.notification.warn.maven_failed.title=Amazon Q Code Transform unable to zip dependencies +codemodernizer.notification.warn.on_resume.unknown_status_response.content=We received data from Amazon Q in a format that the plugin cannot handle. You may need to update the plugin and then try again. +codemodernizer.notification.warn.on_resume.unknown_status_response.title=Unable to resume job +codemodernizer.notification.warn.submit_feedback=Submit feedback +codemodernizer.notification.warn.unable_to_start_job=Amazon Q could not begin the transformation. Try starting the transformation again. {0} +codemodernizer.notification.warn.unknown_start_failure=Amazon Q could not begin the transformation. Try starting the transformation again. +codemodernizer.notification.warn.unknown_status_response=Amazon Q could not complete the transformation. Try starting the transformation again. +codemodernizer.notification.warn.upload_failed=Amazon Q could not upload your module. Try starting the transformation again. {0} +codemodernizer.notification.warn.upload_failed_expired_credentials.content=Unable to upload results as your credentials expired, please reauthenticate to Q and try again. +codemodernizer.notification.warn.validation.no_jdk=Amazon Q couldn't build your module with your JDK configuration. Go to File and choose Project Structure to update your project SDK and module SDK. +codemodernizer.notification.warn.view_build_log_failed.content=Unable to display the failure build log due to an error. +codemodernizer.notification.warn.view_build_log_failed.title=Unable to display failure build log +codemodernizer.notification.warn.view_diff_failed.content=Amazon Q could not download and parse the diff with your upgraded code. {0} +codemodernizer.notification.warn.view_diff_failed.title=Failed to download upgraded code +codemodernizer.notification.warn.view_summary_failed.content=Unable to display the transformation summary due to an error. +codemodernizer.notification.warn.view_summary_failed.title=Unable to display transformation summary +codemodernizer.notification.warn.zip_creation_failed=Amazon Q could not zip the selected module and begin the transformation. Try starting the transformation again. {0} +codemodernizer.notification.warn.zip_creation_failed.reasons.unknown=An unexpected error occurred +codemodernizer.notification.warn.zip_too_large.content=Sorry, your module size exceeds the Amazon Q Code Transformation upload limit of 2GB. +codemodernizer.notification.warn.zip_too_large.title=Module size exceeds limit +codemodernizer.toolwindow.banner.action.feedback=Provide Feedback +codemodernizer.toolwindow.banner.action.plan=View transformation plan +codemodernizer.toolwindow.banner.action.summary=View transformation summary +codemodernizer.toolwindow.banner.job_failed_to_start=Job could not be started. +codemodernizer.toolwindow.banner.job_failed_while_running=Job failed. +codemodernizer.toolwindow.banner.job_is_running=Job is running. +codemodernizer.toolwindow.banner.job_is_stopped=Job is stopped. +codemodernizer.toolwindow.banner.job_starting=Job is starting. +codemodernizer.toolwindow.banner.no_ongoing_job=No job ongoing. +codemodernizer.toolwindow.banner.run_scan_complete=All steps of transformation successful +codemodernizer.toolwindow.banner.run_scan_info=Select 'Transform' in toolbar to upgrade this package. +codemodernizer.toolwindow.job_status.header=Transformation status +codemodernizer.toolwindow.label=Transformation Hub - {0} +codemodernizer.toolwindow.label_no_job=Transformation Hub +codemodernizer.toolwindow.problems_mvn_window_not_found=Unable to display Code Transform results as the Maven window cannot be fetched. +codemodernizer.toolwindow.problems_window_not_found=Unable to display Code Transform results as the Problems View tool window cannot be fetched. +codemodernizer.toolwindow.progress.building=Analyze uploaded code in secure environment +codemodernizer.toolwindow.progress.parent=Transformation progress +codemodernizer.toolwindow.progress.planning=Generate transformation plan +codemodernizer.toolwindow.progress.transforming=Transform your code +codemodernizer.toolwindow.progress.uploading=Uploading your code +codemodernizer.toolwindow.scan_display=Transformation details +codemodernizer.toolwindow.scan_in_progress=Amazon Q is scanning the project files and getting ready to start the job. To start the job, Amazon Q needs to upload the project artifacts. Once that's done, Q can start the transformation job. The estimated time for this operation ranges from a few seconds to several minutes. +codemodernizer.toolwindow.scan_in_progress.accepted=Files have been uploaded to Amazon Q, transformation job has been accepted and is preparing to start. +codemodernizer.toolwindow.scan_in_progress.building=Amazon Q is building your code using Java {0} in a secure build environment. +codemodernizer.toolwindow.scan_in_progress.failed=The step failed, fetching additional details... +codemodernizer.toolwindow.scan_in_progress.planning=Amazon Q is analyzing your code in order to generate a transformation plan. +codemodernizer.toolwindow.scan_in_progress.stopping=Stopping the job... +codemodernizer.toolwindow.scan_in_progress.transforming=Amazon Q is transforming your code. +codemodernizer.toolwindow.stop_scan=Stop job +codemodernizer.toolwindow.table.header.date=Date +codemodernizer.toolwindow.table.header.job_id=Job ID +codemodernizer.toolwindow.table.header.module_name=Module name +codemodernizer.toolwindow.table.header.run_length=Job running time +codemodernizer.toolwindow.table.header.status=Status +codemodernizer.toolwindow.transformation.progress.header=Transformation progress +codemodernizer.toolwindow.transformation.progress.job_id=Job ID: {0} +codemodernizer.toolwindow.transformation.progress.running_time=Running time: {0} +codescan.chat.message.button.fileScan=Review active file +codescan.chat.message.button.openIssues=View in Code Issues Panel +codescan.chat.message.button.projectScan=Review project +codescan.chat.message.error_request=Request failed +codescan.chat.message.not_git_repo=Your workspace is not in a git repository. I'll review your project files for security issues, and your in-flight changes for code quality issues. +codescan.chat.message.project_scan_failed=Sorry, I ran into an issue during the review. Please try again. +codescan.chat.message.scan_begin_file=Okay, I'm reviewing your file for code issues. +codescan.chat.message.scan_begin_project=Okay, I'm reviewing your project for code issues. +codescan.chat.message.scan_begin_wait_time=This may take a few minutes. I'll share updates here as I work on this. +codescan.chat.message.scan_file_in_progress=File review is in progress... +codescan.chat.message.scan_project_in_progress=Project review is in progress... +codescan.chat.message.scan_step_1=Initiating code review. +codescan.chat.message.scan_step_2=Waiting for review to finish. +codescan.chat.message.scan_step_3=Processing review results. +codescan.chat.new_scan.input.message=Which type of review would you like to run? +codescan.chat.placeholder.scan_in_progress=Reviewing code issues... +codescan.chat.placeholder.waiting_for_inputs=Waiting on your inputs... +codewhisperer.actions.connect_github.title=Connect with Us on GitHub +codewhisperer.actions.open_settings.title=Open Settings +codewhisperer.actions.send_feedback.title=Send Feedback +codewhisperer.actions.view_documentation.title=View Documentation +codewhisperer.codefix.code_fix_job_timed_out=Amazon Q: Timed out generating code fix +codewhisperer.codefix.create_code_fix_error=Amazon Q: Failed to generate fix for the issue +codewhisperer.codefix.invalid_zip_error=Amazon Q: Failed to create valid zip +codewhisperer.codescan.apply_fix_button_label=Fix +codewhisperer.codescan.apply_fix_button_tooltip=Generate and apply fix +codewhisperer.codescan.build_artifacts_not_found=Cannot find build artifacts for the project. Try rebuilding the Java project in IDE or specify compilation output path in File | Project Structure... | Project | Compiler output: +codewhisperer.codescan.cancelled_by_user_exception=Code review job cancelled by user. +codewhisperer.codescan.cannot_read_file=Amazon Q encountered an error while parsing a file. +codewhisperer.codescan.clear_filters=Clear Filters +codewhisperer.codescan.cwe_label=Common Weakness Enumeration (CWE) +codewhisperer.codescan.detector_library_label=Detector library +codewhisperer.codescan.explain_button_label=Explain +codewhisperer.codescan.explain_button_tooltip=Explain Issue +codewhisperer.codescan.file_ext_not_supported=File extension {0} is not supported for the Amazon Q Code Review feature. Please try again with a valid file format - java, python, javascript, typescript, csharp, yaml, json, tf, hcl, ruby, go. +codewhisperer.codescan.file_name_issues_count= {0} {1} {2, choice, 1#1 issue|2#{2,number} issues} +codewhisperer.codescan.file_not_found=For file path {0} with error message: {0} +codewhisperer.codescan.file_path_label=File Path +codewhisperer.codescan.file_too_large=Amazon Q: The selected file exceeds the input artifact limit. Try again with a smaller file. For more information about review limits, see the Amazon Q documentation. +codewhisperer.codescan.file_too_large_telemetry=Payload size limit reached +codewhisperer.codescan.fix_applied_fail=Apply fix command failed. {0} +codewhisperer.codescan.fix_available_label=Code fix available +codewhisperer.codescan.fix_button_label=Fix with Q +codewhisperer.codescan.generate_fix_button_label=Generate Fix +codewhisperer.codescan.ignore_all_button=Ignore All +codewhisperer.codescan.ignore_button=Ignore +codewhisperer.codescan.invalid_source_zip_telemetry=Failed to create valid source zip. +codewhisperer.codescan.java_module_not_found=Java plugin is required for reviewing Java files, install Java plugin or perform the code review in Intellij Idea instead. +codewhisperer.codescan.no_file_open=Amazon Q: No file is open in an active editor. Open a file to start a Code Review. +codewhisperer.codescan.no_file_open_telemetry=Open a valid file to review. +codewhisperer.codescan.problems_window_not_found=Unable to display Code Review results as the Problems View tool window cannot be fetched. +codewhisperer.codescan.quota_exceeded=You've reached the monthly quota for Amazon Q Developer's agent capabilities. You can try again next month. For more information on usage limits, see the Amazon Q Developer pricing page. +codewhisperer.codescan.regenerate_fix_button_label=Regenerate Fix +codewhisperer.codescan.run_scan_complete= Code Review completed for {0, choice, 1#1 file|2#{0,number} files}. {1, choice, 0#No issues|1#1 issue|2#{1,number} issues} found in {2}. Last Run {4} +codewhisperer.codescan.run_scan_error=Amazon Q encountered an error while reviewing for code issues. Please try again later. +codewhisperer.codescan.run_scan_error_telemetry=Code Review failed. +codewhisperer.codescan.run_scan_info=Ask the agent in Amazon Q Chat Panel to run code reviews. +codewhisperer.codescan.scan_complete_count=- {0}: `{1, choice, 1#{1,number} issue|2#{1,number} issues}` +codewhisperer.codescan.scan_complete_file=Reviewing your File is complete. Here's what I found: +codewhisperer.codescan.scan_complete_project=Reviewing your Project is complete. Here's what I found: +codewhisperer.codescan.scan_display=Amazon Q Code Issues +codewhisperer.codescan.scan_display_with_issues=Amazon Q Code Issues {0} +codewhisperer.codescan.scan_in_progress=Code review in progress... +codewhisperer.codescan.scan_recommendation= {0} {1} +codewhisperer.codescan.scan_recommendation_invalid= {0} {1} [No longer valid: Re-run the review to validate the fix] +codewhisperer.codescan.scan_recommendation_invalid.tooltip_text=No longer valid. Re-run the review to validate the fix. +codewhisperer.codescan.scan_results_hidden_by_filters=All code review results are hidden by current filters. +codewhisperer.codescan.scan_timed_out=Code Review failed. Amazon Q timed out. +codewhisperer.codescan.scanned_files_heading= {0} files were reviewed during the last code review. +codewhisperer.codescan.severity_issues_count= {0} {1, choice, 1#{1,number} issue|2#{1,number} issues} +codewhisperer.codescan.stop_scan_confirm_button=Stop review +codewhisperer.codescan.stop_scan_confirm_message=Are you sure you want to stop ongoing code review? This review will be counted as one complete review towards your monthly code review limits. +codewhisperer.codescan.stopping_scan=Stopping Code Review... +codewhisperer.codescan.suggested_fix_description=Why are we recommending this? +codewhisperer.codescan.suggested_fix_label=Suggested code fix preview +codewhisperer.codescan.unsupported_language_error=Amazon Q: Project does not contain valid files to review +codewhisperer.codescan.unsupported_language_error_telemetry=Project does not contain valid files to review +codewhisperer.codescan.upload_to_s3_failed=Amazon Q is unable to upload your project artifacts to Amazon S3 for code reviews. For more information, see the Amazon Q documentation. +codewhisperer.codescan.view_scanned_files=View {0} reviewed files +codewhisperer.credential.login.dialog.exception.cancel_login=Login cancelled +codewhisperer.credential.login.dialog.ok_button=Connect +codewhisperer.credential.login.dialog.prompt=Select a connection option to start using Amazon Q +codewhisperer.credential.login.exception.general=Ran into unknown error: {0} +codewhisperer.credential.login.exception.general.oidc=Fail to fetch credential +codewhisperer.credential.login.exception.invalid_grant=Access denied +codewhisperer.credential.login.exception.invalid_input=Invalid start url, region, or scopes +codewhisperer.credential.login.exception.io=Unable to access SSO disk cache: {0} +codewhisperer.custom.dialog.customization.no_description=No description provided +codewhisperer.custom.dialog.model.default.comment=Receive suggestions from Amazon Q base model. +codewhisperer.custom.dialog.ok_button.text=Select +codewhisperer.custom.dialog.option.customization=Customization +codewhisperer.custom.dialog.option.customization.description=Receive Amazon Q suggestions based on your company's codebase. +codewhisperer.custom.dialog.option.customization.description.no_customization=Contact your administrator for access to Amazon Q customizations. After you have access, they will be displayed in the dropdown below. Learn more (external link) +codewhisperer.custom.dialog.option.default=Amazon Q foundation (Default) +codewhisperer.custom.dialog.option.no_data=No customizations available +codewhisperer.custom.dialog.panel.title=Select an Amazon Q customization +codewhisperer.custom.dialog.title=Amazon Q Customization +codewhisperer.experiment=Amazon Q +codewhisperer.explorer.code_reference.open=Open Code Reference Log +codewhisperer.explorer.customization.select=Select Customization +codewhisperer.explorer.enable=Start +codewhisperer.explorer.enabled=\ Enabled +codewhisperer.explorer.learn=Learn +codewhisperer.explorer.node.dismiss=Dismiss +codewhisperer.explorer.node.install_q=Install the Amazon Q Plugin +codewhisperer.explorer.pause_auto=Pause Auto-Suggestions +codewhisperer.explorer.pause_auto_scans=Pause Auto-Reviews +codewhisperer.explorer.paused=\ Paused +codewhisperer.explorer.reconnect=Reconnect +codewhisperer.explorer.resume_auto=Resume Auto-Suggestions +codewhisperer.explorer.resume_auto_scans=Resume Auto-Reviews +codewhisperer.explorer.tooltip.comment=Start with auto-suggestions and find more features here! +codewhisperer.explorer.tooltip.title=Get started with Amazon Q +codewhisperer.explorer.usage_limit_hit=\ Free tier limit met, paused until {0} +codewhisperer.explorer.what_is=Learn More about Amazon Q +codewhisperer.gettingstarted.panel.comment=Build, maintain and transform applications
using generative AI. +codewhisperer.gettingstarted.panel.learn_more=Learn more +codewhisperer.gettingstarted.panel.learn_more.with.q=Learn more about Amazon Q and Codewhisperer +codewhisperer.gettingstarted.panel.licence_comment=Already have a license? +codewhisperer.gettingstarted.panel.login_button=Use for free, no AWS account required +codewhisperer.inline.accept=Accept Amazon Q Suggestion +codewhisperer.inline.force.accept=Force Accept Suggestion +codewhisperer.inline.navigate.next=Navigate to Next Amazon Q Suggestion +codewhisperer.inline.navigate.previous=Navigate to Previous Amazon Q Suggestion +codewhisperer.inline.settings.tab_priority.choice.amazonq=Amazon Q +codewhisperer.inline.settings.tab_priority.choice.jetbrains=JetBrains(IntelliSense) +codewhisperer.inline.settings.tab_priority.notification.text=You can adjust the behavior of the Tab key to prioritize accepting either Amazon Q suggestions or JetBrains(IntelliSense) suggestions. +codewhisperer.inline.settings.tab_priority.prefix=When both suggestions show, press Tab to accept +codewhisperer.inline.settings.tab_priority.suffix= suggestions first +codewhisperer.language.error={0} is currently not supported by Amazon Q +codewhisperer.learn_page.banner.dismiss=Dismiss +codewhisperer.learn_page.banner.message.new_user=You can always return to this page by clicking "Learn" in the Amazon Q status bar menu. +codewhisperer.learn_page.examples.description.part_1=Amazon Q inline suggestions support +codewhisperer.learn_page.examples.description.part_2=15 programming languages +codewhisperer.learn_page.examples.description.part_3=, including Java, JavaScript, Python, Go, and more. +codewhisperer.learn_page.examples.tasks.button=Try example +codewhisperer.learn_page.examples.tasks.description_1=Generate code suggestions as you type +codewhisperer.learn_page.examples.tasks.description_2.mac=Generate code suggestions manually using Option + C +codewhisperer.learn_page.examples.tasks.description_2.win=Generate code suggestions manually using Alt + C +codewhisperer.learn_page.examples.tasks.description_3=Generate unit test cases +codewhisperer.learn_page.examples.title=Generate code suggestions with examples +codewhisperer.learn_page.header.description=An AI assistant that reimagines your experience across the entire development lifecycle +codewhisperer.learn_page.header.title=Amazon Q inline code suggestions +codewhisperer.loading_licenses=Loading Licenses Info +codewhisperer.notification.accountless.error.action.connect=Connect with AWS +codewhisperer.notification.accountless.warn.action.connect=Connect with AWS to Continue +codewhisperer.notification.custom.new_customization=You have access to new Amazon Q customizations +codewhisperer.notification.custom.not_available=Selected Amazon Q customization is not available. Contact your administrator. Your instance of Amazon Q is using the foundation model. +codewhisperer.notification.custom.simple.button.got_it=Got it +codewhisperer.notification.custom.simple.button.select_another_customization=Select another customization +codewhisperer.notification.custom.simple.button.select_customization=Select customization +codewhisperer.notification.remote.ide_unsupported.message=Please update your IDE backend to a 2023.3 or later version to continue using Amazon Q inline suggestions. +codewhisperer.notification.remote.ide_unsupported.title=Amazon Q inline suggestion not supported in this IDE version +codewhisperer.notification.usage_limit.codescan.warn.content=Amazon Q: You have reached the monthly limit for project reviews. +codewhisperer.notification.usage_limit.codesuggestion.warn.content=You have reached the monthly fair use limit of code recommendations. +codewhisperer.popup.button.accept=
 Insert Code 
\u21E5
+codewhisperer.popup.button.next=
Next
{1}
+codewhisperer.popup.button.prev=
Previous
{1}
+codewhisperer.popup.import_info=

If you insert code, "{0}"{2, choice, 0#|1# and {1} other import|2# and {1} other imports} will also be added.

+codewhisperer.popup.no_recommendations=No Amazon Q recommendations available at this point +codewhisperer.popup.pagination_info=Loading additional Amazon Q suggestions... +codewhisperer.popup.recommendation_info=Suggestion {0} of {1} from Amazon Q +codewhisperer.popup.reference.license_info.prefix= Reference code under  +codewhisperer.popup.reference.panel_link=View Log +codewhisperer.statusbar.display_name=Amazon Q +codewhisperer.statusbar.popup.title=Reconnect to Amazon Q? +codewhisperer.statusbar.sub_menu.connect_help.title=Connect / Help +codewhisperer.statusbar.sub_menu.inline.title=Inline Suggestions +codewhisperer.statusbar.sub_menu.other_features.title=Other Features +codewhisperer.statusbar.sub_menu.security_scans.title=Code Reviews +codewhisperer.statusbar.tooltip=Amazon Q status +codewhisperer.toolwindow.entry.prefix=[{0}] ACCEPTED recommendation with the following code provided with reference under +codewhisperer.toolwindow.entry.suffix={1, choice, 0#|1#. Added to {0}} at line {2} +codewhisperer.toolwindow.popup.text=Reference code under the {0} license from repository {1} +codewhisperer.toolwindow.settings=Amazon Q Settings +codewhisperer.toolwindow.settings.prefix=Don't want suggestions that include code with references? Uncheck this option in +codewhisperer.toolwindow.settings.prefix_sso=Your organization controls whether suggestions include code with references. To update these settings, please contact your admin +codewhisperer.trigger.document.unsupported=Amazon Q inline suggestion is not supported for this file +codewhisperer.trigger.error.client_side=Unable to show recommendations, please try again later +codewhisperer.trigger.error.server_side=Unable to get recommendations, please try again later +codewhisperer.trigger.ide.unsupported=Amazon Q inline suggestion is only supported in 2023.3+ IDE backends +codewhisperer.trigger.service=Invoke Amazon Q inline suggestions +common.none=None +configure.toolkit=Configure AWS Connection +configure.toolkit.upsert_credentials.action=Edit AWS Credential file(s)... +configure.toolkit.upsert_credentials.confirm_file_create=Credentials file {0} does not exist. Create it? +configure.toolkit.upsert_credentials.confirm_file_create.okay=Create +configure.toolkit.upsert_credentials.confirm_file_create.title=Create Credential File +configure.validate.no_region_specified=Must specify a region. +connection.pinning.unlink=Unlink connection from {0} +credentials.could_not_open=Could not open credential file {0} for editing +credentials.file.notification=AWS Toolkit profiles will be reloaded on save +credentials.individual_identity.connected=(connected to tools) +credentials.individual_identity.expired=(expired or invalid) +credentials.individual_identity.reconnect=Reconnect +credentials.individual_identity.signout=Sign out +credentials.invalid.description=Failed to validate your AWS credentials +credentials.invalid.invalid_selection=Please select a valid credential profile +credentials.invalid.more_info=More info... +credentials.invalid.not_found=''{0}'' (not found) +credentials.invalid.title=Invalid AWS Connection +credentials.mfa.action=Enter MFA code +credentials.mfa.display=''{0}'' requires MFA to connect +credentials.mfa.display.short=MFA code required +credentials.mfa.message=Enter MFA code for device: {0} +credentials.mfa.title=MFA Required to Connect to AWS Using {0} +credentials.pending.title=Waiting for sign in +credentials.pending.user_cancel.message=Login canceled by user +credentials.profile.assume_role.duplicate_source=profile ''{0}'' contains both properties ''source_profile'' and ''credential_source'' +credentials.profile.assume_role.invalid_credential_source=profile ''{0}'' is not using a valid ''credential_source'' +credentials.profile.assume_role.missing_source=profile ''{0}'' missing either property ''source_profile'' or ''credential_source'' +credentials.profile.circular_profiles=Profile ''{0}'' is invalid due to a circular profile dependency found between {1} +credentials.profile.credential_process.execution_exception_prefix=Failed to execute credential_process ({0}) +credentials.profile.credential_process.parse_exception_prefix=Failed to parse credential_process response +credentials.profile.credential_process.timeout_exception_prefix=Execution of credential_process ({0}) timed out +credentials.profile.failed_load=Failed to load AWS profiles +credentials.profile.missing_property=Profile ''{0}'' is missing required property {1} +credentials.profile.name=Profile:{0} +credentials.profile.not_configured=No active credential provider configured +credentials.profile.refresh_errors=Failed to load {0, choice, 1#1 profile|2#{0,number} profiles}. +credentials.profile.refresh_ok_message={0, choice, 1#1 profile|2#{0,number} profiles} found. +credentials.profile.refresh_ok_title=Reloaded AWS Credential Profiles +credentials.profile.source_profile_not_found=Profile ''{0}'' references source profile ''{1}'' which does not exist +credentials.profile.unsupported=Profile ''{0}'' is not using role-based, session-based, process-based, or basic credentials. +credentials.profile.validation_error=Failed to switch to profile ''{0}'' +credentials.refreshing=Refreshing tokens +credentials.retrieving=Retrieving AWS credentials +credentials.sono.login=Sign in with AWS Builder ID +credentials.sono.login.cancelled=AWS Builder ID login cancelled +credentials.sono.login.failed=AWS Builder ID login Failed +credentials.sono.login.message=Sign in with AWS Builder ID at {0}, code: {1} +credentials.sono.login.pending=Waiting for AWS Builder ID sign in +credentials.sono.login.refreshing=Refreshing tokens from AWS Builder ID +credentials.sono.login.title=AWS Builder ID Login Required +credentials.sso.action=Start AWS IAM Identity Center login +credentials.sso.display=''{0}'' requires you to re-login with AWS IAM Identity Center +credentials.sso.display.short=AWS IAM Identity Center login required +credentials.sso.login.cancelled=AWS IAM Identity Center login cancelled +credentials.sso.login.failed=AWS IAM Identity Center Login Failed +credentials.sso.login.message=Login to AWS at {0}, code: {1} +credentials.sso.login.open_browser=Open Browser +credentials.sso.login.pending=Waiting for AWS IAM Identity Center sign in +credentials.sso.login.refreshing=Refreshing tokens from AWS IAM Identity Center +credentials.sso.login.session=Sign in to SSO session ''{0}'' +credentials.sso.login.title=AWS IAM Identity Center Login Required +credentials.ssoSession.validation_error=Profile ''{0}'' references sso-session ''{1}'' which does not exist +credentials.switch.confirmation.comment=Use {0} with {1} while using {2} with other services. +credentials.switch.confirmation.no=Not now +credentials.switch.confirmation.title=Stay connected to {0} with {1}? +credentials.switch.confirmation.yes=Yes +credentials.switch.notification.title={0} will now always use {1} +credentials.welcome.provide=Provide AWS Credentials +datagrip.auth.secrets_manager=SecretsManager Auth +datagrip.secret_host=Use the url and port from the secret +datagrip.secret_id=Secret Name/ARN: +datagrip.secretsmanager.action=Connect with Secrets Manager... +datagrip.secretsmanager.action.confirm_continue=

{0}


Continue setting up the database?

+datagrip.secretsmanager.action.confirm_continue_title=The selected secret seems to be for a different database +datagrip.secretsmanager.action.title=Select a Secret +datagrip.secretsmanager.validating=Validating selected secret +datagrip.secretsmanager.validation.different_address=Secret {0} specifies a different database address ''{1}'' +datagrip.secretsmanager.validation.different_engine=Secret {0} specifies a different database engine ''{1}'' +datagrip.secretsmanager.validation.exception=Exception thrown while validating secret +datagrip.secretsmanager.validation.failed_to_get=Failed to retrieve secret {0} +datagrip.secretsmanager.validation.no_host=Secret {0} does not have a field named ''host'' +datagrip.secretsmanager.validation.no_password=Secret {0} does not have a field named ''password'' +datagrip.secretsmanager.validation.no_port=Secret {0} does not have a field named ''port'' +datagrip.secretsmanager.validation.no_secret=No SecretsManager secret specified +datagrip.secretsmanager.validation.no_username=Secret {0} does not have a field named ''username'' +datagrip.secretsmanager.validation.unkown_engine=Unknown database engine {0} +datagrip.validation.invalid_credential_specified=Invalid credential profile {0} selected! +datagrip.validation.invalid_region_specified=Invalid region {0} selected! +date.in.n.hours={0,choice, 0#0 hours|1#1 hour|2#{0,number} hours} +date.in.n.minutes={0,choice, 0#0 minutes|1#1 minute|2#{0,number} minutes} +delete_resource.confirmation_text=delete me +delete_resource.delete_failed=Failed to delete {0} ''{1}'' +delete_resource.deleted=Deleted {0} ''{1}'' +delete_resource.message=Are you sure you want to delete {0} ''{1}''?\n\nType "delete me" below to confirm. +delete_resource.title=Delete {0} {1} +developerTool.toolWindow.help=Need help? +developerTool.toolWindow.help.docs=Learn more about connecting to AWS +developerTool.toolWindow.help.github=Connect with us on GitHub +developerTool.toolWindow.title=Developer Tools +developerTool.toolWindow.welcome.connect=Connect to an account to get started: +developerTool.toolWindow.welcome.creds.about=You can connect to and switch between\naccounts at any time in the toolkit. +docker.not.found=Docker not found +dockerfile.building=Building Dockerfile: {0} +dockerfile.label=Dockerfile: +dynamic_resources.add_remove_resources_tooltip={0} resources available +dynamic_resources.begin_resource_creation=Started new {0} creation... +dynamic_resources.create_resource_action=Create {0} +dynamic_resources.create_resource_file_empty=Resource model is empty. Do you want to continue? +dynamic_resources.create_resource_file_empty_title=Empty Resource Model +dynamic_resources.create_resource_file_name=Create new {0} +dynamic_resources.create_resource_instruction=Creating a resource: Enter resource properties, click Create +dynamic_resources.delete_resource=Delete resource... +dynamic_resources.edit_resource_instruction=File is read-only, click Edit to begin editing the resource +dynamic_resources.editor.enableEditingResource_text=Edit +dynamic_resources.editor.submitResourceUpdateRequest_text=Update +dynamic_resources.experiment.description=Enables basic JSON-based create, update, and delete support for cloud resources through the Resources node in the AWS Explorer +dynamic_resources.experiment.title=JSON Resource Modification +dynamic_resources.fetch.fail.content=Failed to retrieve resource model for {0} +dynamic_resources.fetch.fail.title=Open resource model +dynamic_resources.fetch.fetch=Retrieving model +dynamic_resources.fetch.indicator_title=Open resource model for {0} +dynamic_resources.fetch.open=Opening model in IDE +dynamic_resources.loading_manifest=Loading AWS resource manifest +dynamic_resources.openFileForUpdate_text=Update resource... +dynamic_resources.openReadOnlyFile_text=View Resource +dynamic_resources.operation_status_failed=Failed to {1} {0}: {2} +dynamic_resources.operation_status_failed_no_message=Failed to {1} {0} +dynamic_resources.operation_status_notification_title={0} {1} status +dynamic_resources.operation_status_success=Successfully {1}d {0} +dynamic_resources.resource_creation={0} Creation +dynamic_resources.type.explorer.create_resource=Create resource... +dynamic_resources.type.explorer.view_documentation=View documentation +dynamic_resources.unavailable_in_region=Unavailable in {0} +dynamic_resources.update_resource_instruction=Click Update to save your changes +dynamic_resources.update_resource_no_changes_made=No changes made, do you want to continue editing? +dynamic_resources.update_resource_no_changes_made_title=Resource model unchanged +dynamodb.experiment.description=List, open and query Amazon DynamoDB tables +dynamodb.experiment.title=DynamoDB table viewer +dynamodb.viewer.open.failed=Failed to open DynamoDB table +dynamodb.viewer.open.failed.with_error=Failed to open DynamoDB table: {0} +dynamodb.viewer.search.index.global=Global +dynamodb.viewer.search.index.label=Table / Index: +dynamodb.viewer.search.index.local=Local +dynamodb.viewer.search.run.title=Run +dynamodb.viewer.search.title=Scan +ecr.copy_image_uri.action=Copy Tag URI +ecr.copy_uri.action=Copy Repository URI +ecr.create.app_runner_service.action=Create App Runner Service... +ecr.create.repo.action=Create Repository... +ecr.create.repo.title=Create ECR Repository +ecr.create.repo.validation.empty=Enter a name +ecr.delete.repo.action=Delete Repository... +ecr.delete.tag.action=Delete {0, choice, 1#Tag|2#{0,number} Tags}... +ecr.delete.tag.deleting=Deleting Tags +ecr.delete.tag.description=Are you sure you want to delete {0, choice, 1#1 tag|2#{0,number} tags} from repository {1}? +ecr.delete.tag.failed=Failed to delete {0, choice, 1#tag|2#{0,number} tags} from repository {1} +ecr.delete.tag.succeeded=Deleted {0, choice, 1#tag|2#{0,number} tags} from repository {1} +ecr.dockerfile.configuration.add=New configuration from Dockerfile... +ecr.dockerfile.configuration.edit=Edit Dockerfile configuration... +ecr.dockerfile.configuration.invalid=Invalid Dockerfile run configuration selected +ecr.dockerfile.configuration.invalid_server=Dockerfile run configuration has an invalid server connection +ecr.dockerfile.configuration.label=Dockerfile configuration: +ecr.dockerfile.label=Dockerfile: +ecr.image.not_selected=Image must be selected +ecr.pull.confirm=Pull +ecr.pull.progress=Pulling image from ECR: {0}:{1} +ecr.pull.title=Pull from ECR +ecr.push.confirm=Push +ecr.push.credential_fetch_failed=Failed to obtain authorization token from ECR +ecr.push.failed=Push failed: {0} +ecr.push.in_progress=Deployment with name '{0}' is already being pushed. +ecr.push.remoteTag=Remote Tag: +ecr.push.source=Local Image: +ecr.push.title=Push to ECR +ecr.push.type.dockerfile.label=Dockerfile +ecr.push.type.local_image.label=Local Image +ecr.push.unknown_exception=Failed to push to ECR +ecr.repo.label=ECR Repository: +ecr.repo.not_selected=ECR Repository must be selected +ecr.tag.invalid=Tag provided is not valid +ecs.clusters=Clusters +ecs.container_actions_group.label=Containers +ecs.execute_command.experiment.description=Enable executing commands and opening interactive terminal against a running ECS task/service +ecs.execute_command.experiment.title=ECS Execute Command +ecs.execute_command.label=Command: +ecs.execute_command.production_warning.checkbox_label=I confirm {0} is not a production service and wish to continue +ecs.execute_command_call_service=Executing command +ecs.execute_command_disable=Disable Command Execution +ecs.execute_command_disable_failed=Execute Command could not be disabled for {0} +ecs.execute_command_disable_in_progress=Cannot execute action since Execute Command is being disabled for {0} +ecs.execute_command_disable_progress_indicator_message=Disabling Command Execution for {0} +ecs.execute_command_disable_success=Execute Command for {0} has been disabled. +ecs.execute_command_disable_warning=Disabling command execution will change the state of resources in your AWS account, including but not limited to stopping and restarting the service.
Do you wish to continue? +ecs.execute_command_disable_warning_title=Disable Execute Command Warning +ecs.execute_command_enable=Enable Command Execution +ecs.execute_command_enable_failed=Execute Command could not be enabled for {0} +ecs.execute_command_enable_in_progress=Cannot execute action since Execute Command is being enabled for {0} +ecs.execute_command_enable_progress_indicator_message=Enabling Command Execution for {0} +ecs.execute_command_enable_success=Execute Command for {0} has been enabled. +ecs.execute_command_enable_warning=Enabling command execution will change the state of resources in your AWS account, including but not limited to stopping and restarting the service.
Altering the state of resources while the Execute Command is enabled can lead to unpredictable results.
Do you wish to continue? +ecs.execute_command_enable_warning_title=Enable Execute Command Warning +ecs.execute_command_failed=ECS Execute Command Failed +ecs.execute_command_no_command=Command to execute must be entered +ecs.execute_command_no_task_role_found_exception=No roles found +ecs.execute_command_permissions_not_verified=Permissions for ECS Exec could not be verified. Please ensure you have the right role and permissions before proceeding +ecs.execute_command_permissions_required_title=Permissions required for ECS Exec +ecs.execute_command_run=Run Command... +ecs.execute_command_run_command_default_text=Enter command... +ecs.execute_command_run_command_in_shell=Open Interactive Shell... +ecs.execute_command_shell.label=Shell: +ecs.execute_command_shell_comboBox_empty=Shell must be selected +ecs.execute_command_task.label=Task: +ecs.execute_command_task_comboBox_empty=Task must be selected +ecs.execute_command_task_role_invalid_warning=You may not have the Permissions Required for using the execute command
Please set up a Task Role with the required permissions and retry... +ecs.execute_command_task_role_invalid_warning_title=Set up Valid Task Role with Permissions +ecs.no_services_in_cluster=Cluster has no services +ecs.run_config.container.loading.error=Error loading containers: {0} +ecs.service.container_logs.action_label=View Logs +ecs.service.logs.cannot_determine_log_stream=Cannot determine log streams for service {0}. Note: only AWSLOGS LogDriver is supported. +ecs.service.logs.cannot_find_container_log_stream=Cannot determine log stream for container {0}. +ecs.service.logs.empty=No events found in log stream: {0} +ecs.service.logs.no_log_stream=Running task has not sent events to CloudWatch, opening Log Group instead. +ecs.service.logs.no_running_tasks=Service has no running tasks, opening Log Group instead. +ecs.service.logs.title=ECS Service Logs +ecs.service.not_found=Service {0} not found in cluster {1} +ecs.task_definition.json_schema_name=AWS ECS Task Definition +ecs.task_definitions=Task Definitions +environment.variables.dialog.title=Environment Variables +executableCommon.auto_managed=Managed by AWS +executableCommon.auto_resolved=Auto-detected: {0} +executableCommon.cli_not_configured={0} executable not configured +executableCommon.configurable.title=External Tools +executableCommon.downloading=Downloading {0}... +executableCommon.empty_info={0} did not provide any output +executableCommon.failed_install=Failed to install {0} +executableCommon.installing=Installing {0} +executableCommon.latest_not_compatible=Latest version of {0} is not compatible with the toolkit''s supported range: {1} +executableCommon.missing_executable=Validation of {0} failed: {1} +executableCommon.not_installed=Not installed. +executableCommon.unexpected_output={0} provided unexpected output. Ensure that your {0} is up-to-date: "{1}" +executableCommon.updating=Updating {0} +executableCommon.validating=Validating {0} +executableCommon.version_parse_error=Could not parse {0} executable version from "{1}" +executableCommon.version_range_wrong=Bad {0} executable version. Expected version must be within the following ranges: {1} , but was {2} +executableCommon.version_too_high=Upgrade your AWS Toolkit plugin to resolve this issue. +executableCommon.version_too_low=Upgrade your {0} to resolve this issue. +executableCommon.version_too_low2=Upgrade {0} to resolve this issue. Minimum version for this feature is {1} +executableCommon.version_wrong=Bad {0} executable version. Expected {1} ≤ version < {2} but was {3}. +explorer.copy_arn=Copy Arn +explorer.copy_identifier=Copy identifier +explorer.create_new_issue=Create a New Issue on GitHub +explorer.empty_node=empty +explorer.error_loading_resources=Error Loading Resources ({0}) +explorer.error_loading_resources_default_details=check log for details +explorer.error_loading_resources_not_connected=check credentials/region +explorer.label=AWS Explorer +explorer.node.apprunner=App Runner +explorer.node.cloudformation=CloudFormation +explorer.node.cloudwatch=CloudWatch Logs +explorer.node.codemodernizer=CodeModernizer +explorer.node.dynamo=DynamoDB +explorer.node.ecr=ECR +explorer.node.ecs=ECS +explorer.node.lambda=Lambda +explorer.node.other=Resources +explorer.node.other.add_remove=Add or remove resource types... +explorer.node.rds=RDS +explorer.node.redshift=Redshift +explorer.node.s3=S3 +explorer.node.schemas=Schemas +explorer.node.sqs=SQS +explorer.registry.no.schema.resources=Registry has no Schemas +explorer.results_truncated=Results truncated, double click to load more +explorer.stack.no.serverless.resources=Stack has no Lambda Functions +explorer.toolwindow.title=Explorer +explorer.view_documentation=View Documentation +explorer.view_source=View Source on GitHub +feedback.comment.emptyText=optional +feedback.comment.label=Please enter your feedback +feedback.comment.textbox.initial.length=2000 characters remaining +feedback.comment.textbox.title=What do you like about {0}? What can we improve? +feedback.comment.textbox.title.amazonq=How was your experience with the upgrade of your Java application? +feedback.comment.textbox.title.amazonq.feature_dev=How has your experience with Amazon Q been? What can we improve? +feedback.comment.textbox.title.amazonq.test_generation=How has your experience with Amazon Q Test Generation been? What can we improve? +feedback.connect.with.github.title=Join us on GitHub +feedback.customer.alert.info=Please don't add personally identifiable information (PII), confidential or sensitive information in your feedback.
Remove any PII when sharing file paths, error messages, etc. +feedback.description=Submit quick feedback about the AWS Toolkit for JetBrains +feedback.github.link=Have an issue or feature request?
Talk to us on GitHub instead! +feedback.initial.help.text=
Looking for help? View the Getting Started Guide or search our documentation +feedback.limit.label={0} characters remaining +feedback.report.issue.link=Report an issue +feedback.request.feature.link=Request a feature +feedback.share.feedback.title=Share Feedback +feedback.smiley.happyTooltip=Positive +feedback.smiley.question=How was your experience? +feedback.smiley.sadTooltip=Negative +feedback.submit_button=Share +feedback.submit_failed=An exception occurred while sharing your feedback: {0} +feedback.submit_failed_title=Failed to share feedback +feedback.submit_success=Thanks for the feedback! +feedback.submitting=Sharing... +feedback.title=Share Feedback for {0}... +feedback.title.amazonq=Send feedback for Code Transformation by Amazon Q +feedback.title.amazonq.send_feedback=Send feedback for Amazon Q +feedback.validation.comment_too_long=Comment is too long. +feedback.validation.empty_comment=Please provide a comment. +feedback.validation.no_sentiment=Please select how you're feeling. +feedback.view.source.code.link=View Source Code and Contribute +gateway.auth.different.account.already.signed.in=You are signed in with {0}. Do you want to continue? +gateway.auth.different.account.required=You are signed into a different account. \nDo you want to sign in with the account associated with {0}? +gateway.auth.different.account.sign.in=Sign in with a different account +gateway.connection.workflow.copy_scripts=Copy Scripts +gateway.connection.workflow.git_clone=Git Clone +gateway.connection.workflow.install_toolkit=Install AWS Toolkit +gateway.connection.workflow.prime_ssh_agent=Prime SSH Agent +gateway.connection.workflow.start_ide=Start IDE +gateway.connection.workflow.step_failed=\nStep failed exceptionally\n +gateway.connection.workflow.step_skipped=Step skipped +gateway.connection.workflow.step_successful=\nStep completed successfully\n +general.acknowledge=Acknowledge +general.add.another=Add another +general.auth.reauthenticate=Reauthenticate +general.cancel=Cancel +general.canceling=Canceling +general.close_button=Close +general.configure_button=Configure +general.confirm=Confirm +general.confirm_proceed=Please confirm before proceeding +general.create=Create +general.create_button=Create +general.create_in_progress=Creating... +general.default=Default +general.delete=Delete +general.delete_accessible_name=Delete confirmation box +general.details=(details) +general.dismiss=Dismiss +general.execute_button=Execute +general.execution.canceled=canceled +general.execution.cli_error=Command did not exit successfully, exit code: {0}\n +general.execution.failed=failed +general.execution.running=running... +general.execution.success=completed successfully +general.file_not_found=File not found: "{0}" +general.get_started=Get started +general.help=Help +general.in_progress_button=In progress +general.logs=Logs +general.message=Message +general.more=More +general.more_dialog=More... +general.name.label=Name: +general.no_changes=No changes were provided +general.notification.action.hide_forever=Don't show again +general.notification.action.hide_once=Dismiss +general.ok=OK +general.open.in.progress=Opening... +general.open_in_aws_console=Open in AWS Console +general.open_in_aws_console.error=Failed to open link in browser +general.open_in_aws_console.no_permission=Failed to open link in browser because user credentials are not allowed to perform sts:GetFederationToken +general.optional=Optional +general.policy=Policy +general.refresh=Refresh +general.reject=Reject +general.save=Save +general.select_button=Select +general.step.canceled={0} has been canceled +general.step.failed={0} has failed: {1} +general.success=Complete... +general.time=Time +general.time.five_minutes=Five Minutes +general.time.one_minute=One Minute +general.time.ten_minutes=Ten Minutes +general.unknown_error=Unknown error. See IDE logs for more details. +general.update_button=Update +general.update_in_progress=Updating... +gettingstarted.auth.builderid.expired=AWS Builder ID Expired +gettingstarted.auth.config.issue=There was an issue reading your AWS config file(s): {0} +gettingstarted.auth.connected.builderid=Connected with AWS Builder ID +gettingstarted.auth.connected.iam=Connected with IAM +gettingstarted.auth.connected.idc=Connected with IAM Identity Center +gettingstarted.auth.failed=Failed to Authenticate +gettingstarted.auth.iam.invalid=Invalid IAM Credentials +gettingstarted.auth.idc.expired=IAM Identity Center Expired +gettingstarted.auth.idc.sign.out.confirmation=Signing out will remove profiles associated with this connection from the ~/.aws/config file +gettingstarted.auth.idc.sign.out.confirmation.title=Confirm deletion of profile +gettingstarted.builderid.description=Personal profile for builders +gettingstarted.codecatalyst.open.explorer=Open CodeCatalyst menu +gettingstarted.codecatalyst.panel.create.space=Create a CodeCatalyst space +gettingstarted.codecatalyst.panel.setup=Set up CodeCatalyst +gettingstarted.codewhisperer.remote=Amazon Q chat are unavailable in JetBrains Gateway +gettingstarted.connecting.in.browser=Connecting in browser... +gettingstarted.editor.title=Authenticate with AWS Toolkit +gettingstarted.explorer.gotit.codecatalyst.body=Launch Dev Environments and find more features here. +gettingstarted.explorer.gotit.codecatalyst.title=Get started with CodeCatalyst +gettingstarted.explorer.gotit.explorer.body=IAM Credentials you add will be accessible from this dropdown. Add and edit credentials from the "..." menu. +gettingstarted.explorer.gotit.explorer.title=Switch and edit credentials +gettingstarted.explorer.iam.add=Add IAM Credentials +gettingstarted.explorer.iam.add.info=Provide IAM Credentials to work with AWS Services and Resources from the Explorer. +gettingstarted.explorer.iam.switch=Switch to an IAM role in the dropdown above to work with AWS Services and Resources in the Explorer +gettingstarted.explorer.new.setup=Setup Authentication +gettingstarted.explorer.new.setup.info=To get started with the Explorer, setup authentication to AWS +gettingstarted.explorer.open.menu=Open Resource Explorer +gettingstarted.iam.description=Long-term programmatic access +gettingstarted.panel.learn_more=Learn more +gettingstarted.panel.login_button=Use for free, no AWS account required +gettingstarted.setup.auth.failure.body=We are unable to connect or the process was cancelled. Please try again, visit documentation, or file an issue on GitHub to troubleshoot +gettingstarted.setup.auth.failure.title=Authentication Failed +gettingstarted.setup.auth.no_iam=Amazon Q do not support authentication with IAM Credentials +gettingstarted.setup.auth.success.body=You can start using {0} or setup another authentication method below. +gettingstarted.setup.auth.success.iam.body=We've connected your local credentials. Get started with the Resource Explorer or setup another feature below. +gettingstarted.setup.auth.success.iam.title=Connected to IAM Credentials +gettingstarted.setup.auth.success.title=Connected to {0} +gettingstarted.setup.builderid.bullets=Sign up for free\nComplement your existing AWS accounts\nSecure login with optional MFA +gettingstarted.setup.builderid.notice=AWS Builder ID is a new personal profile for builders. Learn more +gettingstarted.setup.codecatalyst.no_iam=CodeCatalyst does not support authentication with IAM Credentials +gettingstarted.setup.codewhisperer.use_builder_id=If you haven't been provided an Amazon Q license, use AWS Builder ID +gettingstarted.setup.codewhisperer.use_identity_center=If you have an Amazon Q license, please use IAM Identity Center +gettingstarted.setup.connect=Connect +gettingstarted.setup.error.not_empty=Must not be empty +gettingstarted.setup.error.not_selected=Selection must be made +gettingstarted.setup.explorer.no_builder_id=Resource Explorer does not support authentication with AWS Builder ID. +gettingstarted.setup.iam.access_key=Access Key ID: +gettingstarted.setup.iam.access_key.invalid=Access key must be alphanumeric and between 16 and 128 characters +gettingstarted.setup.iam.notice=Credentials will be automatically added to your local ~/.aws/config file as a profile.
Edit credential file directly... +gettingstarted.setup.iam.profile=Profile Name: +gettingstarted.setup.iam.profile.comment=Used by AWS Toolkit to list added credentials +gettingstarted.setup.iam.profile.exists=Profile with name ''{0}'' already exists +gettingstarted.setup.iam.profile.invalid_credentials=The provided credentials are not valid +gettingstarted.setup.iam.secret_key=Secret Access Key: +gettingstarted.setup.iam.session.exists=SSO Session with name ''{0}'' already exists +gettingstarted.setup.idc.no_builder_id=User should not perform Identity Center login with AWS Builder ID url +gettingstarted.setup.idc.profile.comment=User-specified name used to label credentials locally +gettingstarted.setup.idc.region=Region: +gettingstarted.setup.idc.role.title=AWS Toolkit: Add IAM Identity Center Roles +gettingstarted.setup.idc.roleLabel=Select roles from IAM Identity Center to use with the AWS Toolkit +gettingstarted.setup.idc.startUrl=Start URL: +gettingstarted.setup.idc.startUrl.comment=URL for your organization, provided by an administrator or help desk +gettingstarted.setup.learnmore=Learn More +gettingstarted.setup.tabs.builderid=AWS Builder ID +gettingstarted.setup.tabs.iam=IAM Credentials +gettingstarted.setup.tabs.idc=IAM Identity Center +gettingstarted.setup.title=AWS Toolkit: Setup Authentication +group.aws.toolkit.dynamoViewer.changeMaxResults.text=Max Results +group.aws.toolkit.dynamoViewer.toolbar.settings.text=Settings +group.aws.toolkit.jetbrains.core.services.cwc.actions.ContextMenuActions.text=Amazon Q +group.aws.toolkit.s3viewer.contextMenu.copyGroup.text=Copy +iam.create.role.in_progress=Creating... +iam.create.role.managed_policies=Managed Policies: +iam.create.role.missing.role.name=Role name is required. +iam.create.role.name.label=Name: +iam.create.role.policy.editor.name=Permissions +iam.create.role.title=Create IAM Role +iam.create.role.trust.editor.name=Trust Relationships: +iam.name=IAM +iam_identity_center.name=IAM Identity Center +iam_identity_center.service_name=IAM Identity Center ({0}) +iam_identity_center.sign_out=Sign out of IAM Identity Center +lambda.build.java.unsupported_build_system=Module ''{0}'' is not managed by Maven or Gradle +lambda.build.module_with_no_content_root=Module ''{0}'' lacks a content root +lambda.build.typescript.compiler.annotation_results=Annotation Results: +lambda.build.typescript.compiler.creating_config=Creating TypeScript config from scratch +lambda.build.typescript.compiler.emitted_files=Emitted Files: +lambda.build.typescript.compiler.ide_error=TypeScript compiler failed to start +lambda.build.typescript.compiler.processed_files=Processed Files: +lambda.build.typescript.compiler.running=Running TypeScript compiler against temporary config file ''{0}'' +lambda.build.typescript.compiler.step=Compile TypeScript +lambda.build.typescript.compiler.using_base=Using TypeScript config ''{0}'' as a base +lambda.build.typescript.compiler.using_base_error=Unable to read existing TypeScript config ''{0}'' +lambda.build.unable_to_locate_handler_root=Unable to locate root directory of the handler +lambda.build.unable_to_locate_project_root=Unable to locate project directory for Module ''{0}'' +lambda.create.description.label=Description: +lambda.create.enable.xray.label=Enable AWS X-Ray +lambda.create.env_vars.label=Environment Variables: +lambda.create.function_name.label=Name: +lambda.create.iam.role.label=IAM Role: +lambda.create.memory.label=Memory (MB): +lambda.create.source_bucket.label=Source Bucket: +lambda.create.step.build=SAM Build +lambda.create.step.create_lambda=Creating new Lambda +lambda.create.step.package=SAM Package +lambda.create.step.update_lambda=Updating Lambda +lambda.create.timeout.label=Timeout (seconds): +lambda.create_new=Create new AWS Lambda... +lambda.credentials.tooltip=The existing AWS account connection to use for this configuration. +lambda.debug.attach.error=Debugger attach error +lambda.debug.attach.fail=Unable to attach to debugger process. +lambda.debug.docker.not_connected=Running/Debugging Lambdas locally requires Docker. Please make sure it is installed and running +lambda.debug.process.start.timeout=Timeout waiting for Debugger Worker process to start +lambda.debug.step.start_sam=Running SAM local invoke +lambda.debug.waiting=Waiting for debugger... +lambda.execute.function_error=Function error: {0} +lambda.execute.invoke=Invoking Lambda function: {0} +lambda.execute.logs=Logs: \n{0} +lambda.execute.output=Output: \n{0} +lambda.execute.service_error=Error invoking Lambda: {0} +lambda.function.architecture.label=Architecture: +lambda.function.architecture.tooltip=The AWS Lambda architecture to use when running this function. +lambda.function.code_updated.notification=Code updated for function ''{0}''. +lambda.function.configuration_updated.notification=Configuration updated for function ''{0}''. +lambda.function.created.notification=Function ''{0}'' created. +lambda.function.delete.action=Delete Function... +lambda.function.enableXRay.tooltip=Enables AWS X-Ray to detect, analyze, and optimize performance issues when this function runs. +lambda.function.env_vars.tooltip=The environment variables available to this function, specified as key/value pairs. +lambda.function.handler.tooltip=The name of the method within your code that AWS Lambda calls to execute your function. It must be a fully qualified name depending on the runtime. +lambda.function.iamRole.tooltip=The function's execution role. +lambda.function.label=Function: +lambda.function.memory.tooltip=The memory available to AWS Lambda when running this function, if needed. +lambda.function.name.tooltip=The function's name. It can only contain alphanumeric (A-Z, a-z, 0-9), underscore (_) and hyphen (-) characters and must not exceed 64 characters in length. +lambda.function.runtime.tooltip=The AWS Lambda runtime to use when running this function. +lambda.function.sourceBucket.tooltip=The Amazon S3 bucket in the same AWS Region to deploy this AWS Lambda function. +lambda.function.timeout.tooltip=The number of seconds until AWS Lambda stops running this function, if necessary. +lambda.function.updateCode.action=Update Function Code +lambda.function.updateConfiguration.action=Update Function Configuration +lambda.handler.label=Handler: +lambda.image.missing_debugger=Runtime ''{0}'' is unsupported for debugging image based Lambdas +lambda.image.sam_version_too_low=Sam CLI version {0} is too old to run image-based configurations, {1} or higher is required +lambda.input.label=Input +lambda.logs.does_not_exist=Lambda ''{0}'' has not sent events to CloudWatch +lambda.region.tooltip=The AWS Region to use for the connected AWS account. +lambda.run.configuration.handler_root_not_found=Failed to locate the root of the handler +lambda.run_configuration.credential_error=Error retrieving credentials: {0}. +lambda.run_configuration.credential_not_found_error=The credential provider with ID ''{0}'' could not be found. +lambda.run_configuration.debug_host=Debug Host: +lambda.run_configuration.description=Invoke AWS Lambda Function +lambda.run_configuration.fromTemplate.tooltip=The location and file name of the AWS SAM template to use for this configuration, and the resource in that template to associate with this configuration. +lambda.run_configuration.handler.validation.in_progress=Lambda handler validation is in progress +lambda.run_configuration.handler_not_found=Cannot find handler ''{0}'' in project. +lambda.run_configuration.input.file.label=File: +lambda.run_configuration.input.file.tooltip=The location and file name of the data to pass into the function. The data must follow the syntax for AWS Lambda event data. +lambda.run_configuration.input.samples.confirm=Selecting this template will replace the input text. Continue? +lambda.run_configuration.input.samples.confirm.title=Confirm Template Selection +lambda.run_configuration.input.samples.label=-- Event Templates -- +lambda.run_configuration.input.text.label=Text: +lambda.run_configuration.input.text.tooltip=The data to pass into the function. The data must follow the syntax for AWS Lambda event data. +lambda.run_configuration.input_file_error=Failed to load input file: {0} +lambda.run_configuration.local=Local +lambda.run_configuration.no_architecture_specified=Must specify an architecture. +lambda.run_configuration.no_credentials_specified=Select AWS credentials in 'AWS Connection' +lambda.run_configuration.no_function_specified=Must specify function name or ARN. +lambda.run_configuration.no_handler_specified=Must specify a handler. +lambda.run_configuration.no_input_specified=Must specify an input. +lambda.run_configuration.no_runtime_specified=Must specify a supported runtime. +lambda.run_configuration.remote=Remote +lambda.run_configuration.remote.function.tooltip=The name of the AWS Lambda function to use. +lambda.run_configuration.sam=SAM CLI +lambda.run_configuration.sam.additional_build_args=Build Args: +lambda.run_configuration.sam.additional_local_args=Local Invoke Args: +lambda.run_configuration.sam.docker_network=Docker Network: +lambda.run_configuration.sam.invalid_executable=Invalid SAM CLI executable configured: {0} +lambda.run_configuration.sam.no_function_specified=Function logical name must be specified. +lambda.run_configuration.sam.no_such_function=Function {0} doesn''t exist in template {1}. +lambda.run_configuration.sam.no_template_specified=Template must be specified. +lambda.run_configuration.sam.skip_image_pull=Skip checking for newer container images +lambda.run_configuration.sam.template_file_not_found=Template file not found. +lambda.run_configuration.sam.validating=Validating SAM executable... +lambda.run_configuration.sam.validation.in_progress=SAM executable validation is in progress +lambda.run_configuration.source.from_handler=From &handler +lambda.run_configuration.source.from_template=From &template +lambda.run_configuration.unsupported_architecture=The architecture ''{0}'' is unsupported. +lambda.run_configuration.unsupported_runtime=The runtime ''{0}'' is unsupported. +lambda.runtime.label=Runtime: +lambda.sam.additionalBuildArgs.tooltip=Additional args to add to the 'sam build' command +lambda.sam.additionalLocalArgs.tooltip=Additional args to add to the 'sam local invoke' command +lambda.sam.buildInContainer.tooltip=Builds the serverless application's functions inside an AWS Lambda-like Docker container locally before deployment. +lambda.sam.debugHost.tooltip=The host to connect the debugger to +lambda.sam.dockerNetwork.tooltip=The name or ID of an existing Docker network that the AWS Lambda Docker containers should connect to, along with the default bridge network. +lambda.sam.skipCheckingNewerContainerImage.tooltip=Skips pulling down the latest Docker image for the specified AWS Lambda runtime. +lambda.service_name=AWS Lambda +lambda.slider_validation=The specified value must be an integer and between {0} and {1} +lambda.upload.build_settings=Build Settings +lambda.upload.code_location_settings=Code Location Settings +lambda.upload.configuration_settings=Configuration Settings +lambda.upload.create.title=Create Function +lambda.upload.deployment_settings=Deployment Settings +lambda.upload.updateCode.title=Update Code for {0} +lambda.upload.updateConfiguration.title=Update Configuration for {0} +lambda.upload.update_settings_button.title=Update +lambda.upload_validation.dockerfile_not_found=Dockerfile not found +lambda.upload_validation.function_name=Function Name must be specified +lambda.upload_validation.function_name_invalid=Function names can only contain alphanumerics, hyphen (-) and underscore (_) +lambda.upload_validation.function_name_too_long=Function names must not exceed {0} characters in length +lambda.upload_validation.handler=Handler must be specified +lambda.upload_validation.handler_not_found=Must be able to locate the handler in the project in order to deploy to Lambda +lambda.upload_validation.iam_role=IAM role must be specified +lambda.upload_validation.iam_role.loading=Still loading IAM role... +lambda.upload_validation.module_not_found=Failed to locate module for {0} +lambda.upload_validation.repo=Repository must be specified in order to deploy to Lambda +lambda.upload_validation.runtime=Runtime must be specified +lambda.upload_validation.source_bucket=Bucket must be specified in order to deploy to Lambda +lambda.upload_validation.source_bucket.loading=Still loading S3 bucket... +lambda.upload_validation.unsupported_runtime=Deploying using the runtime {0} is not supported +lambda.workflow.create_new.name=Create new Lambda +lambda.workflow.create_new.wait_for_stable=Waiting for function to be created +lambda.workflow.update_code.name=Update Lambda code +lambda.workflow.update_code.wait_for_stable=Waiting for function to stabilize +lambda.workflow.update_code.wait_for_updatable=Waiting for function to transition to an updatable state +loading_resource.failed=Failed loading resources +loading_resource.loading=Loading... +loading_resource.still_loading=Resources are still loading +notification.changelog=Changelog +notification.expand=Expand +notification.learn_more=Learn more +notification.update=Update +q.connection.disconnected=You don't have access to Amazon Q. Please authenticate to get started. +q.connection.expired=Your Amazon Q session has timed out. Re-authenticate to continue. +q.connection.invalid=You don't have access to Amazon Q. Please authenticate to get started. +q.connection.need_scopes=You haven't enabled Amazon Q in Jetbrains. +q.enable.text=Enable Amazon Q to begin +q.learn.more=Learn More +q.migration.notification.title=Menu moved to status bar +q.node.title=Amazon Q +q.onboarding.button.text=Ask a question +q.onboarding.codewhisperer.description=Amazon Q inline suggestions are also enabled. +q.onboarding.description=Amazon Q is your generative-AI powered assistant. +q.onboarding.title=Meet Amazon Q +q.reauthenticate=Re-authenticate to connect +q.session_configuration=Extend your IDE sessions +q.session_configuration.description=Your maximum session length for Amazon Q can be extended to 90 days by your administrator. For more information, refer to How to extend the session duration for Amazon Q in the IDE in the IAM Identity Center User Guide. +q.sign.in=Get Started +q.ui.prompt.transform=/transform +q.unavailable=\ Amazon Q Chat is not supported in IDE versions <= v2024.2.1 +q.unavailable.node=Please update to the latest IDE version +rds.aurora=Aurora +rds.iam_config=Connect with IAM... +rds.iam_connection_display_name=AWS IAM +rds.iam_help=The "RDS Host" and "RDS Port" are used to sign the request to AWS. They must match the values in the service. To use a proxy, edit the "Host" and "Port" fields. +rds.mysql=MySQL +rds.port=RDS Port: +rds.postgres=PostgreSQL +rds.url=RDS Host: +rds.validation.aurora_mysql_ssl_required=Aurora MySQL requires SSL to be enabled to connect +rds.validation.iam_sso_connection.error_info=No token found, please login and try again +rds.validation.no_iam_auth=Database {0} does not have IAM authentication enabled +rds.validation.no_instance_host=No RDS database host specified +rds.validation.no_instance_port=No RDS database port specified +rds.validation.setup_guide=See the RDS guide for IAM authentication +rds.validation.username=A non-empty username is required +redshift.auth.aws=IAM Auth +redshift.cluster_id=Cluster ID: +redshift.connect_aws_credentials=Connect with IAM... +redshift.validation.cluster_does_not_exist=Cluster {0} does not exist in region {1}! +redshift.validation.invalid_credential_specified=Invalid credential profile {0} selected! +redshift.validation.invalid_region_specified=Invalid region {0} selected! +redshift.validation.no_cluster_id=No cluster ID specified! +redshift.validation.username=A non-empty username is required +resource.delete.warning_text=Deletion of the {0} may take more than a minute to be reflected +run_configuration_extension.currently_selected=Use the currently selected credential profile/region +run_configuration_extension.feature.go.description=Allow injecting AWS Credentials via Environment Variables into GoLang based Run Configurations +run_configuration_extension.feature.go.title=AWS connected GoLang Run Configurations +run_configuration_extension.feature.java.description=Allow injecting AWS Credentials via Environment Variables into some Java-based Run Configurations +run_configuration_extension.feature.java.title=AWS connected Java Run Configurations +run_configuration_extension.feature.python.description=Allow injecting AWS Credentials via Environment Variables into some Python-based Run Configurations +run_configuration_extension.feature.python.title=AWS connected Python Run Configurations +run_configuration_extension.inject_aws_connection_exception=Exception occurred attempting to inject region/credentials +run_configuration_extension.manual=Other credential profile/region +s3.bucket.label=Bucket Name/URI +s3.bucket.load.fail.title=Access denied to bucket +s3.bucket.name.label=Bucket Name: +s3.copy.bucket.action=Copy Name +s3.copy.path=Copy Path +s3.copy.uri=Copy S3 URI +s3.copy.url=Copy URL +s3.copy.url.failed=Failed to copy URL +s3.create.bucket.create=Create +s3.create.bucket.missing.bucket.name=Bucket name is required. +s3.create.bucket.title=Create S3 Bucket +s3.delete.bucket.action=Delete S3 Bucket +s3.delete.object.action=Delete... +s3.delete.object.cancel=Cancel +s3.delete.object.description=Are you sure you want to delete {0, choice, 1#this file|2#{0,number} files}? +s3.delete.object.failed=Delete object failed +s3.download.object.action=Download... +s3.download.object.browse.description.multiple=Select folder to save files to +s3.download.object.browse.description.single=Select file or folder to save file to +s3.download.object.browse.title=Select Download Location... +s3.download.object.conflict.description=File ''{0}'' already exists in directory ''{1}'' +s3.download.object.conflict.overwrite=Overwrite +s3.download.object.conflict.overwrite_rest=Overwrite All Conflicts +s3.download.object.conflict.skip=Skip +s3.download.object.conflict.skip_rest=Skip All Conflicts +s3.download.object.failed=Failed to download object {0} +s3.download.object.progress=Downloading ''{0}'' +s3.error_loading=Failed to load children +s3.last_modified=Last Modified +s3.load_more=load more... +s3.load_more_failed=Failed to load more! load more... +s3.name=Name +s3.new.folder=New Folder... +s3.new.folder.name=Folder name: +s3.object.load.fail.title=Unable to load objects - Access denied +s3.open.file_too_big=The editor cannot open files larger than {0} +s3.open.viewer.bucket.failed=Failed to open bucket +s3.open.viewer.bucket_does_not_exist=Bucket {0} does not exist. +s3.open.viewer.failed=Failed to open file in editor. +s3.open.viewer.failed.unsupported=Failed to open file in editor. Unsupported file type. +s3.open.viewer.prefix.message=Specify S3 Key prefix: +s3.open.viewer.prefix.title=Enter Key Prefix +s3.prefix.filter=Filter by prefix +s3.prefix.label=Prefix: {0} +s3.rename.object.action=Rename... +s3.rename.object.failed=Failed to rename object +s3.rename.object.title=Rename: ''{0}'' to +s3.size=Size +s3.upload.directory.impossible=Cannot upload ''{0}'' because it is a directory +s3.upload.object.action=Upload... +s3.upload.object.failed=Failed to upload object {0} +s3.upload.object.progress=Uploading ''{0}'' +s3.version.history.view=Show versions +sam.build.failed=SAM build command failed +sam.build.running=Running SAM build +sam.build.succeeded=SAM build command succeeded +sam.build.title=SAM build +sam.cli.version.upgrade.instructions=Learn more about upgrading SAM CLI +sam.cli.version.upgrade.reason=Learn more about SAM sync improvements... +sam.cli.version.upgrade.required=Your SAM CLI version is {0} and does not include performance improvements for Sync Serverless Applications. To continue, please upgrade to SAM CLI version {1} or higher. +sam.cli.version.upgrade.required.windows=If you are on SAM CLI version 1.85, 1.86 or 1.86.1, it has known issues. Please consider re-installing / upgrading the latest version. +sam.cli.version.warning=SAM CLI version +sam.debug.attach=Attach Debugger +sam.debug.attach.parent=Attempt Debugger Attachment +sam.debug.dotnet_find_pid=Find DotNet process PID +sam.debug.find_container=Find Container +sam.executable.minimum_too_low_architecture=The architecture {0} requires a minimum SAM CLI version of {1} +sam.executable.minimum_too_low_runtime=The runtime {0} requires a minimum SAM CLI version of {1} +sam.init.description=AWS Serverless Application Model (AWS SAM) prescribes rules for expressing Serverless applications on AWS. +sam.init.error.no.architecture.selected=No architecture selected +sam.init.error.no.project.basepath=Unable to determine project basepath +sam.init.error.no.runtime.selected=No runtime selected +sam.init.error.no.solution.basepath=Unable to determine solution basepath +sam.init.error.no.template.selected=No template selected +sam.init.error.no.virtual.file=Unable to resolve location +sam.init.error.solution.create.fail=Unable to create solution +sam.init.error.subfolder_not_one=The SAM init root folder ''{0}'' should have only one sub folder. +sam.init.execution_error=Could not execute `sam init`! +sam.init.generating.schema=Generating Schema +sam.init.generating.template=Generating SAM template +sam.init.go.sdk=Go SDK: +sam.init.group.name=Other +sam.init.name=AWS Serverless Application +sam.init.node_interpreter.label=Node interpreter: +sam.init.packaging=Package Type: +sam.init.packaging.image=Image +sam.init.packaging.image.description=Artifact is an image uploaded to a ECR image repository +sam.init.packaging.zip=Zip +sam.init.packaging.zip.description=Artifact is a zip file uploaded to S3 +sam.init.sam_template.tooltip=The name of the AWS Serverless Application Model (AWS SAM) template to use. +sam.init.schema.aws_credentials_select=Select AWS Credentials +sam.init.schema.aws_credentials_select_region=Select AWS Region +sam.init.schema.label=Event Schema: +sam.init.schema.pleaseSelect=Choose the EventBridge serverless event schema +sam.init.schema.registry.name=Registry: {0} +sam.init.sdk.error=Check SDK settings +sam.init.sdk.label=SDK: +sam.init.sdk.runtime.not.selected=No runtime selected +sam.init.select_sam_template=SAM Template: +sam.init.template.dynamodb_cookiecutter.description=Sample SAM Template to interact with DynamoDB Events +sam.init.template.dynamodb_cookiecutter.name=AWS SAM DynamoDB Event Example +sam.init.template.event_bridge_hello_world.description=A Hello World app for Amazon EventBridge that invokes a Lambda for every EC2 instance state change in your account +sam.init.template.event_bridge_hello_world.name=AWS SAM EventBridge Hello World +sam.init.template.event_bridge_hello_world_gradle.name=AWS SAM EventBridge Hello World (Gradle) +sam.init.template.event_bridge_hello_world_maven.name=AWS SAM EventBridge Hello World (Maven) +sam.init.template.event_bridge_starter_app.description=A Starter app for Amazon EventBridge that invokes a Lambda based on a dynamic event trigger for an EventBridge Schema of your choice +sam.init.template.event_bridge_starter_app.name=AWS SAM EventBridge App from Scratch (for an Event schema) +sam.init.template.event_bridge_starter_app_gradle.name=AWS SAM EventBridge App from Scratch (for an Event schema) (Gradle) +sam.init.template.event_bridge_starter_app_maven.name=AWS SAM EventBridge App from Scratch (for an Event schema) (Maven) +sam.init.template.hello_world.description=A basic SAM app +sam.init.template.hello_world.name=AWS SAM Hello World +sam.init.template.hello_world_gradle.name=AWS SAM Hello World (Gradle) +sam.init.template.hello_world_maven.name=AWS SAM Hello World (Maven) +sam.init.template.hello_world_typescript.description=A sample app using TypeScript +sam.init.template.hello_world_typescript.name=AWS SAM TypeScript Hello World +schemas.schema.could_not_open=Could not fetch and display schema {0} contents +schemas.schema.download_code_bindings.action=Download Code Bindings +schemas.schema.download_code_bindings.download=Download +schemas.schema.download_code_bindings.failed_to_download=Unable to download schema code +schemas.schema.download_code_bindings.failed_to_extract=Unable to place schema code in workspace +schemas.schema.download_code_bindings.failed_to_extract_collision=Unable to place schema code in workspace because there is already a file {0} in the folder hierarchy +schemas.schema.download_code_bindings.failed_to_generate=Unable to generate schema code +schemas.schema.download_code_bindings.failed_to_poll=Unable to poll for generated schema code +schemas.schema.download_code_bindings.heading=Download code bindings for schema {0} in registry {1} +schemas.schema.download_code_bindings.language.label=Language: +schemas.schema.download_code_bindings.language.tooltip=The programming language of the Schema to download generated code bindings for +schemas.schema.download_code_bindings.latest= +schemas.schema.download_code_bindings.location.label=File Location: +schemas.schema.download_code_bindings.location.tooltip=Location for the generated Event schema code binding files to be placed into +schemas.schema.download_code_bindings.notification.downloading={0}: Downloading code... +schemas.schema.download_code_bindings.notification.extracting={0}: Extracting/copying code... +schemas.schema.download_code_bindings.notification.finished=Downloaded code for schema {0}! +schemas.schema.download_code_bindings.notification.generating={0}: Generating code (this may take a few seconds the first time)... +schemas.schema.download_code_bindings.notification.start=Downloading code for schema {0}... +schemas.schema.download_code_bindings.title=Download Code Bindings +schemas.schema.download_code_bindings.validation.fileLocation_invalid=Invalid code folder location +schemas.schema.download_code_bindings.validation.fileLocation_required=Schema code folder location must be specified +schemas.schema.download_code_bindings.validation.language_required=Schema code language must be specified +schemas.schema.download_code_bindings.validation.version_required=Schema version must be specified +schemas.schema.download_code_bindings.version.label=Version: +schemas.schema.download_code_bindings.version.tooltip=The version of the Schema to download generated code bindings for +schemas.schema.language.go1=Go 1+ +schemas.schema.language.java8=Java 8+ +schemas.schema.language.python3_6=Python 3.6+ +schemas.schema.language.typescript=Typescript 3+ +schemas.schema.view.action=View Schema +schemas.search.download.label=Download code for selected schema +schemas.search.error=Unable to search registry {0} +schemas.search.error.registry={0} ({1}) +schemas.search.header.text.allRegistries=Search across all registries +schemas.search.header.text.singleRegistry=Search "{0}" registry +schemas.search.no_results=No schemas found +schemas.search.searching=Searching for schemas... +schemas.search.title=EventBridge Schemas Search +schemas.search.version.prefix=Search matched version: {0} +schemas.service_name=Amazon EventBridge Schemas +serverless.application.deploy=Deploy Serverless Application +serverless.application.deploy.abort=Process aborted +serverless.application.deploy.action.description=Deploy Serverless Application +serverless.application.deploy.action.name=Deploy +serverless.application.deploy.button.bucket.create=Create +serverless.application.deploy.change_set=Change set ARN: +serverless.application.deploy.change_set.title=Deploy The Change Set? +serverless.application.deploy.change_set_not_found=Failed to locate change set ARN +serverless.application.deploy.error.bad_parse=Error parsing SAM template {0}\n\n {1} +serverless.application.deploy.error.no_resources=Cannot find any resources in SAM template {0} +serverless.application.deploy.error.unsupported_runtime_group=The runtime {0} is not supported for deploying the SAM template {1} +serverless.application.deploy.execute_change_set=Continue Deployment +serverless.application.deploy.execution_failed=SAM did not complete successfully +serverless.application.deploy.label.bucket=S3 Bucket: +serverless.application.deploy.label.repo=ECR Repository: +serverless.application.deploy.label.stack.new=Create Stack: +serverless.application.deploy.label.stack.select=Update Stack: +serverless.application.deploy.review_required=Require confirmation before deploying +serverless.application.deploy.step_name.build=Build +serverless.application.deploy.step_name.create_change_set=Create Change Set +serverless.application.deploy.step_name.package=Package +serverless.application.deploy.template.parameters=Template Parameters +serverless.application.deploy.title=Deploy Serverless Application +serverless.application.deploy.toast.template_file_failure=Could not detect template file +serverless.application.deploy.tooltip.createStack=The name of the AWS CloudFormation stack that is created when deploying this serverless application. +serverless.application.deploy.tooltip.deploymentConfirmation=Instructs AWS CloudFormation to wait for you to finish creating or updating the corresponding stack by executing the stack's current change set in AWS CloudFormation. +serverless.application.deploy.tooltip.ecrRepo=An Amazon Elastic Container Registry Repository in the same AWS Region to deploy the Lambda images to. +serverless.application.deploy.tooltip.s3Bucket=An Amazon S3 bucket in the same AWS Region to deploy this serverless application. +serverless.application.deploy.tooltip.template.parameters=The parameters that the AWS Toolkit detected in the project's AWS SAM template file for this serverless application. +serverless.application.deploy.tooltip.updateStack=The name of the existing AWS CloudFormation stack that is used when deploying this serverless application. +serverless.application.deploy.use_container=Build function inside a container +serverless.application.deploy.validation.ecr.repo.empty=Select an ECR Repository +serverless.application.deploy.validation.ecr.repo.loading=Still loading the list of ECR Repos... +serverless.application.deploy.validation.new.stack.name.duplicate=New stack name must be different than the existing stack name(s) +serverless.application.deploy.validation.new.stack.name.invalid=A stack name can contain only alphanumeric characters (case-sensitive) and hyphens. It must start with an alphabetic character. +serverless.application.deploy.validation.new.stack.name.missing=Enter the name of your new Stack +serverless.application.deploy.validation.new.stack.name.too.long=Stack names must not exceed {0} characters in length +serverless.application.deploy.validation.s3.bucket.empty=Select an S3 Bucket +serverless.application.deploy.validation.s3.bucket.loading=Still loading the list of S3 buckets... +serverless.application.deploy.validation.stack.loading=Still loading CloudFormation stacks... +serverless.application.deploy.validation.stack.missing=Specify a Stack to deploy to +serverless.application.deploy.validation.template.values.badRegex=AllowedPattern for {0} is not valid: {1} +serverless.application.deploy.validation.template.values.failsRegex=Template value for {0} does not match AllowedPattern defined in template: {1} +serverless.application.deploy.validation.template.values.notANumber=Template value for {0} is not a number: {1} +serverless.application.deploy.validation.template.values.tooBig=Template value for {0} is larger than MaxValue defined in template: {1} +serverless.application.deploy.validation.template.values.tooLong=Template value for {0} exceeds MaxLength defined in template: {1} +serverless.application.deploy.validation.template.values.tooShort=Template value for {0} does not meet MinLength defined in template: {1} +serverless.application.deploy.validation.template.values.tooSmall=Template value for {0} is smaller than MinValue defined in template: {1} +serverless.application.deploy_in_progress.title=Deploying Application {0} +serverless.application.sync=Sync Serverless Application (formerly Deploy) +serverless.application.sync.action.description=Sync Serverless Application with cloud +serverless.application.sync.action.name=Sync +serverless.application.sync.code=Sync Serverless Application (code only) +serverless.application.sync.confirm.dev.stack.title=Confirm development stack +serverless.application.sync.dev.mode.warning.text=The SAM CLI will use the AWS Lambda, Amazon API Gateway, and AWS StepFunctions APIs to upload your code without
performing a CloudFormation deployment. This will cause drift in your CloudFormation stack.

The sync command should only be used against a development stack.

Confirm that you are synchronizing a development stack.

+serverless.application.sync.error.bad_parse=Error parsing SAM template {0}\n\n {1} +serverless.application.sync.error.no_resources=Cannot find any resources in SAM template {0} +serverless.application.sync.error.unsupported_runtime_group=The runtime {0} is not supported for syncing the SAM template {1} +serverless.application.sync.execution_failed=SAM sync did not complete successfully +serverless.application.sync.fetch.stacks.progress.bar=Fetching stacks +serverless.application.sync.label.bucket=S3 Bucket: +serverless.application.sync.label.repo=ECR Repository: +serverless.application.sync.label.stack.new=Create Stack: +serverless.application.sync.label.stack.select=Update Stack: +serverless.application.sync.template.parameters=Template Parameters +serverless.application.sync.toast.template_file_failure=Could not detect template file +serverless.application.sync.tooltip.createStack=The name of the AWS CloudFormation stack that is created when syncing this Serverless application. +serverless.application.sync.tooltip.ecrRepo=An Amazon Elastic Container Registry Repository in the same AWS Region to deploy the Lambda images to. +serverless.application.sync.tooltip.s3Bucket=An Amazon S3 bucket in the same AWS Region to sync this serverless application. +serverless.application.sync.tooltip.template.parameters=The parameters that the AWS Toolkit detected in the project's AWS SAM template file for this Serverless application. +serverless.application.sync.tooltip.updateStack=The name of the existing AWS CloudFormation stack that is used when syncing this Serverless application. +serverless.application.sync.use_container=Build function inside a container +serverless.application.sync.validation.ecr.repo.empty=Select an ECR Repository +serverless.application.sync.validation.ecr.repo.loading=Still loading the list of ECR Repos... +serverless.application.sync.validation.new.stack.name.duplicate=New stack name must be different than the existing stack name(s) +serverless.application.sync.validation.new.stack.name.invalid=A stack name can contain only alphanumeric characters (case-sensitive) and hyphens. It must start with an alphabetic character. +serverless.application.sync.validation.new.stack.name.missing=Enter the name of your new Stack +serverless.application.sync.validation.new.stack.name.too.long=Stack names must not exceed {0} characters in length +serverless.application.sync.validation.s3.bucket.empty=Select an S3 Bucket +serverless.application.sync.validation.s3.bucket.loading=Still loading the list of S3 buckets... +serverless.application.sync.validation.stack.loading=Still loading CloudFormation stacks... +serverless.application.sync.validation.stack.missing=Specify a Stack to sync to +session_manager_plugin_installation_warning=ECS Exec makes use of AWS Systems Manager (SSM) Session Manager to establish a connection with the running container.
Please Install the Session Manager Plugin before proceeding! +session_manager_plugin_installation_warning_title=Install Session Manager Plugin +settings.credentials=IAM Credentials (Locally Configured) +settings.credentials.get_started=Connect to AWS to get started +settings.credentials.iam=IAM Credentials +settings.credentials.iam.add=Add IAM credentials to view resources +settings.credentials.iam.none_selected=No IAM credentials linked +settings.credentials.iam.select=Select from list above to view resources +settings.credentials.iam_and_regions=IAM Credentials & Regions +settings.credentials.individual_identity_sub_menu=Individual Identity +settings.credentials.none_selected=No credentials selected +settings.credentials.profile_sub_menu=All Local Credentials +settings.credentials.prompt_for_default_region_switch=Change region to default region: {0}? +settings.credentials.prompt_for_default_region_switch.always=Always +settings.credentials.prompt_for_default_region_switch.always.description=Always select default region when a credential is selected +settings.credentials.prompt_for_default_region_switch.ask.description=Ask before changing regions when a credential is selected +settings.credentials.prompt_for_default_region_switch.never=Never +settings.credentials.prompt_for_default_region_switch.never.description=Never change region when a credential is selected +settings.credentials.prompt_for_default_region_switch.setting_label=Default region handling: +settings.credentials.prompt_for_default_region_switch.yes=Yes +settings.credentials.recent=Recent IAM Credentials (Locally Configured) +settings.never_show_again=Never Show Again +settings.none_selected=No region or credentials selected +settings.partitions=Other Partitions +settings.profiles.always=Always show when profiles are loaded +settings.profiles.label=AWS profiles notification: +settings.profiles.never=Never show +settings.profiles.on_failure=Show when one or more profiles fail to load +settings.refresh.description=Refresh AWS Connection +settings.regions.none_selected=No region selected +settings.regions.recent=Recent Regions +settings.regions.region_sub_menu=All Regions +settings.retry=Retry +settings.states.initializing=Toolkit initializing... +settings.states.invalid=Unable to connect to AWS:\n{0} +settings.states.invalid.short=Unable to connect +settings.states.validating=Validating connection to AWS... +settings.states.validating.short=Validating connection +settings.statusbar.widget.connections.n={0} Connections +settings.statusbar.widget.expired.1={0} Expired +settings.statusbar.widget.expired.n={0} Connections Expired +settings.statusbar.widget.format=AWS: {0} +settings.title=AWS Connection Settings +sqs.configure.lambda=Configure Lambda Trigger... +sqs.configure.lambda.configure.tooltip=It may take a few seconds to configure. +sqs.configure.lambda.error=Failed to add trigger for function {0}. Try again later. +sqs.configure.lambda.function=Lambda Function: +sqs.configure.lambda.in_progress=Configuring... +sqs.configure.lambda.select=Select Lambda Function: +sqs.configure.lambda.success=Trigger successfully added to function {0} +sqs.configure.lambda.tooltip=

Configure the queue to trigger an AWS Lambda function when new messages arrive in the queue.

The queue and Lambda function must be in the same AWS Region.

+sqs.configure.lambda.validation.function=Lambda function must be specified. +sqs.confirm.iam=Create IAM Role +sqs.confirm.iam.create=Add Policy +sqs.confirm.iam.failed=Failed to create and attach role policy. +sqs.confirm.iam.in_progress=Creating... +sqs.confirm.iam.warning.sqs_queue_permissions=The selected Queue does not have permission to receive messages from the selected SNS Topic.

The following policy statement will be added to the queue policy:

+sqs.confirm.iam.warning.text=The selected Lambda function does not have permission to access SQS.

The following IAM role policy will be added to the function's execution role:

+sqs.copy.message=Copy {0, choice, 1#message|2#{0,number} messages} +sqs.create.fifo.label=FIFO +sqs.create.queue=Create Queue... +sqs.create.queue.create=Create +sqs.create.queue.failed=Failed to create queue {0} +sqs.create.queue.name.label=Queue Name: +sqs.create.queue.title=Create Queue +sqs.create.queue.tooltip=It may take up to 60 seconds for the queue to be visible in the AWS Explorer. +sqs.create.queue.type.label=Queue Type: +sqs.create.standard.label=Standard +sqs.create.validation.empty.queue.name=Queue name must be specified. +sqs.create.validation.long.queue.name=Queue name must not exceed {0} characters in length. +sqs.create.validation.queue.name.invalid=Queue names can only contain alphanumeric characters, hyphens (-), and underscores (_). +sqs.delete.message.action=Delete {0, choice, 1#message|2#{0,number} messages} +sqs.delete.message.failed=Failed to delete {0, choice, 1#message|2#{0,number} messages}. +sqs.delete.message.partial_successs=Failed to delete some messages! {0, choice, 1#message|2#{0,number} messages} were deleted. +sqs.delete.message.succeeded=Successfully deleted {0, choice, 1#1 message|2#{0,number} messages}. +sqs.delete.queue.action=Delete Queue... +sqs.edit.attributes=Edit Queue Parameters +sqs.edit.attributes.action=Edit Queue Parameters... +sqs.edit.attributes.delivery_delay=Delivery Delay (seconds): +sqs.edit.attributes.delivery_delay.tooltip=The amount of time to delay the first delivery of each message. +sqs.edit.attributes.failed=Failed to get queue parameters for queue {0} +sqs.edit.attributes.message_size=Maximum Message Size (bytes): +sqs.edit.attributes.message_size.tooltip=How many bytes a message can contain before it is rejected. Must be between {0} and {1} bytes. +sqs.edit.attributes.queue.attributes=Queue Parameters +sqs.edit.attributes.retention_period=Message Retention Period (seconds): +sqs.edit.attributes.retention_period.tooltip=The amount of time that a message is retained in the queue. Must be between {0} and {1} seconds. +sqs.edit.attributes.retrieving_from_service=Retrieving queue parameters from SQS +sqs.edit.attributes.save=Save +sqs.edit.attributes.updated=Updated queue parameters for {0}. +sqs.edit.attributes.visibility_timeout=Visibility Timeout (seconds): +sqs.edit.attributes.visibility_timeout.tooltip=The length of time that a received message will not be visible to other message consumers. +sqs.edit.attributes.wait_time=Receive Message Wait Time (seconds): +sqs.edit.attributes.wait_time.tooltip=The amount of time that polling will wait for messages to become available. +sqs.failed_to_load_total=Failed to load number of messages available +sqs.failed_to_poll_messages=Failed to view messages +sqs.failed_to_send_message=Failed to send message +sqs.fifo.queue.tooltip=Supports first-in-first-out delivery and message ordering is preserved. +sqs.message.deduplication_id.tooltip=

Used for deduplication of sent messages.

If a message with a particular message deduplication ID is sent successfully, any messages sent with the same message deduplication ID are accepted successfully but aren't delivered during the 5-minute deduplication interval.

+sqs.message.group_id.tooltip=

Specifies that a message belongs to a specific message group.

Messages that belong to the same message group are always processed in a strict order relative to the message group.

+sqs.message.message_body=Message Body +sqs.message.message_id=Message ID +sqs.message.no_messages=No messages retrieved +sqs.message.sender_id=Sender ID +sqs.message.timestamp=Sent Timestamp +sqs.message.validation.empty.deduplication_id=Enter a deduplication ID. +sqs.message.validation.empty.group_id=Enter a group ID. +sqs.message.validation.empty.message.body=Enter a message body +sqs.message.validation.long.id=ID exceeds the maximum length of 128 characters. +sqs.message_table_initial_text=Press "{0}" to load messages +sqs.messages.available.text={0, choice, 0#No messages|1#1 message|2#{0,number} messages} available +sqs.poll.message=View Messages +sqs.poll.warning.text=

Messages are returned to the queue immediately upon viewing.

+sqs.purge_queue=Purge Queue +sqs.purge_queue.action=Purge Queue... +sqs.purge_queue.confirm=Are you sure you want to purge queue {0} (approximately {1, choice, 1#1 message|2#{1,number} messages})? +sqs.purge_queue.confirm.title=Purge Queue? +sqs.purge_queue.failed=Failed to purge queue {0} +sqs.purge_queue.failed.60_seconds=Purge queue request already in progress for queue {0} +sqs.purge_queue.succeeded=Started purging queue {0} +sqs.queue.name.tooltip=

A queue name is case-sensitive and can have up to 80 characters of alphanumeric characters, hyphens (-), and underscores ( _ ).

If FIFO is selected, '.fifo' will be appended to the specified name.

+sqs.queue.polled.messages=Polled Messages +sqs.required.empty.text=(Required) +sqs.send.message=Send a Message +sqs.send.message.body.empty.text=Enter message body +sqs.send.message.clear.button=Clear +sqs.send.message.deduplication_id=Deduplication ID: +sqs.send.message.group_id=Group ID: +sqs.send.message.send.button=Send +sqs.send.message.success=Sent message ID: {0} +sqs.service_name=Amazon SQS +sqs.standard.queue.tooltip=Supports at-least-once delivery and message ordering is not preserved. +sqs.subscribe.sns=Subscribe to SNS topic... +sqs.subscribe.sns.failed=Failed to subscribe {0} to {1} +sqs.subscribe.sns.in_progress=Subscribing... +sqs.subscribe.sns.select=Select SNS topic +sqs.subscribe.sns.select.tooltip=Messages from the selected topic are sent to the queue. +sqs.subscribe.sns.subscribe=Subscribe +sqs.subscribe.sns.success=Subscribed successfully to topic {0}. +sqs.subscribe.sns.topic=SNS topic: +sqs.subscribe.sns.validation.empty_topic=Topic must be specified. +sqs.toolwindow=SQS +sqs.url.parse_error=Error parsing SQS queue URL +tags.title=Tags +testgen.button.feedback=How can we make /test better? +testgen.error.generic_error_message=Amazon Q encountered an error while generating tests. Try again later. +testgen.error.generic_technical_error_message=I am experiencing technical difficulties at the moment. Please try again in a few minutes. +testgen.error.maximum_generations_reach=You've reached the monthly quota for Amazon Q Developer's agent capabilities. You can try again next month. For more information on usage limits, see the Amazon Q Developer pricing page. +testgen.message.cancelled=Unit test generation cancelled. +testgen.message.failed=Test generation failed +testgen.message.regenerate_input=Sure thing. Please provide new instructions for me to generate the tests, and select the function(s) you would like to test. +testgen.message.success=Unit test generation completed. +testgen.no_file_found=Sorry, there isn't a source file open right now that I can generate a test for. Make sure you open a source file so I can generate tests. +testgen.placeholder.enter_slash_quick_actions=Enter "/" for quick actions +testgen.placeholder.newtab=Ask any coding question or type \u0022/\u0022 for actions +testgen.placeholder.select_an_option = Please select an action to proceed (Accept or Reject) +testgen.placeholder.view_diff=Select View Diff to see the generated unit tests +testgen.placeholder.waiting_on_your_inputs=Waiting on your inputs... +testgen.progressbar.generate_unit_tests=Generating unit tests... +toolkit.login.aws_builder_id.already_connected.cancel=Use existing AWS Builder ID +toolkit.login.aws_builder_id.already_connected.message=You already signed in with an AWS Builder ID.\nSign out to add another? +toolkit.login.aws_builder_id.already_connected.reconnect=Sign out +toolkit.login.aws_builder_id.already_connected.title=Sign out of current AWS Builder ID? +toolkit.login.dialog.aws_builder_id.comment=AWS Builder ID is a new personal profile for builders. Learn More +toolkit.login.dialog.aws_builder_id.title=Use a personal email to sign up and sign in with AWS Builder ID +toolkit.login.dialog.connect_button=Connect +toolkit.login.dialog.connect_inprogress=Waiting to Authenticate... +toolkit.login.dialog.iam.comment=Edit AWS credentials file(s) +toolkit.login.dialog.iam.text_field.access_key_id=Access Key ID: +toolkit.login.dialog.iam.text_field.secret_access_key=Secret Access Key: +toolkit.login.dialog.iam.title=Use IAM Credentials +toolkit.login.dialog.label=Select a connection option +toolkit.login.dialog.sso.comment=Sign in to your company's IAM Identity Center access portal login page. +toolkit.login.dialog.sso.text_field.region=Region: +toolkit.login.dialog.sso.text_field.start_url=Start URL: +toolkit.login.dialog.sso.title=Connect using AWS IAM Identity Center +toolkit.login.dialog.title=AWS Toolkit: Add Connection +toolkit.login.singleton=Only one browser authorization flow may be active at once +toolkit.sso_expire.dialog.cancel_button=Cancel +toolkit.sso_expire.dialog.no_button=Don't show again +toolkit.sso_expire.dialog.title=Connection Expired +toolkit.sso_expire.dialog.yes_button=Re-authenticate +toolkit.sso_expire.dialog_message=Your Amazon Q connection has expired. Please re-authenticate. +toolwindow.stripe.amazon.q.window=Amazon Q Chat +toolwindow.stripe.aws.codewhisperer.codereference=Code Reference Log +toolwindow.stripe.aws.codewhisperer.codetransform=Transformation Hub diff --git a/plugins/core-q/resources/src/software/aws/toolkits/resources/BundledResources.kt b/plugins/core-q/resources/src/software/aws/toolkits/resources/BundledResources.kt new file mode 100644 index 00000000000..003a7846a4d --- /dev/null +++ b/plugins/core-q/resources/src/software/aws/toolkits/resources/BundledResources.kt @@ -0,0 +1,10 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.resources + +import java.io.InputStream + +object BundledResources { + val ENDPOINTS_FILE: InputStream get() = javaClass.getResourceAsStream("endpoints.json") +} diff --git a/plugins/core-q/resources/src/software/aws/toolkits/resources/Localization.kt b/plugins/core-q/resources/src/software/aws/toolkits/resources/Localization.kt new file mode 100644 index 00000000000..967020d18aa --- /dev/null +++ b/plugins/core-q/resources/src/software/aws/toolkits/resources/Localization.kt @@ -0,0 +1,26 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +@file:JvmName("Localization") +package software.aws.toolkits.resources + +import org.jetbrains.annotations.PropertyKey +import java.text.MessageFormat +import java.util.ResourceBundle + +private const val BUNDLE_NAME = "software.aws.toolkits.resources.MessagesBundle" +private val BUNDLE by lazy { + ResourceBundle.getBundle(BUNDLE_NAME) ?: throw RuntimeException("Cannot find resource bundle '$BUNDLE_NAME'.") +} + +@Deprecated( + "Use extension-specific localization bundle instead", + ReplaceWith("AwsToolkitBundle.message(key, *params)", "software.aws.toolkits.resources.AwsToolkitBundle") +) +fun message(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg params: Any): String { + val value = BUNDLE.getString(key) ?: throw RuntimeException("Key $key not found in $BUNDLE") + if (params.isNotEmpty() && value.contains("{")) { + return MessageFormat.format(value, *params) + } + return value +} diff --git a/plugins/core-q/resources/tst/software/aws/toolkits/resources/BundledResourcesTest.kt b/plugins/core-q/resources/tst/software/aws/toolkits/resources/BundledResourcesTest.kt new file mode 100644 index 00000000000..6b2019732d1 --- /dev/null +++ b/plugins/core-q/resources/tst/software/aws/toolkits/resources/BundledResourcesTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.resources + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.InputStream + +@RunWith(Parameterized::class) +class BundledResourcesTest(@Suppress("UNUSED_PARAMETER") name: String, private val file: InputStream) { + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data() = listOf( + arrayOf("ENDPOINTS_FILE", BundledResources.ENDPOINTS_FILE) + ) + } + + @Test + fun fileExistsAndHasContent() { + file.use { + assertThat(it.read()).isGreaterThan(0) + } + } +} diff --git a/plugins/core-q/sdk-codegen/README.md b/plugins/core-q/sdk-codegen/README.md new file mode 100644 index 00000000000..3e499a546b1 --- /dev/null +++ b/plugins/core-q/sdk-codegen/README.md @@ -0,0 +1,22 @@ +# SDK Code Generation + +This module automatically generates AWS SDK Clients based off service modules, +leveraging the [`software.amazon.awssdk:codegen`](https://mvnrepository.com/artifact/software.amazon.awssdk/codegen-maven-plugin) module +vended as part of the [AWS SDK for Java v2](https://github.com/aws/aws-sdk-java-v2). + +This is used primarily for our custom Telemetry system which is not a public AWS Service (and thus does not have an SDK published to Maven). + +The other use-case for this is working on as yet unreleased AWS Services (or unreleased changes to existing services). + +## Usage +To generate a new service client, create a new directory in the `codegen-resources` directory and add the following model files: +* `service-2.json` - [required] this is the main service API model +* `customization.config` - [optional] determines any overrides necessary to generate the SDK +* `waiters-2.json` - [optional] used to generate SDK waiter configuration (see [here](https://aws.amazon.com/blogs/developer/using-waiters-in-the-aws-sdk-for-java-2-x/)) +* `paginators-1.json` - [optional] used to generate SDK paginated operations (see [here](https://aws.amazon.com/blogs/developer/auto-pagination-feature-in-java-sdk-2-0/)) + +To test the new models generate the desired SDK clients run: + +``` +./gradlew generateSdk +``` diff --git a/plugins/core-q/sdk-codegen/build.gradle.kts b/plugins/core-q/sdk-codegen/build.gradle.kts new file mode 100644 index 00000000000..7e2a2249ec7 --- /dev/null +++ b/plugins/core-q/sdk-codegen/build.gradle.kts @@ -0,0 +1,17 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id("toolkit-generate-sdks") +} + +sdkGenerator { + c2jFolder.set(file("$projectDir/codegen-resources")) + outputDir.set(layout.buildDirectory.dir("generated-sources").get().asFile) +} + +dependencies { + implementation(libs.aws.services) + implementation(libs.aws.jsonProtocol) + implementation(libs.aws.queryProtocol) +} diff --git a/plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/paginators-1.json b/plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/paginators-1.json new file mode 100644 index 00000000000..381687933c1 --- /dev/null +++ b/plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/paginators-1.json @@ -0,0 +1,31 @@ +{ + "pagination": { + "GenerateCompletions": { + "input_token": "nextToken", + "output_token": "nextToken", + "limit_key": "maxResults" + }, + "ListAvailableCustomizations": { + "input_token": "nextToken", + "output_token": "nextToken", + "limit_key": "maxResults", + "result_key": "customizations" + }, + "ListAvailableProfiles": { + "input_token": "nextToken", + "output_token": "nextToken", + "limit_key": "maxResults", + "result_key": "profiles" + }, + "ListCodeAnalysisFindings": { + "input_token": "nextToken", + "output_token": "nextToken" + }, + "ListWorkspaceMetadata": { + "input_token": "nextToken", + "output_token": "nextToken", + "limit_key": "maxResults", + "result_key": "workspaces" + } + } +} diff --git a/plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/service-2.json b/plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/service-2.json new file mode 100644 index 00000000000..d75fdc527bd --- /dev/null +++ b/plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/service-2.json @@ -0,0 +1,3933 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2022-11-11", + "auth":["smithy.api#httpBearerAuth"], + "endpointPrefix":"amazoncodewhispererservice", + "jsonVersion":"1.0", + "protocol":"json", + "protocols":["json"], + "serviceFullName":"Amazon CodeWhisperer", + "serviceId":"CodeWhispererRuntime", + "signatureVersion":"bearer", + "signingName":"amazoncodewhispererservice", + "targetPrefix":"AmazonCodeWhispererService", + "uid":"codewhispererruntime-2022-11-11" + }, + "operations":{ + "CreateArtifactUploadUrl":{ + "name":"CreateArtifactUploadUrl", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"CreateUploadUrlRequest"}, + "output":{"shape":"CreateUploadUrlResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "idempotent":true + }, + "CreateTaskAssistConversation":{ + "name":"CreateTaskAssistConversation", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"CreateTaskAssistConversationRequest"}, + "output":{"shape":"CreateTaskAssistConversationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ServiceQuotaExceededException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "CreateUploadUrl":{ + "name":"CreateUploadUrl", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"CreateUploadUrlRequest"}, + "output":{"shape":"CreateUploadUrlResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"ServiceQuotaExceededException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "idempotent":true + }, + "CreateWorkspace":{ + "name":"CreateWorkspace", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"CreateWorkspaceRequest"}, + "output":{"shape":"CreateWorkspaceResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "DeleteTaskAssistConversation":{ + "name":"DeleteTaskAssistConversation", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"DeleteTaskAssistConversationRequest"}, + "output":{"shape":"DeleteTaskAssistConversationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "DeleteWorkspace":{ + "name":"DeleteWorkspace", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"DeleteWorkspaceRequest"}, + "output":{"shape":"DeleteWorkspaceResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GenerateCompletions":{ + "name":"GenerateCompletions", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GenerateCompletionsRequest"}, + "output":{"shape":"GenerateCompletionsResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GetCodeAnalysis":{ + "name":"GetCodeAnalysis", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GetCodeAnalysisRequest"}, + "output":{"shape":"GetCodeAnalysisResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GetCodeFixJob":{ + "name":"GetCodeFixJob", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GetCodeFixJobRequest"}, + "output":{"shape":"GetCodeFixJobResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GetTaskAssistCodeGeneration":{ + "name":"GetTaskAssistCodeGeneration", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GetTaskAssistCodeGenerationRequest"}, + "output":{"shape":"GetTaskAssistCodeGenerationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GetTestGeneration":{ + "name":"GetTestGeneration", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GetTestGenerationRequest"}, + "output":{"shape":"GetTestGenerationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GetTransformation":{ + "name":"GetTransformation", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GetTransformationRequest"}, + "output":{"shape":"GetTransformationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "GetTransformationPlan":{ + "name":"GetTransformationPlan", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GetTransformationPlanRequest"}, + "output":{"shape":"GetTransformationPlanResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ListAvailableCustomizations":{ + "name":"ListAvailableCustomizations", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ListAvailableCustomizationsRequest"}, + "output":{"shape":"ListAvailableCustomizationsResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ListAvailableProfiles":{ + "name":"ListAvailableProfiles", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ListAvailableProfilesRequest"}, + "output":{"shape":"ListAvailableProfilesResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ListCodeAnalysisFindings":{ + "name":"ListCodeAnalysisFindings", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ListCodeAnalysisFindingsRequest"}, + "output":{"shape":"ListCodeAnalysisFindingsResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ListEvents":{ + "name":"ListEvents", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ListEventsRequest"}, + "output":{"shape":"ListEventsResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ListFeatureEvaluations":{ + "name":"ListFeatureEvaluations", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ListFeatureEvaluationsRequest"}, + "output":{"shape":"ListFeatureEvaluationsResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ListWorkspaceMetadata":{ + "name":"ListWorkspaceMetadata", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ListWorkspaceMetadataRequest"}, + "output":{"shape":"ListWorkspaceMetadataResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "ResumeTransformation":{ + "name":"ResumeTransformation", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"ResumeTransformationRequest"}, + "output":{"shape":"ResumeTransformationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "SendTelemetryEvent":{ + "name":"SendTelemetryEvent", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"SendTelemetryEventRequest"}, + "output":{"shape":"SendTelemetryEventResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "idempotent":true + }, + "StartCodeAnalysis":{ + "name":"StartCodeAnalysis", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"StartCodeAnalysisRequest"}, + "output":{"shape":"StartCodeAnalysisResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "idempotent":true + }, + "StartCodeFixJob":{ + "name":"StartCodeFixJob", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"StartCodeFixJobRequest"}, + "output":{"shape":"StartCodeFixJobResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "StartTaskAssistCodeGeneration":{ + "name":"StartTaskAssistCodeGeneration", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"StartTaskAssistCodeGenerationRequest"}, + "output":{"shape":"StartTaskAssistCodeGenerationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"ServiceQuotaExceededException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "StartTestGeneration":{ + "name":"StartTestGeneration", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"StartTestGenerationRequest"}, + "output":{"shape":"StartTestGenerationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "idempotent":true + }, + "StartTransformation":{ + "name":"StartTransformation", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"StartTransformationRequest"}, + "output":{"shape":"StartTransformationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + }, + "StopTransformation":{ + "name":"StopTransformation", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"StopTransformationRequest"}, + "output":{"shape":"StopTransformationResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + } + }, + "shapes":{ + "AccessDeniedException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"}, + "reason":{"shape":"AccessDeniedExceptionReason"} + }, + "exception":true + }, + "AccessDeniedExceptionReason":{ + "type":"string", + "enum":["UNAUTHORIZED_CUSTOMIZATION_RESOURCE_ACCESS"] + }, + "ActiveFunctionalityList":{ + "type":"list", + "member":{"shape":"FunctionalityName"}, + "max":10, + "min":0 + }, + "AdditionalContentEntry":{ + "type":"structure", + "required":[ + "name", + "description" + ], + "members":{ + "name":{"shape":"AdditionalContentEntryNameString"}, + "description":{"shape":"AdditionalContentEntryDescriptionString"}, + "innerContext":{"shape":"AdditionalContentEntryInnerContextString"} + } + }, + "AdditionalContentEntryDescriptionString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AdditionalContentEntryInnerContextString":{ + "type":"string", + "max":8192, + "min":1, + "sensitive":true + }, + "AdditionalContentEntryNameString":{ + "type":"string", + "max":1024, + "min":1, + "pattern":"[a-z]+(?:-[a-z0-9]+)*", + "sensitive":true + }, + "AdditionalContentList":{ + "type":"list", + "member":{"shape":"AdditionalContentEntry"}, + "max":20, + "min":0 + }, + "AppStudioState":{ + "type":"structure", + "required":[ + "namespace", + "propertyName", + "propertyContext" + ], + "members":{ + "namespace":{"shape":"AppStudioStateNamespaceString"}, + "propertyName":{"shape":"AppStudioStatePropertyNameString"}, + "propertyValue":{"shape":"AppStudioStatePropertyValueString"}, + "propertyContext":{"shape":"AppStudioStatePropertyContextString"} + } + }, + "AppStudioStateNamespaceString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AppStudioStatePropertyContextString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AppStudioStatePropertyNameString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AppStudioStatePropertyValueString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "ApplicationProperties":{ + "type":"structure", + "required":[ + "tenantId", + "applicationArn", + "tenantUrl", + "applicationType" + ], + "members":{ + "tenantId":{"shape":"TenantId"}, + "applicationArn":{"shape":"ResourceArn"}, + "tenantUrl":{"shape":"Url"}, + "applicationType":{"shape":"FunctionalityName"} + } + }, + "ApplicationPropertiesList":{ + "type":"list", + "member":{"shape":"ApplicationProperties"} + }, + "ArtifactId":{ + "type":"string", + "max":126, + "min":1, + "pattern":"[a-zA-Z0-9-_]+" + }, + "ArtifactMap":{ + "type":"map", + "key":{"shape":"ArtifactType"}, + "value":{"shape":"UploadId"}, + "max":64, + "min":1 + }, + "ArtifactType":{ + "type":"string", + "enum":[ + "SourceCode", + "BuiltJars" + ] + }, + "AssistantResponseMessage":{ + "type":"structure", + "required":["content"], + "members":{ + "messageId":{"shape":"MessageId"}, + "content":{"shape":"AssistantResponseMessageContentString"}, + "supplementaryWebLinks":{"shape":"SupplementaryWebLinks"}, + "references":{"shape":"References"}, + "followupPrompt":{"shape":"FollowupPrompt"}, + "toolUses":{"shape":"ToolUses"} + } + }, + "AssistantResponseMessageContentString":{ + "type":"string", + "max":100000, + "min":0, + "sensitive":true + }, + "Base64EncodedPaginationToken":{ + "type":"string", + "max":2048, + "min":1, + "pattern":"(?:[A-Za-z0-9\\+/]{4})*(?:[A-Za-z0-9\\+/]{2}\\=\\=|[A-Za-z0-9\\+/]{3}\\=)?" + }, + "Boolean":{ + "type":"boolean", + "box":true + }, + "ByUserAnalytics":{ + "type":"structure", + "required":["toggle"], + "members":{ + "s3Uri":{"shape":"S3Uri"}, + "toggle":{"shape":"OptInFeatureToggle"} + } + }, + "ChatAddMessageEvent":{ + "type":"structure", + "required":[ + "conversationId", + "messageId" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "messageId":{"shape":"MessageId"}, + "customizationArn":{"shape":"CustomizationArn"}, + "userIntent":{"shape":"UserIntent"}, + "hasCodeSnippet":{"shape":"Boolean"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "activeEditorTotalCharacters":{"shape":"Integer"}, + "timeToFirstChunkMilliseconds":{"shape":"Double"}, + "timeBetweenChunks":{"shape":"timeBetweenChunks"}, + "fullResponselatency":{"shape":"Double"}, + "requestLength":{"shape":"Integer"}, + "responseLength":{"shape":"Integer"}, + "numberOfCodeBlocks":{"shape":"Integer"}, + "hasProjectLevelContext":{"shape":"Boolean"} + } + }, + "ChatHistory":{ + "type":"list", + "member":{"shape":"ChatMessage"}, + "max":100, + "min":0 + }, + "ChatInteractWithMessageEvent":{ + "type":"structure", + "required":[ + "conversationId", + "messageId" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "messageId":{"shape":"MessageId"}, + "customizationArn":{"shape":"CustomizationArn"}, + "interactionType":{"shape":"ChatMessageInteractionType"}, + "interactionTarget":{"shape":"ChatInteractWithMessageEventInteractionTargetString"}, + "acceptedCharacterCount":{"shape":"Integer"}, + "acceptedLineCount":{"shape":"Integer"}, + "acceptedSnippetHasReference":{"shape":"Boolean"}, + "hasProjectLevelContext":{"shape":"Boolean"}, + "userIntent":{"shape":"UserIntent"}, + "addedIdeDiagnostics":{"shape":"IdeDiagnosticList"}, + "removedIdeDiagnostics":{"shape":"IdeDiagnosticList"} + } + }, + "ChatInteractWithMessageEventInteractionTargetString":{ + "type":"string", + "max":1024, + "min":1 + }, + "ChatMessage":{ + "type":"structure", + "members":{ + "userInputMessage":{"shape":"UserInputMessage"}, + "assistantResponseMessage":{"shape":"AssistantResponseMessage"} + }, + "union":true + }, + "ChatMessageInteractionType":{ + "type":"string", + "enum":[ + "INSERT_AT_CURSOR", + "COPY_SNIPPET", + "COPY", + "CLICK_LINK", + "CLICK_BODY_LINK", + "CLICK_FOLLOW_UP", + "HOVER_REFERENCE", + "UPVOTE", + "DOWNVOTE" + ] + }, + "ChatTriggerType":{ + "type":"string", + "enum":[ + "MANUAL", + "DIAGNOSTIC", + "INLINE_CHAT" + ] + }, + "ChatUserModificationEvent":{ + "type":"structure", + "required":[ + "conversationId", + "messageId", + "modificationPercentage" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "customizationArn":{"shape":"CustomizationArn"}, + "messageId":{"shape":"MessageId"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "modificationPercentage":{"shape":"Double"}, + "hasProjectLevelContext":{"shape":"Boolean"} + } + }, + "ClientId":{ + "type":"string", + "max":255, + "min":1 + }, + "CodeAnalysisFindingsSchema":{ + "type":"string", + "enum":["codeanalysis/findings/1.0"] + }, + "CodeAnalysisScope":{ + "type":"string", + "enum":[ + "FILE", + "PROJECT" + ] + }, + "CodeAnalysisStatus":{ + "type":"string", + "enum":[ + "Completed", + "Pending", + "Failed" + ] + }, + "CodeAnalysisUploadContext":{ + "type":"structure", + "required":["codeScanName"], + "members":{ + "codeScanName":{"shape":"CodeScanName"} + } + }, + "CodeCoverageEvent":{ + "type":"structure", + "required":[ + "programmingLanguage", + "acceptedCharacterCount", + "totalCharacterCount", + "timestamp" + ], + "members":{ + "customizationArn":{"shape":"CustomizationArn"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "acceptedCharacterCount":{"shape":"PrimitiveInteger"}, + "totalCharacterCount":{"shape":"PrimitiveInteger"}, + "timestamp":{"shape":"Timestamp"}, + "unmodifiedAcceptedCharacterCount":{"shape":"PrimitiveInteger"}, + "totalNewCodeCharacterCount":{"shape":"PrimitiveInteger"}, + "totalNewCodeLineCount":{"shape":"PrimitiveInteger"}, + "userWrittenCodeCharacterCount":{"shape":"CodeCoverageEventUserWrittenCodeCharacterCountInteger"}, + "userWrittenCodeLineCount":{"shape":"CodeCoverageEventUserWrittenCodeLineCountInteger"} + } + }, + "CodeCoverageEventUserWrittenCodeCharacterCountInteger":{ + "type":"integer", + "min":0 + }, + "CodeCoverageEventUserWrittenCodeLineCountInteger":{ + "type":"integer", + "min":0 + }, + "CodeFixAcceptanceEvent":{ + "type":"structure", + "required":["jobId"], + "members":{ + "jobId":{"shape":"String"}, + "ruleId":{"shape":"String"}, + "detectorId":{"shape":"String"}, + "findingId":{"shape":"String"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "linesOfCodeAccepted":{"shape":"Integer"}, + "charsOfCodeAccepted":{"shape":"Integer"} + } + }, + "CodeFixGenerationEvent":{ + "type":"structure", + "required":["jobId"], + "members":{ + "jobId":{"shape":"String"}, + "ruleId":{"shape":"String"}, + "detectorId":{"shape":"String"}, + "findingId":{"shape":"String"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "linesOfCodeGenerated":{"shape":"Integer"}, + "charsOfCodeGenerated":{"shape":"Integer"} + } + }, + "CodeFixJobStatus":{ + "type":"string", + "enum":[ + "Succeeded", + "InProgress", + "Failed" + ] + }, + "CodeFixName":{ + "type":"string", + "max":128, + "min":1, + "pattern":"[a-zA-Z0-9-_$:.]*" + }, + "CodeFixUploadContext":{ + "type":"structure", + "required":["codeFixName"], + "members":{ + "codeFixName":{"shape":"CodeFixName"} + } + }, + "CodeGenerationId":{ + "type":"string", + "max":128, + "min":1 + }, + "CodeGenerationStatus":{ + "type":"structure", + "required":[ + "status", + "currentStage" + ], + "members":{ + "status":{"shape":"CodeGenerationWorkflowStatus"}, + "currentStage":{"shape":"CodeGenerationWorkflowStage"} + } + }, + "CodeGenerationStatusDetail":{ + "type":"string", + "sensitive":true + }, + "CodeGenerationWorkflowStage":{ + "type":"string", + "enum":[ + "InitialCodeGeneration", + "CodeRefinement" + ] + }, + "CodeGenerationWorkflowStatus":{ + "type":"string", + "enum":[ + "InProgress", + "Complete", + "Failed" + ] + }, + "CodeScanEvent":{ + "type":"structure", + "required":[ + "programmingLanguage", + "codeScanJobId", + "timestamp" + ], + "members":{ + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "codeScanJobId":{"shape":"CodeScanJobId"}, + "timestamp":{"shape":"Timestamp"}, + "codeAnalysisScope":{"shape":"CodeAnalysisScope"} + } + }, + "CodeScanFailedEvent":{ + "type":"structure", + "required":[ + "programmingLanguage", + "codeScanJobId", + "timestamp" + ], + "members":{ + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "codeScanJobId":{"shape":"CodeScanJobId"}, + "timestamp":{"shape":"Timestamp"}, + "codeAnalysisScope":{"shape":"CodeAnalysisScope"} + } + }, + "CodeScanJobId":{ + "type":"string", + "max":128, + "min":1 + }, + "CodeScanName":{ + "type":"string", + "max":128, + "min":1 + }, + "CodeScanRemediationsEvent":{ + "type":"structure", + "members":{ + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "CodeScanRemediationsEventType":{"shape":"CodeScanRemediationsEventType"}, + "timestamp":{"shape":"Timestamp"}, + "detectorId":{"shape":"String"}, + "findingId":{"shape":"String"}, + "ruleId":{"shape":"String"}, + "component":{"shape":"String"}, + "reason":{"shape":"String"}, + "result":{"shape":"String"}, + "includesFix":{"shape":"Boolean"} + } + }, + "CodeScanRemediationsEventType":{ + "type":"string", + "enum":[ + "CODESCAN_ISSUE_HOVER", + "CODESCAN_ISSUE_APPLY_FIX", + "CODESCAN_ISSUE_VIEW_DETAILS" + ] + }, + "CodeScanSucceededEvent":{ + "type":"structure", + "required":[ + "programmingLanguage", + "codeScanJobId", + "timestamp", + "numberOfFindings" + ], + "members":{ + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "codeScanJobId":{"shape":"CodeScanJobId"}, + "timestamp":{"shape":"Timestamp"}, + "numberOfFindings":{"shape":"PrimitiveInteger"}, + "codeAnalysisScope":{"shape":"CodeAnalysisScope"} + } + }, + "Completion":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{"shape":"CompletionContentString"}, + "references":{"shape":"References"}, + "mostRelevantMissingImports":{"shape":"Imports"} + } + }, + "CompletionContentString":{ + "type":"string", + "max":5120, + "min":1, + "sensitive":true + }, + "CompletionType":{ + "type":"string", + "enum":[ + "BLOCK", + "LINE" + ] + }, + "Completions":{ + "type":"list", + "member":{"shape":"Completion"}, + "max":10, + "min":0 + }, + "ConflictException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"}, + "reason":{"shape":"ConflictExceptionReason"} + }, + "exception":true + }, + "ConflictExceptionReason":{ + "type":"string", + "enum":[ + "CUSTOMER_KMS_KEY_INVALID_KEY_POLICY", + "CUSTOMER_KMS_KEY_DISABLED", + "MISMATCHED_KMS_KEY" + ] + }, + "ConsoleState":{ + "type":"structure", + "members":{ + "region":{"shape":"String"}, + "consoleUrl":{"shape":"SensitiveString"}, + "serviceId":{"shape":"String"}, + "serviceConsolePage":{"shape":"String"}, + "serviceSubconsolePage":{"shape":"String"}, + "taskName":{"shape":"SensitiveString"} + } + }, + "ContentChecksumType":{ + "type":"string", + "enum":["SHA_256"] + }, + "ContextTruncationScheme":{ + "type":"string", + "enum":[ + "ANALYSIS", + "GUMBY" + ] + }, + "ConversationId":{ + "type":"string", + "max":128, + "min":1 + }, + "ConversationState":{ + "type":"structure", + "required":[ + "currentMessage", + "chatTriggerType" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "history":{"shape":"ChatHistory"}, + "currentMessage":{"shape":"ChatMessage"}, + "chatTriggerType":{"shape":"ChatTriggerType"}, + "customizationArn":{"shape":"ResourceArn"} + } + }, + "CreateTaskAssistConversationRequest":{ + "type":"structure", + "members":{ + "profileArn":{"shape":"ProfileArn"} + } + }, + "CreateTaskAssistConversationResponse":{ + "type":"structure", + "required":["conversationId"], + "members":{ + "conversationId":{"shape":"ConversationId"} + } + }, + "CreateUploadUrlRequest":{ + "type":"structure", + "members":{ + "contentMd5":{"shape":"CreateUploadUrlRequestContentMd5String"}, + "contentChecksum":{"shape":"CreateUploadUrlRequestContentChecksumString"}, + "contentChecksumType":{"shape":"ContentChecksumType"}, + "contentLength":{"shape":"CreateUploadUrlRequestContentLengthLong"}, + "artifactType":{"shape":"ArtifactType"}, + "uploadIntent":{"shape":"UploadIntent"}, + "uploadContext":{"shape":"UploadContext"}, + "uploadId":{"shape":"UploadId"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "CreateUploadUrlRequestContentChecksumString":{ + "type":"string", + "max":512, + "min":1, + "sensitive":true + }, + "CreateUploadUrlRequestContentLengthLong":{ + "type":"long", + "box":true, + "min":1 + }, + "CreateUploadUrlRequestContentMd5String":{ + "type":"string", + "max":128, + "min":1, + "sensitive":true + }, + "CreateUploadUrlResponse":{ + "type":"structure", + "required":[ + "uploadId", + "uploadUrl" + ], + "members":{ + "uploadId":{"shape":"UploadId"}, + "uploadUrl":{"shape":"PreSignedUrl"}, + "kmsKeyArn":{"shape":"ResourceArn"}, + "requestHeaders":{"shape":"RequestHeaders"} + } + }, + "CreateWorkspaceRequest":{ + "type":"structure", + "required":["workspaceRoot"], + "members":{ + "workspaceRoot":{"shape":"CreateWorkspaceRequestWorkspaceRootString"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "CreateWorkspaceRequestWorkspaceRootString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "CreateWorkspaceResponse":{ + "type":"structure", + "required":["workspace"], + "members":{ + "workspace":{"shape":"WorkspaceMetadata"} + } + }, + "CursorState":{ + "type":"structure", + "members":{ + "position":{"shape":"Position"}, + "range":{"shape":"Range"} + }, + "union":true + }, + "Customization":{ + "type":"structure", + "required":["arn"], + "members":{ + "arn":{"shape":"CustomizationArn"}, + "name":{"shape":"CustomizationName"}, + "description":{"shape":"Description"} + } + }, + "CustomizationArn":{ + "type":"string", + "max":950, + "min":0, + "pattern":"arn:[-.a-z0-9]{1,63}:codewhisperer:([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}" + }, + "CustomizationName":{ + "type":"string", + "max":100, + "min":1, + "pattern":"[a-zA-Z][a-zA-Z0-9_-]*" + }, + "Customizations":{ + "type":"list", + "member":{"shape":"Customization"} + }, + "DashboardAnalytics":{ + "type":"structure", + "required":["toggle"], + "members":{ + "toggle":{"shape":"OptInFeatureToggle"} + } + }, + "DeleteTaskAssistConversationRequest":{ + "type":"structure", + "required":["conversationId"], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "DeleteTaskAssistConversationResponse":{ + "type":"structure", + "required":["conversationId"], + "members":{ + "conversationId":{"shape":"ConversationId"} + } + }, + "DeleteWorkspaceRequest":{ + "type":"structure", + "required":["workspaceId"], + "members":{ + "workspaceId":{"shape":"UUID"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "DeleteWorkspaceResponse":{ + "type":"structure", + "members":{ + } + }, + "Description":{ + "type":"string", + "max":256, + "min":0, + "pattern":"[\\sa-zA-Z0-9_-]*" + }, + "Diagnostic":{ + "type":"structure", + "members":{ + "textDocumentDiagnostic":{"shape":"TextDocumentDiagnostic"}, + "runtimeDiagnostic":{"shape":"RuntimeDiagnostic"} + }, + "union":true + }, + "DiagnosticSeverity":{ + "type":"string", + "enum":[ + "ERROR", + "WARNING", + "INFORMATION", + "HINT" + ] + }, + "Dimension":{ + "type":"structure", + "members":{ + "name":{"shape":"DimensionNameString"}, + "value":{"shape":"DimensionValueString"} + } + }, + "DimensionList":{ + "type":"list", + "member":{"shape":"Dimension"}, + "max":30, + "min":0 + }, + "DimensionNameString":{ + "type":"string", + "max":255, + "min":1, + "pattern":"[-a-zA-Z0-9._]*" + }, + "DimensionValueString":{ + "type":"string", + "max":1024, + "min":1, + "pattern":"[-a-zA-Z0-9._]*" + }, + "DocFolderLevel":{ + "type":"string", + "enum":[ + "SUB_FOLDER", + "ENTIRE_WORKSPACE" + ] + }, + "DocGenerationEvent":{ + "type":"structure", + "required":["conversationId"], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "numberOfAddChars":{"shape":"PrimitiveInteger"}, + "numberOfAddLines":{"shape":"PrimitiveInteger"}, + "numberOfAddFiles":{"shape":"PrimitiveInteger"}, + "userDecision":{"shape":"DocUserDecision"}, + "interactionType":{"shape":"DocInteractionType"}, + "userIdentity":{"shape":"String"}, + "numberOfNavigation":{"shape":"PrimitiveInteger"}, + "folderLevel":{"shape":"DocFolderLevel"} + } + }, + "DocInteractionType":{ + "type":"string", + "enum":[ + "GENERATE_README", + "UPDATE_README", + "EDIT_README" + ] + }, + "DocUserDecision":{ + "type":"string", + "enum":[ + "ACCEPT", + "REJECT" + ] + }, + "DocV2AcceptanceEvent":{ + "type":"structure", + "required":[ + "conversationId", + "numberOfAddedChars", + "numberOfAddedLines", + "numberOfAddedFiles", + "userDecision", + "interactionType", + "numberOfNavigations", + "folderLevel" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "numberOfAddedChars":{"shape":"DocV2AcceptanceEventNumberOfAddedCharsInteger"}, + "numberOfAddedLines":{"shape":"DocV2AcceptanceEventNumberOfAddedLinesInteger"}, + "numberOfAddedFiles":{"shape":"DocV2AcceptanceEventNumberOfAddedFilesInteger"}, + "userDecision":{"shape":"DocUserDecision"}, + "interactionType":{"shape":"DocInteractionType"}, + "numberOfNavigations":{"shape":"DocV2AcceptanceEventNumberOfNavigationsInteger"}, + "folderLevel":{"shape":"DocFolderLevel"} + } + }, + "DocV2AcceptanceEventNumberOfAddedCharsInteger":{ + "type":"integer", + "min":0 + }, + "DocV2AcceptanceEventNumberOfAddedFilesInteger":{ + "type":"integer", + "min":0 + }, + "DocV2AcceptanceEventNumberOfAddedLinesInteger":{ + "type":"integer", + "min":0 + }, + "DocV2AcceptanceEventNumberOfNavigationsInteger":{ + "type":"integer", + "min":0 + }, + "DocV2GenerationEvent":{ + "type":"structure", + "required":[ + "conversationId", + "numberOfGeneratedChars", + "numberOfGeneratedLines", + "numberOfGeneratedFiles" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "numberOfGeneratedChars":{"shape":"DocV2GenerationEventNumberOfGeneratedCharsInteger"}, + "numberOfGeneratedLines":{"shape":"DocV2GenerationEventNumberOfGeneratedLinesInteger"}, + "numberOfGeneratedFiles":{"shape":"DocV2GenerationEventNumberOfGeneratedFilesInteger"}, + "interactionType":{"shape":"DocInteractionType"}, + "numberOfNavigations":{"shape":"DocV2GenerationEventNumberOfNavigationsInteger"}, + "folderLevel":{"shape":"DocFolderLevel"} + } + }, + "DocV2GenerationEventNumberOfGeneratedCharsInteger":{ + "type":"integer", + "min":0 + }, + "DocV2GenerationEventNumberOfGeneratedFilesInteger":{ + "type":"integer", + "min":0 + }, + "DocV2GenerationEventNumberOfGeneratedLinesInteger":{ + "type":"integer", + "min":0 + }, + "DocV2GenerationEventNumberOfNavigationsInteger":{ + "type":"integer", + "min":0 + }, + "DocumentSymbol":{ + "type":"structure", + "required":[ + "name", + "type" + ], + "members":{ + "name":{"shape":"DocumentSymbolNameString"}, + "type":{"shape":"SymbolType"}, + "source":{"shape":"DocumentSymbolSourceString"} + } + }, + "DocumentSymbolNameString":{ + "type":"string", + "max":256, + "min":1 + }, + "DocumentSymbolSourceString":{ + "type":"string", + "max":256, + "min":1 + }, + "DocumentSymbols":{ + "type":"list", + "member":{"shape":"DocumentSymbol"}, + "max":1000, + "min":0 + }, + "DocumentationIntentContext":{ + "type":"structure", + "required":["type"], + "members":{ + "scope":{"shape":"DocumentationIntentContextScopeString"}, + "type":{"shape":"DocumentationType"} + } + }, + "DocumentationIntentContextScopeString":{ + "type":"string", + "max":4096, + "min":1, + "sensitive":true + }, + "DocumentationType":{ + "type":"string", + "enum":["README"] + }, + "Double":{ + "type":"double", + "box":true + }, + "EditorState":{ + "type":"structure", + "members":{ + "document":{"shape":"TextDocument"}, + "cursorState":{"shape":"CursorState"}, + "relevantDocuments":{"shape":"RelevantDocumentList"}, + "useRelevantDocuments":{"shape":"Boolean"}, + "workspaceFolders":{"shape":"WorkspaceFolderList"} + } + }, + "EnvState":{ + "type":"structure", + "members":{ + "operatingSystem":{"shape":"EnvStateOperatingSystemString"}, + "currentWorkingDirectory":{"shape":"EnvStateCurrentWorkingDirectoryString"}, + "environmentVariables":{"shape":"EnvironmentVariables"}, + "timezoneOffset":{"shape":"EnvStateTimezoneOffsetInteger"} + } + }, + "EnvStateCurrentWorkingDirectoryString":{ + "type":"string", + "max":256, + "min":1, + "sensitive":true + }, + "EnvStateOperatingSystemString":{ + "type":"string", + "max":32, + "min":1, + "pattern":"(macos|linux|windows)" + }, + "EnvStateTimezoneOffsetInteger":{ + "type":"integer", + "box":true, + "max":1440, + "min":-1440 + }, + "EnvironmentVariable":{ + "type":"structure", + "members":{ + "key":{"shape":"EnvironmentVariableKeyString"}, + "value":{"shape":"EnvironmentVariableValueString"} + } + }, + "EnvironmentVariableKeyString":{ + "type":"string", + "max":256, + "min":1, + "sensitive":true + }, + "EnvironmentVariableValueString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "EnvironmentVariables":{ + "type":"list", + "member":{"shape":"EnvironmentVariable"}, + "max":100, + "min":0 + }, + "ErrorDetails":{ + "type":"string", + "max":2048, + "min":0 + }, + "Event":{ + "type":"structure", + "required":[ + "eventId", + "generationId", + "eventTimestamp", + "eventType", + "eventBlob" + ], + "members":{ + "eventId":{"shape":"UUID"}, + "generationId":{"shape":"UUID"}, + "eventTimestamp":{"shape":"SyntheticTimestamp_date_time"}, + "eventType":{"shape":"EventType"}, + "eventBlob":{"shape":"EventBlob"} + } + }, + "EventBlob":{ + "type":"blob", + "max":400000, + "min":1, + "sensitive":true + }, + "EventList":{ + "type":"list", + "member":{"shape":"Event"}, + "max":10, + "min":1 + }, + "EventType":{ + "type":"string", + "max":100, + "min":1 + }, + "ExternalIdentityDetails":{ + "type":"structure", + "members":{ + "issuerUrl":{"shape":"IssuerUrl"}, + "clientId":{"shape":"ClientId"}, + "scimEndpoint":{"shape":"String"} + } + }, + "FeatureDevCodeAcceptanceEvent":{ + "type":"structure", + "required":[ + "conversationId", + "linesOfCodeAccepted", + "charactersOfCodeAccepted" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "linesOfCodeAccepted":{"shape":"FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger"}, + "charactersOfCodeAccepted":{"shape":"FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"} + } + }, + "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger":{ + "type":"integer", + "min":0 + }, + "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger":{ + "type":"integer", + "min":0 + }, + "FeatureDevCodeGenerationEvent":{ + "type":"structure", + "required":[ + "conversationId", + "linesOfCodeGenerated", + "charactersOfCodeGenerated" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "linesOfCodeGenerated":{"shape":"FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger"}, + "charactersOfCodeGenerated":{"shape":"FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"} + } + }, + "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger":{ + "type":"integer", + "min":0 + }, + "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger":{ + "type":"integer", + "min":0 + }, + "FeatureDevEvent":{ + "type":"structure", + "required":["conversationId"], + "members":{ + "conversationId":{"shape":"ConversationId"} + } + }, + "FeatureEvaluation":{ + "type":"structure", + "required":[ + "feature", + "variation", + "value" + ], + "members":{ + "feature":{"shape":"FeatureName"}, + "variation":{"shape":"FeatureVariation"}, + "value":{"shape":"FeatureValue"} + } + }, + "FeatureEvaluationsList":{ + "type":"list", + "member":{"shape":"FeatureEvaluation"}, + "max":50, + "min":0 + }, + "FeatureName":{ + "type":"string", + "max":128, + "min":1, + "pattern":"[-a-zA-Z0-9._]*" + }, + "FeatureValue":{ + "type":"structure", + "members":{ + "boolValue":{"shape":"Boolean"}, + "doubleValue":{"shape":"Double"}, + "longValue":{"shape":"Long"}, + "stringValue":{"shape":"FeatureValueStringType"} + }, + "union":true + }, + "FeatureValueStringType":{ + "type":"string", + "max":512, + "min":0 + }, + "FeatureVariation":{ + "type":"string", + "max":128, + "min":1, + "pattern":"[-a-zA-Z0-9._]*" + }, + "FileContext":{ + "type":"structure", + "required":[ + "leftFileContent", + "rightFileContent", + "filename", + "programmingLanguage" + ], + "members":{ + "leftFileContent":{"shape":"FileContextLeftFileContentString"}, + "rightFileContent":{"shape":"FileContextRightFileContentString"}, + "filename":{"shape":"FileContextFilenameString"}, + "fileUri": {"shape":"FileContextFileUriString"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"} + } + }, + "FileContextFileUriString": { + "type": "string", + "max": 1024, + "min": 1, + "sensitive": true + }, + "FileContextFilenameString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "FileContextLeftFileContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "FileContextRightFileContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "FollowupPrompt":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{"shape":"FollowupPromptContentString"}, + "userIntent":{"shape":"UserIntent"} + } + }, + "FollowupPromptContentString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "FunctionalityName":{ + "type":"string", + "enum":[ + "COMPLETIONS", + "ANALYSIS", + "CONVERSATIONS", + "TASK_ASSIST", + "TRANSFORMATIONS", + "CHAT_CUSTOMIZATION", + "TRANSFORMATIONS_WEBAPP", + "FEATURE_DEVELOPMENT" + ], + "max":64, + "min":1 + }, + "GenerateCompletionsRequest":{ + "type":"structure", + "required":["fileContext"], + "members":{ + "fileContext":{"shape":"FileContext"}, + "maxResults":{"shape":"GenerateCompletionsRequestMaxResultsInteger"}, + "nextToken":{"shape":"GenerateCompletionsRequestNextTokenString"}, + "referenceTrackerConfiguration":{"shape":"ReferenceTrackerConfiguration"}, + "supplementalContexts":{"shape":"SupplementalContextList"}, + "customizationArn":{"shape":"CustomizationArn"}, + "optOutPreference":{"shape":"OptOutPreference"}, + "userContext":{"shape":"UserContext"}, + "profileArn":{"shape":"ProfileArn"}, + "workspaceId":{"shape":"UUID"} + } + }, + "GenerateCompletionsRequestMaxResultsInteger":{ + "type":"integer", + "box":true, + "max":10, + "min":1 + }, + "GenerateCompletionsRequestNextTokenString":{ + "type":"string", + "max":2048, + "min":0, + "pattern":"(?:[A-Za-z0-9\\+/]{4})*(?:[A-Za-z0-9\\+/]{2}\\=\\=|[A-Za-z0-9\\+/]{3}\\=)?", + "sensitive":true + }, + "GenerateCompletionsResponse":{ + "type":"structure", + "members":{ + "completions":{"shape":"Completions"}, + "nextToken":{"shape":"SensitiveString"} + } + }, + "GetCodeAnalysisRequest":{ + "type":"structure", + "required":["jobId"], + "members":{ + "jobId":{"shape":"GetCodeAnalysisRequestJobIdString"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "GetCodeAnalysisRequestJobIdString":{ + "type":"string", + "max":256, + "min":1 + }, + "GetCodeAnalysisResponse":{ + "type":"structure", + "required":["status"], + "members":{ + "status":{"shape":"CodeAnalysisStatus"}, + "errorMessage":{"shape":"SensitiveString"} + } + }, + "GetCodeFixJobRequest":{ + "type":"structure", + "required":["jobId"], + "members":{ + "jobId":{"shape":"GetCodeFixJobRequestJobIdString"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "GetCodeFixJobRequestJobIdString":{ + "type":"string", + "max":256, + "min":1, + "pattern":".*[A-Za-z0-9-:]+.*" + }, + "GetCodeFixJobResponse":{ + "type":"structure", + "members":{ + "jobStatus":{"shape":"CodeFixJobStatus"}, + "suggestedFix":{"shape":"SuggestedFix"} + } + }, + "GetTaskAssistCodeGenerationRequest":{ + "type":"structure", + "required":[ + "conversationId", + "codeGenerationId" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "codeGenerationId":{"shape":"CodeGenerationId"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "GetTaskAssistCodeGenerationResponse":{ + "type":"structure", + "required":[ + "conversationId", + "codeGenerationStatus" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "codeGenerationStatus":{"shape":"CodeGenerationStatus"}, + "codeGenerationStatusDetail":{"shape":"CodeGenerationStatusDetail"}, + "codeGenerationRemainingIterationCount":{"shape":"Integer"}, + "codeGenerationTotalIterationCount":{"shape":"Integer"} + } + }, + "GetTestGenerationRequest":{ + "type":"structure", + "required":[ + "testGenerationJobGroupName", + "testGenerationJobId" + ], + "members":{ + "testGenerationJobGroupName":{"shape":"TestGenerationJobGroupName"}, + "testGenerationJobId":{"shape":"UUID"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "GetTestGenerationResponse":{ + "type":"structure", + "members":{ + "testGenerationJob":{"shape":"TestGenerationJob"} + } + }, + "GetTransformationPlanRequest":{ + "type":"structure", + "required":["transformationJobId"], + "members":{ + "transformationJobId":{"shape":"TransformationJobId"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "GetTransformationPlanResponse":{ + "type":"structure", + "required":["transformationPlan"], + "members":{ + "transformationPlan":{"shape":"TransformationPlan"} + } + }, + "GetTransformationRequest":{ + "type":"structure", + "required":["transformationJobId"], + "members":{ + "transformationJobId":{"shape":"TransformationJobId"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "GetTransformationResponse":{ + "type":"structure", + "required":["transformationJob"], + "members":{ + "transformationJob":{"shape":"TransformationJob"} + } + }, + "GitState":{ + "type":"structure", + "members":{ + "status":{"shape":"GitStateStatusString"} + } + }, + "GitStateStatusString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "IdeCategory":{ + "type":"string", + "enum":[ + "JETBRAINS", + "VSCODE", + "CLI", + "JUPYTER_MD", + "JUPYTER_SM", + "ECLIPSE", + "VISUAL_STUDIO" + ], + "max":64, + "min":1 + }, + "IdeDiagnostic":{ + "type":"structure", + "required":["ideDiagnosticType"], + "members":{ + "range":{"shape":"Range"}, + "source":{"shape":"IdeDiagnosticSourceString"}, + "severity":{"shape":"DiagnosticSeverity"}, + "ideDiagnosticType":{"shape":"IdeDiagnosticType"} + } + }, + "IdeDiagnosticList":{ + "type":"list", + "member":{"shape":"IdeDiagnostic"}, + "max":1024, + "min":0 + }, + "IdeDiagnosticSourceString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "IdeDiagnosticType":{ + "type":"string", + "enum":[ + "SYNTAX_ERROR", + "TYPE_ERROR", + "REFERENCE_ERROR", + "BEST_PRACTICE", + "SECURITY", + "OTHER" + ] + }, + "IdempotencyToken":{ + "type":"string", + "max":256, + "min":1 + }, + "IdentityDetails":{ + "type":"structure", + "members":{ + "ssoIdentityDetails":{"shape":"SSOIdentityDetails"}, + "externalIdentityDetails":{"shape":"ExternalIdentityDetails"} + }, + "union":true + }, + "ImageBlock":{ + "type":"structure", + "required":[ + "format", + "source" + ], + "members":{ + "format":{"shape":"ImageFormat"}, + "source":{"shape":"ImageSource"} + } + }, + "ImageBlocks":{ + "type":"list", + "member":{"shape":"ImageBlock"}, + "max":10, + "min":0 + }, + "ImageFormat":{ + "type":"string", + "enum":[ + "png", + "jpeg", + "gif", + "webp" + ] + }, + "ImageSource":{ + "type":"structure", + "members":{ + "bytes":{"shape":"ImageSourceBytesBlob"} + }, + "sensitive":true, + "union":true + }, + "ImageSourceBytesBlob":{ + "type":"blob", + "max":1500000, + "min":1 + }, + "Import":{ + "type":"structure", + "members":{ + "statement":{"shape":"ImportStatementString"} + } + }, + "ImportStatementString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "Imports":{ + "type":"list", + "member":{"shape":"Import"}, + "max":10, + "min":0 + }, + "InlineChatEvent":{ + "type":"structure", + "required":[ + "requestId", + "timestamp" + ], + "members":{ + "requestId":{"shape":"UUID"}, + "timestamp":{"shape":"Timestamp"}, + "inputLength":{"shape":"PrimitiveInteger"}, + "numSelectedLines":{"shape":"PrimitiveInteger"}, + "numSuggestionAddChars":{"shape":"PrimitiveInteger"}, + "numSuggestionAddLines":{"shape":"PrimitiveInteger"}, + "numSuggestionDelChars":{"shape":"PrimitiveInteger"}, + "numSuggestionDelLines":{"shape":"PrimitiveInteger"}, + "codeIntent":{"shape":"Boolean"}, + "userDecision":{"shape":"InlineChatUserDecision"}, + "responseStartLatency":{"shape":"Double"}, + "responseEndLatency":{"shape":"Double"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"} + } + }, + "InlineChatUserDecision":{ + "type":"string", + "enum":[ + "ACCEPT", + "REJECT", + "DISMISS" + ] + }, + "Integer":{ + "type":"integer", + "box":true + }, + "Intent":{ + "type":"string", + "enum":[ + "DEV", + "DOC" + ] + }, + "IntentContext":{ + "type":"structure", + "members":{ + "documentation":{"shape":"DocumentationIntentContext"} + }, + "union":true + }, + "InternalServerException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"} + }, + "exception":true, + "fault":true, + "retryable":{"throttling":false} + }, + "IssuerUrl":{ + "type":"string", + "max":255, + "min":1 + }, + "LineRangeList":{ + "type":"list", + "member":{"shape":"Range"} + }, + "ListAvailableCustomizationsRequest":{ + "type":"structure", + "members":{ + "maxResults":{"shape":"ListAvailableCustomizationsRequestMaxResultsInteger"}, + "nextToken":{"shape":"Base64EncodedPaginationToken"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "ListAvailableCustomizationsRequestMaxResultsInteger":{ + "type":"integer", + "box":true, + "max":100, + "min":1 + }, + "ListAvailableCustomizationsResponse":{ + "type":"structure", + "required":["customizations"], + "members":{ + "customizations":{"shape":"Customizations"}, + "nextToken":{"shape":"Base64EncodedPaginationToken"} + } + }, + "ListAvailableProfilesRequest":{ + "type":"structure", + "members":{ + "maxResults":{"shape":"ListAvailableProfilesRequestMaxResultsInteger"}, + "nextToken":{"shape":"Base64EncodedPaginationToken"} + } + }, + "ListAvailableProfilesRequestMaxResultsInteger":{ + "type":"integer", + "box":true, + "max":10, + "min":1 + }, + "ListAvailableProfilesResponse":{ + "type":"structure", + "required":["profiles"], + "members":{ + "profiles":{"shape":"ProfileList"}, + "nextToken":{"shape":"Base64EncodedPaginationToken"} + } + }, + "ListCodeAnalysisFindingsRequest":{ + "type":"structure", + "required":[ + "jobId", + "codeAnalysisFindingsSchema" + ], + "members":{ + "jobId":{"shape":"ListCodeAnalysisFindingsRequestJobIdString"}, + "nextToken":{"shape":"PaginationToken"}, + "codeAnalysisFindingsSchema":{"shape":"CodeAnalysisFindingsSchema"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "ListCodeAnalysisFindingsRequestJobIdString":{ + "type":"string", + "max":256, + "min":1 + }, + "ListCodeAnalysisFindingsResponse":{ + "type":"structure", + "required":["codeAnalysisFindings"], + "members":{ + "nextToken":{"shape":"PaginationToken"}, + "codeAnalysisFindings":{"shape":"SensitiveString"} + } + }, + "ListEventsRequest":{ + "type":"structure", + "required":["conversationId"], + "members":{ + "conversationId":{"shape":"UUID"}, + "maxResults":{"shape":"ListEventsRequestMaxResultsInteger"}, + "nextToken":{"shape":"NextToken"} + } + }, + "ListEventsRequestMaxResultsInteger":{ + "type":"integer", + "box":true, + "max":50, + "min":1 + }, + "ListEventsResponse":{ + "type":"structure", + "required":[ + "conversationId", + "events" + ], + "members":{ + "conversationId":{"shape":"UUID"}, + "events":{"shape":"EventList"}, + "nextToken":{"shape":"NextToken"} + } + }, + "ListFeatureEvaluationsRequest":{ + "type":"structure", + "required":["userContext"], + "members":{ + "userContext":{"shape":"UserContext"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "ListFeatureEvaluationsResponse":{ + "type":"structure", + "required":["featureEvaluations"], + "members":{ + "featureEvaluations":{"shape":"FeatureEvaluationsList"} + } + }, + "ListWorkspaceMetadataRequest":{ + "type":"structure", + "required":["workspaceRoot"], + "members":{ + "workspaceRoot":{"shape":"ListWorkspaceMetadataRequestWorkspaceRootString"}, + "nextToken":{"shape":"String"}, + "maxResults":{"shape":"Integer"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "ListWorkspaceMetadataRequestWorkspaceRootString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "ListWorkspaceMetadataResponse":{ + "type":"structure", + "required":["workspaces"], + "members":{ + "workspaces":{"shape":"WorkspaceList"}, + "nextToken":{"shape":"String"} + } + }, + "Long":{ + "type":"long", + "box":true + }, + "MessageId":{ + "type":"string", + "max":128, + "min":0 + }, + "MetricData":{ + "type":"structure", + "required":[ + "metricName", + "metricValue", + "timestamp", + "product" + ], + "members":{ + "metricName":{"shape":"MetricDataMetricNameString"}, + "metricValue":{"shape":"Double"}, + "timestamp":{"shape":"Timestamp"}, + "product":{"shape":"MetricDataProductString"}, + "dimensions":{"shape":"DimensionList"} + } + }, + "MetricDataMetricNameString":{ + "type":"string", + "max":1024, + "min":1, + "pattern":"[-a-zA-Z0-9._]*" + }, + "MetricDataProductString":{ + "type":"string", + "max":128, + "min":1, + "pattern":"[-a-zA-Z0-9._]*" + }, + "NextToken":{ + "type":"string", + "max":1000, + "min":0 + }, + "Notifications":{ + "type":"list", + "member":{"shape":"NotificationsFeature"}, + "max":10, + "min":0 + }, + "NotificationsFeature":{ + "type":"structure", + "required":[ + "feature", + "toggle" + ], + "members":{ + "feature":{"shape":"FeatureName"}, + "toggle":{"shape":"OptInFeatureToggle"} + } + }, + "OperatingSystem":{ + "type":"string", + "enum":[ + "MAC", + "WINDOWS", + "LINUX" + ], + "max":64, + "min":1 + }, + "OptInFeatureToggle":{ + "type":"string", + "enum":[ + "ON", + "OFF" + ] + }, + "OptInFeatures":{ + "type":"structure", + "members":{ + "promptLogging":{"shape":"PromptLogging"}, + "byUserAnalytics":{"shape":"ByUserAnalytics"}, + "dashboardAnalytics":{"shape":"DashboardAnalytics"}, + "notifications":{"shape":"Notifications"}, + "workspaceContext":{"shape":"WorkspaceContext"} + } + }, + "OptOutPreference":{ + "type":"string", + "enum":[ + "OPTIN", + "OPTOUT" + ] + }, + "Origin":{ + "type":"string", + "enum":[ + "CHATBOT", + "CONSOLE", + "DOCUMENTATION", + "MARKETING", + "MOBILE", + "SERVICE_INTERNAL", + "UNIFIED_SEARCH", + "UNKNOWN", + "MD", + "IDE", + "SAGE_MAKER", + "CLI", + "AI_EDITOR", + "OPENSEARCH_DASHBOARD", + "GITLAB" + ] + }, + "PackageInfo":{ + "type":"structure", + "members":{ + "executionCommand":{"shape":"SensitiveString"}, + "buildCommand":{"shape":"SensitiveString"}, + "buildOrder":{"shape":"PackageInfoBuildOrderInteger"}, + "testFramework":{"shape":"String"}, + "packageSummary":{"shape":"PackageInfoPackageSummaryString"}, + "packagePlan":{"shape":"PackageInfoPackagePlanString"}, + "targetFileInfoList":{"shape":"TargetFileInfoList"} + } + }, + "PackageInfoBuildOrderInteger":{ + "type":"integer", + "box":true, + "min":0 + }, + "PackageInfoList":{ + "type":"list", + "member":{"shape":"PackageInfo"} + }, + "PackageInfoPackagePlanString":{ + "type":"string", + "max":30720, + "min":0, + "sensitive":true + }, + "PackageInfoPackageSummaryString":{ + "type":"string", + "max":30720, + "min":0, + "sensitive":true + }, + "PaginationToken":{ + "type":"string", + "max":2048, + "min":1, + "pattern":"\\S+" + }, + "Position":{ + "type":"structure", + "required":[ + "line", + "character" + ], + "members":{ + "line":{"shape":"Integer"}, + "character":{"shape":"Integer"} + } + }, + "PreSignedUrl":{ + "type":"string", + "max":2048, + "min":1, + "sensitive":true + }, + "PrimitiveInteger":{"type":"integer"}, + "Profile":{ + "type":"structure", + "required":[ + "arn", + "profileName" + ], + "members":{ + "arn":{"shape":"ProfileArn"}, + "identityDetails":{"shape":"IdentityDetails"}, + "profileName":{"shape":"ProfileName"}, + "description":{"shape":"ProfileDescription"}, + "referenceTrackerConfiguration":{"shape":"ReferenceTrackerConfiguration"}, + "kmsKeyArn":{"shape":"ResourceArn"}, + "activeFunctionalities":{"shape":"ActiveFunctionalityList"}, + "status":{"shape":"ProfileStatus"}, + "errorDetails":{"shape":"ErrorDetails"}, + "resourcePolicy":{"shape":"ResourcePolicy"}, + "profileType":{"shape":"ProfileType"}, + "optInFeatures":{"shape":"OptInFeatures"}, + "permissionUpdateRequired":{"shape":"Boolean"}, + "applicationProperties":{"shape":"ApplicationPropertiesList"} + } + }, + "ProfileArn":{ + "type":"string", + "max":950, + "min":0, + "pattern":"arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}" + }, + "ProfileDescription":{ + "type":"string", + "max":256, + "min":1, + "pattern":"[\\sa-zA-Z0-9_-]*" + }, + "ProfileList":{ + "type":"list", + "member":{"shape":"Profile"} + }, + "ProfileName":{ + "type":"string", + "max":100, + "min":1, + "pattern":"[a-zA-Z][a-zA-Z0-9_-]*" + }, + "ProfileStatus":{ + "type":"string", + "enum":[ + "ACTIVE", + "CREATING", + "CREATE_FAILED", + "UPDATING", + "UPDATE_FAILED", + "DELETING", + "DELETE_FAILED" + ] + }, + "ProfileType":{ + "type":"string", + "enum":[ + "Q_DEVELOPER", + "CODEWHISPERER" + ] + }, + "ProgrammingLanguage":{ + "type":"structure", + "required":["languageName"], + "members":{ + "languageName":{"shape":"ProgrammingLanguageLanguageNameString"} + } + }, + "ProgrammingLanguageLanguageNameString":{ + "type":"string", + "max":128, + "min":1, + "pattern":"(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext|systemverilog|dart|lua|swift|powershell|r)" + }, + "ProgressUpdates":{ + "type":"list", + "member":{"shape":"TransformationProgressUpdate"} + }, + "PromptLogging":{ + "type":"structure", + "required":[ + "s3Uri", + "toggle" + ], + "members":{ + "s3Uri":{"shape":"S3Uri"}, + "toggle":{"shape":"OptInFeatureToggle"} + } + }, + "Range":{ + "type":"structure", + "required":[ + "start", + "end" + ], + "members":{ + "start":{"shape":"Position"}, + "end":{"shape":"Position"} + } + }, + "RecommendationsWithReferencesPreference":{ + "type":"string", + "enum":[ + "BLOCK", + "ALLOW" + ] + }, + "Reference":{ + "type":"structure", + "members":{ + "licenseName":{"shape":"ReferenceLicenseNameString"}, + "repository":{"shape":"ReferenceRepositoryString"}, + "url":{"shape":"ReferenceUrlString"}, + "recommendationContentSpan":{"shape":"Span"} + } + }, + "ReferenceLicenseNameString":{ + "type":"string", + "max":1024, + "min":1 + }, + "ReferenceRepositoryString":{ + "type":"string", + "max":1024, + "min":1 + }, + "ReferenceTrackerConfiguration":{ + "type":"structure", + "required":["recommendationsWithReferences"], + "members":{ + "recommendationsWithReferences":{"shape":"RecommendationsWithReferencesPreference"} + } + }, + "ReferenceUrlString":{ + "type":"string", + "max":1024, + "min":1 + }, + "References":{ + "type":"list", + "member":{"shape":"Reference"}, + "max":10, + "min":0 + }, + "RelevantDocumentList":{ + "type":"list", + "member":{"shape":"RelevantTextDocument"}, + "max":30, + "min":0 + }, + "RelevantTextDocument":{ + "type":"structure", + "required":["relativeFilePath"], + "members":{ + "relativeFilePath":{"shape":"RelevantTextDocumentRelativeFilePathString"}, + "programmingLanguage":{"shape":"ProgrammingLanguage"}, + "text":{"shape":"RelevantTextDocumentTextString"}, + "documentSymbols":{"shape":"DocumentSymbols"} + } + }, + "RelevantTextDocumentRelativeFilePathString":{ + "type":"string", + "max":4096, + "min":1, + "sensitive":true + }, + "RelevantTextDocumentTextString":{ + "type":"string", + "max":40960, + "min":0, + "sensitive":true + }, + "RequestHeaderKey":{ + "type":"string", + "max":64, + "min":1 + }, + "RequestHeaderValue":{ + "type":"string", + "max":256, + "min":1 + }, + "RequestHeaders":{ + "type":"map", + "key":{"shape":"RequestHeaderKey"}, + "value":{"shape":"RequestHeaderValue"}, + "max":16, + "min":1, + "sensitive":true + }, + "ResourceArn":{ + "type":"string", + "max":1224, + "min":0, + "pattern":"arn:([-.a-z0-9]{1,63}:){2}([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}" + }, + "ResourceNotFoundException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"} + }, + "exception":true + }, + "ResourcePolicy":{ + "type":"structure", + "required":["effect"], + "members":{ + "effect":{"shape":"ResourcePolicyEffect"} + } + }, + "ResourcePolicyEffect":{ + "type":"string", + "enum":[ + "ALLOW", + "DENY" + ] + }, + "ResumeTransformationRequest":{ + "type":"structure", + "required":["transformationJobId"], + "members":{ + "transformationJobId":{"shape":"TransformationJobId"}, + "userActionStatus":{"shape":"TransformationUserActionStatus"}, + "profileArn":{"shape":"ProfileArn"} + } + }, + "ResumeTransformationResponse":{ + "type":"structure", + "required":["transformationStatus"], + "members":{ + "transformationStatus":{"shape":"TransformationStatus"} + } + }, + "RuntimeDiagnostic":{ + "type":"structure", + "required":[ + "source", + "severity", + "message" + ], + "members":{ + "source":{"shape":"RuntimeDiagnosticSourceString"}, + "severity":{"shape":"DiagnosticSeverity"}, + "message":{"shape":"RuntimeDiagnosticMessageString"} + } + }, + "RuntimeDiagnosticMessageString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "RuntimeDiagnosticSourceString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "S3Uri":{ + "type":"string", + "max":1024, + "min":1, + "pattern":"s3://((?!xn--)[a-z0-9](?![^/]*[.]{2})[a-z0-9-.]{1,61}[a-z0-9](?API to export operation result as an archive

" + }, + "GenerateAssistantResponse":{ + "name":"GenerateAssistantResponse", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GenerateAssistantResponseRequest"}, + "output":{"shape":"GenerateAssistantResponseResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "documentation":"

API to generate assistant response.

" + }, + "GenerateTaskAssistPlan":{ + "name":"GenerateTaskAssistPlan", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"GenerateTaskAssistPlanRequest"}, + "output":{"shape":"GenerateTaskAssistPlanResponse"}, + "errors":[ + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"ServiceQuotaExceededException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ], + "documentation":"

API to generate task assist plan.

" + }, + "SendMessage":{ + "name":"SendMessage", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"SendMessageRequest"}, + "output":{"shape":"SendMessageResponse"}, + "errors":[ + {"shape":"DryRunOperationException"}, + {"shape":"ThrottlingException"}, + {"shape":"ConflictException"}, + {"shape":"ServiceQuotaExceededException"}, + {"shape":"ResourceNotFoundException"}, + {"shape":"InternalServerException"}, + {"shape":"ValidationException"}, + {"shape":"AccessDeniedException"} + ] + } + }, + "shapes":{ + "AccessDeniedException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"}, + "reason":{"shape":"AccessDeniedExceptionReason"} + }, + "documentation":"

This exception is thrown when the user does not have sufficient access to perform this action.

", + "exception":true + }, + "AccessDeniedExceptionReason":{ + "type":"string", + "documentation":"

Reason for AccessDeniedException

", + "enum":["UNAUTHORIZED_CUSTOMIZATION_RESOURCE_ACCESS"] + }, + "Action":{ + "type":"structure", + "members":{ + "webLink":{"shape":"WebLink"}, + "moduleLink":{"shape":"ModuleLink"} + } + }, + "AdditionalContentEntry":{ + "type":"structure", + "required":[ + "name", + "description" + ], + "members":{ + "name":{ + "shape":"AdditionalContentEntryNameString", + "documentation":"

The name/identifier for this context entry

" + }, + "description":{ + "shape":"AdditionalContentEntryDescriptionString", + "documentation":"

A description of what this context entry represents

" + }, + "innerContext":{ + "shape":"AdditionalContentEntryInnerContextString", + "documentation":"

The actual contextual content

" + } + }, + "documentation":"

Structure representing a single entry of additional contextual content

" + }, + "AdditionalContentEntryDescriptionString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AdditionalContentEntryInnerContextString":{ + "type":"string", + "max":8192, + "min":1, + "sensitive":true + }, + "AdditionalContentEntryNameString":{ + "type":"string", + "max":1024, + "min":1, + "pattern":"[a-z]+(?:-[a-z0-9]+)*", + "sensitive":true + }, + "AdditionalContentList":{ + "type":"list", + "member":{"shape":"AdditionalContentEntry"}, + "documentation":"

A list of additional content entries, limited to 20 items

", + "max":20, + "min":0 + }, + "Alert":{ + "type":"structure", + "required":[ + "type", + "content" + ], + "members":{ + "type":{"shape":"AlertType"}, + "content":{ + "shape":"AlertComponentList", + "documentation":"

Contains the content of the alert, which may include sensitive information.

" + } + }, + "documentation":"

Structure representing an alert with a type and content.

" + }, + "AlertComponent":{ + "type":"structure", + "members":{ + "text":{"shape":"Text"} + } + }, + "AlertComponentList":{ + "type":"list", + "member":{"shape":"AlertComponent"} + }, + "AlertType":{ + "type":"string", + "documentation":"

Enum defining types of alerts that can be issued.

", + "enum":[ + "INFO", + "ERROR", + "WARNING" + ] + }, + "AppStudioState":{ + "type":"structure", + "required":[ + "namespace", + "propertyName", + "propertyContext" + ], + "members":{ + "namespace":{ + "shape":"AppStudioStateNamespaceString", + "documentation":"

The namespace of the context. Examples: 'ui.Button', 'ui.Table.DataSource', 'ui.Table.RowActions.Button', 'logic.invokeAWS', 'logic.JavaScript'

" + }, + "propertyName":{ + "shape":"AppStudioStatePropertyNameString", + "documentation":"

The name of the property. Examples: 'visibility', 'disability', 'value', 'code'

" + }, + "propertyValue":{ + "shape":"AppStudioStatePropertyValueString", + "documentation":"

The value of the property.

" + }, + "propertyContext":{ + "shape":"AppStudioStatePropertyContextString", + "documentation":"

Context about how the property is used

" + } + }, + "documentation":"

Description of a user's context when they are calling Q Chat from AppStudio

" + }, + "AppStudioStateNamespaceString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AppStudioStatePropertyContextString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AppStudioStatePropertyNameString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "AppStudioStatePropertyValueString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "ArtifactId":{ + "type":"string", + "max":126, + "min":1, + "pattern":"[a-zA-Z0-9-_]+" + }, + "AssistantResponseEvent":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"AssistantResponseEventContentString", + "documentation":"

The content of the text message in markdown format.

" + } + }, + "documentation":"

Streaming Response Event for Assistant Markdown text message.

", + "event":true + }, + "AssistantResponseEventContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "AssistantResponseMessage":{ + "type":"structure", + "required":["content"], + "members":{ + "messageId":{"shape":"MessageId"}, + "content":{ + "shape":"AssistantResponseMessageContentString", + "documentation":"

The content of the text message in markdown format.

" + }, + "supplementaryWebLinks":{ + "shape":"SupplementaryWebLinks", + "documentation":"

Web References

" + }, + "references":{ + "shape":"References", + "documentation":"

Code References

" + }, + "followupPrompt":{ + "shape":"FollowupPrompt", + "documentation":"

Followup Prompt

" + }, + "toolUses":{ + "shape":"ToolUses", + "documentation":"

ToolUse Request

" + } + }, + "documentation":"

Markdown text message.

" + }, + "AssistantResponseMessageContentString":{ + "type":"string", + "max":100000, + "min":0, + "sensitive":true + }, + "BinaryMetadataEvent":{ + "type":"structure", + "members":{ + "size":{ + "shape":"Long", + "documentation":"

Content length of the binary payload

" + }, + "mimeType":{ + "shape":"String", + "documentation":"

Content type of the response

" + }, + "contentChecksum":{ + "shape":"ContentChecksum", + "documentation":"

Content checksum of the binary payload

" + }, + "contentChecksumType":{ + "shape":"ContentChecksumType", + "documentation":"

Content checksum type of the binary payload

" + } + }, + "documentation":"

Payload Part

", + "event":true, + "sensitive":true + }, + "BinaryPayloadEvent":{ + "type":"structure", + "members":{ + "bytes":{"shape":"PartBody"} + }, + "documentation":"

Payload Part

", + "event":true, + "sensitive":true + }, + "Boolean":{ + "type":"boolean", + "box":true + }, + "ChatHistory":{ + "type":"list", + "member":{"shape":"ChatMessage"}, + "documentation":"

Indicates Participant in Chat conversation

", + "max":250, + "min":0 + }, + "ChatMessage":{ + "type":"structure", + "members":{ + "userInputMessage":{"shape":"UserInputMessage"}, + "assistantResponseMessage":{"shape":"AssistantResponseMessage"} + }, + "union":true + }, + "ChatResponseStream":{ + "type":"structure", + "members":{ + "messageMetadataEvent":{ + "shape":"MessageMetadataEvent", + "documentation":"

Message Metadata event

" + }, + "assistantResponseEvent":{ + "shape":"AssistantResponseEvent", + "documentation":"

Assistant response event - Text / Code snippet

" + }, + "dryRunSucceedEvent":{ + "shape":"DryRunSucceedEvent", + "documentation":"

DryRun Succeed Event

" + }, + "codeReferenceEvent":{ + "shape":"CodeReferenceEvent", + "documentation":"

Code References event

" + }, + "supplementaryWebLinksEvent":{ + "shape":"SupplementaryWebLinksEvent", + "documentation":"

Web Reference links event

" + }, + "followupPromptEvent":{ + "shape":"FollowupPromptEvent", + "documentation":"

Followup prompt event

" + }, + "codeEvent":{ + "shape":"CodeEvent", + "documentation":"

Code Generated event

" + }, + "intentsEvent":{ + "shape":"IntentsEvent", + "documentation":"

Intents event

" + }, + "interactionComponentsEvent":{ + "shape":"InteractionComponentsEvent", + "documentation":"

Interactions components event

" + }, + "toolUseEvent":{ + "shape":"ToolUseEvent", + "documentation":"

ToolUse event

" + }, + "toolResultEvent":{ + "shape":"ToolResultEvent", + "documentation":"

Tool use result

" + }, + "citationEvent":{ + "shape":"CitationEvent", + "documentation":"

Citation event

" + }, + "invalidStateEvent":{ + "shape":"InvalidStateEvent", + "documentation":"

Invalid State event

" + }, + "error":{ + "shape":"InternalServerException", + "documentation":"

Internal Server Exception

" + } + }, + "documentation":"

Streaming events from UniDirectional Streaming Conversational APIs.

", + "eventstream":true + }, + "ChatTriggerType":{ + "type":"string", + "documentation":"

Trigger Reason for Chat

", + "enum":[ + "MANUAL", + "DIAGNOSTIC", + "INLINE_CHAT" + ] + }, + "CitationEvent":{ + "type":"structure", + "required":[ + "target", + "citationLink" + ], + "members":{ + "target":{ + "shape":"CitationTarget", + "documentation":"

The position or the range of the response text to be cited

" + }, + "citationText":{ + "shape":"SensitiveString", + "documentation":"

The text inside the citation '1' in [1]

" + }, + "citationLink":{ + "shape":"SensitiveString", + "documentation":"

The link to the document being cited

" + } + }, + "documentation":"

Streaming response event for citations

", + "event":true + }, + "CitationTarget":{ + "type":"structure", + "members":{ + "location":{ + "shape":"Offset", + "documentation":"

Represents a position in the response text where a citation should be added

" + }, + "range":{ + "shape":"Span", + "documentation":"

Represents the range in the response text to be targetted by a citation

" + } + }, + "documentation":"

Represents the target of a citation event

", + "union":true + }, + "CloudWatchTroubleshootingLink":{ + "type":"structure", + "required":[ + "label", + "investigationPayload" + ], + "members":{ + "label":{ + "shape":"CloudWatchTroubleshootingLinkLabelString", + "documentation":"

A label for the link.

" + }, + "investigationPayload":{ + "shape":"CloudWatchTroubleshootingLinkInvestigationPayloadString", + "documentation":"

Stringified JSON payload. See spec here https://code.amazon.com/packages/CloudWatchOdysseyModel/blobs/50c0832f0e393e4ab68827eb4f04d832366821c1/--/model/events.smithy#L28 .

" + }, + "defaultText":{ + "shape":"CloudWatchTroubleshootingLinkDefaultTextString", + "documentation":"

Fallback string, if target channel does not support the CloudWatchTroubleshootingLink.

" + } + }, + "documentation":"

For CloudWatch Troubleshooting Link Module

" + }, + "CloudWatchTroubleshootingLinkDefaultTextString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "CloudWatchTroubleshootingLinkInvestigationPayloadString":{ + "type":"string", + "max":16384, + "min":0, + "sensitive":true + }, + "CloudWatchTroubleshootingLinkLabelString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "CodeDescription":{ + "type":"structure", + "required":["href"], + "members":{ + "href":{ + "shape":"CodeDescriptionHrefString", + "documentation":"

An URI to open with more information about the diagnostic error.

" + } + }, + "documentation":"

Structure to capture a description for an error code.

" + }, + "CodeDescriptionHrefString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "CodeEvent":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"CodeEventContentString", + "documentation":"

Generated code snippet.

" + } + }, + "documentation":"

Streaming response event for generated code text.

", + "event":true + }, + "CodeEventContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "CodeReferenceEvent":{ + "type":"structure", + "members":{ + "references":{ + "shape":"References", + "documentation":"

Code References for Assistant Response Message

" + } + }, + "documentation":"

Streaming Response Event for CodeReferences

", + "event":true + }, + "ConflictException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"}, + "reason":{"shape":"ConflictExceptionReason"} + }, + "documentation":"

This exception is thrown when the action to perform could not be completed because the resource is in a conflicting state.

", + "exception":true + }, + "ConflictExceptionReason":{ + "type":"string", + "documentation":"

Reason for ConflictException

", + "enum":[ + "CUSTOMER_KMS_KEY_INVALID_KEY_POLICY", + "CUSTOMER_KMS_KEY_DISABLED", + "MISMATCHED_KMS_KEY" + ] + }, + "ConsoleState":{ + "type":"structure", + "members":{ + "region":{"shape":"String"}, + "consoleUrl":{"shape":"SensitiveString"}, + "serviceId":{"shape":"String"}, + "serviceConsolePage":{"shape":"String"}, + "serviceSubconsolePage":{"shape":"String"}, + "taskName":{"shape":"SensitiveString"} + }, + "documentation":"

Information about the state of the AWS management console page from which the user is calling

" + }, + "ContentChecksum":{ + "type":"string", + "max":512, + "min":1 + }, + "ContentChecksumType":{ + "type":"string", + "enum":["SHA_256"] + }, + "ContextTruncationScheme":{ + "type":"string", + "documentation":"

Workspace context truncation schemes based on usecase

", + "enum":[ + "ANALYSIS", + "GUMBY" + ] + }, + "ConversationId":{ + "type":"string", + "documentation":"

ID which represents a multi-turn conversation

", + "max":128, + "min":1 + }, + "ConversationState":{ + "type":"structure", + "required":[ + "currentMessage", + "chatTriggerType" + ], + "members":{ + "conversationId":{ + "shape":"ConversationId", + "documentation":"

Unique identifier for the chat conversation stream

" + }, + "history":{ + "shape":"ChatHistory", + "documentation":"

Holds the history of chat messages.

" + }, + "currentMessage":{ + "shape":"ChatMessage", + "documentation":"

Holds the current message being processed or displayed.

" + }, + "chatTriggerType":{ + "shape":"ChatTriggerType", + "documentation":"

Trigger Reason for Chat

" + }, + "customizationArn":{"shape":"ResourceArn"} + }, + "documentation":"

Structure to represent the current state of a chat conversation.

" + }, + "CursorState":{ + "type":"structure", + "members":{ + "position":{ + "shape":"Position", + "documentation":"

Represents a cursor position in a Text Document

" + }, + "range":{ + "shape":"Range", + "documentation":"

Represents a text selection in a Text Document

" + } + }, + "documentation":"

Represents the state of the Cursor in an Editor

", + "union":true + }, + "Diagnostic":{ + "type":"structure", + "members":{ + "textDocumentDiagnostic":{ + "shape":"TextDocumentDiagnostic", + "documentation":"

Diagnostics originating from a TextDocument

" + }, + "runtimeDiagnostic":{ + "shape":"RuntimeDiagnostic", + "documentation":"

Diagnostics originating from a Runtime

" + } + }, + "documentation":"

Represents a Diagnostic message

", + "union":true + }, + "DiagnosticLocation":{ + "type":"structure", + "required":[ + "uri", + "range" + ], + "members":{ + "uri":{"shape":"DiagnosticLocationUriString"}, + "range":{"shape":"Range"} + }, + "documentation":"

Represents a location inside a resource, such as a line inside a text file.

" + }, + "DiagnosticLocationUriString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "DiagnosticRelatedInformation":{ + "type":"structure", + "required":[ + "location", + "message" + ], + "members":{ + "location":{ + "shape":"DiagnosticLocation", + "documentation":"

The location of this related diagnostic information.

" + }, + "message":{ + "shape":"DiagnosticRelatedInformationMessageString", + "documentation":"

The message of this related diagnostic information.

" + } + }, + "documentation":"

Represents a related message and source code location for a diagnostic.

" + }, + "DiagnosticRelatedInformationList":{ + "type":"list", + "member":{"shape":"DiagnosticRelatedInformation"}, + "documentation":"

List of DiagnosticRelatedInformation

", + "max":1024, + "min":0 + }, + "DiagnosticRelatedInformationMessageString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "DiagnosticSeverity":{ + "type":"string", + "documentation":"

Diagnostic Error types

", + "enum":[ + "ERROR", + "WARNING", + "INFORMATION", + "HINT" + ] + }, + "DiagnosticTag":{ + "type":"string", + "documentation":"

The diagnostic tags.

", + "enum":[ + "UNNECESSARY", + "DEPRECATED" + ] + }, + "DiagnosticTagList":{ + "type":"list", + "member":{"shape":"DiagnosticTag"}, + "documentation":"

List of DiagnosticTag

", + "max":1024, + "min":0 + }, + "DocumentSymbol":{ + "type":"structure", + "required":[ + "name", + "type" + ], + "members":{ + "name":{ + "shape":"DocumentSymbolNameString", + "documentation":"

Name of the Document Symbol

" + }, + "type":{ + "shape":"SymbolType", + "documentation":"

Symbol type - DECLARATION / USAGE

" + }, + "source":{ + "shape":"DocumentSymbolSourceString", + "documentation":"

Symbol package / source for FullyQualified names

" + } + } + }, + "DocumentSymbolNameString":{ + "type":"string", + "max":256, + "min":1 + }, + "DocumentSymbolSourceString":{ + "type":"string", + "max":256, + "min":1 + }, + "DocumentSymbols":{ + "type":"list", + "member":{"shape":"DocumentSymbol"}, + "max":1000, + "min":0 + }, + "DryRunOperationException":{ + "type":"structure", + "members":{ + "message":{"shape":"String"}, + "responseCode":{"shape":"Integer"} + }, + "documentation":"

This exception is translated to a 204 as it succeeded the IAM Auth.

", + "exception":true + }, + "DryRunSucceedEvent":{ + "type":"structure", + "members":{ + }, + "documentation":"

Streaming Response Event when DryRun is succeessful

", + "event":true + }, + "EditorState":{ + "type":"structure", + "members":{ + "document":{ + "shape":"TextDocument", + "documentation":"

Represents currently edited file

" + }, + "cursorState":{ + "shape":"CursorState", + "documentation":"

Position of the cursor

" + }, + "relevantDocuments":{ + "shape":"RelevantDocumentList", + "documentation":"

Represents IDE provided relevant files

" + }, + "useRelevantDocuments":{ + "shape":"Boolean", + "documentation":"

Whether service should use relevant document in prompt

" + }, + "workspaceFolders":{ + "shape":"WorkspaceFolderList", + "documentation":"

Represents IDE provided list of workspace folders

" + } + }, + "documentation":"

Represents the state of an Editor

" + }, + "EnvState":{ + "type":"structure", + "members":{ + "operatingSystem":{ + "shape":"EnvStateOperatingSystemString", + "documentation":"

The name of the operating system in use

" + }, + "currentWorkingDirectory":{ + "shape":"EnvStateCurrentWorkingDirectoryString", + "documentation":"

The current working directory of the environment

" + }, + "environmentVariables":{ + "shape":"EnvironmentVariables", + "documentation":"

The environment variables set in the current environment

" + }, + "timezoneOffset":{ + "shape":"EnvStateTimezoneOffsetInteger", + "documentation":"

Local timezone offset of the client. For more information, see documentation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset

" + } + }, + "documentation":"

State related to the user's environment

" + }, + "EnvStateCurrentWorkingDirectoryString":{ + "type":"string", + "max":256, + "min":1, + "sensitive":true + }, + "EnvStateOperatingSystemString":{ + "type":"string", + "max":32, + "min":1, + "pattern":"(macos|linux|windows)" + }, + "EnvStateTimezoneOffsetInteger":{ + "type":"integer", + "box":true, + "max":1440, + "min":-1440 + }, + "EnvironmentVariable":{ + "type":"structure", + "members":{ + "key":{ + "shape":"EnvironmentVariableKeyString", + "documentation":"

The key of an environment variable

" + }, + "value":{ + "shape":"EnvironmentVariableValueString", + "documentation":"

The value of an environment variable

" + } + }, + "documentation":"

An environment variable

" + }, + "EnvironmentVariableKeyString":{ + "type":"string", + "max":256, + "min":1, + "sensitive":true + }, + "EnvironmentVariableValueString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "EnvironmentVariables":{ + "type":"list", + "member":{"shape":"EnvironmentVariable"}, + "documentation":"

A list of environment variables

", + "max":100, + "min":0 + }, + "ExportContext":{ + "type":"structure", + "members":{ + "transformationExportContext":{"shape":"TransformationExportContext"}, + "unitTestGenerationExportContext":{"shape":"UnitTestGenerationExportContext"} + }, + "documentation":"

Export Context

", + "union":true + }, + "ExportIntent":{ + "type":"string", + "documentation":"

Export Intent

", + "enum":[ + "TRANSFORMATION", + "TASK_ASSIST", + "UNIT_TESTS" + ] + }, + "ExportResultArchiveRequest":{ + "type":"structure", + "required":[ + "exportId", + "exportIntent" + ], + "members":{ + "exportId":{"shape":"ExportResultArchiveRequestExportIdString"}, + "exportIntent":{"shape":"ExportIntent"}, + "exportContext":{"shape":"ExportContext"}, + "profileArn":{"shape":"ProfileArn"} + }, + "documentation":"

Structure to represent a new ExportResultArchive request.

" + }, + "ExportResultArchiveRequestExportIdString":{ + "type":"string", + "max":1024, + "min":0 + }, + "ExportResultArchiveResponse":{ + "type":"structure", + "required":["body"], + "members":{ + "body":{"shape":"ResultArchiveStream"} + }, + "documentation":"

Structure to represent ExportResultArchive response.

" + }, + "FollowupPrompt":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"FollowupPromptContentString", + "documentation":"

The content of the text message in markdown format.

" + }, + "userIntent":{ + "shape":"UserIntent", + "documentation":"

User Intent

" + } + }, + "documentation":"

Followup Prompt for the Assistant Response

" + }, + "FollowupPromptContentString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "FollowupPromptEvent":{ + "type":"structure", + "members":{ + "followupPrompt":{"shape":"FollowupPrompt"} + }, + "documentation":"

Streaming Response Event for Followup Prompt.

", + "event":true + }, + "GenerateAssistantResponseRequest":{ + "type":"structure", + "required":["conversationState"], + "members":{ + "conversationState":{"shape":"ConversationState"}, + "profileArn":{"shape":"ProfileArn"} + }, + "documentation":"

Structure to represent a new generate assistant response request.

" + }, + "GenerateAssistantResponseResponse":{ + "type":"structure", + "required":[ + "conversationId", + "generateAssistantResponseResponse" + ], + "members":{ + "conversationId":{"shape":"ConversationId"}, + "generateAssistantResponseResponse":{"shape":"ChatResponseStream"} + }, + "documentation":"

Structure to represent generate assistant response response.

" + }, + "GenerateTaskAssistPlanRequest":{ + "type":"structure", + "required":[ + "conversationState", + "workspaceState" + ], + "members":{ + "conversationState":{"shape":"ConversationState"}, + "workspaceState":{"shape":"WorkspaceState"}, + "profileArn":{"shape":"ProfileArn"} + }, + "documentation":"

Structure to represent execute planning interaction request.

" + }, + "GenerateTaskAssistPlanResponse":{ + "type":"structure", + "members":{ + "planningResponseStream":{"shape":"ChatResponseStream"} + }, + "documentation":"

Structure to represent execute planning interaction response.

" + }, + "GitState":{ + "type":"structure", + "members":{ + "status":{ + "shape":"GitStateStatusString", + "documentation":"

The output of the command git status --porcelain=v1 -b

" + } + }, + "documentation":"

State related to the Git VSC

" + }, + "GitStateStatusString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "ImageBlock":{ + "type":"structure", + "required":[ + "format", + "source" + ], + "members":{ + "format":{"shape":"ImageFormat"}, + "source":{"shape":"ImageSource"} + }, + "documentation":"

Represents the image source itself and the format of the image.

" + }, + "ImageBlocks":{ + "type":"list", + "member":{"shape":"ImageBlock"}, + "max":10, + "min":0 + }, + "ImageFormat":{ + "type":"string", + "enum":[ + "png", + "jpeg", + "gif", + "webp" + ] + }, + "ImageSource":{ + "type":"structure", + "members":{ + "bytes":{"shape":"ImageSourceBytesBlob"} + }, + "documentation":"

Image bytes limited to ~10MB considering overhead of base64 encoding

", + "sensitive":true, + "union":true + }, + "ImageSourceBytesBlob":{ + "type":"blob", + "max":1500000, + "min":1 + }, + "InfrastructureUpdate":{ + "type":"structure", + "members":{ + "transition":{"shape":"InfrastructureUpdateTransition"} + }, + "documentation":"

Structure representing different types of infrastructure updates.

" + }, + "InfrastructureUpdateTransition":{ + "type":"structure", + "required":[ + "currentState", + "nextState" + ], + "members":{ + "currentState":{ + "shape":"InfrastructureUpdateTransitionCurrentStateString", + "documentation":"

The current state of the infrastructure before the update.

" + }, + "nextState":{ + "shape":"InfrastructureUpdateTransitionNextStateString", + "documentation":"

The next state of the infrastructure following the update.

" + } + }, + "documentation":"

Structure describing a transition between two states in an infrastructure update.

" + }, + "InfrastructureUpdateTransitionCurrentStateString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "InfrastructureUpdateTransitionNextStateString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "Integer":{ + "type":"integer", + "box":true + }, + "IntentData":{ + "type":"map", + "key":{"shape":"String"}, + "value":{"shape":"IntentDataType"}, + "max":100, + "min":0, + "sensitive":true + }, + "IntentDataType":{ + "type":"structure", + "members":{ + "string":{"shape":"String"} + }, + "union":true + }, + "IntentMap":{ + "type":"map", + "key":{"shape":"IntentType"}, + "value":{"shape":"IntentData"}, + "max":5, + "min":0 + }, + "IntentType":{ + "type":"string", + "enum":[ + "SUPPORT", + "GLUE_SENSEI", + "RESOURCE_DATA" + ] + }, + "IntentsEvent":{ + "type":"structure", + "members":{ + "intents":{ + "shape":"IntentMap", + "documentation":"

A map of Intent objects

" + } + }, + "documentation":"

Streaming Response Event for Intents

", + "event":true + }, + "InteractionComponent":{ + "type":"structure", + "members":{ + "text":{"shape":"Text"}, + "alert":{"shape":"Alert"}, + "infrastructureUpdate":{"shape":"InfrastructureUpdate"}, + "progress":{"shape":"Progress"}, + "step":{"shape":"Step"}, + "taskDetails":{"shape":"TaskDetails"}, + "taskReference":{"shape":"TaskReference"}, + "suggestions":{"shape":"Suggestions"}, + "section":{"shape":"Section"}, + "resource":{"shape":"Resource"}, + "resourceList":{"shape":"ResourceList"}, + "action":{"shape":"Action"} + }, + "documentation":"

Structure representing different types of interaction components.

" + }, + "InteractionComponentEntry":{ + "type":"structure", + "required":["interactionComponent"], + "members":{ + "interactionComponentId":{ + "shape":"InteractionComponentId", + "documentation":"

Identifier that can uniquely identify the interaction component within stream response. This field is optional.

" + }, + "interactionComponent":{ + "shape":"InteractionComponent", + "documentation":"

Interaction component

" + } + }, + "documentation":"

Interaction component with an identifier

" + }, + "InteractionComponentEntryList":{ + "type":"list", + "member":{"shape":"InteractionComponentEntry"}, + "documentation":"

List of identifiable interaction components

" + }, + "InteractionComponentId":{ + "type":"string", + "documentation":"

Unique identifier for interaction component

", + "max":128, + "min":0 + }, + "InteractionComponentsEvent":{ + "type":"structure", + "required":["interactionComponentEntries"], + "members":{ + "interactionComponentEntries":{ + "shape":"InteractionComponentEntryList", + "documentation":"

List of identifiable interaction components

" + } + }, + "documentation":"

Streaming Event for interaction components list

", + "event":true + }, + "InternalServerException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"} + }, + "documentation":"

This exception is thrown when an unexpected error occurred during the processing of a request.

", + "exception":true, + "fault":true, + "retryable":{"throttling":false} + }, + "InvalidStateEvent":{ + "type":"structure", + "required":[ + "reason", + "message" + ], + "members":{ + "reason":{"shape":"InvalidStateReason"}, + "message":{"shape":"InvalidStateEventMessageString"} + }, + "documentation":"

Streaming Response Event when an Invalid State is reached

", + "event":true + }, + "InvalidStateEventMessageString":{ + "type":"string", + "max":10240, + "min":0 + }, + "InvalidStateReason":{ + "type":"string", + "documentation":"

Reasons for Invalid State Event

", + "enum":["INVALID_TASK_ASSIST_PLAN"] + }, + "Long":{ + "type":"long", + "box":true + }, + "MessageId":{ + "type":"string", + "documentation":"

Unique identifier for the chat message

", + "max":128, + "min":0 + }, + "MessageMetadataEvent":{ + "type":"structure", + "members":{ + "conversationId":{ + "shape":"MessageMetadataEventConversationIdString", + "documentation":"

Unique identifier for the conversation

" + }, + "utteranceId":{ + "shape":"MessageMetadataEventUtteranceIdString", + "documentation":"

Unique identifier for the utterance

" + } + }, + "documentation":"

Streaming Response Event for AssistantResponse Metadata

", + "event":true + }, + "MessageMetadataEventConversationIdString":{ + "type":"string", + "max":128, + "min":0 + }, + "MessageMetadataEventUtteranceIdString":{ + "type":"string", + "max":128, + "min":0 + }, + "ModuleLink":{ + "type":"structure", + "members":{ + "cloudWatchTroubleshootingLink":{"shape":"CloudWatchTroubleshootingLink"} + } + }, + "Offset":{ + "type":"integer", + "documentation":"

Offset in the response text

", + "box":true, + "min":0 + }, + "Origin":{ + "type":"string", + "documentation":"

Enum to represent the origin application conversing with Sidekick.

", + "enum":[ + "CHATBOT", + "CONSOLE", + "DOCUMENTATION", + "MARKETING", + "MOBILE", + "SERVICE_INTERNAL", + "UNIFIED_SEARCH", + "UNKNOWN", + "MD", + "IDE", + "SAGE_MAKER", + "CLI", + "AI_EDITOR", + "OPENSEARCH_DASHBOARD", + "GITLAB" + ] + }, + "PartBody":{ + "type":"blob", + "documentation":"

Payload Part's body

", + "max":1000000, + "min":0, + "sensitive":true + }, + "Position":{ + "type":"structure", + "required":[ + "line", + "character" + ], + "members":{ + "line":{ + "shape":"Integer", + "documentation":"

Line position in a document.

" + }, + "character":{ + "shape":"Integer", + "documentation":"

Character offset on a line in a document (zero-based)

" + } + }, + "documentation":"

Indicates Cursor postion in a Text Document

" + }, + "ProfileArn":{ + "type":"string", + "max":950, + "min":0, + "pattern":"arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}" + }, + "ProgrammingLanguage":{ + "type":"structure", + "required":["languageName"], + "members":{ + "languageName":{"shape":"ProgrammingLanguageLanguageNameString"} + }, + "documentation":"

Programming Languages supported by CodeWhisperer

" + }, + "ProgrammingLanguageLanguageNameString":{ + "type":"string", + "max":128, + "min":1, + "pattern":"(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext|systemverilog|dart|lua|swift|powershell|r)" + }, + "Progress":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"ProgressComponentList", + "documentation":"

A collection of steps that make up a process. Each step is detailed using the Step structure.

" + } + }, + "documentation":"

Structure representing a collection of steps in a process.

" + }, + "ProgressComponent":{ + "type":"structure", + "members":{ + "step":{"shape":"Step"} + } + }, + "ProgressComponentList":{ + "type":"list", + "member":{"shape":"ProgressComponent"} + }, + "Range":{ + "type":"structure", + "required":[ + "start", + "end" + ], + "members":{ + "start":{ + "shape":"Position", + "documentation":"

The range's start position.

" + }, + "end":{ + "shape":"Position", + "documentation":"

The range's end position.

" + } + }, + "documentation":"

Indicates Range / Span in a Text Document

" + }, + "Reference":{ + "type":"structure", + "members":{ + "licenseName":{ + "shape":"ReferenceLicenseNameString", + "documentation":"

License name

" + }, + "repository":{ + "shape":"ReferenceRepositoryString", + "documentation":"

Code Repsitory for the associated reference

" + }, + "url":{ + "shape":"ReferenceUrlString", + "documentation":"

Respository URL

" + }, + "recommendationContentSpan":{ + "shape":"Span", + "documentation":"

Span / Range for the Reference

" + } + }, + "documentation":"

Code Reference / Repository details

" + }, + "ReferenceLicenseNameString":{ + "type":"string", + "max":1024, + "min":1 + }, + "ReferenceRepositoryString":{ + "type":"string", + "max":1024, + "min":1 + }, + "ReferenceUrlString":{ + "type":"string", + "max":1024, + "min":1 + }, + "References":{ + "type":"list", + "member":{"shape":"Reference"}, + "max":10, + "min":0 + }, + "RelevantDocumentList":{ + "type":"list", + "member":{"shape":"RelevantTextDocument"}, + "max":30, + "min":0 + }, + "RelevantTextDocument":{ + "type":"structure", + "required":["relativeFilePath"], + "members":{ + "relativeFilePath":{ + "shape":"RelevantTextDocumentRelativeFilePathString", + "documentation":"

Filepath relative to the root of the workspace

" + }, + "programmingLanguage":{ + "shape":"ProgrammingLanguage", + "documentation":"

The text document's language identifier.

" + }, + "text":{ + "shape":"RelevantTextDocumentTextString", + "documentation":"

Content of the text document

" + }, + "documentSymbols":{ + "shape":"DocumentSymbols", + "documentation":"

DocumentSymbols parsed from a text document

" + } + }, + "documentation":"

Represents an IDE retrieved relevant Text Document / File

" + }, + "RelevantTextDocumentRelativeFilePathString":{ + "type":"string", + "max":4096, + "min":1, + "sensitive":true + }, + "RelevantTextDocumentTextString":{ + "type":"string", + "max":40960, + "min":0, + "sensitive":true + }, + "Resource":{ + "type":"structure", + "required":[ + "title", + "link", + "description", + "type", + "ARN", + "resourceJsonString" + ], + "members":{ + "title":{ + "shape":"ResourceTitleString", + "documentation":"

Card title.

" + }, + "link":{ + "shape":"ResourceLinkString", + "documentation":"

Link for the resource item

" + }, + "description":{ + "shape":"ResourceDescriptionString", + "documentation":"

Short text about that resource for example Region: us-east-1

" + }, + "type":{ + "shape":"ResourceTypeString", + "documentation":"

Resource type e.g AWS EC2

" + }, + "ARN":{ + "shape":"ResourceARNString", + "documentation":"

Amazon resource number e.g arn:aws:aec:.....

" + }, + "resourceJsonString":{ + "shape":"ResourceResourceJsonStringString", + "documentation":"

A stringified object

" + } + }, + "documentation":"

Structure representing a resource item

" + }, + "ResourceARNString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "ResourceArn":{ + "type":"string", + "max":1224, + "min":0, + "pattern":"arn:([-.a-z0-9]{1,63}:){2}([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}" + }, + "ResourceDescriptionString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "ResourceLinkString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "ResourceList":{ + "type":"structure", + "required":["items"], + "members":{ + "action":{ + "shape":"Action", + "documentation":"

Action associated with the list

" + }, + "items":{ + "shape":"ResourceListItemsList", + "documentation":"

List of resources

" + } + }, + "documentation":"

Structure representing a list of Items

" + }, + "ResourceListItemsList":{ + "type":"list", + "member":{"shape":"Resource"}, + "documentation":"

List for resources

", + "max":10, + "min":0 + }, + "ResourceNotFoundException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"} + }, + "documentation":"

This exception is thrown when describing a resource that does not exist.

", + "exception":true + }, + "ResourceResourceJsonStringString":{ + "type":"string", + "max":8192, + "min":0, + "sensitive":true + }, + "ResourceTitleString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "ResourceTypeString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "ResultArchiveStream":{ + "type":"structure", + "members":{ + "binaryMetadataEvent":{"shape":"BinaryMetadataEvent"}, + "binaryPayloadEvent":{"shape":"BinaryPayloadEvent"}, + "internalServerException":{"shape":"InternalServerException"} + }, + "documentation":"

Response Stream

", + "eventstream":true + }, + "RuntimeDiagnostic":{ + "type":"structure", + "required":[ + "source", + "severity", + "message" + ], + "members":{ + "source":{ + "shape":"RuntimeDiagnosticSourceString", + "documentation":"

A human-readable string describing the source of the diagnostic

" + }, + "severity":{ + "shape":"DiagnosticSeverity", + "documentation":"

Diagnostic Error type

" + }, + "message":{ + "shape":"RuntimeDiagnosticMessageString", + "documentation":"

The diagnostic's message.

" + } + }, + "documentation":"

Structure to represent metadata about a Runtime Diagnostics

" + }, + "RuntimeDiagnosticMessageString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "RuntimeDiagnosticSourceString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "Section":{ + "type":"structure", + "required":[ + "title", + "content" + ], + "members":{ + "title":{ + "shape":"SectionTitleString", + "documentation":"

Contains text content that may include sensitive information and can support Markdown formatting.

" + }, + "content":{ + "shape":"SectionContentList", + "documentation":"

Contains a list of interaction components e.g Text, Alert, List, etc.

" + }, + "action":{ + "shape":"Action", + "documentation":"

Action associated with the Section

" + } + }, + "documentation":"

Structure representing a collapsable section

" + }, + "SectionComponent":{ + "type":"structure", + "members":{ + "text":{"shape":"Text"}, + "alert":{"shape":"Alert"}, + "resource":{"shape":"Resource"}, + "resourceList":{"shape":"ResourceList"} + } + }, + "SectionContentList":{ + "type":"list", + "member":{"shape":"SectionComponent"}, + "max":5, + "min":0 + }, + "SectionTitleString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "SendMessageRequest":{ + "type":"structure", + "required":["conversationState"], + "members":{ + "conversationState":{"shape":"ConversationState"}, + "profileArn":{"shape":"ProfileArn"}, + "source":{ + "shape":"Origin", + "documentation":"

The origin of the caller

" + }, + "dryRun":{"shape":"Boolean"} + }, + "documentation":"

Structure to represent a SendMessage request.

" + }, + "SendMessageResponse":{ + "type":"structure", + "required":["sendMessageResponse"], + "members":{ + "sendMessageResponse":{"shape":"ChatResponseStream"} + }, + "documentation":"

Structure to represent a SendMessage response.

" + }, + "SensitiveDocument":{ + "type":"structure", + "members":{ + }, + "document":true, + "sensitive":true + }, + "SensitiveString":{ + "type":"string", + "sensitive":true + }, + "ServiceQuotaExceededException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"} + }, + "documentation":"

This exception is thrown when request was denied due to caller exceeding their usage limits

", + "exception":true + }, + "ShellHistory":{ + "type":"list", + "member":{"shape":"ShellHistoryEntry"}, + "documentation":"

A list of shell history entries

", + "max":20, + "min":0 + }, + "ShellHistoryEntry":{ + "type":"structure", + "required":["command"], + "members":{ + "command":{ + "shape":"ShellHistoryEntryCommandString", + "documentation":"

The shell command that was run

" + }, + "directory":{ + "shape":"ShellHistoryEntryDirectoryString", + "documentation":"

The directory the command was ran in

" + }, + "exitCode":{ + "shape":"Integer", + "documentation":"

The exit code of the command after it finished

" + }, + "stdout":{ + "shape":"ShellHistoryEntryStdoutString", + "documentation":"

The stdout from the command

" + }, + "stderr":{ + "shape":"ShellHistoryEntryStderrString", + "documentation":"

The stderr from the command

" + } + }, + "documentation":"

An single entry in the shell history

" + }, + "ShellHistoryEntryCommandString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "ShellHistoryEntryDirectoryString":{ + "type":"string", + "max":256, + "min":1, + "sensitive":true + }, + "ShellHistoryEntryStderrString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "ShellHistoryEntryStdoutString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "ShellState":{ + "type":"structure", + "required":["shellName"], + "members":{ + "shellName":{ + "shape":"ShellStateShellNameString", + "documentation":"

The name of the current shell

" + }, + "shellHistory":{ + "shape":"ShellHistory", + "documentation":"

The history previous shell commands for the current shell

" + } + }, + "documentation":"

Represents the state of a shell

" + }, + "ShellStateShellNameString":{ + "type":"string", + "max":32, + "min":1, + "pattern":"(zsh|bash|fish|pwsh|nu)" + }, + "Span":{ + "type":"structure", + "members":{ + "start":{"shape":"SpanStartInteger"}, + "end":{"shape":"SpanEndInteger"} + }, + "documentation":"

Represents span in a text.

" + }, + "SpanEndInteger":{ + "type":"integer", + "box":true, + "min":0 + }, + "SpanStartInteger":{ + "type":"integer", + "box":true, + "min":0 + }, + "Step":{ + "type":"structure", + "required":[ + "id", + "state", + "label" + ], + "members":{ + "id":{ + "shape":"StepIdInteger", + "documentation":"

A unique identifier for the step. It must be a non-negative integer to ensure each step is distinct.

" + }, + "state":{"shape":"StepState"}, + "label":{ + "shape":"StepLabelString", + "documentation":"

A label for the step, providing a concise description.

" + }, + "content":{ + "shape":"StepComponentList", + "documentation":"

Optional content providing additional details about the step.

" + } + }, + "documentation":"

Structure representing an individual step in a process.

" + }, + "StepComponent":{ + "type":"structure", + "members":{ + "text":{"shape":"Text"} + } + }, + "StepComponentList":{ + "type":"list", + "member":{"shape":"StepComponent"} + }, + "StepIdInteger":{ + "type":"integer", + "box":true, + "max":128, + "min":0 + }, + "StepLabelString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "StepState":{ + "type":"string", + "documentation":"

Enum representing all possible step states, combining terminal and non-terminal states.

", + "enum":[ + "FAILED", + "SUCCEEDED", + "STOPPED", + "PENDING", + "IN_PROGRESS", + "LOADING", + "PAUSED" + ] + }, + "String":{"type":"string"}, + "Suggestion":{ + "type":"structure", + "required":["value"], + "members":{ + "value":{"shape":"SuggestionValueString"} + }, + "documentation":"

Structure representing a suggestion for follow-ups.

" + }, + "SuggestionList":{ + "type":"list", + "member":{"shape":"Suggestion"} + }, + "SuggestionValueString":{ + "type":"string", + "max":1000, + "min":1, + "sensitive":true + }, + "Suggestions":{ + "type":"structure", + "required":["items"], + "members":{ + "items":{"shape":"SuggestionList"} + }, + "documentation":"

Structure containing a list of suggestions.

" + }, + "SupplementaryWebLink":{ + "type":"structure", + "required":[ + "url", + "title" + ], + "members":{ + "url":{ + "shape":"SupplementaryWebLinkUrlString", + "documentation":"

URL of the web reference link.

" + }, + "title":{ + "shape":"SupplementaryWebLinkTitleString", + "documentation":"

Title of the web reference link.

" + }, + "snippet":{ + "shape":"SupplementaryWebLinkSnippetString", + "documentation":"

Relevant text snippet from the link.

" + } + }, + "documentation":"

Represents an additional reference link retured with the Chat message

" + }, + "SupplementaryWebLinkSnippetString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "SupplementaryWebLinkTitleString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "SupplementaryWebLinkUrlString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "SupplementaryWebLinks":{ + "type":"list", + "member":{"shape":"SupplementaryWebLink"}, + "max":10, + "min":0 + }, + "SupplementaryWebLinksEvent":{ + "type":"structure", + "members":{ + "supplementaryWebLinks":{ + "shape":"SupplementaryWebLinks", + "documentation":"

Web References for Assistant Response Message

" + } + }, + "documentation":"

Streaming Response Event for SupplementaryWebLinks

", + "event":true + }, + "SymbolType":{ + "type":"string", + "enum":[ + "DECLARATION", + "USAGE" + ] + }, + "TaskAction":{ + "type":"structure", + "required":[ + "label", + "payload" + ], + "members":{ + "label":{ + "shape":"TaskActionLabelString", + "documentation":"

A label for the action.

" + }, + "note":{"shape":"TaskActionNote"}, + "primary":{ + "shape":"Boolean", + "documentation":"

Indicates whether the action is primary or not.

" + }, + "disabled":{ + "shape":"Boolean", + "documentation":"

Indicates whether the action is disabled or not.

" + }, + "payload":{"shape":"TaskActionPayload"}, + "confirmation":{"shape":"TaskActionConfirmation"} + }, + "documentation":"

Structure representing an action associated with a task.

" + }, + "TaskActionConfirmation":{ + "type":"structure", + "members":{ + "content":{ + "shape":"TaskActionConfirmationContentString", + "documentation":"

Confirmation message related to the action note, which may include sensitive information.

" + } + }, + "documentation":"

Structure representing a confirmation message related to a task action.

" + }, + "TaskActionConfirmationContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "TaskActionLabelString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "TaskActionList":{ + "type":"list", + "member":{"shape":"TaskAction"} + }, + "TaskActionNote":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"TaskActionNoteContentString", + "documentation":"

Content of the note, which may include sensitive information.

" + }, + "type":{"shape":"TaskActionNoteType"} + }, + "documentation":"

Structure representing a note associated with a task action.

" + }, + "TaskActionNoteContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "TaskActionNoteType":{ + "type":"string", + "documentation":"

Enum defining the types of notes that can be associated with a task action.

", + "enum":[ + "INFO", + "WARNING" + ] + }, + "TaskActionPayload":{ + "type":"map", + "key":{ + "shape":"TaskActionPayloadKeyString", + "documentation":"

The key for the payload entry.

" + }, + "value":{ + "shape":"TaskActionPayloadValueString", + "documentation":"

The sensitive value associated with the key.

" + }, + "documentation":"

Map representing key-value pairs for the payload of a task action.

", + "max":32, + "min":0 + }, + "TaskActionPayloadKeyString":{ + "type":"string", + "max":1024, + "min":1 + }, + "TaskActionPayloadValueString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "TaskComponent":{ + "type":"structure", + "members":{ + "text":{"shape":"Text"}, + "infrastructureUpdate":{"shape":"InfrastructureUpdate"}, + "alert":{"shape":"Alert"}, + "progress":{"shape":"Progress"} + }, + "documentation":"

Structure representing different types of components that can be part of a task.

" + }, + "TaskComponentList":{ + "type":"list", + "member":{"shape":"TaskComponent"} + }, + "TaskDetails":{ + "type":"structure", + "required":[ + "overview", + "content" + ], + "members":{ + "overview":{"shape":"TaskOverview"}, + "content":{ + "shape":"TaskComponentList", + "documentation":"

Lists the components that can be used to form the task's content.

" + }, + "actions":{ + "shape":"TaskActionList", + "documentation":"

Optional list of actions associated with the task.

" + } + }, + "documentation":"

Structure containing details about a task.

" + }, + "TaskOverview":{ + "type":"structure", + "required":[ + "label", + "description" + ], + "members":{ + "label":{ + "shape":"TaskOverviewLabelString", + "documentation":"

A label for the task overview.

" + }, + "description":{ + "shape":"TaskOverviewDescriptionString", + "documentation":"

Text description providing details about the task. This field may include sensitive information and supports Markdown formatting.

" + } + }, + "documentation":"

Structure representing an overview of a task, including a label and description.

" + }, + "TaskOverviewDescriptionString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "TaskOverviewLabelString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "TaskReference":{ + "type":"structure", + "required":["taskId"], + "members":{ + "taskId":{ + "shape":"TaskReferenceTaskIdString", + "documentation":"

Unique identifier for the task.

" + } + }, + "documentation":"

Structure representing a reference to a task.

" + }, + "TaskReferenceTaskIdString":{ + "type":"string", + "max":128, + "min":1 + }, + "TestGenerationJobGroupName":{ + "type":"string", + "documentation":"

Test generation job group name

", + "max":128, + "min":1, + "pattern":"[a-zA-Z0-9-_]+" + }, + "Text":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"TextContentString", + "documentation":"

Contains text content that may include sensitive information and can support Markdown formatting.

" + } + }, + "documentation":"

Structure representing a simple text component with sensitive content, which can include Markdown formatting.

" + }, + "TextContentString":{ + "type":"string", + "max":10240, + "min":0, + "sensitive":true + }, + "TextDocument":{ + "type":"structure", + "required":["relativeFilePath"], + "members":{ + "relativeFilePath":{ + "shape":"TextDocumentRelativeFilePathString", + "documentation":"

Filepath relative to the root of the workspace

" + }, + "programmingLanguage":{ + "shape":"ProgrammingLanguage", + "documentation":"

The text document's language identifier.

" + }, + "text":{ + "shape":"TextDocumentTextString", + "documentation":"

Content of the text document

" + }, + "documentSymbols":{ + "shape":"DocumentSymbols", + "documentation":"

DocumentSymbols parsed from a text document

" + } + }, + "documentation":"

Represents a Text Document / File

" + }, + "TextDocumentDiagnostic":{ + "type":"structure", + "required":[ + "document", + "range", + "source", + "severity", + "message" + ], + "members":{ + "document":{ + "shape":"TextDocument", + "documentation":"

Represents a Text Document associated with Diagnostic

" + }, + "range":{ + "shape":"Range", + "documentation":"

The range at which the message applies.

" + }, + "source":{ + "shape":"SensitiveString", + "documentation":"

A human-readable string describing the source of the diagnostic

" + }, + "severity":{ + "shape":"DiagnosticSeverity", + "documentation":"

Diagnostic Error type

" + }, + "message":{ + "shape":"TextDocumentDiagnosticMessageString", + "documentation":"

The diagnostic's message.

" + }, + "code":{ + "shape":"Integer", + "documentation":"

The diagnostic's code, which might appear in the user interface.

" + }, + "codeDescription":{ + "shape":"CodeDescription", + "documentation":"

An optional property to describe the error code.

" + }, + "tags":{ + "shape":"DiagnosticTagList", + "documentation":"

Additional metadata about the diagnostic.

" + }, + "relatedInformation":{ + "shape":"DiagnosticRelatedInformationList", + "documentation":"

an array of related diagnostic information, e.g. when symbol-names within a scope collide all definitions can be marked via this property.

" + }, + "data":{ + "shape":"TextDocumentDiagnosticDataString", + "documentation":"

A data entry field that is preserved between a textDocument/publishDiagnostics notification and textDocument/codeAction request.

" + } + }, + "documentation":"

Structure to represent metadata about a TextDocument Diagnostic

" + }, + "TextDocumentDiagnosticDataString":{ + "type":"string", + "max":4096, + "min":0, + "sensitive":true + }, + "TextDocumentDiagnosticMessageString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "TextDocumentRelativeFilePathString":{ + "type":"string", + "max":4096, + "min":1, + "sensitive":true + }, + "TextDocumentTextString":{ + "type":"string", + "max":40000, + "min":0, + "sensitive":true + }, + "ThrottlingException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"}, + "reason":{"shape":"ThrottlingExceptionReason"} + }, + "documentation":"

This exception is thrown when request was denied due to request throttling.

", + "exception":true, + "retryable":{"throttling":true} + }, + "ThrottlingExceptionReason":{ + "type":"string", + "documentation":"

Reason for ThrottlingException

", + "enum":["MONTHLY_REQUEST_COUNT"] + }, + "Tool":{ + "type":"structure", + "members":{ + "toolSpecification":{"shape":"ToolSpecification"} + }, + "documentation":"

Information about a tool that can be used.

", + "union":true + }, + "ToolDescription":{ + "type":"string", + "documentation":"

The description for the tool.

", + "max":10240, + "min":1, + "sensitive":true + }, + "ToolInputSchema":{ + "type":"structure", + "members":{ + "json":{"shape":"SensitiveDocument"} + }, + "documentation":"

The input schema for the tool in JSON format.

" + }, + "ToolName":{ + "type":"string", + "documentation":"

The name for the tool.

", + "max":64, + "min":0, + "pattern":"[a-zA-Z][a-zA-Z0-9_]*", + "sensitive":true + }, + "ToolResult":{ + "type":"structure", + "required":[ + "toolUseId", + "content" + ], + "members":{ + "toolUseId":{"shape":"ToolUseId"}, + "content":{ + "shape":"ToolResultContent", + "documentation":"

Content of the tool result.

" + }, + "status":{"shape":"ToolResultStatus"} + }, + "documentation":"

A tool result that contains the results for a tool request that was previously made.

" + }, + "ToolResultContent":{ + "type":"list", + "member":{"shape":"ToolResultContentBlock"} + }, + "ToolResultContentBlock":{ + "type":"structure", + "members":{ + "text":{ + "shape":"ToolResultContentBlockTextString", + "documentation":"

A tool result that is text.

" + }, + "json":{ + "shape":"SensitiveDocument", + "documentation":"

A tool result that is JSON format data.

" + } + }, + "union":true + }, + "ToolResultContentBlockTextString":{ + "type":"string", + "max":800000, + "min":0, + "sensitive":true + }, + "ToolResultEvent":{ + "type":"structure", + "members":{ + "toolResult":{"shape":"ToolResult"} + }, + "event":true + }, + "ToolResultStatus":{ + "type":"string", + "documentation":"

Status of the tools result.

", + "enum":[ + "success", + "error" + ] + }, + "ToolResults":{ + "type":"list", + "member":{"shape":"ToolResult"}, + "max":10, + "min":0 + }, + "ToolSpecification":{ + "type":"structure", + "required":[ + "inputSchema", + "name" + ], + "members":{ + "inputSchema":{"shape":"ToolInputSchema"}, + "name":{"shape":"ToolName"}, + "description":{"shape":"ToolDescription"} + }, + "documentation":"

The specification for the tool.

" + }, + "ToolUse":{ + "type":"structure", + "required":[ + "toolUseId", + "name", + "input" + ], + "members":{ + "toolUseId":{"shape":"ToolUseId"}, + "name":{"shape":"ToolName"}, + "input":{ + "shape":"SensitiveDocument", + "documentation":"

The input to pass to the tool.

" + } + }, + "documentation":"

Contains information about a tool that the model is requesting be run. The model uses the result from the tool to generate a response.

" + }, + "ToolUseEvent":{ + "type":"structure", + "required":[ + "toolUseId", + "name" + ], + "members":{ + "toolUseId":{"shape":"ToolUseId"}, + "name":{"shape":"ToolName"}, + "input":{ + "shape":"ToolUseEventInputString", + "documentation":"

Represents the serialized json input for the ToolUse request. This field should be concatenated until 'stop' is true.

" + }, + "stop":{ + "shape":"Boolean", + "documentation":"

This field is true when all of the serialized input for this ToolUse request has been sent.

" + } + }, + "documentation":"

Event for a ToolUse request. Multiple ToolUse requests can be returned from a single request, so each ToolUse has a unique 'toolUseId'.

", + "event":true + }, + "ToolUseEventInputString":{ + "type":"string", + "max":30720, + "min":0, + "sensitive":true + }, + "ToolUseId":{ + "type":"string", + "documentation":"

The ID for the tool request.

", + "max":64, + "min":0, + "pattern":"[a-zA-Z0-9_-]+" + }, + "ToolUses":{ + "type":"list", + "member":{"shape":"ToolUse"}, + "max":10, + "min":0 + }, + "Tools":{ + "type":"list", + "member":{"shape":"Tool"} + }, + "TransformationDownloadArtifactType":{ + "type":"string", + "enum":[ + "ClientInstructions", + "Logs", + "GeneratedCode" + ] + }, + "TransformationExportContext":{ + "type":"structure", + "required":[ + "downloadArtifactId", + "downloadArtifactType" + ], + "members":{ + "downloadArtifactId":{"shape":"ArtifactId"}, + "downloadArtifactType":{"shape":"TransformationDownloadArtifactType"} + }, + "documentation":"

Transformation export context

" + }, + "UUID":{ + "type":"string", + "max":36, + "min":36 + }, + "UnitTestGenerationExportContext":{ + "type":"structure", + "required":["testGenerationJobGroupName"], + "members":{ + "testGenerationJobGroupName":{"shape":"TestGenerationJobGroupName"}, + "testGenerationJobId":{"shape":"UUID"} + }, + "documentation":"

Unit test generation export context

" + }, + "UploadId":{ + "type":"string", + "documentation":"

Upload ID returned by CreateUploadUrl API

", + "max":128, + "min":1 + }, + "UserInputMessage":{ + "type":"structure", + "required":["content"], + "members":{ + "content":{ + "shape":"UserInputMessageContentString", + "documentation":"

The content of the chat message.

" + }, + "userInputMessageContext":{ + "shape":"UserInputMessageContext", + "documentation":"

Chat message context associated with the Chat Message.

" + }, + "userIntent":{ + "shape":"UserIntent", + "documentation":"

User Intent.

" + }, + "origin":{ + "shape":"Origin", + "documentation":"

User Input Origin.

" + }, + "images":{ + "shape":"ImageBlocks", + "documentation":"

Images associated with the Chat Message.

" + } + }, + "documentation":"

Structure to represent a chat input message from User.

" + }, + "UserInputMessageContentString":{ + "type":"string", + "max":600000, + "min":0, + "sensitive":true + }, + "UserInputMessageContext":{ + "type":"structure", + "members":{ + "editorState":{ + "shape":"EditorState", + "documentation":"

Editor state chat message context.

" + }, + "shellState":{ + "shape":"ShellState", + "documentation":"

Shell state chat message context.

" + }, + "gitState":{ + "shape":"GitState", + "documentation":"

Git state chat message context.

" + }, + "envState":{ + "shape":"EnvState", + "documentation":"

Environment state chat message context.

" + }, + "appStudioContext":{ + "shape":"AppStudioState", + "documentation":"

The state of a user's AppStudio UI when sending a message.

" + }, + "diagnostic":{ + "shape":"Diagnostic", + "documentation":"

Diagnostic chat message context.

" + }, + "consoleState":{ + "shape":"ConsoleState", + "documentation":"

Contextual information about the environment from which the user is calling.

" + }, + "userSettings":{ + "shape":"UserSettings", + "documentation":"

Settings information, e.g., whether the user has enabled cross-region API calls.

" + }, + "additionalContext":{ + "shape":"AdditionalContentList", + "documentation":"

List of additional contextual content entries that can be included with the message.

" + }, + "toolResults":{ + "shape":"ToolResults", + "documentation":"

ToolResults for the requested ToolUses.

" + }, + "tools":{ + "shape":"Tools", + "documentation":"

Tools that can be used.

" + } + }, + "documentation":"

Additional Chat message context associated with the Chat Message

" + }, + "UserIntent":{ + "type":"string", + "documentation":"

User Intent

", + "enum":[ + "SUGGEST_ALTERNATE_IMPLEMENTATION", + "APPLY_COMMON_BEST_PRACTICES", + "IMPROVE_CODE", + "SHOW_EXAMPLES", + "CITE_SOURCES", + "EXPLAIN_LINE_BY_LINE", + "EXPLAIN_CODE_SELECTION", + "GENERATE_CLOUDFORMATION_TEMPLATE", + "GENERATE_UNIT_TESTS", + "CODE_GENERATION" + ] + }, + "UserSettings":{ + "type":"structure", + "members":{ + "hasConsentedToCrossRegionCalls":{"shape":"Boolean"} + }, + "documentation":"

Settings information passed by the Q widget

" + }, + "ValidationException":{ + "type":"structure", + "required":["message"], + "members":{ + "message":{"shape":"String"}, + "reason":{"shape":"ValidationExceptionReason"} + }, + "documentation":"

This exception is thrown when the input fails to satisfy the constraints specified by the service.

", + "exception":true + }, + "ValidationExceptionReason":{ + "type":"string", + "documentation":"

Reason for ValidationException

", + "enum":[ + "INVALID_CONVERSATION_ID", + "CONTENT_LENGTH_EXCEEDS_THRESHOLD", + "INVALID_KMS_GRANT" + ] + }, + "WebLink":{ + "type":"structure", + "required":[ + "label", + "url" + ], + "members":{ + "label":{ + "shape":"WebLinkLabelString", + "documentation":"

A label for the link

" + }, + "url":{ + "shape":"WebLinkUrlString", + "documentation":"

URL of the Weblink

" + } + } + }, + "WebLinkLabelString":{ + "type":"string", + "max":1024, + "min":0, + "sensitive":true + }, + "WebLinkUrlString":{ + "type":"string", + "max":1024, + "min":1, + "sensitive":true + }, + "WorkspaceFolderList":{ + "type":"list", + "member":{"shape":"WorkspaceFolderListMemberString"}, + "max":100, + "min":0 + }, + "WorkspaceFolderListMemberString":{ + "type":"string", + "max":4096, + "min":1, + "sensitive":true + }, + "WorkspaceState":{ + "type":"structure", + "required":[ + "uploadId", + "programmingLanguage" + ], + "members":{ + "uploadId":{ + "shape":"UploadId", + "documentation":"

Upload ID representing an Upload using a PreSigned URL

" + }, + "programmingLanguage":{ + "shape":"ProgrammingLanguage", + "documentation":"

Primary programming language of the Workspace

" + }, + "contextTruncationScheme":{ + "shape":"ContextTruncationScheme", + "documentation":"

Workspace context truncation schemes based on usecase

" + } + }, + "documentation":"

Represents a Workspace state uploaded to S3 for Async Code Actions

" + } + } +} diff --git a/plugins/core-q/sdk-codegen/codegen-resources/telemetry/customization.config b/plugins/core-q/sdk-codegen/codegen-resources/telemetry/customization.config new file mode 100644 index 00000000000..18fb4f1423d --- /dev/null +++ b/plugins/core-q/sdk-codegen/codegen-resources/telemetry/customization.config @@ -0,0 +1,6 @@ +{ + "renameShapes": { + // "Unit" conflicts with Kotlin stdlib [kotlin.Unit] + "Unit" : "MetricUnit" + } +} diff --git a/plugins/core-q/sdk-codegen/codegen-resources/telemetry/service-2.json b/plugins/core-q/sdk-codegen/codegen-resources/telemetry/service-2.json new file mode 100644 index 00000000000..743c01a8d29 --- /dev/null +++ b/plugins/core-q/sdk-codegen/codegen-resources/telemetry/service-2.json @@ -0,0 +1,257 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2017-07-25", + "endpointPrefix":"client-telemetry", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"ToolkitTelemetry", + "serviceFullName":"A beautiful and amazing ToolkitTelemetry", + "serviceId":"ToolkitTelemetry", + "signatureVersion":"v4", + "signingName":"execute-api" + }, + "operations":{ + "PostErrorReport":{ + "name":"PostErrorReport", + "http":{ + "method":"POST", + "requestUri":"/errorReport" + }, + "input":{"shape":"PostErrorReportRequest"} + }, + "PostFeedback":{ + "name":"PostFeedback", + "http":{ + "method":"POST", + "requestUri":"/feedback" + }, + "input":{"shape":"PostFeedbackRequest"} + }, + "PostMetrics":{ + "name":"PostMetrics", + "http":{ + "method":"POST", + "requestUri":"/metrics" + }, + "input":{"shape":"PostMetricsRequest"} + } + }, + "shapes":{ + "AWSProduct":{ + "type":"string", + "enum":[ + "canary", + "AWS Toolkit For JetBrains", + "AWS Toolkit For Eclipse", + "AWS Toolkit For VisualStudio", + "AWS Toolkit For VS Code", + "Amazon Q For JetBrains", + "Amazon Q For VS Code" + ] + }, + "AWSProductVersion":{ + "type":"string", + "pattern":"^[\\w+-.]{1,512}$" + }, + "ClientID":{ + "type":"string", + "pattern":"^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$" + }, + "Command":{ + "type":"string", + "max":2000 + }, + "Comment":{ + "type":"string", + "max":2000 + }, + "ComputeEnv":{ + "type":"string", + "enum":[ + "cloud9", + "cloud9-codecatalyst", + "cloud9-ec2", + "codecatalyst", + "ec2", + "local", + "sagemaker", + "ssh", + "test", + "web", + "wsl", + "other" + ] + }, + "Datapoint":{ + "type":"double", + "min":0 + }, + "Email":{ + "type":"string", + "pattern":"^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" + }, + "EpochTimestamp":{ + "type":"long", + "min":0 + }, + "ErrorDetails":{ + "type":"structure", + "required":[ + "Command", + "EpochTimestamp", + "Type", + "StackTrace" + ], + "members":{ + "Command":{"shape":"Command"}, + "EpochTimestamp":{"shape":"EpochTimestamp"}, + "Type":{"shape":"Type"}, + "Message":{"shape":"Message"}, + "StackTrace":{"shape":"StackTrace"} + } + }, + "Key":{ + "type":"string", + "pattern":"^\\w[\\w\\.]{0,149}$" + }, + "Message":{ + "type":"string", + "max":2048 + }, + "Metadata":{ + "type":"list", + "member":{"shape":"MetadataEntry"} + }, + "MetadataEntry":{ + "type":"structure", + "members":{ + "Key":{"shape":"Key"}, + "Value":{"shape":"Value"} + } + }, + "MetricData":{ + "type":"list", + "member":{"shape":"MetricDatum"}, + "max":20, + "min":1 + }, + "MetricDatum":{ + "type":"structure", + "required":[ + "MetricName", + "EpochTimestamp", + "Unit", + "Value" + ], + "members":{ + "MetricName":{"shape":"MetricName"}, + "EpochTimestamp":{"shape":"EpochTimestamp"}, + "Unit":{"shape":"Unit"}, + "Value":{"shape":"Datapoint"}, + "Metadata":{"shape":"Metadata"}, + "Passive":{"shape":"Passive"} + } + }, + "MetricName":{ + "type":"string", + "pattern":"^[\\w+-.:]{1,255}$" + }, + "Passive":{"type":"boolean"}, + "PostErrorReportRequest":{ + "type":"structure", + "required":[ + "AWSProduct", + "AWSProductVersion", + "ErrorDetails" + ], + "members":{ + "AWSProduct":{"shape":"AWSProduct"}, + "AWSProductVersion":{"shape":"AWSProductVersion"}, + "Metadata":{"shape":"Metadata"}, + "Userdata":{"shape":"Userdata"}, + "ErrorDetails":{"shape":"ErrorDetails"} + } + }, + "PostFeedbackRequest":{ + "type":"structure", + "required":[ + "AWSProduct", + "AWSProductVersion", + "ParentProduct", + "ParentProductVersion", + "Sentiment", + "Comment" + ], + "members":{ + "AWSProduct":{"shape":"AWSProduct"}, + "AWSProductVersion":{"shape":"AWSProductVersion"}, + "OS":{"shape":"Value"}, + "OSVersion":{"shape":"Value"}, + "ComputeEnv": {"shape":"ComputeEnv"}, + "ParentProduct":{"shape":"Value"}, + "ParentProductVersion":{"shape":"Value"}, + "Metadata":{"shape":"Metadata"}, + "Sentiment":{"shape":"Sentiment"}, + "Comment":{"shape":"Comment"} + } + }, + "PostMetricsRequest":{ + "type":"structure", + "required":[ + "AWSProduct", + "AWSProductVersion", + "ClientID", + "MetricData" + ], + "members":{ + "AWSProduct":{"shape":"AWSProduct"}, + "AWSProductVersion":{"shape":"AWSProductVersion"}, + "ClientID":{"shape":"ClientID"}, + "OS":{"shape":"Value"}, + "OSArchitecture":{"shape":"Value"}, + "OSVersion":{"shape":"Value"}, + "ComputeEnv": {"shape":"ComputeEnv"}, + "ParentProduct":{"shape":"Value"}, + "ParentProductVersion":{"shape":"Value"}, + "MetricData":{"shape":"MetricData"} + } + }, + "Sentiment":{ + "type":"string", + "enum":[ + "Positive", + "Negative" + ] + }, + "StackTrace":{ + "type":"string", + "max":16384 + }, + "Type":{ + "type":"string", + "max":1024 + }, + "Unit":{ + "type":"string", + "enum":[ + "Milliseconds", + "Bytes", + "Percent", + "Count", + "None" + ] + }, + "Userdata":{ + "type":"structure", + "members":{ + "Email":{"shape":"Email"}, + "Comment":{"shape":"Comment"} + } + }, + "Value":{ + "type":"string", + "max":65536 + } + } +} diff --git a/plugins/core-q/webview/.gitignore b/plugins/core-q/webview/.gitignore new file mode 100644 index 00000000000..2b7c966d25f --- /dev/null +++ b/plugins/core-q/webview/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +gradle_build diff --git a/plugins/core-q/webview/build.gradle.kts b/plugins/core-q/webview/build.gradle.kts new file mode 100644 index 00000000000..cb5c685986c --- /dev/null +++ b/plugins/core-q/webview/build.gradle.kts @@ -0,0 +1,35 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import com.github.gradle.node.npm.task.NpmTask + +plugins { + id("toolkit-jvm-conventions") + alias(libs.plugins.node.gradle) +} + +layout.buildDirectory = file("gradle_build") + +val buildGetStartUI = tasks.register("buildWebviewUI") { + dependsOn(tasks.npmInstall) + npmCommand.set(listOf("run", "build-ui")) + + inputs.dir("src") + inputs.files( + file("package.json"), + file("package-lock.json"), + file("tsconfig.json"), + file("webpack.config.js") + ) + + outputs.dir(file("build")) +} + +tasks.processResources { + dependsOn(buildGetStartUI) +} + +tasks.jar { + from(buildGetStartUI) { + into("webview") + } +} diff --git a/plugins/core-q/webview/package-lock.json b/plugins/core-q/webview/package-lock.json new file mode 100644 index 00000000000..5d32af8fda8 --- /dev/null +++ b/plugins/core-q/webview/package-lock.json @@ -0,0 +1,3931 @@ +{ + "name": "webview", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webview", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/node": "^14.18.5", + "fs-extra": "^10.0.1", + "sanitize-html": "^2.8.0", + "ts-node": "^10.7.0", + "uuid": "^8.3.2", + "vue": "^3.3.8", + "vuex": "^4.0.2", + "web-tree-sitter": "^0.20.7" + }, + "devDependencies": { + "@aws/fully-qualified-names": "^2.1.1", + "@types/sanitize-html": "^2.8.0", + "@typescript-eslint/eslint-plugin": "^5.38.0", + "@typescript-eslint/parser": "^5.38.0", + "@vue/compiler-sfc": "^3.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.6.0", + "eslint": "^8.26.0", + "eslint-config-prettier": "8.3", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-no-null": "^1.0.2", + "sass": "^1.49.8", + "sass-loader": "^12.6.0", + "style-loader": "^3.3.1", + "ts-loader": "^9.2.6", + "typescript": "^4.5.2", + "vue-loader": "^17.2.2", + "vue-style-loader": "^4.1.3", + "webpack": "^5.61.0", + "webpack-cli": "^4.7.2" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@aws/fully-qualified-names": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@aws/fully-qualified-names/-/fully-qualified-names-2.1.4.tgz", + "integrity": "sha512-+fR0mMvfRwde6wwbyF69igeIzqQx7g/HCfjt6Mi6D6MblU538gmJl29EKxJcRXhDFolH20sXbtMosbyMIRhs2w==", + "dev": true, + "dependencies": { + "web-tree-sitter": "^0.20.8" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz", + "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@types/sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", + "dev": true, + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.20.tgz", + "integrity": "sha512-l7M+xUuL8hrGtRLkrf+62d9zucAdgqNBTbJ/NufCOIuJQhauhfyAKH9ra/qUctCXcULwmclGAVpvmxjbBO30qg==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.20", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.20.tgz", + "integrity": "sha512-/cSBGL79HFBYgDnqCNKErOav3bPde3n0sJwJM2Z09rXlkiowV/2SG1tgDAiWS1CatS4Cvo0o74e1vNeCK1R3RA==", + "dependencies": { + "@vue/compiler-core": "3.4.20", + "@vue/shared": "3.4.20" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.20.tgz", + "integrity": "sha512-nPuTZz0yxTPzjyYe+9nQQsFYImcz/57UX8N3jyhl5oIUUs2jqqAMaULsAlJwve3qNYfjQzq0bwy3pqJrN9ecZw==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.20", + "@vue/compiler-dom": "3.4.20", + "@vue/compiler-ssr": "3.4.20", + "@vue/shared": "3.4.20", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.7", + "postcss": "^8.4.35", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.20.tgz", + "integrity": "sha512-b3gFQPiHLvI12C56otzBPpQhZ5kgkJ5RMv/zpLjLC2BIFwX5GktDqYQ7xg0Q2grP6uFI8al3beVKvAVxFtXmIg==", + "dependencies": { + "@vue/compiler-dom": "3.4.20", + "@vue/shared": "3.4.20" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + }, + "node_modules/@vue/reactivity": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.20.tgz", + "integrity": "sha512-P5LJcxUkG6inlHr6MHVA4AVFAmRYJQ7ONGWJILNjMjoYuEXFhYviSCb9BEMyszSG/1kWCZbtWQlKSLasFRpThw==", + "dependencies": { + "@vue/shared": "3.4.20" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.20.tgz", + "integrity": "sha512-MPvsQpGAxoBqLHjqopt4YPtUYBpq0K6oAWDTwIR1CTNZ3y9O/J2ZVh+i2JpxKNYwANJBiZ20O99NE20uisB7xw==", + "dependencies": { + "@vue/reactivity": "3.4.20", + "@vue/shared": "3.4.20" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.20.tgz", + "integrity": "sha512-OkbPVP69H+8m74543zMAAx/LIkajxufYyow41gc0s5iF0uplT5uTQ4llDYu1GeJZEI8wjL5ueiPQruk4qwOMmA==", + "dependencies": { + "@vue/runtime-core": "3.4.20", + "@vue/shared": "3.4.20", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.20.tgz", + "integrity": "sha512-w3VH2GuwxQHA6pJo/HCV22OfVC8Mw4oeHQM+vKeqtRK0OPE1Wilnh+P/SDVGGxPjJsGmyfphi0dbw8UKZQJH9w==", + "dependencies": { + "@vue/compiler-ssr": "3.4.20", + "@vue/shared": "3.4.20" + }, + "peerDependencies": { + "vue": "3.4.20" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.20.tgz", + "integrity": "sha512-KTEngal0aiUvNJ6I1Chk5Ew5XqChsFsxP4GKAYXWb99zKJWjNU72p2FWEOmZWHxHcqtniOJsgnpd3zizdpfEag==" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001589", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", + "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.682", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.682.tgz", + "integrity": "sha512-oCglfs8yYKs9RQjJFOHonSnhikPK3y+0SvSYc/YpYJV//6rqc0/hbwd0c7vgK4vrl6y2gJAwjkhkSGWK+z4KRA==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-header": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", + "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", + "dev": true, + "peerDependencies": { + "eslint": ">=7.7.0" + } + }, + "node_modules/eslint-plugin-no-null": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz", + "integrity": "sha512-uRDiz88zCO/2rzGfgG15DBjNsgwWtWiSo4Ezy7zzajUgpnFIqd1TjepKeRmJZHEfBGu58o2a8S0D7vglvvhkVA==", + "dev": true, + "engines": { + "node": ">=5.0.0" + }, + "peerDependencies": { + "eslint": ">=3.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", + "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", + "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sanitize-html": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", + "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sass": { + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz", + "integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", + "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/vue": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", + "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", + "dependencies": { + "@vue/compiler-dom": "3.4.20", + "@vue/compiler-sfc": "3.4.20", + "@vue/runtime-dom": "3.4.20", + "@vue/server-renderer": "3.4.20", + "@vue/shared": "3.4.20" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-loader": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", + "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/vue-style-loader": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", + "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", + "dev": true, + "dependencies": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "node_modules/vue-style-loader/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "node_modules/vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.0.2" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.20.8", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.8.tgz", + "integrity": "sha512-weOVgZ3aAARgdnb220GqYuh7+rZU0Ka9k9yfKtGAzEYMa6GgiCzW9JjQRJyCJakvibQW+dfjJdihjInKuuCAUQ==" + }, + "node_modules/webpack": { + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/plugins/core-q/webview/package.json b/plugins/core-q/webview/package.json new file mode 100644 index 00000000000..1877890eb6d --- /dev/null +++ b/plugins/core-q/webview/package.json @@ -0,0 +1,61 @@ +{ + "name": "webview", + "version": "1.0.0", + "description": "", + "main": "webpack.config.js", + "browser": { + "fs": false + }, + "scripts": { + "webpack": "webpack --config webpack.config.js --mode development", + "webpack-watch": "webpack --config webpack.config.js --mode development --watch", + "test": "echo \"Error: no test specified\" && exit 0", + "build-ui": "npm install && npm run webpack", + "lint": "eslint -c .eslintrc.js --ext .ts .", + "lintfix": "eslint -c .eslintrc.js --fix --ext .ts ." + }, + "dependencies": { + "@types/node": "^14.18.5", + "fs-extra": "^10.0.1", + "sanitize-html": "^2.8.0", + "ts-node": "^10.7.0", + "uuid": "^8.3.2", + "vue": "^3.3.8", + "vuex": "^4.0.2", + "web-tree-sitter": "^0.20.7" + }, + "devDependencies": { + "@aws/fully-qualified-names": "^2.1.1", + "@types/sanitize-html": "^2.8.0", + "@typescript-eslint/eslint-plugin": "^5.38.0", + "@typescript-eslint/parser": "^5.38.0", + "@vue/compiler-sfc": "^3.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.6.0", + "eslint": "^8.26.0", + "eslint-config-prettier": "8.3", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-no-null": "^1.0.2", + "sass": "^1.49.8", + "sass-loader": "^12.6.0", + "style-loader": "^3.3.1", + "ts-loader": "^9.2.6", + "typescript": "^4.5.2", + "vue-loader": "^17.2.2", + "vue-style-loader": "^4.1.3", + "webpack": "^5.61.0", + "webpack-cli": "^4.7.2" + }, + "author": "", + "license": "ISC", + "prettier": { + "printWidth": 120, + "trailingComma": "es5", + "tabWidth": 4, + "singleQuote": true, + "semi": false, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf" + } +} diff --git a/plugins/core-q/webview/src/constants.ts b/plugins/core-q/webview/src/constants.ts new file mode 100644 index 00000000000..1c85c334715 --- /dev/null +++ b/plugins/core-q/webview/src/constants.ts @@ -0,0 +1,6 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export const AWS_BUILDER_ID_NAME = 'AWS Builder ID' + +export const IDENTITY_CENTER_NAME = 'IAM Identity Center (SSO)' diff --git a/plugins/core-q/webview/src/defs.d.ts b/plugins/core-q/webview/src/defs.d.ts new file mode 100644 index 00000000000..f66575f4204 --- /dev/null +++ b/plugins/core-q/webview/src/defs.d.ts @@ -0,0 +1,23 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import {IdeClient} from "./ideClient"; +import {Store} from "vuex"; +import {State} from "./model"; + +export {} + +declare global { + interface Window { + // TODO: make postMessage api type safe + ideApi: { postMessage: (arg: { command: string } & any) => any } + ideClient: IdeClient + changeTheme: (darkMode: boolean) => void + } +} + +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + $store: Store + } +} diff --git a/plugins/core-q/webview/src/ideClient.ts b/plugins/core-q/webview/src/ideClient.ts new file mode 100644 index 00000000000..b427682eb0d --- /dev/null +++ b/plugins/core-q/webview/src/ideClient.ts @@ -0,0 +1,92 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import {Store} from "vuex"; +import { + IdcInfo, + State, + AuthSetupMessageFromIde, + ListProfileResult, + ListProfileSuccessResult, + ListProfileFailureResult, ListProfilePendingResult, ListProfilesMessageFromIde +} from "./model"; +import {WebviewTelemetry} from './webviewTelemetry' + +export class IdeClient { + constructor(private readonly store: Store) {} + + // TODO: design and improve the API here + + prepareUi(state: AuthSetupMessageFromIde | ListProfilesMessageFromIde) { + WebviewTelemetry.instance.reset() + console.log('browser is preparing UI with state ', state) + // hack as window.onerror don't have access to vuex store + void ((window as any).uiState = state.stage) + WebviewTelemetry.instance.willShowPage(state.stage) + + this.store.commit('setStage', state.stage) + + switch (state.stage) { + case "PROFILE_SELECT": + this.handleProfileSelectMessage(state as ListProfilesMessageFromIde) + break + + default: + this.handleAuthSetupMessage(state as AuthSetupMessageFromIde) + } + } + + private handleProfileSelectMessage(msg: ListProfilesMessageFromIde) { + let result: ListProfileResult | undefined + switch (msg.status) { + case 'succeeded': + result = new ListProfileSuccessResult(msg.profiles) + break + case 'failed': + result = new ListProfileFailureResult(msg.errorMessage) + break + case 'pending': + result = new ListProfilePendingResult() + break + } + this.store.commit('setProfilesResult', result) + } + + private handleAuthSetupMessage(msg: AuthSetupMessageFromIde) { + this.store.commit('setSsoRegions', msg.regions) + this.updateLastLoginIdcInfo(msg.idcInfo) + this.store.commit("setCancellable", msg.cancellable) + this.store.commit("setFeature", msg.feature) + const existConnections = msg.existConnections.map(it => { + return { + sessionName: it.sessionName, + startUrl: it.startUrl, + region: it.region, + scopes: it.scopes, + id: it.id + } + }) + + this.store.commit("setExistingConnections", existConnections) + this.updateAuthorization(undefined) + } + + updateAuthorization(code: string | undefined) { + this.store.commit('setAuthorizationCode', code) + // TODO: mutage stage to AUTHing here probably makes life easier + } + + updateLastLoginIdcInfo(idcInfo: IdcInfo) { + this.store.commit('setLastLoginIdcInfo', idcInfo) + } + + reset() { + this.store.commit('setStage', 'START') + } + + cancelLogin(): void { + // this.reset() + this.store.commit('setStage', 'START') + window.ideApi.postMessage({ command: 'cancelLogin' }) + } +} diff --git a/plugins/core-q/webview/src/model.ts b/plugins/core-q/webview/src/model.ts new file mode 100644 index 00000000000..8ace3779882 --- /dev/null +++ b/plugins/core-q/webview/src/model.ts @@ -0,0 +1,149 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export type AuthSetupMessageFromIde = { + stage: Stage, + regions: Region[], + idcInfo: IdcInfo, + cancellable: boolean, + feature: string, + existConnections: AwsBearerTokenConnection[], +} + +export type ListProfilesMessageFromIde = { + stage: Stage, + status: 'succeeded' | 'failed' | 'pending', + profiles: Profile[], + errorMessage: string +} + + +// plugin interface [AwsBearerTokenConnection] +export interface AwsBearerTokenConnection { + sessionName: string, + startUrl: string, + region: string, + scopes: string[], + id: string +} + +export const SONO_URL = "https://view.awsapps.com/start" + +export type Stage = + 'START' | + 'SSO_FORM' | + 'CONNECTED' | + 'AUTHENTICATING' | + 'AWS_PROFILE' | + 'REAUTH' | + 'PROFILE_SELECT' + +export type Feature = 'Q' | 'codecatalyst' | 'awsExplorer' + +export interface Region { + id: string, + name: string, + partitionId: string, + category: string, + displayName: string +} + +export interface IdcInfo { + startUrl: string, + region: string, +} + +export interface State { + stage: Stage, + ssoRegions: Region[], + authorizationCode: string | undefined, + lastLoginIdcInfo: IdcInfo, + feature: Feature, + cancellable: boolean, + existingConnections: AwsBearerTokenConnection[], + listProfilesResult: ListProfileResult | undefined, + selectedProfile: Profile | undefined +} + +export interface ListProfileResult { + status: 'succeeded' | 'failed' | 'pending' +} + +export class ListProfileSuccessResult implements ListProfileResult { + status: 'succeeded' = 'succeeded' + constructor(readonly profiles: Profile[]) {} +} + +export class ListProfileFailureResult implements ListProfileResult { + status: 'failed' = 'failed' + constructor(readonly errorMessage: string) {} +} + +export class ListProfilePendingResult implements ListProfileResult { + status: 'pending' = 'pending' + constructor() {} +} + +export enum LoginIdentifier { + NONE = 'none', + BUILDER_ID = 'builderId', + ENTERPRISE_SSO = 'idc', + IAM_CREDENTIAL = 'iam', + EXISTING_LOGINS = 'existing', +} + +export interface LoginOption { + id: LoginIdentifier + + requiresBrowser(): boolean +} + +export interface Profile { + profileName: string + accountId: string + region: string + arn: String +} + +export const GENERIC_PROFILE_LOAD_ERROR = "We couldn't load your Q Developer profiles. Please try again."; + +export class LongLivedIAM implements LoginOption { + id: LoginIdentifier = LoginIdentifier.IAM_CREDENTIAL + + constructor(readonly profileName: string, readonly accessKey: string, readonly secret: string) { + } + + requiresBrowser(): boolean { + return false + } +} + +export class IdC implements LoginOption { + id: LoginIdentifier = LoginIdentifier.ENTERPRISE_SSO + + constructor(readonly url: string, readonly region: string) { + } + + requiresBrowser(): boolean { + return true + } +} + +export class BuilderId implements LoginOption { + id: LoginIdentifier = LoginIdentifier.BUILDER_ID + + requiresBrowser(): boolean { + return true + } +} + +export class ExistConnection implements LoginOption { + id: LoginIdentifier = LoginIdentifier.EXISTING_LOGINS + + constructor(readonly pluginConnectionId: string) {} + + // this case only happens for bearer connection for now + requiresBrowser(): boolean { + return true + } +} diff --git a/plugins/core-q/webview/src/q-ui/assets/common.scss b/plugins/core-q/webview/src/q-ui/assets/common.scss new file mode 100644 index 00000000000..6e66f44a113 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/assets/common.scss @@ -0,0 +1,105 @@ +/* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* Common styles */ +input, select { + height: 37px; + border-radius: 4px; + width: 100%; +} + +input { + padding-left: 10px; + box-sizing: border-box; +} + +select { + padding-left: 6px; +} + +a { + color: #29a7ff; +} + +.font-amazon { + font-family: "Amazon Ember", sans-serif; + user-select: none; + font-size: 15px; +} + +.bottom-small-gap { + margin-bottom: 20px !important; +} + +.login-flow-button { + width: 100%; + height: 55px; + border-radius: 3px; + border-width: 0; + font-weight: bold; + margin-bottom: 100px; +} + +.continue-button { + background-color: #3574f0; +} + +.back-button { + background: none; + border: none; + cursor: pointer; + padding: 0; +} + +.title { + margin-bottom: 5px; + margin-top: 5px; + font-size: 15px; + font-weight: bold; +} + +.no-bold { + font-weight: normal !important; +} + +/* Theme specific css */ +body.jb-dark { + .login-flow-button, .title, .text, .back-button, .confirmation-code { + color: white; + } + .cancel-button { + background-color: #FFFFFF1A; + } + .continue-button:disabled { + background-color: #FFFFFF1A !important; + color: #6f6f6f !important; + } +} + +body.jb-light { + .title, .text, .back-button, .confirmation-code { + color: black; + } + .login-flow-button { + color: white; + } + .cancel-button { + background-color: #0000001A; + color: black !important; + } + .continue-button:disabled { + background-color: #0000001A !important; + color: #6f6f6f !important; + } +} + +button:not([disabled]), .item-container { + cursor: pointer; +} + +.centered-with-max-width { + max-width: 300px; + margin: 0 auto; +} + + diff --git a/plugins/core-q/webview/src/q-ui/components/authenticating.vue b/plugins/core-q/webview/src/q-ui/components/authenticating.vue new file mode 100644 index 00000000000..a88ba6142d3 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/authenticating.vue @@ -0,0 +1,74 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/awsProfileForm.vue b/plugins/core-q/webview/src/q-ui/components/awsProfileForm.vue new file mode 100644 index 00000000000..7165e7d0abf --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/awsProfileForm.vue @@ -0,0 +1,84 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/login.vue b/plugins/core-q/webview/src/q-ui/components/login.vue new file mode 100644 index 00000000000..6b31e1efe4d --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/login.vue @@ -0,0 +1,135 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/loginOptions.vue b/plugins/core-q/webview/src/q-ui/components/loginOptions.vue new file mode 100644 index 00000000000..bce0f2a469b --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/loginOptions.vue @@ -0,0 +1,45 @@ + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/logo.vue b/plugins/core-q/webview/src/q-ui/components/logo.vue new file mode 100644 index 00000000000..948c9de8c7a --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/logo.vue @@ -0,0 +1,120 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/profileSelection.vue b/plugins/core-q/webview/src/q-ui/components/profileSelection.vue new file mode 100644 index 00000000000..d3d66d37c3d --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/profileSelection.vue @@ -0,0 +1,227 @@ + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/qOptions.vue b/plugins/core-q/webview/src/q-ui/components/qOptions.vue new file mode 100644 index 00000000000..3fe29b5efd7 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/qOptions.vue @@ -0,0 +1,142 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/reauth.vue b/plugins/core-q/webview/src/q-ui/components/reauth.vue new file mode 100644 index 00000000000..11bfc5853f9 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/reauth.vue @@ -0,0 +1,108 @@ + + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/root.vue b/plugins/core-q/webview/src/q-ui/components/root.vue new file mode 100644 index 00000000000..5197267b2a7 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/root.vue @@ -0,0 +1,99 @@ + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/selectableItem.vue b/plugins/core-q/webview/src/q-ui/components/selectableItem.vue new file mode 100644 index 00000000000..345c9adc3bd --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/selectableItem.vue @@ -0,0 +1,150 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/ssoLoginForm.vue b/plugins/core-q/webview/src/q-ui/components/ssoLoginForm.vue new file mode 100644 index 00000000000..46f38a1a78b --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/ssoLoginForm.vue @@ -0,0 +1,181 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/components/toolkitOptions.vue b/plugins/core-q/webview/src/q-ui/components/toolkitOptions.vue new file mode 100644 index 00000000000..1cc0acd5ac6 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/components/toolkitOptions.vue @@ -0,0 +1,156 @@ + + + + + + + + diff --git a/plugins/core-q/webview/src/q-ui/index.ts b/plugins/core-q/webview/src/q-ui/index.ts new file mode 100644 index 00000000000..28b7cf6a96c --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/index.ts @@ -0,0 +1,82 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// eslint-disable-next-line header/header +import { createApp } from 'vue' +import {createStore, Store} from 'vuex' +import HelloWorld from './components/root.vue' +import {AwsBearerTokenConnection, Feature, IdcInfo, ListProfileResult, Profile, Region, Stage, State} from "../model"; +import {IdeClient} from "../ideClient"; +import './assets/common.scss' + +const app = createApp(HelloWorld, { app: 'AMAZONQ' }) +const store = createStore({ + state: { + stage: 'START' as Stage, + ssoRegions: [] as Region[], + authorizationCode: undefined, + lastLoginIdcInfo: { + startUrl: '', + region: '', + }, + feature: 'Q', + cancellable: false, + existingConnections: [] as AwsBearerTokenConnection[], + listProfilesResult: undefined as ListProfileResult | undefined, + selectedProfile: undefined + }, + getters: {}, + mutations: { + setStage(state: State, stage: Stage) { + state.stage = stage + }, + setSsoRegions(state: State, regions: Region[]) { + state.ssoRegions = regions + }, + setCancellable() { + + }, + setAuthorizationCode(state: State, code: string) { + state.authorizationCode = code + }, + setFeature(state: State, feature: Feature) { + state.feature = feature + }, + setLastLoginIdcInfo(state: State, idcInfo: IdcInfo) { + console.log('state idc info is updated') + state.lastLoginIdcInfo.startUrl = idcInfo.startUrl + state.lastLoginIdcInfo.region = idcInfo.region + }, + setExistingConnections(state: State, connections: AwsBearerTokenConnection[]) { + state.existingConnections = connections + }, + setProfilesResult(state: State, profileResult: ListProfileResult) { + state.listProfilesResult = profileResult + }, + setSelectedProfile(state, profile: Profile) { + state.selectedProfile = profile + }, + reset(state: State) { + state.stage = 'START' + state.ssoRegions = [] + state.authorizationCode = undefined + state.lastLoginIdcInfo = { + startUrl: '', + region: '' + } + state.selectedProfile = undefined + state.listProfilesResult = undefined + } + }, + actions: {}, + modules: {}, +}) + +window.ideClient = new IdeClient(store) +app.directive('autofocus', { + // When the bound element is inserted into the DOM... + mounted: function (el) { + el.focus(); + } +}); +app.use(store).mount('#app') diff --git a/plugins/core-q/webview/src/q-ui/toolkit.ts b/plugins/core-q/webview/src/q-ui/toolkit.ts new file mode 100644 index 00000000000..a10b0cfd356 --- /dev/null +++ b/plugins/core-q/webview/src/q-ui/toolkit.ts @@ -0,0 +1,74 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// eslint-disable-next-line header/header +import {createApp} from 'vue' +import {createStore, Store} from 'vuex' +import root from './components/root.vue' +import {AwsBearerTokenConnection, Feature, IdcInfo, Region, Stage, State} from "../model"; +import {IdeClient} from "../ideClient"; +import './assets/common.scss' + +const app = createApp(root, { app: 'TOOLKIT' }) + +const store = createStore({ + state: { + stage: 'START' as Stage, + ssoRegions: [] as Region[], + authorizationCode: undefined, + lastLoginIdcInfo: { + startUrl: '', + region: '', + }, + feature: 'awsExplorer', + cancellable: false, + existingConnections: [] as AwsBearerTokenConnection[], + listProfilesResult : undefined, + selectedProfile: undefined, + }, + getters: {}, + mutations: { + setStage(state: State, stage: Stage) { + state.stage = stage + }, + setSsoRegions(state: State, regions: Region[]) { + state.ssoRegions = regions + }, + setAuthorizationCode(state: State, code: string | undefined) { + state.authorizationCode = code + }, + setFeature(state: State, feature: Feature) { + state.feature = feature + }, + setLastLoginIdcInfo(state: State, idcInfo: IdcInfo) { + state.lastLoginIdcInfo.startUrl = idcInfo.startUrl + state.lastLoginIdcInfo.region = idcInfo.region + }, + setCancellable(state: State, cancellable: boolean) { + state.cancellable = cancellable + }, + setExistingConnections(state: State, connections: AwsBearerTokenConnection[]) { + state.existingConnections = connections + }, + reset(state: State) { + state.stage = 'START' + state.ssoRegions = [] + state.authorizationCode = undefined + state.lastLoginIdcInfo = { + startUrl: '', + region: '' + } + } + }, + actions: {}, + modules: {}, +}) + +window.ideClient = new IdeClient(store) +app.directive('autofocus', { + // When the bound element is inserted into the DOM... + mounted: function (el) { + el.focus(); + } +}); +app.use(store).mount('#app') diff --git a/plugins/core-q/webview/src/vue.shims.d.ts b/plugins/core-q/webview/src/vue.shims.d.ts new file mode 100644 index 00000000000..08898fc9635 --- /dev/null +++ b/plugins/core-q/webview/src/vue.shims.d.ts @@ -0,0 +1,11 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// temporary ambient declaration +// this does not really extract types from vue components +// it just prevents TSC from complaining when trying to import them +declare module '*.vue' { + import type { DefineComponent } from "vue"; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/plugins/core-q/webview/src/webviewTelemetry.ts b/plugins/core-q/webview/src/webviewTelemetry.ts new file mode 100644 index 00000000000..2262bb8006e --- /dev/null +++ b/plugins/core-q/webview/src/webviewTelemetry.ts @@ -0,0 +1,68 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import {error} from "console"; +import {Stage} from "./model"; + +export class WebviewTelemetry { + static #instance: WebviewTelemetry + public static get instance() { + return (this.#instance ??= new WebviewTelemetry()) + } + + private intentTimestamp: number = 0 + + willShowPage(page: Stage) { + if (page === 'START' || page === 'REAUTH' || page === 'PROFILE_SELECT') { + this.intentTimestamp = Date.now() + } + } + + didShowPage(page: Stage, errorMessage: string | undefined = undefined) { + // align with other ides + let module = '' + if (page === 'START') { + module = 'login' + } else if (page === 'REAUTH') { + module = 'reauth' + } else if (page === 'PROFILE_SELECT') { + module = 'selectProfile' + } else { + console.log(`didShowPage ${page}`) + return + } + const duration = Date.now() - this.intentTimestamp + + const event: Toolkit_didLoadModule = errorMessage ? { + metricName: 'toolkit_didLoadModule', + module: module, + result: 'Failed', + reason: errorMessage, + } : { + metricName: 'toolkit_didLoadModule', + module: module, + result: 'Succeeded', + duration: duration, + } + + this.publishTelemetryToIde(event) + } + + reset() { + this.intentTimestamp = 0 + } + + private publishTelemetryToIde(event: Toolkit_didLoadModule) { + console.log(`publishing telemetry event to IDE`, event) + const eventStr = JSON.stringify(event) + window.ideApi.postMessage({command: 'webviewTelemetry', event: eventStr}) + } +} + +type Toolkit_didLoadModule = { + metricName: string, + module: string, + result: 'Failed' | 'Succeeded', + reason?: string, + duration?: number +} diff --git a/plugins/core-q/webview/tsconfig.json b/plugins/core-q/webview/tsconfig.json new file mode 100644 index 00000000000..57c8d5a507f --- /dev/null +++ b/plugins/core-q/webview/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "commonjs", + "target": "ES2019", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["ES2020", "es5", "es6", "dom"], + "outDir": "out", + "sourceMap": true, + "strict": true, + "typeRoots": [ + "./node_modules/@types" + ], + "strictPropertyInitialization": false + }, + "include": ["./src/**/*"], + "exclude": ["build", "out", "node_modules", ".vscode-test"], +} diff --git a/plugins/core-q/webview/webpack.config.js b/plugins/core-q/webview/webpack.config.js new file mode 100644 index 00000000000..40a1a2ab27a --- /dev/null +++ b/plugins/core-q/webview/webpack.config.js @@ -0,0 +1,134 @@ + +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//@ts-check + +// eslint-disable-next-line header/header +'use strict'; + +const path = require('path'); +const {VueLoaderPlugin} = require("vue-loader"); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +/**@type {import('webpack').Configuration}*/ +const vueConfig = { + name: 'vue', + target: 'web', + entry: './src/q-ui/index.ts', + output: { + path: path.resolve(__dirname, 'build/assets/js'), + filename: 'getStart.js', + library: 'getStart', + libraryTarget: 'var', + devtoolModuleFilenameTemplate: '../[resource-path]', + }, + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.js', '.vue'], + fallback: { + fs: false, + path: false, + util: false + }, + alias: { + 'vue': 'vue/dist/vue.esm-bundler.js', + '@': path.resolve(__dirname, "./src/") + } + }, + experiments: {asyncWebAssembly: true}, + module: { + rules: [ + {test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader']}, + { + test: /\.ts$/, + exclude: /node_modules/, + loader: 'ts-loader', + options: { + appendTsSuffixTo: [/\.vue$/] + } + }, + { + test: /\.vue$/, + loader: 'vue-loader', + }, + { + test: /\.css$/, + use: ['vue-style-loader', 'css-loader'], + }, + { + test: /\.(png|jpg|gif|svg)$/, + use: 'file-loader', + } + ] + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + {from: './node_modules/web-tree-sitter/tree-sitter.wasm', to: ''} + ] + }), + new VueLoaderPlugin() + ], +}; + +const toolkitVueConfig = { + name: 'vue', + target: 'web', + entry: './src/q-ui/toolkit.ts', + output: { + path: path.resolve(__dirname, 'build/assets/js'), + filename: 'toolkitGetStart.js', + library: 'toolkitGetStart', + libraryTarget: 'var', + devtoolModuleFilenameTemplate: '../[resource-path]', + }, + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.js', '.vue'], + fallback: { + fs: false, + path: false, + util: false + }, + alias: { + 'vue': 'vue/dist/vue.esm-bundler.js', + '@': path.resolve(__dirname, "./src/") + } + }, + experiments: {asyncWebAssembly: true}, + module: { + rules: [ + {test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader']}, + { + test: /\.ts$/, + exclude: /node_modules/, + loader: 'ts-loader', + options: { + appendTsSuffixTo: [/\.vue$/] + } + }, + { + test: /\.vue$/, + loader: 'vue-loader', + }, + { + test: /\.css$/, + use: ['vue-style-loader', 'css-loader'], + }, + { + test: /\.(png|jpg|gif|svg)$/, + use: 'file-loader', + } + ] + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + {from: './node_modules/web-tree-sitter/tree-sitter.wasm', to: ''} + ] + }), + new VueLoaderPlugin() + ], +}; +module.exports = [vueConfig, toolkitVueConfig]; diff --git a/plugins/core/build.gradle.kts b/plugins/core/build.gradle.kts index 2bf25ac620a..f9bbfba3944 100644 --- a/plugins/core/build.gradle.kts +++ b/plugins/core/build.gradle.kts @@ -38,14 +38,3 @@ tasks.check { dependsOn(":plugin-core:${it.name}:check") } } - -tasks.shadowJar { - archiveBaseName.set("plugin-core-shadow") - archiveVersion.set(rootProject.version.toString()) - archiveClassifier.set("") - - exclude("/META-INF/plugin.xml") - - configurations = project.configurations.runtimeClasspath.map { listOf(it) } - destinationDirectory.set(layout.buildDirectory.dir("libs")) -} diff --git a/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml b/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml deleted file mode 100644 index 3eb636bb0de..00000000000 --- a/plugins/core/jetbrains-community/resources/META-INF/aws.toolkit.core.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/core/jetbrains-community/resources/META-INF/module-core.xml b/plugins/core/jetbrains-community/resources/META-INF/module-core.xml deleted file mode 100644 index fdcd84ba8ee..00000000000 --- a/plugins/core/jetbrains-community/resources/META-INF/module-core.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt index aece5fe9a25..a5b91fd40e1 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt @@ -108,9 +108,7 @@ object NotificationManager { AwsCoreBundle.message("aws.settings.auto_update.progress.message") ) { override fun run(indicator: ProgressIndicator) { - pluginUpdateManager.checkForUpdates(indicator, AwsPlugin.CORE) pluginUpdateManager.checkForUpdates(indicator, AwsPlugin.TOOLKIT) - pluginUpdateManager.checkForUpdates(indicator, AwsPlugin.Q) } }) } diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt index 384c38bf1e6..9fa2910a733 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt @@ -53,9 +53,7 @@ class PluginUpdateManager : Disposable { AwsCoreBundle.message("aws.settings.auto_update.progress.message") ) { override fun run(indicator: ProgressIndicator) { - checkForUpdates(indicator, AwsPlugin.CORE) checkForUpdates(indicator, AwsPlugin.TOOLKIT) - checkForUpdates(indicator, AwsPlugin.Q) } }) } @@ -115,7 +113,6 @@ class PluginUpdateManager : Disposable { return } if (!AwsSettings.getInstance().isAutoUpdateNotificationEnabled) return - if (plugin == AwsPlugin.CORE) return notifyInfo( title = AwsCoreBundle.message("aws.notification.auto_update.title", pluginName), content = AwsCoreBundle.message("aws.settings.auto_update.notification.message"), diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 8a7beee4573..b11daf273a4 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -37,6 +37,7 @@ action.aws.toolkit.toolwindow.explorer.newConnection.text=Setup authentication t action.aws.toolkit.toolwindow.newConnection.text=Add Another Connection... action.dynamic.open.text=Open Resource... action.q.openchat.text=Open Chat Panel +amazon.q.settings.title=Amazon Q amazonqChat.project_context.index_in_progress=By the way, I'm still indexing this project for full context from your workspace. I may have a better response in a few minutes when it's complete if you'd like to try again then. amazonqChat.stopChatResponse=You stopped your current work, please provide additional examples or ask another question. amazonqDoc.answer.codeResult=You can accept the changes to your files, or describe any additional changes you'd like me to make. @@ -328,7 +329,7 @@ aws.settings.telemetry.description=Help us improve our product! Allow us to coll aws.settings.telemetry.option=Send usage metrics to AWS aws.settings.telemetry.prompt.message=Usage metrics are collected by default. Click here to adjust this behavior. aws.settings.telemetry.prompt.title=AWS IDE plugins telemetry -aws.settings.title=AWS +aws.settings.title=AWS Toolkit aws.settings.toolkit.configurable.title=AWS Toolkit aws.sso.signing.device.code=Proceed To Browser aws.sso.signing.device.code.copy=Copy Code diff --git a/plugins/core/src/main/resources/META-INF/plugin.xml b/plugins/core/src/main/resources/META-INF/plugin.xml deleted file mode 100644 index 9a4cde9f80f..00000000000 --- a/plugins/core/src/main/resources/META-INF/plugin.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - aws.toolkit.core - AWS Core - This plugin is required to use the Amazon Q or AWS Toolkit plugins. It will be automatically installed if you install either plugin.

- ]]>
- AWS - 1.0 - - com.intellij.modules.platform - - com.intellij.cwm.guest - com.intellij.jetbrains.client - com.intellij.gateway - - - - - - - - - - - - - - - -
diff --git a/plugins/toolkit/intellij-standalone/build.gradle.kts b/plugins/toolkit/intellij-standalone/build.gradle.kts index 8d3b1deb553..34d8fe39186 100644 --- a/plugins/toolkit/intellij-standalone/build.gradle.kts +++ b/plugins/toolkit/intellij-standalone/build.gradle.kts @@ -19,7 +19,7 @@ intellijPlatform { } dependencies { - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core")) } tasks.prepareJarSearchableOptions { diff --git a/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml b/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml deleted file mode 100644 index c6d9995c08d..00000000000 --- a/plugins/toolkit/intellij-standalone/resources/META-INF/plugin-shim.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - aws.toolkit.core - - - - - - - - - diff --git a/plugins/toolkit/jetbrains-core/build.gradle.kts b/plugins/toolkit/jetbrains-core/build.gradle.kts index 8fc2f13b9a6..6928e1d1510 100644 --- a/plugins/toolkit/jetbrains-core/build.gradle.kts +++ b/plugins/toolkit/jetbrains-core/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { bundledModule("intellij.platform.vcs.dvcs.impl") bundledModule("intellij.libraries.microba") } - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core")) } val changelog = tasks.register("pluginChangeLog") { diff --git a/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml b/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml index 39be6ec4e6d..da7c4881d38 100644 --- a/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml +++ b/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml @@ -6,11 +6,6 @@ AWS Toolkit 1.0 - - - - - Amazon Q and CodeWhisperer

CodeWhisperer is now part of Amazon Q. Try the Amazon Q extension.

@@ -137,6 +132,12 @@ + + + + + @@ -174,9 +175,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -196,7 +281,6 @@ - - - + + @@ -341,10 +425,13 @@ - + + + + diff --git a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt deleted file mode 100644 index d0b12a79236..00000000000 --- a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.core.startup - -import com.intellij.ide.BrowserUtil -import com.intellij.ide.plugins.PluginManager -import com.intellij.ide.plugins.PluginManagerConfigurable -import com.intellij.ide.plugins.PluginManagerCore -import com.intellij.ide.plugins.PluginManagerMain -import com.intellij.notification.NotificationAction -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.extensions.PluginId -import com.intellij.openapi.options.ShowSettingsUtil -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.Task -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.StartupActivity -import software.aws.toolkits.core.utils.debug -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.AwsToolkit -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection -import software.aws.toolkits.jetbrains.services.amazonq.QConstants -import software.aws.toolkits.jetbrains.settings.AwsSettings -import software.aws.toolkits.jetbrains.utils.notifyError -import software.aws.toolkits.jetbrains.utils.notifyInfo -import software.aws.toolkits.resources.AwsToolkitBundle.message -import software.aws.toolkits.telemetry.Component -import software.aws.toolkits.telemetry.Result -import software.aws.toolkits.telemetry.ToolkitTelemetry -import java.net.URI -import java.util.concurrent.atomic.AtomicBoolean - -class QMigrationActivity : StartupActivity.DumbAware { - private val qMigrationShownOnce = AtomicBoolean(false) - - override fun runActivity(project: Project) { - if (!qMigrationShownOnce.getAndSet(true)) { - displayQMigrationInfo(project) - } - } - - // For the Q migration notification, we want to notify it only once the first time user has updated Toolkit, - // if we have detected Q is not yet installed. - // Check the user's current connection, if it contains CW or Q, auto-install for them, if they don't have one, - // it means they have not used CW or Q before so show the opt-in/install notification for them. - private fun displayQMigrationInfo(project: Project) { - if (AwsSettings.getInstance().isQMigrationNotificationShownOnce) return - - val hasUsedQ = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) != null - if (hasUsedQ) { - // do auto-install - installQPlugin(project, autoInstall = true) - } else if (!PluginManager.isPluginInstalled(PluginId.getId(AwsToolkit.Q_PLUGIN_ID))) { - // show opt-in/install notification - notifyInfo( - title = message("aws.q.migration.new_users.notify.title"), - content = message("aws.q.migration.new_users.notify.message"), - project = project, - notificationActions = listOf( - NotificationAction.createSimple(message("aws.q.migration.action.read_more.text")) { - BrowserUtil.browse(Q_JB_MARKETPLACE_URI) - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_CHANGE_ID, - component = Component.ReadMore, - result = Result.Succeeded - ) - }, - NotificationAction.createSimpleExpiring(message("aws.q.migration.action.install.text")) { - installQPlugin(project, autoInstall = false) - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_CHANGE_ID, - component = Component.Install, - result = Result.Succeeded - ) - } - ) - ) - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_CHANGE_ID, - component = Component.Unknown, - result = Result.Succeeded - ) - } - AwsSettings.getInstance().isQMigrationNotificationShownOnce = true - } - - private fun installQPlugin(project: Project, autoInstall: Boolean) { - val qPluginId = PluginId.getId(AwsToolkit.Q_PLUGIN_ID) - if (PluginManagerCore.isPluginInstalled(qPluginId)) { - LOG.debug { "Amazon Q plugin is already installed, not performing migration" } - return - } - - ProgressManager.getInstance().run( - // TODO: change title - object : Task.Backgroundable(project, "Installing Amazon Q...") { - override fun run(indicator: ProgressIndicator) { - val succeeded = lookForPluginToInstall(PluginId.getId(AwsToolkit.Q_PLUGIN_ID), indicator) - if (succeeded) { - if (!autoInstall) { - PluginManagerMain.notifyPluginsUpdated(project) - } else { - notifyInfo( - title = message("aws.q.migration.existing_users.notify.title"), - content = message("aws.q.migration.existing_users.notify.message"), - project = project, - notificationActions = listOf( - NotificationAction.createSimple(message("aws.q.migration.action.read_more.text")) { - BrowserUtil.browse(URI(QConstants.Q_MARKETPLACE_URI)) - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_INSTALLED_ID, - component = Component.ReadMore, - result = Result.Succeeded - ) - }, - NotificationAction.createSimple(message("aws.q.migration.action.manage_plugins.text")) { - ShowSettingsUtil.getInstance().showSettingsDialog( - project, - PluginManagerConfigurable::class.java - ) { configurable: PluginManagerConfigurable -> - configurable.openMarketplaceTab("Amazon Q") - } - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_INSTALLED_ID, - component = Component.ManageExtensions, - result = Result.Succeeded - ) - }, - NotificationAction.createSimpleExpiring(message("aws.q.migration.action.restart.text")) { - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_INSTALLED_ID, - component = Component.GotIt, - result = Result.Succeeded - ) - ApplicationManager.getApplication().restart() - }, - ) - ) - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_INSTALLED_ID, - component = Component.Unknown, - result = Result.Succeeded - ) - } - } else { - notifyError( - title = message("aws.q.migration.failed_to_install.message"), - project = project, - notificationActions = listOf( - NotificationAction.createSimpleExpiring(message("aws.q.migration.action.manage_plugins.text")) { - // TODO: change search text - ShowSettingsUtil.getInstance().showSettingsDialog( - project, - PluginManagerConfigurable::class.java - ) { configurable: PluginManagerConfigurable -> - configurable.openMarketplaceTab("Amazon Q") - } - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_INSTALLED_ID, - component = Component.ManageExtensions, - result = Result.Failed - ) - } - ) - ) - ToolkitTelemetry.showNotification( - id = Q_STANDALONE_INSTALLED_ID, - component = Component.Unknown, - result = Result.Failed - ) - } - } - } - ) - } - - companion object { - private val LOG = getLogger() - - private const val Q_STANDALONE_INSTALLED_ID = "amazonQStandaloneInstalled" - private const val Q_STANDALONE_CHANGE_ID = "amazonQStandaloneChange" - private const val Q_JB_MARKETPLACE_URI = "https://plugins.jetbrains.com/plugin/24267-amazon-q" - } -} diff --git a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml deleted file mode 100644 index b887dcdb460..00000000000 --- a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/plugins/toolkit/jetbrains-ultimate/build.gradle.kts b/plugins/toolkit/jetbrains-ultimate/build.gradle.kts index 85023835d81..8d7c6862cec 100644 --- a/plugins/toolkit/jetbrains-ultimate/build.gradle.kts +++ b/plugins/toolkit/jetbrains-ultimate/build.gradle.kts @@ -16,7 +16,7 @@ intellijToolkit { } dependencies { - implementation(project(path = ":plugin-core", configuration = "shadow")) + implementation(project(":plugin-core")) compileOnlyApi(project(":plugin-toolkit:jetbrains-core")) compileOnlyApi(project(":plugin-core:jetbrains-ultimate")) From 1720992fb5ba13acf48829b212497f134b54946a Mon Sep 17 00:00:00 2001 From: Sherry Lu Date: Mon, 5 Jan 2026 16:04:02 -0800 Subject: [PATCH 05/60] fix the tests in amazonq and toolkit plugin and run the core tests in toolkit --- ...eWhispererCodeModernizerGumbyClientTest.kt | 2 +- .../codewhisperer/CodeWhispererActionTest.kt | 2 +- .../CodeWhispererBasicTestBase.kt | 2 +- .../CodeWhispererClientAdaptorTest.kt | 2 +- .../CodeWhispererEditorUtilTest.kt | 2 +- .../CodeWhispererLanguageManagerTest.kt | 2 +- .../CodeWhispererReferenceManagerTest.kt | 2 +- .../CodeWhispererSettingsTest.kt | 2 +- .../CodeWhispererTelemetryTest.kt | 4 +- .../codewhisperer/CodeWhispererTestBase.kt | 2 +- .../CodeWhispererUserGroupSettingsTest.kt | 4 +- .../codewhisperer/CodeWhispererUtilTest.kt | 2 +- .../services/codewhisperer/QEndpointsTest.kt | 2 +- .../codescan/CodeWhispererCodeFileScanTest.kt | 2 +- .../codescan/CodeWhispererCodeScanTest.kt | 2 +- .../CodeWhispererProjectCodeScanTest.kt | 2 +- .../CodeWhispererFallbackImportAdderTest.kt | 2 +- .../CodeWhispererPythonImportAdderTest.kt | 2 +- .../CodeWhispererJSImportAdderTest.kt | 2 +- plugins/amazonq/mynah-ui/package-lock.json | 14 +++- .../lsp/AmazonQLanguageClientImplTest.kt | 2 +- .../aws/toolkits/core/s3/BucketUtilsTest.kt | 2 +- .../AwsCredentialsExtensionsTest.kt | 6 +- .../q}/core/credentials/Mocks.kt | 6 +- .../core/credentials/SsoUrlIdentifierTest.kt | 2 +- .../lambda/LambdaSampleEventProviderTest.kt | 2 +- .../core/parser/EndpointsJsonValidatorTest.kt | 2 +- .../parser/LambdaManifestValidatorTest.kt | 2 +- .../LambdaSampleEventJsonValidatorTest.kt | 2 +- .../q}/core/region/AwsRegionTest.kt | 6 +- .../q}/core/region/PartitionParserTest.kt | 2 +- .../q}/core/rules/ECSTemporaryServiceRule.kt | 6 +- .../core/rules/EcrTemporaryRepositoryRule.kt | 6 +- .../core/rules/EnvironmentVariableHelper.kt | 2 +- .../q}/core/rules/S3TemporaryBucketRule.kt | 6 +- .../q}/core/rules/SystemPropertyHelper.kt | 2 +- .../q}/core/telemetry/TelemetryBatcherTest.kt | 2 +- .../q}/core/utils/CollectionUtilsTest.kt | 2 +- .../q}/core/utils/CompletionStageUtils.kt | 2 +- .../q}/core/utils/DelegateSdkConsumers.kt | 2 +- .../q}/core/utils/EitherTest.kt | 2 +- .../q}/core/utils/ExceptionUtilsTest.kt | 2 +- .../core/utils/IntegrationTestCredentials.kt | 2 +- .../q}/core/utils/LogUtilsTest.kt | 3 +- .../core/utils/RemoteResourceResolverTest.kt | 2 +- .../q}/core/utils/RuleUtils.kt | 2 +- .../q}/core/utils/RuleUtilsTest.kt | 2 +- .../q}/core/utils/ZipUtilsTest.kt | 2 +- .../q}/core/utils/test/AssertJAsserts.kt | 2 +- .../q}/core/utils/test/TestUtils.kt | 2 +- .../q}/core/utils/test/TestUtilsTest.kt | 2 +- .../q}/jetbrains/utils/AttributeBagTest.kt | 2 +- .../q}/jetbrains/utils/StringUtilsTest.kt | 2 +- .../DefaultToolkitAuthManagerTest.kt | 2 +- .../core/gettingstarted/IdcRolePopupTest.kt | 2 +- .../SetupAuthenticationDialogTest.kt | 2 +- .../q/jetbrains/core/AwsClientManagerTest.kt | 4 +- .../q/jetbrains/core/AwsResourceCacheTest.kt | 2 +- .../q/jetbrains/core/AwsSdkClientTest.kt | 4 +- .../core/credentials/CredentialManagerTest.kt | 4 +- .../CredentialsRegionHandlerTest.kt | 4 +- .../DefaultAwsConnectionManagerTest.kt | 8 +- .../DefaultConfigFilesFacadeTest.kt | 2 +- .../DefaultToolkitConnectionManagerTest.kt | 2 +- .../RefreshConnectionActionTest.kt | 6 +- .../profiles/Ec2MetadataConfigProviderTest.kt | 6 +- .../profiles/ProfileAssumeRoleProviderTest.kt | 4 +- .../ProfileCredentialProviderFactoryTest.kt | 4 +- .../credentials/profiles/ProfileReaderTest.kt | 2 +- .../credentials/profiles/ProfileTestUtils.kt | 2 +- .../profiles/ProfileWatcherTest.kt | 2 +- .../core/credentials/sso/DiskCacheTest.kt | 2 +- .../sso/SsoAccessTokenProviderTest.kt | 6 +- .../sso/SsoCredentialProviderTest.kt | 4 +- .../sso/bearer/BearerTokenTestUtil.kt | 4 +- .../InteractiveBearerTokenProviderTest.kt | 4 +- .../ProfileSdkTokenProviderWrapperTest.kt | 4 +- .../AwsCognitoCredentialsProviderTest.kt | 2 +- .../DefaultTelemetryPublisherTest.kt | 2 +- .../amazon/q/jetbrains/core/DummyResource.kt | 2 +- .../q/jetbrains/core/MockClientManager.kt | 2 +- .../credentials/MockCredentialsManager.kt | 2 +- .../core/region/MockRegionProvider.kt | 6 +- plugins/core-q/webview/package-lock.json | 15 +++- .../core/jetbrains-community/build.gradle.kts | 4 + .../SetupAuthenticationDialogTest.kt | 9 +++ .../jetbrains/core/AwsClientManagerTest.kt | 8 ++ .../jetbrains/core/coroutines/ScopeTest.kt | 8 ++ .../core/credentials/CredentialManagerTest.kt | 9 +++ .../ProfileCredentialsIdentifierSsoTest.kt | 23 ++++-- .../core/credentials/sso/DiskCacheTest.kt | 6 +- .../services/telemetry/otel/OtelBaseTest.kt | 9 +++ .../ToolkitTelemetryOTelSpanProcessorTest.kt | 2 + .../jetbrains/settings/AwsSettingsTest.kt | 2 + .../jetbrains/core/CoreTestHelper.kt.kt | 78 +++++++++++++++++++ plugins/core/webview/package-lock.json | 15 +++- .../toolkit/jetbrains-core/build.gradle.kts | 9 ++- .../META-INF/inactive/plugin-gateway.xml | 2 +- .../resources/META-INF/plugin.xml | 1 - .../jetbrains-ultimate/build.gradle.kts | 2 +- 100 files changed, 320 insertions(+), 131 deletions(-) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/credentials/AwsCredentialsExtensionsTest.kt (94%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/credentials/Mocks.kt (86%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/credentials/SsoUrlIdentifierTest.kt (98%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/lambda/LambdaSampleEventProviderTest.kt (98%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/parser/EndpointsJsonValidatorTest.kt (94%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/parser/LambdaManifestValidatorTest.kt (94%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/parser/LambdaSampleEventJsonValidatorTest.kt (95%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/region/AwsRegionTest.kt (96%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/region/PartitionParserTest.kt (95%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/rules/ECSTemporaryServiceRule.kt (92%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/rules/EcrTemporaryRepositoryRule.kt (91%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/rules/EnvironmentVariableHelper.kt (98%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/rules/S3TemporaryBucketRule.kt (91%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/rules/SystemPropertyHelper.kt (94%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/telemetry/TelemetryBatcherTest.kt (99%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/CollectionUtilsTest.kt (94%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/CompletionStageUtils.kt (92%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/DelegateSdkConsumers.kt (98%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/EitherTest.kt (97%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/ExceptionUtilsTest.kt (91%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/IntegrationTestCredentials.kt (97%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/LogUtilsTest.kt (99%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/RemoteResourceResolverTest.kt (99%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/RuleUtils.kt (95%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/RuleUtilsTest.kt (93%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/ZipUtilsTest.kt (98%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/test/AssertJAsserts.kt (96%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/test/TestUtils.kt (96%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/core/utils/test/TestUtilsTest.kt (95%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/jetbrains/utils/AttributeBagTest.kt (96%) rename plugins/core-q/core-q/tst/software/{aws/toolkits => amazon/q}/jetbrains/utils/StringUtilsTest.kt (95%) create mode 100644 plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt index fa02cab42ad..1e0bb638ad0 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt @@ -52,7 +52,7 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId -import software.aws.toolkits.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.AwsSettings import java.util.concurrent.CompletableFuture class CodeWhispererCodeModernizerGumbyClientTest : CodeWhispererCodeModernizerTestBase() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererActionTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererActionTest.kt index 03aa6836078..e8b2031e7b9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererActionTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererActionTest.kt @@ -24,7 +24,7 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doNothing import org.mockito.kotlin.eq import org.mockito.kotlin.whenever -import software.aws.toolkits.jetbrains.services.amazonq.QConstants +import software.amazon.q.jetbrains.services.amazonq.QConstants import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererLearnMoreAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererShowSettingsAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererWhatIsAction diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt index ff0b63bf159..2ca5847c2ca 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt @@ -5,7 +5,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.DisposableRule import org.junit.Rule -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule open class CodeWhispererBasicTestBase { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt index 56cc30ae7c0..3cf3fc45c82 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt @@ -58,7 +58,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestU import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.sdkHttpResponse import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptorImpl import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.aws.toolkits.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule class CodeWhispererClientAdaptorTest { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt index fd64e77fe4b..84332655d74 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt @@ -18,7 +18,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestU import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererEditorUtilTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt index a3605871643..3d0f0728692 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt @@ -46,7 +46,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererVue import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererYaml -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import kotlin.reflect.full.createInstance import kotlin.reflect.full.primaryConstructor diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt index df4c28892db..8ce8947cce6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt @@ -12,7 +12,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererReferenceManagerTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt index c5afe5892a9..6723666c00c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt @@ -23,7 +23,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import software.amazon.q.jetbrains.core.ToolWindowHeadlessManagerImpl +import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index d42c6b1a0e2..db2604aa94e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -22,8 +22,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.P import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Resume import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService -import software.aws.toolkits.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.settings.AwsSettings class CodeWhispererTelemetryTest : CodeWhispererTestBase() { private val awsModifySetting = "aws_modifySetting" diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index 705adf33f4d..dcf3472bc3b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -81,7 +81,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.settings.CodeWhispererConfigurationType import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.resources.message import java.util.concurrent.CompletableFuture import java.util.concurrent.Future diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserGroupSettingsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserGroupSettingsTest.kt index f8ab6a17a5d..b486c5148f2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserGroupSettingsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserGroupSettingsTest.kt @@ -10,8 +10,8 @@ import org.junit.Rule import org.junit.Test import org.mockito.kotlin.spy import org.mockito.kotlin.verify -import software.aws.toolkits.jetbrains.AwsPlugin -import software.aws.toolkits.jetbrains.AwsToolkit +import software.amazon.q.jetbrains.AwsPlugin +import software.amazon.q.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererExpThresholdGroup import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererUserGroup import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererUserGroupSettings diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt index d8643cdbcca..d2cd8d96221 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt @@ -35,7 +35,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.isWithin import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled import software.aws.toolkits.jetbrains.services.codewhisperer.util.toCodeChunk import software.aws.toolkits.jetbrains.services.codewhisperer.util.truncateLineByLine -import software.aws.toolkits.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererCompletionType diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt index 6dc55668365..1f12013022f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt @@ -11,7 +11,7 @@ import org.junit.jupiter.api.extension.RegisterExtension import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionEndpoint -import software.amazon.q.jetbrains.utils.rules.RegistryExtension +import software.aws.toolkits.jetbrains.utils.rules.RegistryExtension import software.amazon.q.jetbrains.utils.satisfiesKt @ExtendWith(ApplicationExtension::class) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt index b2a8dc52985..14674d6ed6a 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt @@ -34,7 +34,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.FileInputStream import java.lang.management.ManagementFactory diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt index ce628533791..0907a9f1e0d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt @@ -32,7 +32,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.File import java.io.FileInputStream diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt index f600b345d0f..03f3e783185 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt @@ -15,7 +15,7 @@ import org.mockito.kotlin.stub import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getNormalizedRelativePath -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.amazon.q.jetbrains.utils.rules.addModule import software.aws.toolkits.telemetry.CodewhispererLanguage diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt index 30bd57bd6c2..6051a7789f5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt @@ -11,7 +11,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererFallbackImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererFallbackImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt index 7d03bd0a39e..7964948b555 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt @@ -15,7 +15,7 @@ import com.jetbrains.python.psi.PyImportStatement import com.jetbrains.python.psi.PyImportStatementBase import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererPythonImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererPythonImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt index be815a3f282..a483812930e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt @@ -13,7 +13,7 @@ import com.intellij.testFramework.runInEdtAndWait import compat.com.intellij.lang.javascript.JavascriptLanguage import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.amazon.q.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule import kotlin.test.fail class CodeWhispererJSImportAdderTest : CodeWhispererImportAdderTestBase( diff --git a/plugins/amazonq/mynah-ui/package-lock.json b/plugins/amazonq/mynah-ui/package-lock.json index 829f7f5bb36..9713c3ae903 100644 --- a/plugins/amazonq/mynah-ui/package-lock.json +++ b/plugins/amazonq/mynah-ui/package-lock.json @@ -366,7 +366,8 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "peer": true }, "node_modules/@types/sanitize-html": { "version": "2.9.5", @@ -422,6 +423,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -775,6 +777,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -813,6 +816,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -986,6 +990,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001580", "electron-to-chromium": "^1.4.648", @@ -1474,6 +1479,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2703,6 +2709,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3003,6 +3010,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3077,6 +3085,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3490,6 +3499,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3604,6 +3614,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3650,6 +3661,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt index a66da4a458c..8b746578612 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt @@ -37,7 +37,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credential import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator -import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings.Companion.CONTEXT_INDEX_SIZE import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings.Companion.CONTEXT_INDEX_THREADS diff --git a/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt b/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt index 5ad1ac43143..cfa827c4b9f 100644 --- a/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt +++ b/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt @@ -13,7 +13,7 @@ import software.amazon.awssdk.services.s3.model.BucketVersioningStatus import software.amazon.awssdk.services.s3.model.PutObjectRequest import software.amazon.q.core.s3.deleteBucketAndContents import software.amazon.q.core.s3.regionForBucket -import software.aws.toolkits.core.rules.S3TemporaryBucketRule +import software.amazon.q.core.rules.S3TemporaryBucketRule class BucketUtilsTest { private val usEast1Client = S3Client.builder().region(Region.US_EAST_1).build() diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt similarity index 94% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt index 1114ed00160..1c49e4273cc 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/AwsCredentialsExtensionsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt @@ -1,7 +1,7 @@ -// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.credentials +package software.amazon.q.core.credentials import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -9,7 +9,7 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.AwsSessionCredentials import software.amazon.q.core.credentials.mergeWithExistingEnvironmentVariables import software.amazon.q.core.credentials.toEnvironmentVariables -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString class AwsCredentialsExtensionsTest { diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt similarity index 86% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt index 621acb14514..aa3513d96ab 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/Mocks.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt @@ -1,7 +1,7 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.credentials +package software.amazon.q.core.credentials import org.mockito.kotlin.mock import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider @@ -9,7 +9,7 @@ import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.CredentialIdentifierBase import software.amazon.q.core.credentials.CredentialType import software.amazon.q.core.credentials.ToolkitCredentialsProvider -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString fun aToolkitCredentialsProvider( identifier: CredentialIdentifier = aCredentialsIdentifier(), diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt similarity index 98% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt index c316d5cc870..f02b16c115b 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/credentials/SsoUrlIdentifierTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt @@ -1,7 +1,7 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.credentials +package software.amazon.q.core.credentials import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.assertThrows diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt similarity index 98% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt index 321728344c6..7f98d720fba 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/lambda/LambdaSampleEventProviderTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.lambda +package software.amazon.q.core.lambda import org.assertj.core.api.Assertions.assertThat import org.junit.Rule diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/parser/EndpointsJsonValidatorTest.kt similarity index 94% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/parser/EndpointsJsonValidatorTest.kt index 3559bd9a9e4..fe08e3aed4a 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/EndpointsJsonValidatorTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/parser/EndpointsJsonValidatorTest.kt @@ -1,7 +1,7 @@ // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.parser +package software.amazon.q.core.parser import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaManifestValidatorTest.kt similarity index 94% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaManifestValidatorTest.kt index 76a88a42a2d..f3013b24074 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaManifestValidatorTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaManifestValidatorTest.kt @@ -1,7 +1,7 @@ // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.parser +package software.amazon.q.core.parser import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaSampleEventJsonValidatorTest.kt similarity index 95% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaSampleEventJsonValidatorTest.kt index 8234ef2b774..7405ef75e9f 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/parser/LambdaSampleEventJsonValidatorTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaSampleEventJsonValidatorTest.kt @@ -1,7 +1,7 @@ // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.parser +package software.amazon.q.core.parser import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt similarity index 96% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt index 62c0a399802..2d8e7633768 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/AwsRegionTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt @@ -1,7 +1,7 @@ -// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.region +package software.amazon.q.core.region import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -10,7 +10,7 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.region.mergeWithExistingEnvironmentVariables -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import kotlin.random.Random @RunWith(Enclosed::class) diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt similarity index 95% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt index d71edd7894d..38eece70fea 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/region/PartitionParserTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.region +package software.amazon.q.core.region import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/ECSTemporaryServiceRule.kt similarity index 92% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/rules/ECSTemporaryServiceRule.kt index 44494c5d85e..6671cf71686 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/ECSTemporaryServiceRule.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/ECSTemporaryServiceRule.kt @@ -1,14 +1,14 @@ -// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.rules +package software.amazon.q.core.rules import org.junit.rules.ExternalResource import software.amazon.awssdk.services.ecs.EcsClient import software.amazon.awssdk.services.ecs.model.CreateServiceRequest import software.amazon.awssdk.services.ecs.model.Service import software.amazon.awssdk.services.ecs.model.ServiceNotFoundException -import software.aws.toolkits.core.utils.RuleUtils +import software.amazon.q.core.utils.RuleUtils class ECSTemporaryServiceRule(val ecsClient: EcsClient) : ExternalResource() { private val services = mutableListOf() diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/EcrTemporaryRepositoryRule.kt similarity index 91% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/rules/EcrTemporaryRepositoryRule.kt index 4db7f61370f..dfa4c554ddc 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EcrTemporaryRepositoryRule.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/EcrTemporaryRepositoryRule.kt @@ -1,13 +1,13 @@ -// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.rules +package software.amazon.q.core.rules import org.junit.rules.ExternalResource import software.amazon.awssdk.services.ecr.EcrClient import software.amazon.awssdk.services.ecr.model.Repository import software.amazon.awssdk.services.ecr.model.RepositoryNotFoundException -import software.aws.toolkits.core.utils.RuleUtils +import software.amazon.q.core.utils.RuleUtils class EcrTemporaryRepositoryRule(private val ecrClientSupplier: () -> EcrClient) : ExternalResource() { constructor(ecrClient: EcrClient) : this({ ecrClient }) diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/EnvironmentVariableHelper.kt similarity index 98% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/rules/EnvironmentVariableHelper.kt index 166de73b8f3..0095a6b24bd 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/EnvironmentVariableHelper.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/EnvironmentVariableHelper.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.rules +package software.amazon.q.core.rules import org.junit.rules.ExternalResource import java.security.AccessController diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/S3TemporaryBucketRule.kt similarity index 91% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/rules/S3TemporaryBucketRule.kt index dc120535de6..c6a13055fd6 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/S3TemporaryBucketRule.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/S3TemporaryBucketRule.kt @@ -1,13 +1,13 @@ -// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.rules +package software.amazon.q.core.rules import org.junit.rules.ExternalResource import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.NoSuchBucketException import software.amazon.q.core.s3.deleteBucketAndContents -import software.aws.toolkits.core.utils.RuleUtils +import software.amazon.q.core.utils.RuleUtils class S3TemporaryBucketRule(private val s3ClientSupplier: () -> S3Client) : ExternalResource() { constructor(s3Client: S3Client) : this({ s3Client }) diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/SystemPropertyHelper.kt similarity index 94% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/rules/SystemPropertyHelper.kt index cfeca9ccb65..704c995c821 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/rules/SystemPropertyHelper.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/rules/SystemPropertyHelper.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.rules +package software.amazon.q.core.rules import org.junit.rules.ExternalResource import java.util.Properties diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt similarity index 99% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt index edac2ef845b..363ec48db19 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/telemetry/TelemetryBatcherTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt @@ -1,7 +1,7 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.telemetry +package software.amazon.q.core.telemetry import org.assertj.core.api.Assertions.assertThat import org.junit.Before diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt similarity index 94% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt index 6021056643d..fe122d53b2d 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CollectionUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt @@ -1,7 +1,7 @@ // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CompletionStageUtils.kt similarity index 92% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/CompletionStageUtils.kt index 18aa9841259..04a7ef7e045 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/CompletionStageUtils.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CompletionStageUtils.kt @@ -1,7 +1,7 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.jetbrains.annotations.TestOnly import java.util.concurrent.CompletionStage diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/DelegateSdkConsumers.kt similarity index 98% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/DelegateSdkConsumers.kt index b59d463bef2..150b4c804e8 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/DelegateSdkConsumers.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/DelegateSdkConsumers.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.mockito.Mockito import org.mockito.invocation.InvocationOnMock diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt similarity index 97% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt index 48b3a625357..79fc46e3f94 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/EitherTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt similarity index 91% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt index cbdce661ee3..3b55969b126 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ExceptionUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/IntegrationTestCredentials.kt similarity index 97% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/IntegrationTestCredentials.kt index db5648b40ea..38e5fa9e263 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/IntegrationTestCredentials.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/IntegrationTestCredentials.kt @@ -1,7 +1,7 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt similarity index 99% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt index 867f35bce8b..65f7c9e4ade 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/LogUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 @file:Suppress("LazyLog") -package software.aws.toolkits.core.utils + +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Before diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt similarity index 99% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt index 21f37d5d316..1e9acdfc504 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RemoteResourceResolverTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Rule diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtils.kt similarity index 95% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtils.kt index 5556fe2ba52..163dc19c4c5 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtils.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtils.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import java.util.Random diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtilsTest.kt similarity index 93% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtilsTest.kt index c1402150fd6..ec391c8769c 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/RuleUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtilsTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Before diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt similarity index 98% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt index 03daa790ee2..74108ca4ed4 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/ZipUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils +package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/AssertJAsserts.kt similarity index 96% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/AssertJAsserts.kt index ee005af612a..353fa5eba27 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/AssertJAsserts.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/AssertJAsserts.kt @@ -1,7 +1,7 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils.test +package software.amazon.q.core.utils.test import org.assertj.core.api.AbstractIterableAssert import org.assertj.core.api.Assertions.assertThat diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtils.kt similarity index 96% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtils.kt index 26fbdd924bc..66d8ad0e3a7 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtils.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtils.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils.test +package software.amazon.q.core.utils.test import java.time.Duration import java.time.Instant diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtilsTest.kt similarity index 95% rename from plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtilsTest.kt index 83b3a5b5de7..813adcf4f4f 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/core/utils/test/TestUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtilsTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.core.utils.test +package software.amazon.q.core.utils.test import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/AttributeBagTest.kt similarity index 96% rename from plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/AttributeBagTest.kt index 3fa8c33fb1c..d09b9580b4f 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/AttributeBagTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/AttributeBagTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.jetbrains.utils +package software.amazon.q.jetbrains.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/StringUtilsTest.kt similarity index 95% rename from plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt rename to plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/StringUtilsTest.kt index 528b5e8ecab..cf7274b32f5 100644 --- a/plugins/core-q/core-q/tst/software/aws/toolkits/jetbrains/utils/StringUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/StringUtilsTest.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package software.aws.toolkits.jetbrains.utils +package software.amazon.q.jetbrains.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt index ae014eb2159..f3ec390ad65 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt @@ -38,7 +38,7 @@ import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.core.telemetry.MetricEvent import software.amazon.q.core.telemetry.TelemetryBatcher import software.amazon.q.core.telemetry.TelemetryPublisher -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt index e70374e9abb..24f35896e84 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt @@ -21,7 +21,7 @@ import software.amazon.q.jetbrains.core.MockClientManagerExtension import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension import software.amazon.q.resources.AwsCoreBundle -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.satisfiesKt @ExtendWith(MockKExtension::class) diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt index 78ab1fede27..8db493b164a 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt @@ -37,7 +37,7 @@ import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.region.Endpoint import software.amazon.q.core.region.Service -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.telemetry.FeatureId diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt index 946148fa227..64555c33777 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt @@ -50,8 +50,8 @@ import software.amazon.q.core.credentials.ToolkitBearerTokenProvider import software.amazon.q.core.credentials.ToolkitBearerTokenProviderDelegate import software.amazon.q.core.region.Endpoint import software.amazon.q.core.region.Service -import software.aws.toolkits.core.region.anAwsRegion -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.anAwsRegion +import software.amazon.q.core.utils.test.aString import java.net.URI import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.jvm.isAccessible diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt index 58844e253eb..c9a527a13e6 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt @@ -33,7 +33,7 @@ import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion -import software.aws.toolkits.core.utils.test.retryableAssert +import software.amazon.q.core.utils.test.retryableAssert import software.amazon.q.jetbrains.utils.hasCauseWithMessage import software.amazon.q.jetbrains.utils.hasException import software.amazon.q.jetbrains.utils.hasValue diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt index 65f189b82b2..0abf8f560a6 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt @@ -20,8 +20,8 @@ import org.junit.Test import software.amazon.awssdk.http.HttpExecuteRequest import software.amazon.awssdk.http.SdkHttpFullRequest import software.amazon.awssdk.http.SdkHttpMethod -import software.aws.toolkits.core.rules.EnvironmentVariableHelper -import software.aws.toolkits.core.rules.SystemPropertyHelper +import software.amazon.q.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.rules.SystemPropertyHelper import java.net.URI class AwsSdkClientTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt index 7ea4bc711aa..5343b801fc0 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt @@ -29,8 +29,8 @@ import software.amazon.q.core.credentials.CredentialSourceId import software.amazon.q.core.credentials.CredentialsChangeEvent import software.amazon.q.core.credentials.CredentialsChangeListener import software.amazon.q.core.region.AwsRegion -import software.aws.toolkits.core.region.anAwsRegion -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.anAwsRegion +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.isInstanceOf import kotlin.test.assertNotNull diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt index 2fb6201a2d3..6290be9fbd6 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt @@ -15,8 +15,8 @@ import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion import software.amazon.q.resources.AwsCoreBundle -import software.aws.toolkits.core.credentials.aCredentialsIdentifier -import software.aws.toolkits.core.region.anAwsRegion +import software.amazon.q.core.credentials.aCredentialsIdentifier +import software.amazon.q.core.region.anAwsRegion import software.amazon.q.jetbrains.settings.AwsSettingsRule import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt index b09b1e77250..a078ac0695c 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt @@ -19,11 +19,11 @@ import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.jetbrains.core.region.getDefaultRegion import software.amazon.q.jetbrains.services.sts.StsResources import software.amazon.q.core.credentials.CredentialIdentifier -import software.aws.toolkits.core.credentials.aCredentialsIdentifier +import software.amazon.q.core.credentials.aCredentialsIdentifier import software.amazon.q.core.region.AwsRegion -import software.aws.toolkits.core.rules.EnvironmentVariableHelper -import software.aws.toolkits.core.rules.SystemPropertyHelper -import software.aws.toolkits.core.utils.test.notNull +import software.amazon.q.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.core.utils.test.notNull import software.amazon.q.jetbrains.utils.deserializeState import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.satisfiesKt diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt index 522184f439c..9c9cc95122e 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt @@ -11,7 +11,7 @@ import org.junit.rules.TemporaryFolder import software.amazon.awssdk.profiles.Profile import software.amazon.q.core.utils.createParentDirectories import software.amazon.q.core.utils.writeText -import software.aws.toolkits.core.utils.test.assertPosixPermissions +import software.amazon.q.core.utils.test.assertPosixPermissions import software.amazon.q.jetbrains.utils.satisfiesKt import java.nio.file.Paths diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt index 68b794c8b81..b8311f53c9f 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt @@ -18,7 +18,7 @@ import software.amazon.awssdk.services.ssooidc.SsoOidcClient import software.amazon.q.jetbrains.core.MockClientManagerRule import software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.isInstanceOf class DefaultToolkitConnectionManagerTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt index 4ceb247cf93..f715c15e462 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt @@ -11,9 +11,9 @@ import org.junit.Rule import org.junit.Test import software.amazon.q.jetbrains.core.MockResourceCacheRule import software.amazon.q.jetbrains.core.dummyResource -import software.aws.toolkits.core.credentials.aToolkitCredentialsProvider -import software.aws.toolkits.core.region.anAwsRegion -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.credentials.aToolkitCredentialsProvider +import software.amazon.q.core.region.anAwsRegion +import software.amazon.q.core.utils.test.aString import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt index 0eb4a9bea05..563a06c7112 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt @@ -8,9 +8,9 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Rule import org.junit.Test import software.amazon.q.jetbrains.core.credentials.profiles.Ec2MetadataConfigProvider.getEc2MedataEndpoint -import software.aws.toolkits.core.rules.EnvironmentVariableHelper -import software.aws.toolkits.core.rules.SystemPropertyHelper -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.isInstanceOf class Ec2MetadataConfigProviderTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt index 8db74777281..66bf432a1b2 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt @@ -25,8 +25,8 @@ import software.amazon.awssdk.services.sts.model.AssumeRoleRequest import software.amazon.awssdk.services.sts.model.AssumeRoleResponse import software.amazon.awssdk.utils.SdkAutoCloseable import software.amazon.q.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.core.region.anAwsRegion -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.anAwsRegion +import software.amazon.q.core.utils.test.aString import java.time.Duration import java.time.Instant diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt index a6c67bf3bef..ff7f475c2d1 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt @@ -61,8 +61,8 @@ import software.amazon.q.core.credentials.CredentialsChangeEvent import software.amazon.q.core.credentials.CredentialsChangeListener import software.amazon.q.core.credentials.SsoSessionIdentifier import software.amazon.q.core.credentials.ToolkitBearerTokenProvider -import software.aws.toolkits.core.rules.SystemPropertyHelper -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt index 0d153eed6d1..ec2ed7aa09d 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt @@ -9,7 +9,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import software.amazon.q.resources.AwsCoreBundle -import software.aws.toolkits.core.rules.SystemPropertyHelper +import software.amazon.q.core.rules.SystemPropertyHelper import java.io.File class ProfileReaderTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt index fb5a1698e92..09cd3cdbf31 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt @@ -4,7 +4,7 @@ package software.amazon.q.jetbrains.core.credentials.profiles import software.amazon.awssdk.profiles.Profile -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString fun profile(name: String = aString(), properties: MutableMap.() -> Unit = {}): Profile = Profile.builder() .name(name) diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt index 529bf12d6eb..a0c6453025f 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt @@ -20,7 +20,7 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import org.opentest4j.AssertionFailedError import software.amazon.q.jetbrains.utils.spinUntil -import software.aws.toolkits.core.rules.SystemPropertyHelper +import software.amazon.q.core.rules.SystemPropertyHelper import java.io.File import java.io.IOException import java.time.Duration diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt index 558ba8ba3c0..e669f073d76 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt @@ -14,7 +14,7 @@ import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.io.TempDir import software.amazon.q.core.utils.readText -import software.aws.toolkits.core.utils.test.assertPosixPermissions +import software.amazon.q.core.utils.test.assertPosixPermissions import software.amazon.q.core.utils.writeText import java.nio.file.Files import java.nio.file.Path diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt index 799b64d722d..be5a13d3ec6 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt @@ -39,9 +39,9 @@ import software.amazon.awssdk.services.ssooidc.model.StartDeviceAuthorizationReq import software.amazon.awssdk.services.ssooidc.model.StartDeviceAuthorizationResponse import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService -import software.aws.toolkits.core.region.aRegionId -import software.aws.toolkits.core.utils.delegateMock -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.aRegionId +import software.amazon.q.core.utils.delegateMock +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.rules.RegistryRule import software.amazon.q.jetbrains.utils.rules.SsoLoginCallbackProviderRule import java.time.Clock diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt index bfa6109aecb..a138c1df3ae 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt @@ -20,8 +20,8 @@ import software.amazon.awssdk.services.sso.model.GetRoleCredentialsRequest import software.amazon.awssdk.services.sso.model.GetRoleCredentialsResponse import software.amazon.awssdk.services.sso.model.RoleCredentials import software.amazon.awssdk.services.sso.model.UnauthorizedException -import software.aws.toolkits.core.utils.delegateMock -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.delegateMock +import software.amazon.q.core.utils.test.aString import java.time.Instant import java.time.temporal.ChronoUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt index 9d481606132..6d92e8dc29f 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt @@ -4,8 +4,8 @@ package software.amazon.q.jetbrains.core.credentials.sso.bearer import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken -import software.aws.toolkits.core.region.aRegionId -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.aRegionId +import software.amazon.q.core.utils.test.aString import java.time.Instant fun anAccessToken(refreshToken: String? = aString(), expiresAt: Instant) = DeviceAuthorizationGrantToken( diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt index 018e379f025..d204edd394b 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt @@ -46,8 +46,8 @@ import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrant import software.amazon.q.jetbrains.core.credentials.sso.DeviceGrantAccessTokenCacheKey import software.amazon.q.jetbrains.core.credentials.sso.DiskCache import software.amazon.q.jetbrains.core.credentials.sso.PKCEAccessTokenCacheKey -import software.aws.toolkits.core.region.aRegionId -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.aRegionId +import software.amazon.q.core.utils.test.aString import java.time.Clock import java.time.Instant import java.time.temporal.ChronoUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt index 9ea76912984..ceaaac526c3 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt @@ -16,9 +16,9 @@ import org.junit.rules.TemporaryFolder import org.mockito.kotlin.verifyNoInteractions import software.amazon.awssdk.services.ssooidc.SsoOidcClient import software.amazon.q.jetbrains.core.MockClientManagerRule -import software.aws.toolkits.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.rules.EnvironmentVariableHelper import software.amazon.q.core.utils.createParentDirectories -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.amazon.q.core.utils.toHexString import software.amazon.q.core.utils.writeText import java.nio.file.Path diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt index 0f897c0d3a1..39c97c26077 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt @@ -21,7 +21,7 @@ import software.amazon.awssdk.services.cognitoidentity.model.GetCredentialsForId import software.amazon.awssdk.services.cognitoidentity.model.GetIdRequest import software.amazon.awssdk.services.cognitoidentity.model.GetIdResponse import software.amazon.q.core.telemetry.CachedIdentityStorage -import software.aws.toolkits.core.utils.delegateMock +import software.amazon.q.core.utils.delegateMock import java.time.Instant import java.time.temporal.ChronoUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt index 5c3ecf525de..adb6d000b87 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt @@ -18,7 +18,7 @@ import software.amazon.awssdk.services.toolkittelemetry.model.PostFeedbackReques import software.amazon.awssdk.services.toolkittelemetry.model.PostMetricsRequest import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment import software.amazon.q.core.telemetry.DefaultMetricEvent -import software.aws.toolkits.core.utils.delegateMock +import software.amazon.q.core.utils.delegateMock class DefaultTelemetryPublisherTest { @Rule diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt index 0dbaaa0e41e..957ae1ae6b3 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt @@ -4,7 +4,7 @@ package software.amazon.q.jetbrains.core import software.amazon.q.core.ClientConnectionSettings -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import java.util.concurrent.atomic.AtomicInteger open class DummyResource(override val id: String, private val value: T) : Resource.Cached() { diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt index 30b7864b327..fc8507d6d91 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt @@ -23,7 +23,7 @@ import software.amazon.q.core.clients.SdkClientProvider import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.region.ToolkitRegionProvider -import software.aws.toolkits.core.utils.delegateMock +import software.amazon.q.core.utils.delegateMock import kotlin.reflect.KClass class MockClientManager : AwsClientManager() { diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt index c0bdd936ddc..e8dbcf336ec 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt @@ -20,7 +20,7 @@ import software.amazon.q.core.credentials.CredentialsChangeListener import software.amazon.q.core.credentials.SsoSessionIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.rules.ClearableLazy @Deprecated("Use MockCredentialManagerRule") diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt index 3f816763bd9..f23f984254d 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt @@ -12,9 +12,9 @@ import software.amazon.q.core.region.AwsPartition import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.region.Service import software.amazon.q.core.region.ToolkitRegionProvider -import software.aws.toolkits.core.region.aRegionId -import software.aws.toolkits.core.region.anAwsRegion -import software.aws.toolkits.core.utils.test.aString +import software.amazon.q.core.region.aRegionId +import software.amazon.q.core.region.anAwsRegion +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.rules.ClearableLazy private class MockRegionProvider : ToolkitRegionProvider() { diff --git a/plugins/core-q/webview/package-lock.json b/plugins/core-q/webview/package-lock.json index 5d32af8fda8..d0fd3258687 100644 --- a/plugins/core-q/webview/package-lock.json +++ b/plugins/core-q/webview/package-lock.json @@ -304,7 +304,8 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "peer": true }, "node_modules/@types/sanitize-html": { "version": "2.11.0", @@ -360,6 +361,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -808,6 +810,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -846,6 +849,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1028,6 +1032,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -1522,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2783,6 +2789,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3083,6 +3090,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3157,6 +3165,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3570,6 +3579,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3648,6 +3658,7 @@ "version": "3.4.20", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.4.20", "@vue/compiler-sfc": "3.4.20", @@ -3736,6 +3747,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3782,6 +3794,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/core/jetbrains-community/build.gradle.kts b/plugins/core/jetbrains-community/build.gradle.kts index 9a7fbdbc00e..4b6e0f86139 100644 --- a/plugins/core/jetbrains-community/build.gradle.kts +++ b/plugins/core/jetbrains-community/build.gradle.kts @@ -81,6 +81,10 @@ dependencies { testRuntimeOnly(project(":plugin-core:sdk-codegen")) } +tasks.test { + enabled = false +} + // fix implicit dependency on generated source tasks.withType().configureEach { dependsOn(generateTelemetry) diff --git a/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt index 99b794727fe..c0f0c445d43 100644 --- a/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt +++ b/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt @@ -3,9 +3,11 @@ package software.aws.toolkits.jetbrains.core.gettingstarted +import com.intellij.openapi.Disposable import com.intellij.openapi.ui.TestDialog import com.intellij.openapi.ui.TestDialogManager import com.intellij.testFramework.ProjectExtension +import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.runInEdtAndWait import io.mockk.every import io.mockk.junit5.MockKExtension @@ -13,6 +15,7 @@ import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.verify import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -28,6 +31,7 @@ import software.amazon.awssdk.services.sts.model.StsException import software.aws.toolkits.core.region.Endpoint import software.aws.toolkits.core.region.Service import software.aws.toolkits.core.utils.test.aString +import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.MockClientManagerExtension import software.aws.toolkits.jetbrains.core.credentials.ConfigFilesFacade import software.aws.toolkits.jetbrains.core.credentials.UserConfigSsoSessionProfile @@ -57,6 +61,11 @@ class SetupAuthenticationDialogTest { @RegisterExtension val mockRegionProvider = MockRegionProviderExtension() +// @BeforeEach +// fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) +// } + @Test fun `login to IdC tab`() { mockkStatic(::authAndUpdateConfig) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt index 8befef941f9..8bce5e64782 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt @@ -11,6 +11,7 @@ import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching import com.github.tomakehurst.wiremock.core.WireMockConfiguration import com.github.tomakehurst.wiremock.junit.WireMockRule import com.github.tomakehurst.wiremock.matching.ContainsPattern +import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.use @@ -18,10 +19,12 @@ import com.intellij.testFramework.DisposableRule import com.intellij.testFramework.ExtensionTestUtil import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.RuleChain +import com.intellij.testFramework.junit5.TestDisposable import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Rule import org.junit.Test +import org.junit.jupiter.api.BeforeEach import org.junit.rules.TemporaryFolder import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -78,6 +81,11 @@ class AwsClientManagerTest { projectSettingsRule, disposableRule ) +// +// @BeforeEach +// fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) +// } @Test fun canGetAnInstanceOfAClient() { diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt index 7316feacdea..a8944a3996b 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt @@ -14,6 +14,7 @@ import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.TestApplicationManager import com.intellij.testFramework.createTestOpenProjectOptions +import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.replaceService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -26,8 +27,10 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.junit.jupiter.api.BeforeEach import org.junit.rules.TemporaryFolder import org.junit.rules.TestName +import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.utils.isInstanceOf import java.time.Duration import java.util.concurrent.CancellationException @@ -52,6 +55,11 @@ class ScopeTest { @JvmField val testName = TestName() +// @BeforeEach +// fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) +// } + @Test fun `plugin being uploaded cancels application scope`() { val fakePluginScope = createFakePluginScope() diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt index e03ff72d2cc..1b975991493 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt @@ -3,16 +3,19 @@ package software.aws.toolkits.jetbrains.core.credentials +import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.testFramework.ApplicationRule import com.intellij.testFramework.DisposableRule import com.intellij.testFramework.ExtensionTestUtil +import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Rule import org.junit.Test +import org.junit.jupiter.api.BeforeEach import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.AwsCredentials import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider @@ -27,6 +30,7 @@ import software.aws.toolkits.core.credentials.CredentialsChangeListener import software.aws.toolkits.core.region.AwsRegion import software.aws.toolkits.core.region.anAwsRegion import software.aws.toolkits.core.utils.test.aString +import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule import software.aws.toolkits.jetbrains.core.region.getDefaultRegion import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread @@ -47,6 +51,11 @@ class CredentialManagerTest { @JvmField val regionProvider = MockRegionProviderRule() +// @BeforeEach +// fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) +// } + @Test fun testCredentialsCanLoadFromExtensions() { val region = getDefaultRegion() diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt index 00f164ec587..106c6343d8c 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt @@ -3,8 +3,13 @@ package software.aws.toolkits.jetbrains.core.credentials.profiles +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.junit5.TestDisposable +import com.intellij.testFramework.replaceService import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -12,6 +17,8 @@ import org.junit.jupiter.api.extension.RegisterExtension import org.mockito.kotlin.mock import software.amazon.awssdk.services.ssooidc.SsoOidcClient import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.aws.toolkits.jetbrains.core.CoreTestHelper +import software.aws.toolkits.jetbrains.core.MockClientManager import software.aws.toolkits.jetbrains.core.MockClientManagerExtension import software.aws.toolkits.jetbrains.core.credentials.sso.DiskCache import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider @@ -21,9 +28,10 @@ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.NoTokenInitia class ProfileCredentialsIdentifierSsoTest { private val sut = ProfileCredentialsIdentifierSso("", "", "", null) - @JvmField - @RegisterExtension - val mockClientManager = MockClientManagerExtension() +// @BeforeEach +// fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) +// } @Test fun `handles SsoOidcException`() { @@ -42,17 +50,20 @@ class ProfileCredentialsIdentifierSsoTest { } @Test - fun `handles exception from uninitialized token provider`() { + fun `handles exception from uninitialized token provider`(@TestDisposable disposable: Disposable) { + val mockClientManager = MockClientManager() + ApplicationManager.getApplication().replaceService(migration.software.aws.toolkits.core.ToolkitClientManager::class.java, mockClientManager, disposable) + val cache = mock() - mockClientManager.create() + mockClientManager.register(SsoOidcClient::class, mock()) - // IllegalStateException instead of more general base Exception so we know if the type changes val exception = assertThrows { InteractiveBearerTokenProvider("", "us-east-1", listOf("scopes"), cache = cache, id = "test").resolveToken() } assertThat(sut.handleValidationException(exception)).isNotNull() } + @Test fun `ignores arbitrary exception`() { assertThat(sut.handleValidationException(RuntimeException())).isNull() diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt index e5ac374d365..f84f30d6fea 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt @@ -3,9 +3,11 @@ package software.aws.toolkits.jetbrains.core.credentials.sso +import com.intellij.openapi.Disposable import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.NioFiles import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.junit5.TestDisposable import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -16,6 +18,7 @@ import org.junit.jupiter.api.io.TempDir import software.aws.toolkits.core.utils.readText import software.aws.toolkits.core.utils.test.assertPosixPermissions import software.aws.toolkits.core.utils.writeText +import software.aws.toolkits.jetbrains.core.CoreTestHelper import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -42,7 +45,8 @@ class DiskCacheTest { private lateinit var sut: DiskCache @BeforeEach - fun setUp(@TempDir tempFolder: Path) { + fun setUp(@TestDisposable disposable: Disposable, @TempDir tempFolder: Path) { +// CoreTestHelper.registerMissingServices(disposable) cacheRoot = tempFolder.toAbsolutePath() cacheLocation = Paths.get(cacheRoot.toString(), "fakehome", ".aws", "sso", "cache") Files.createDirectories(cacheLocation) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt index 15e17155368..e4046a3b003 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt @@ -3,8 +3,10 @@ package software.aws.toolkits.jetbrains.services.telemetry.otel +import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.junit5.TestDisposable import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.TraceId import io.opentelemetry.context.Context @@ -17,6 +19,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows @@ -31,6 +34,7 @@ import org.junit.jupiter.params.provider.MethodSource import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn +import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.jetbrains.utils.satisfiesKt @@ -60,6 +64,11 @@ class OtelBaseTest { fun `AbstractBaseSpan#end() does not throw if all required attributes are present`() = spanEndArgs() } +// @BeforeEach +// fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) +// } + @Test fun `context propagates from parent to child - happy case`() { spanBuilder("tracer", "parentSpan").use { diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt index 9680c653061..ca2bb900728 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt @@ -35,6 +35,7 @@ import software.aws.toolkits.telemetry.CodewhispererTriggerType import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry import java.time.Instant +//import software.aws.toolkits.jetbrains.core.CoreTestHelper @ExtendWith(ApplicationExtension::class) class ToolkitTelemetryOTelSpanProcessorTest { @@ -48,6 +49,7 @@ class ToolkitTelemetryOTelSpanProcessorTest { @BeforeEach fun setUp(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) batcher = mock() telemetryService = spy(TestTelemetryService(batcher = batcher)) ApplicationManager.getApplication().replaceService(TelemetryService::class.java, telemetryService, disposable) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt index a93b5bebd5b..fc2c65a2756 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt @@ -19,6 +19,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.spy import software.aws.toolkits.core.telemetry.TelemetryBatcher import software.aws.toolkits.core.telemetry.TelemetryPublisher +import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.services.telemetry.NoOpPublisher import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService @@ -36,6 +37,7 @@ class AwsSettingsTest { @BeforeEach fun setup(@TestDisposable disposable: Disposable) { +// CoreTestHelper.registerMissingServices(disposable) batcher = mock() telemetryService = spy(TestTelemetryService(batcher = batcher)) awsSettings = spy(DefaultAwsSettings()) diff --git a/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt b/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt new file mode 100644 index 00000000000..1b9d3d00eb6 --- /dev/null +++ b/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt @@ -0,0 +1,78 @@ +// Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.aws.toolkits.jetbrains.core +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.extensions.ExtensionPoint +import com.intellij.testFramework.replaceService +import org.mockito.kotlin.mock +import software.aws.toolkits.jetbrains.core.credentials.MockCredentialsManager +import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider +import software.aws.toolkits.jetbrains.core.credentials.sso.MockSsoLoginCallbackProvider +import software.aws.toolkits.jetbrains.services.telemetry.NoOpTelemetryService +import software.aws.toolkits.jetbrains.settings.MockAwsSettings +/** +Registers missing core services for tests that were previously registered in plugin.xml + */ +object CoreTestHelper { + fun registerMissingServices(disposable: Disposable) { + val app = ApplicationManager.getApplication() + + val extensionArea = app.extensionArea + if (!extensionArea.hasExtensionPoint("aws.toolkit.core.startupAuthFactory")) { + extensionArea.registerExtensionPoint( + "aws.toolkit.core.startupAuthFactory", + "software.aws.toolkits.jetbrains.core.credentials.ToolkitStartupAuthFactory", + ExtensionPoint.Kind.INTERFACE + ) + } + + app.replaceService( + migration.software.aws.toolkits.jetbrains.settings.AwsSettings::class.java, + MockAwsSettings(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.core.ToolkitClientManager::class.java, + mock(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.jetbrains.services.telemetry.TelemetryService::class.java, + NoOpTelemetryService(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.core.region.ToolkitRegionProvider::class.java, + AwsRegionProvider(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.jetbrains.core.credentials.CredentialManager::class.java, + MockCredentialsManager(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.jetbrains.core.AwsResourceCache::class.java, + MockResourceCache(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.jetbrains.core.credentials.sso.SsoLoginCallbackProvider::class.java, + MockSsoLoginCallbackProvider(), + disposable + ) + + app.replaceService( + migration.software.aws.toolkits.jetbrains.core.coroutines.PluginCoroutineScopeTracker::class.java, + migration.software.aws.toolkits.jetbrains.core.coroutines.PluginCoroutineScopeTracker(), + disposable + ) + } +} diff --git a/plugins/core/webview/package-lock.json b/plugins/core/webview/package-lock.json index 5d32af8fda8..d0fd3258687 100644 --- a/plugins/core/webview/package-lock.json +++ b/plugins/core/webview/package-lock.json @@ -304,7 +304,8 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "peer": true }, "node_modules/@types/sanitize-html": { "version": "2.11.0", @@ -360,6 +361,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -808,6 +810,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -846,6 +849,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1028,6 +1032,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -1522,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2783,6 +2789,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3083,6 +3090,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3157,6 +3165,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3570,6 +3579,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3648,6 +3658,7 @@ "version": "3.4.20", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.4.20", "@vue/compiler-sfc": "3.4.20", @@ -3736,6 +3747,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3782,6 +3794,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/toolkit/jetbrains-core/build.gradle.kts b/plugins/toolkit/jetbrains-core/build.gradle.kts index 6928e1d1510..b5184ed6aa4 100644 --- a/plugins/toolkit/jetbrains-core/build.gradle.kts +++ b/plugins/toolkit/jetbrains-core/build.gradle.kts @@ -196,6 +196,13 @@ dependencies { implementation(libs.zjsonpatch) testFixturesApi(testFixtures(project(":plugin-core:jetbrains-community"))) + testImplementation(project(":plugin-core:jetbrains-community")) +} + +tasks.test { + // Include core test sources + testClassesDirs += project(":plugin-core:jetbrains-community").sourceSets.test.get().output.classesDirs + classpath += project(":plugin-core:jetbrains-community").sourceSets.test.get().runtimeClasspath } fun transformXml(document: Document, path: Path) { @@ -214,7 +221,7 @@ fun transformXml(document: Document, path: Path) { // hack because our test structure currently doesn't make complete sense tasks.prepareTestSandbox { - val pluginXmlJar = project(":plugin-core").tasks.jar + val pluginXmlJar = project(":plugin-toolkit:intellij-standalone").tasks.jar dependsOn(pluginXmlJar) from(pluginXmlJar) { diff --git a/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml b/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml index ec19995f8bd..c46f740897c 100644 --- a/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml +++ b/plugins/toolkit/jetbrains-core/resources/META-INF/inactive/plugin-gateway.xml @@ -19,7 +19,7 @@ - +
diff --git a/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml b/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml index da7c4881d38..fd5f7646201 100644 --- a/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml +++ b/plugins/toolkit/jetbrains-core/resources/META-INF/plugin.xml @@ -528,7 +528,6 @@ - diff --git a/plugins/toolkit/jetbrains-ultimate/build.gradle.kts b/plugins/toolkit/jetbrains-ultimate/build.gradle.kts index 8d7c6862cec..57aae6aeea1 100644 --- a/plugins/toolkit/jetbrains-ultimate/build.gradle.kts +++ b/plugins/toolkit/jetbrains-ultimate/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { // hack because our test structure currently doesn't make complete sense tasks.prepareTestSandbox { - val pluginXmlJar = project(":plugin-core").tasks.jar + val pluginXmlJar = project(":plugin-toolkit:intellij-standalone").tasks.jar dependsOn(pluginXmlJar) from(pluginXmlJar) { From cee5a24a06ade3cef784b367dfe7303770cc259c Mon Sep 17 00:00:00 2001 From: Jonathan Breedlove Date: Mon, 5 Jan 2026 17:06:52 -0800 Subject: [PATCH 06/60] Fix Q tests --- .../services/amazonq/GetAmazonQLogsAction.kt | 2 +- .../services/amazonq/QLoginWebview.kt | 6 +- .../explorerActions/SignInToQAction.kt | 2 +- .../amazonq/toolwindow/AmazonQPanel.kt | 2 +- .../toolwindow/AmazonQToolWindowFactory.kt | 8 +- .../chat/telemetry/TelemetryHelper.kt | 4 +- .../context/file/FileContextExtractor.kt | 4 +- .../focusArea/FocusAreaContextExtractor.kt | 4 +- .../listeners/InlineChatSelectionListener.kt | 2 +- .../services/amazonq/TelemetryHelperTest.kt | 2 +- .../services/amazonq/AmazonQTestBase.kt | 2 +- .../amazonq/webview/BrowserConnectorTest.kt | 2 +- .../codemodernizer/ArtifactHandler.kt | 4 +- .../codemodernizer/CodeModernizerManager.kt | 4 +- .../utils/CodeTransformApiUtils.kt | 2 +- .../feedback/CodeTransformFeedbackDialog.kt | 2 +- .../codemodernizer/CodeTransformChatTest.kt | 2 +- .../CodeTransformHilDownloadArtifactTest.kt | 2 +- .../CodeTransformTelemetryTest.kt | 2 +- ...eWhispererCodeModernizerGumbyClientTest.kt | 2 +- .../CodeWhispererCodeModernizerSessionTest.kt | 4 +- .../CodeWhispererCodeModernizerTestBase.kt | 8 +- .../CodeWhispererCodeModernizerUtilsTest.kt | 4 +- .../utils/CodeTransformModuleUtilsTest.kt | 2 +- .../jetbrains-community/build.gradle.kts | 4 +- .../CodeWhispererCodeScanIntegrationTest.kt | 2 +- ...odeWhispererCodeScanJavaIntegrationTest.kt | 2 +- .../CodeWhispererCompletionIntegrationTest.kt | 2 +- .../CodeWhispererIntegrationTestBase.kt | 6 +- ...hispererReferenceTrackerIntegrationTest.kt | 2 +- .../codescan/CodeWhispererCodeScanManager.kt | 8 +- .../codescan/CodeWhispererCodeScanSession.kt | 2 +- .../utils/CodeWhispererCodeScanIssueUtils.kt | 6 +- .../editor/CodeWhispererEditorManager.kt | 2 +- .../inlay/CodeWhispererInlayManager.kt | 2 +- .../inlay/CodeWhispererInlayManagerNew.kt | 2 +- .../learn/LearnCodeWhispererEditorProvider.kt | 2 +- .../popup/CodeWhispererPopupComponents.kt | 2 +- .../popup/CodeWhispererPopupManager.kt | 2 +- .../popup/CodeWhispererPopupManagerNew.kt | 2 +- .../popup/QInlineCompletionProvider.kt | 4 +- .../service/CodeWhispererService.kt | 4 +- .../service/CodeWhispererServiceNew.kt | 4 +- .../CodeWhispererProjectStartupActivity.kt | 6 +- .../status/CodeWhispererStatusBarWidget.kt | 6 +- .../CodeWhispererTelemetryService.kt | 2 +- .../codewhisperer/util/CodeWhispererUtil.kt | 10 +- .../CodeWhispererClientAdaptorTest.kt | 4 +- .../CodeWhispererEditorUtilTest.kt | 2 +- .../CodeWhispererFileContextProviderTest.kt | 2 +- .../CodeWhispererLicenseInfoManagerTest.kt | 2 +- .../CodeWhispererModelConfiguratorTest.kt | 4 +- .../CodeWhispererReferenceManagerTest.kt | 2 +- .../CodeWhispererSettingsTest.kt | 2 +- .../CodeWhispererTelemetryTest.kt | 6 +- .../codewhisperer/CodeWhispererTestBase.kt | 2 +- .../codewhisperer/CodeWhispererUtilTest.kt | 4 +- .../services/codewhisperer/QEndpointsTest.kt | 2 +- .../QRegionProfileManagerTest.kt | 4 +- .../codescan/CodeWhispererCodeFileScanTest.kt | 6 +- .../codescan/CodeWhispererCodeScanTest.kt | 6 +- .../codescan/CodeWhispererCodeScanTestBase.kt | 2 +- .../CodeWhispererProjectCodeScanTest.kt | 6 +- .../codetest/CodeTestSessionConfigTest.kt | 4 +- .../CodeWhispererFallbackImportAdderTest.kt | 2 +- .../CodeWhispererPythonImportAdderTest.kt | 2 +- .../CodeWhispererJSImportAdderTest.kt | 2 +- plugins/amazonq/mynah-ui/package-lock.json | 14 +- .../jetbrains-community/build.gradle.kts | 6 + .../CodeWhispererFeatureConfigService.kt | 2 +- .../amazonq/lsp/AmazonQLanguageClientImpl.kt | 6 +- .../services/amazonq/lsp/AmazonQLspService.kt | 2 +- .../services/amazonq/lsp/NodeExePatcher.kt | 2 +- .../amazonq/lsp/artifacts/ArtifactHelper.kt | 2 +- .../lsp/auth/DefaultAuthCredentialsService.kt | 4 +- .../amazonq/profile/QRegionProfileDialog.kt | 2 +- .../CodeWhispererCustomizationDialog.kt | 2 +- .../CodeWhispererModelConfigurator.kt | 6 +- .../auth/DefaultAuthCredentialsServiceTest.kt | 6 +- .../auth/DefaultAuthCredentialsServiceTest.kt | 4 +- .../lsp/AmazonQLanguageClientImplTest.kt | 2 +- .../TextDocumentServiceHandlerTest.kt | 4 +- .../q/jetbrains/utils/rules/FakeCPython.kt | 19 ++ .../q/jetbrains/utils/rules/PyTestSdkType.kt | 22 +++ .../rules/PythonCodeInsightTestFixtureRule.kt | 150 +++++++++++++++ .../jetbrains-ultimate/build.gradle.kts | 1 + .../rules/NodeJsCodeInsightTestFixtureRule.kt | 174 ++++++++++++++++++ plugins/core-q/webview/package-lock.json | 15 +- .../jetbrains/core/AwsResourceCacheTest.kt | 2 +- plugins/core/webview/package-lock.json | 15 +- 90 files changed, 513 insertions(+), 177 deletions(-) create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/FakeCPython.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PyTestSdkType.kt create mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PythonCodeInsightTestFixtureRule.kt create mode 100644 plugins/core-q/jetbrains-ultimate/tstFixtures/software/amazon/q/jetbrains/utils/rules/NodeJsCodeInsightTestFixtureRule.kt diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt index 7f286a2b0e3..f9ca023248a 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt @@ -19,8 +19,8 @@ import com.intellij.util.ui.UIUtil import kotlinx.coroutines.runBlocking import software.amazon.q.jetbrains.utils.notifyInfo import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded -import software.aws.toolkits.resources.AmazonQBundle.message import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.resources.AmazonQBundle.message class GetAmazonQLogsAction : DumbAwareAction(message("amazonq.getLogs.tooltip.text")) { private val baseIcon = IconLoader.getIcon("/icons/file.svg", GetAmazonQLogsAction::class.java) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt index d5f18182f37..aa7721ba4ac 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt @@ -39,15 +39,15 @@ import software.amazon.q.jetbrains.core.webview.BrowserState import software.amazon.q.jetbrains.core.webview.LocalAssetJBCefRequestHandler import software.amazon.q.jetbrains.core.webview.LoginBrowser import software.amazon.q.jetbrains.isDeveloperMode +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIntent import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.telemetry.FeatureId import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt index 60e76aa0bc4..ed364f873d1 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt @@ -13,9 +13,9 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded import software.amazon.q.jetbrains.core.gettingstarted.requestCredentialsForQ +import software.amazon.q.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.UiTelemetry diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt index a5d6702c281..cc09349dcca 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt @@ -25,6 +25,7 @@ import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.jetbrains.core.coroutines.EDT import software.amazon.q.jetbrains.isDeveloperMode +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry @@ -42,7 +43,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapte import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.datatransfer.DataFlavor import java.awt.dnd.DropTarget diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt index 3c8ce5d696c..6a8e02dfdfa 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt @@ -25,16 +25,16 @@ import software.amazon.q.jetbrains.core.notifications.NotificationDismissalState import software.amazon.q.jetbrains.core.notifications.NotificationPanel import software.amazon.q.jetbrains.core.notifications.ProcessNotificationsBase import software.amazon.q.jetbrains.core.webview.BrowserState +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isQWebviewsAvailable +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel import software.aws.toolkits.jetbrains.services.amazonq.RefreshQChatPanelButtonPressedListener import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.AmazonQBundle import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.FeatureId diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt index 042bc36d068..abe0aa3a020 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt @@ -17,6 +17,8 @@ import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.utils.notifyError import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator @@ -28,9 +30,7 @@ import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessage import software.aws.toolkits.jetbrains.services.cwc.messages.IncomingCwcMessage import software.aws.toolkits.jetbrains.services.cwc.messages.LinkType import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionStorage -import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.notifyError import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.AuthTelemetry import software.aws.toolkits.telemetry.CwsprChatCommandType diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt index 13425e5dab2..c8fec78f9dc 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt @@ -9,11 +9,11 @@ import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile +import software.amazon.q.jetbrains.utils.computeOnEdt +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util.LanguageExtractor import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util.MatchPolicyExtractor -import software.amazon.q.jetbrains.utils.computeOnEdt -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend class FileContextExtractor(private val fqnWebviewAdapter: FqnWebviewAdapter?, private val project: Project) { private val languageExtractor: LanguageExtractor = LanguageExtractor() diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt index 2dc73ccf145..3b61d76cd65 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt @@ -11,14 +11,14 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.utils.computeOnEdt +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNames import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNamesImpl import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FullyQualifiedNames import software.aws.toolkits.jetbrains.services.cwc.controller.ChatController import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util.LanguageExtractor -import software.amazon.q.jetbrains.utils.computeOnEdt -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import java.awt.Point import kotlin.math.min diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt index f5306c303f0..c4e1e01d5cc 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt @@ -6,8 +6,8 @@ package software.aws.toolkits.jetbrains.services.cwc.inline.listeners import com.intellij.openapi.Disposable import com.intellij.openapi.editor.event.SelectionEvent import com.intellij.openapi.editor.event.SelectionListener -import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatEditorHint import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend +import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatEditorHint class InlineChatSelectionListener : SelectionListener, Disposable { private val inlineChatEditorHint = InlineChatEditorHint() diff --git a/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt b/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt index fd3dc0f457f..1c22bcdfe89 100644 --- a/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt +++ b/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt @@ -40,6 +40,7 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.services.telemetry.MockTelemetryServiceExtension import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization @@ -62,7 +63,6 @@ import software.aws.toolkits.jetbrains.services.cwc.messages.IncomingCwcMessage import software.aws.toolkits.jetbrains.services.cwc.messages.LinkType import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionInfo import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionStorage -import software.amazon.q.jetbrains.services.telemetry.MockTelemetryServiceExtension import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings import software.aws.toolkits.telemetry.CwsprChatConversationType import software.aws.toolkits.telemetry.CwsprChatInteractionType diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt index ec0cf7605a5..01a4856b073 100644 --- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt +++ b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt @@ -20,11 +20,11 @@ import software.amazon.q.core.credentials.ToolkitBearerTokenProvider import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.addModule +import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient open class AmazonQTestBase( @Rule @JvmField diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt index eb586d56dc8..75a5b987e7e 100644 --- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt +++ b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt @@ -23,6 +23,7 @@ import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.jetbrains.services.amazonq.AmazonQTestBase import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager @@ -30,7 +31,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Descripti import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.amazon.q.jetbrains.utils.satisfiesKt class BrowserConnectorTest : AmazonQTestBase() { private lateinit var browserConnector: BrowserConnector diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt index 96dc69a9280..fd7a8a2396a 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt @@ -26,6 +26,8 @@ import software.amazon.q.core.utils.info import software.amazon.q.jetbrains.core.coroutines.EDT import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope import software.amazon.q.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException +import software.amazon.q.jetbrains.utils.notifyStickyInfo +import software.amazon.q.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_EXPIRED import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient @@ -47,8 +49,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHi import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isValidCodeTransformConnection import software.aws.toolkits.jetbrains.services.codemodernizer.utils.openTroubleshootingGuideNotificationAction import software.aws.toolkits.jetbrains.services.codemodernizer.utils.zipToPath -import software.amazon.q.jetbrains.utils.notifyStickyInfo -import software.amazon.q.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformArtifactType import java.io.File diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt index 93c468e026a..e64b71c6a39 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt @@ -30,6 +30,8 @@ import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.utils.notifyStickyError +import software.amazon.q.jetbrains.utils.notifyStickyInfo import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_MVN_FAILURE import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_PROJECT_SIZE import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile @@ -79,8 +81,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseXmlDep import software.aws.toolkits.jetbrains.services.codemodernizer.utils.setDependencyVersionInPom import software.aws.toolkits.jetbrains.services.codemodernizer.utils.tryGetJdk import software.aws.toolkits.jetbrains.ui.feedback.CodeTransformFeedbackDialog -import software.amazon.q.jetbrains.utils.notifyStickyError -import software.amazon.q.jetbrains.utils.notifyStickyInfo import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformBuildSystem import software.aws.toolkits.telemetry.CodeTransformCancelSrcComponents diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 84a3d99299d..d8a7a735985 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -39,6 +39,7 @@ import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.jetbrains.core.coroutines.EDT +import software.amazon.q.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient @@ -51,7 +52,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId import software.aws.toolkits.jetbrains.services.codemodernizer.model.PlanTable -import software.amazon.q.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.resources.message import java.nio.file.Path import java.nio.file.Paths diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt index a28d02ad991..fd07747c746 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt @@ -4,10 +4,10 @@ package software.aws.toolkits.jetbrains.ui.feedback import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.amazon.q.jetbrains.ui.feedback.FEEDBACK_SOURCE import software.amazon.q.jetbrains.ui.feedback.FeedbackDialog +import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState import software.aws.toolkits.resources.message class CodeTransformFeedbackDialog(project: Project) : FeedbackDialog(project) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt index e95caa4e7ff..9657dcf8fd9 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt @@ -5,11 +5,11 @@ package software.aws.toolkits.jetbrains.services.codemodernizer import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTransformResultChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformButtonId import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId -import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.resources.message class CodeTransformChatTest { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt index 5704e621b62..e6498b02a3a 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt @@ -8,9 +8,9 @@ import com.intellij.util.io.delete import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.satisfiesKt +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact import kotlin.io.path.createTempDirectory class CodeTransformHilDownloadArtifactTest : CodeWhispererCodeModernizerTestBase(HeavyJavaCodeInsightTestFixtureRule()) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt index 60814eb8297..50d7efe5cf9 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt @@ -10,8 +10,8 @@ import org.junit.Test import org.mockito.kotlin.doReturn import org.mockito.kotlin.spy import org.mockito.kotlin.whenever -import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeTransformTelemetryState import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeTransformTelemetryState import kotlin.io.path.Path class CodeTransformTelemetryTest : CodeWhispererCodeModernizerTestBase(HeavyJavaCodeInsightTestFixtureRule()) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt index 1e0bb638ad0..a196177e02b 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt @@ -49,10 +49,10 @@ import software.amazon.q.jetbrains.core.credentials.ManagedSsoProfile import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager +import software.amazon.q.jetbrains.settings.AwsSettings import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId -import software.amazon.q.jetbrains.settings.AwsSettings import java.util.concurrent.CompletableFuture class CodeWhispererCodeModernizerGumbyClientTest : CodeWhispererCodeModernizerTestBase() { diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt index 0b24ae92a81..9011bb57dea 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt @@ -49,6 +49,8 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.ssooidc.model.SsoOidcException import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.aws.toolkits.jetbrains.services.codemodernizer.model.CLIENT_SIDE_BUILD import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext @@ -63,8 +65,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.SELECTIVE_T import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.ZipCreationResult import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.addFileToModule import java.io.File import java.io.FileInputStream import java.net.ConnectException diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt index d0039725790..fc67d265a33 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt @@ -48,6 +48,10 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.addModule import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerManifest @@ -60,10 +64,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.C import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState import software.aws.toolkits.jetbrains.services.codemodernizer.toolwindow.CodeModernizerBottomToolWindowFactory import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.addModule import java.io.File import java.time.Instant import kotlin.io.path.Path diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt index bc43df19c1a..219a1358717 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt @@ -30,6 +30,8 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStep import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.amazon.q.jetbrains.utils.notifyStickyWarn +import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact.Companion.MAPPER import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType @@ -44,8 +46,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseBuildF import software.aws.toolkits.jetbrains.services.codemodernizer.utils.pollTransformationStatusAndPlan import software.aws.toolkits.jetbrains.services.codemodernizer.utils.validateCustomVersionsFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.validateSctMetadata -import software.amazon.q.jetbrains.utils.notifyStickyWarn -import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.aws.toolkits.resources.message import java.util.concurrent.atomic.AtomicBoolean import java.util.zip.ZipFile diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt index 247a23368c7..b9accd78451 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt @@ -20,8 +20,8 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever -import software.aws.toolkits.jetbrains.services.codemodernizer.CodeWhispererCodeModernizerTestBase import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.services.codemodernizer.CodeWhispererCodeModernizerTestBase class CodeTransformModuleUtilsTest : CodeWhispererCodeModernizerTestBase(HeavyJavaCodeInsightTestFixtureRule()) { lateinit var javaSdkMock: JavaSdk diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts b/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts index 1a51e8c67da..2edb2cb1985 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts @@ -22,7 +22,9 @@ dependencies { implementation(libs.commons.collections) testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-community"))) - testFixturesApi(project(path = ":plugin-toolkit:jetbrains-core", configuration = "testArtifacts")) + testFixturesApi(project(path = ":plugin-toolkit:jetbrains-core", configuration = "testArtifacts")) { + isTransitive = false + } } // hack because our test structure currently doesn't make complete sense diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt index a25b4e3fe43..7315801e92d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt @@ -6,9 +6,9 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppTestLeftContext -import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt index 32ebb360492..2477b7406dd 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt @@ -5,11 +5,11 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.runInEdtAndWait import org.junit.Test -import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaTestContext import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.amazon.q.jetbrains.utils.rules.addClass import software.amazon.q.jetbrains.utils.rules.addModule +import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaTestContext import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt index 1de0210ffda..19b0fc775d2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt @@ -9,9 +9,9 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.mockito.kotlin.any import org.mockito.kotlin.never import org.mockito.kotlin.verify +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppFileName import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt index e6272b827c6..591deeee01b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt @@ -37,6 +37,9 @@ import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.codeWhispererRecommendationActionId import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext @@ -58,9 +61,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhisp import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials open class CodeWhispererIntegrationTestBase(val projectRule: CodeInsightTestFixtureRule = PythonCodeInsightTestFixtureRule()) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt index 8a04b517eb1..5ff1cbf0c29 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt @@ -10,9 +10,9 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.mockito.kotlin.any import org.mockito.kotlin.never import org.mockito.kotlin.verify +import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.jsFileName import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService -import software.amazon.q.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials import software.aws.toolkits.resources.message @RequiresRealCredentials diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt index 8e6e1432680..434b5f6d7a7 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt @@ -62,12 +62,15 @@ import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn -import software.aws.toolkits.jetbrains.ProblemsViewMutator import software.amazon.q.jetbrains.core.coroutines.EDT import software.amazon.q.jetbrains.core.coroutines.getCoroutineUiContext import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend +import software.aws.toolkits.jetbrains.ProblemsViewMutator import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanDocumentListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanEditorMouseMotionListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanFileListener @@ -95,9 +98,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.Result import java.time.Duration diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt index 62d8c66f5b8..fcd1196e2a8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt @@ -34,6 +34,7 @@ import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext @@ -52,7 +53,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererZipUploadManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage -import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererLanguage import java.nio.file.Path diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt index e2f4f05c203..79c1bc2babb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt @@ -29,6 +29,9 @@ import software.amazon.q.core.utils.convertMarkdownToHTML import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger import software.amazon.q.jetbrains.ToolkitPlaces +import software.amazon.q.jetbrains.utils.applyPatch +import software.amazon.q.jetbrains.utils.notifyError +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReferencePosition import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanHighlightingFilesPanel @@ -45,9 +48,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhi import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_ISSUE_TITLE_MAX_LENGTH import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled -import software.amazon.q.jetbrains.utils.applyPatch -import software.amazon.q.jetbrains.utils.notifyError -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeFixAction import software.aws.toolkits.telemetry.Result diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt index 90bffafd7ff..cd4c9d22cb7 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext @@ -22,7 +23,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CaretMovement import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_BRACKETS import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_QUOTES import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.message import java.util.Stack diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt index 7b0eb0ec303..8db0888be50 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt @@ -10,9 +10,9 @@ import com.intellij.openapi.editor.EditorCustomElementRenderer import com.intellij.openapi.editor.Inlay import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend @Service class CodeWhispererInlayManager { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt index d11e16ab6f0..153583de716 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt @@ -8,9 +8,9 @@ import com.intellij.openapi.components.service import com.intellij.openapi.editor.EditorCustomElementRenderer import com.intellij.openapi.editor.Inlay import com.intellij.openapi.util.Disposer +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend @Service class CodeWhispererInlayManagerNew { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt index af9059020ed..f66573c2445 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt @@ -14,8 +14,8 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger -import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.amazon.q.jetbrains.utils.isQWebviewsAvailable +import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.telemetry.UiTelemetry class LearnCodeWhispererEditorProvider : FileEditorProvider, DumbAware { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt index 61bf54a77df..b22afbfa5e1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt @@ -15,6 +15,7 @@ import com.intellij.openapi.keymap.KeymapUtil import com.intellij.ui.IdeBorderFactory import com.intellij.ui.components.ActionLink import com.intellij.util.ui.UIUtil +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererLearnMoreAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererProvideFeedbackAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererShowSettingsAction @@ -33,7 +34,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_REF_NOTICE_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_BUTTON_TEXT_SIZE import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.GridBagLayout import java.awt.event.MouseAdapter diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 712c7e0fabb..ce03e15b117 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -41,6 +41,7 @@ import com.intellij.util.messages.Topic import com.intellij.util.ui.UIUtil import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionImports import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager @@ -67,7 +68,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_DIM_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.Point import java.awt.Rectangle diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt index a8816165ac5..41420e16541 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt @@ -39,6 +39,7 @@ import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.ui.UIUtil import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionImports import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManagerNew @@ -66,7 +67,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_DIM_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.Point import java.awt.Rectangle diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt index 41c68d6dd50..fd083e9e89b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt @@ -55,6 +55,8 @@ import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.importadder.CodeWhispererImportAdder @@ -70,8 +72,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhi import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDocumentDiagnostics -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.awt.Dimension diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt index bf97ecd95df..a5f11f7d901 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt @@ -42,6 +42,8 @@ import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.utils.isInjectedText +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.GetConfigurationFromServerParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerConfigurations @@ -72,8 +74,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider -import software.amazon.q.jetbrains.utils.isInjectedText -import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.net.URI diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt index 67f4e2249d9..17ed4b8807e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt @@ -41,6 +41,8 @@ import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.utils.isInjectedText +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionContext import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences @@ -69,8 +71,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider -import software.amazon.q.jetbrains.utils.isInjectedText -import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.util.concurrent.TimeUnit diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt index 16cd722e933..e22bd3e9dc2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt @@ -10,6 +10,9 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.calculateIfIamIdentityCenterConnection import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager @@ -21,9 +24,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispere import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FEATURE_CONFIG_POLL_INTERVAL_IN_MS import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread // TODO: add logics to check if we want to remove recommendation suspension date when user open the IDE class CodeWhispererProjectStartupActivity : StartupActivity.DumbAware { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt index d094f3655f5..bb16ac9ffff 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt @@ -20,6 +20,9 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.QActionGroups.Q_SIGNED_OUT_ACTION_GROUP import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile @@ -32,9 +35,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.reconnectCodeWhisperer -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import java.awt.event.MouseEvent import javax.swing.Icon diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt index b0e0d0da763..7a4d166d972 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.launch import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger import software.amazon.q.jetbrains.core.credentials.sono.isInternalUser +import software.amazon.q.jetbrains.settings.AwsSettings import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.InlineCompletionStates import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LogInlineCompletionSessionResultsParams @@ -31,7 +32,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.DiagnosticDif import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDiagnosticDifferences import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDocumentDiagnostics import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl -import software.amazon.q.jetbrains.settings.AwsSettings import software.aws.toolkits.telemetry.CodeFixAction import software.aws.toolkits.telemetry.CodewhispererCodeScanScope import software.aws.toolkits.telemetry.CodewhispererGettingStartedTask diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt index d4eedb68493..89eaf62ddc1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt @@ -46,6 +46,11 @@ import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnectionType import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.utils.isQExpired +import software.amazon.q.jetbrains.utils.notifyError +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.learn.LearnCodeWhispererManager.Companion.taskTypeToFilename @@ -54,11 +59,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.isTelemetryEnabled import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CrossFile.NUMBER_OF_CHUNK_TO_FETCH import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CrossFile.NUMBER_OF_LINE_IN_CHUNK -import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.notifyError -import software.amazon.q.jetbrains.utils.notifyInfo -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodewhispererCompletionType import software.aws.toolkits.telemetry.CodewhispererGettingStartedTask diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt index 3cf3fc45c82..fbdd8b4a1ee 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt @@ -53,13 +53,13 @@ import software.amazon.q.jetbrains.core.credentials.logoutFromSsoConnection import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.amazonq.FEATURE_EVALUATION_PRODUCT_NAME import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.metadata import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.sdkHttpResponse import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptorImpl import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule class CodeWhispererClientAdaptorTest { val projectRule = JavaCodeInsightTestFixtureRule() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt index 84332655d74..fd64e77fe4b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt @@ -18,7 +18,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestU import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererEditorUtilTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt index 387721b74c8..44215569e51 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt @@ -14,11 +14,11 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.mock +import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJava import software.aws.toolkits.jetbrains.services.codewhisperer.util.DefaultCodeWhispererFileContextProvider import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider -import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule class CodeWhispererFileContextProviderTest { @JvmField diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt index 4d2429846cf..795905e1187 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt @@ -8,8 +8,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererLicenseInfoManager import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererLicenseInfoManager import software.aws.toolkits.resources.message class CodeWhispererLicenseInfoManagerTest : CodeWhispererTestBase() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt index fa9b0355986..358c78f8737 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt @@ -10,7 +10,7 @@ import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.registerServiceInstance import com.intellij.testFramework.replaceService import com.intellij.util.xmlb.XmlSerializer -import migration.software.amazon.q.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator +import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import org.assertj.core.api.Assertions.assertThat import org.jdom.output.XMLOutputter import org.junit.Before @@ -41,6 +41,7 @@ import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.amazon.q.jetbrains.core.credentials.sono.isSono import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.utils.xmlElement import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.FeatureContext import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile @@ -50,7 +51,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomizationState import software.aws.toolkits.jetbrains.services.codewhisperer.customization.DefaultCodeWhispererModelConfigurator -import software.amazon.q.jetbrains.utils.xmlElement import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.reflect.full.memberProperties diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt index 8ce8947cce6..df4c28892db 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt @@ -12,7 +12,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererReferenceManagerTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt index 6723666c00c..3ee356da7c2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt @@ -23,6 +23,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import software.amazon.q.jetbrains.utils.xmlElement import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState @@ -31,7 +32,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.status.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceToolWindowFactory import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.xmlElement import kotlin.test.fail class CodeWhispererSettingsTest : CodeWhispererTestBase() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index db2604aa94e..5ef156c640c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -18,12 +18,12 @@ import org.mockito.kotlin.whenever import software.amazon.q.core.telemetry.MetricEvent import software.amazon.q.core.telemetry.TelemetryBatcher import software.amazon.q.core.telemetry.TelemetryPublisher -import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Pause -import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Resume -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.amazon.q.jetbrains.settings.AwsSettings +import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Pause +import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Resume +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants class CodeWhispererTelemetryTest : CodeWhispererTestBase() { private val awsModifySetting = "aws_modifySetting" diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index dcf3472bc3b..705adf33f4d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -81,7 +81,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.settings.CodeWhispererConfigurationType import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.resources.message import java.util.concurrent.CompletableFuture import java.util.concurrent.Future diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt index d2cd8d96221..071aba8183f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt @@ -26,6 +26,8 @@ import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.settings.AwsSettings +import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getTelemetryOptOutPreference import software.aws.toolkits.jetbrains.services.codewhisperer.util.convertSeverity @@ -35,8 +37,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.isWithin import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled import software.aws.toolkits.jetbrains.services.codewhisperer.util.toCodeChunk import software.aws.toolkits.jetbrains.services.codewhisperer.util.truncateLineByLine -import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererCompletionType class CodeWhispererUtilTest { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt index 1f12013022f..bcbf3619920 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt @@ -8,11 +8,11 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.RegisterExtension +import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionEndpoint import software.aws.toolkits.jetbrains.utils.rules.RegistryExtension -import software.amazon.q.jetbrains.utils.satisfiesKt @ExtendWith(ApplicationExtension::class) class QEndpointsTest { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt index b6bb1629ddb..13eeb4fee4c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt @@ -39,6 +39,8 @@ import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.xmlElement import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileResources import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileState @@ -46,8 +48,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIn import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.amazon.q.jetbrains.utils.satisfiesKt -import software.amazon.q.jetbrains.utils.xmlElement import java.net.URI import java.util.function.Consumer import kotlin.test.fail diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt index 14674d6ed6a..db7cefcb04c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt @@ -27,14 +27,14 @@ import software.amazon.awssdk.awscore.exception.AwsErrorDetails import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlRequest import software.amazon.q.core.utils.WaiterTimeoutException +import software.amazon.q.jetbrains.utils.isInstanceOf +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND -import software.amazon.q.jetbrains.utils.isInstanceOf -import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.FileInputStream import java.lang.management.ManagementFactory diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt index 0907a9f1e0d..1d00cf7957f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt @@ -24,15 +24,15 @@ import software.amazon.awssdk.awscore.exception.AwsErrorDetails import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlRequest import software.amazon.q.core.utils.WaiterTimeoutException +import software.amazon.q.jetbrains.utils.isInstanceOf +import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage -import software.amazon.q.jetbrains.utils.isInstanceOf -import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.File import java.io.FileInputStream diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt index cd68ead054e..7333041a3ae 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt @@ -41,6 +41,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Span import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisResponse import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeFixJobResponse import software.amazon.q.jetbrains.core.MockClientManagerRule +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor @@ -48,7 +49,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererZipUploadManager -import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.nio.file.Path import kotlin.test.assertNotNull diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt index 03f3e783185..73082283687 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt @@ -12,12 +12,12 @@ import org.junit.jupiter.api.assertThrows import org.mockito.kotlin.any import org.mockito.kotlin.spy import org.mockito.kotlin.stub +import software.amazon.q.jetbrains.utils.rules.addFileToModule +import software.amazon.q.jetbrains.utils.rules.addModule import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getNormalizedRelativePath -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.rules.addFileToModule -import software.amazon.q.jetbrains.utils.rules.addModule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.BufferedInputStream import java.nio.file.Paths diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt index 92febd922bd..8bfbb39a255 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt @@ -13,12 +13,12 @@ import org.junit.Test import org.junit.jupiter.api.assertThrows import org.mockito.kotlin.spy import org.mockito.kotlin.stub -import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.sessionconfig.CodeTestSessionConfig -import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.amazon.q.jetbrains.utils.rules.addModule +import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.sessionconfig.CodeTestSessionConfig +import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage import java.io.BufferedInputStream import java.util.zip.ZipInputStream diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt index 6051a7789f5..30bd57bd6c2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt @@ -11,7 +11,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererFallbackImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererFallbackImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt index 7964948b555..7d03bd0a39e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt @@ -15,7 +15,7 @@ import com.jetbrains.python.psi.PyImportStatement import com.jetbrains.python.psi.PyImportStatementBase import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererPythonImportAdderTest : CodeWhispererImportAdderTestBase( CodeWhispererPythonImportAdder(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt b/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt index a483812930e..be815a3f282 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt @@ -13,7 +13,7 @@ import com.intellij.testFramework.runInEdtAndWait import compat.com.intellij.lang.javascript.JavascriptLanguage import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.aws.toolkits.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule import kotlin.test.fail class CodeWhispererJSImportAdderTest : CodeWhispererImportAdderTestBase( diff --git a/plugins/amazonq/mynah-ui/package-lock.json b/plugins/amazonq/mynah-ui/package-lock.json index 9713c3ae903..829f7f5bb36 100644 --- a/plugins/amazonq/mynah-ui/package-lock.json +++ b/plugins/amazonq/mynah-ui/package-lock.json @@ -366,8 +366,7 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "peer": true + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" }, "node_modules/@types/sanitize-html": { "version": "2.9.5", @@ -423,7 +422,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -777,7 +775,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -816,7 +813,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -990,7 +986,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001580", "electron-to-chromium": "^1.4.648", @@ -1479,7 +1474,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2709,7 +2703,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3010,7 +3003,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", "dev": true, - "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3085,7 +3077,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3499,7 +3490,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3614,7 +3604,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3661,7 +3650,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts index 0bed9291951..030777c767b 100644 --- a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts @@ -29,6 +29,12 @@ dependencies { testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-community"))) } +tasks.test { + // Include core test sources + testClassesDirs += project(":plugin-core-q:jetbrains-community").sourceSets.test.get().output.classesDirs + classpath += project(":plugin-core-q:jetbrains-community").sourceSets.test.get().runtimeClasspath +} + // hack because our test structure currently doesn't make complete sense tasks.prepareTestSandbox { val pluginXmlJar = project(":plugin-amazonq").tasks.jar diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt index e64b0beaffa..b69943a841d 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt @@ -16,9 +16,9 @@ import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization -import software.amazon.q.jetbrains.utils.isQExpired @Service class CodeWhispererFeatureConfigService { private val featureConfigs = mutableMapOf() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index b8990c3f153..bb3904390b8 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -44,6 +44,9 @@ import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.services.telemetry.TelemetryService +import software.amazon.q.jetbrains.utils.getCleanedContent +import software.amazon.q.jetbrains.utils.notify import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LSPAny @@ -67,10 +70,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.TelemetryParsingUtil import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.applyExtensionFilter import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator -import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.getCleanedContent -import software.amazon.q.jetbrains.utils.notify import software.aws.toolkits.resources.message import java.io.File import java.net.URLDecoder diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 95e0e6d496f..2ffc8d5bb5f 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -67,6 +67,7 @@ import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn import software.amazon.q.core.utils.writeText import software.amazon.q.jetbrains.core.coroutines.ioDispatcher +import software.amazon.q.jetbrains.services.telemetry.ClientMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService @@ -79,7 +80,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolder import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints -import software.amazon.q.jetbrains.services.telemetry.ClientMetadata import software.aws.toolkits.jetbrains.settings.LspSettings import java.io.IOException import java.io.OutputStreamWriter diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt index f82956122f0..ab39ddae9c1 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt @@ -17,9 +17,9 @@ import software.amazon.q.core.utils.exists import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.aws.toolkits.jetbrains.settings.LspSettings -import software.amazon.q.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.Telemetry import java.nio.file.Files diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt index d19536cf92a..c6831fe7b2a 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt @@ -17,8 +17,8 @@ import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.saveFileFromUrl -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.aws.toolkits.telemetry.LanguageServerSetupStage import software.aws.toolkits.telemetry.Telemetry import java.nio.file.Files diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt index 787659d80f8..85bec5fa30c 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt @@ -21,6 +21,8 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerList import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.UpdateConfigurationParams @@ -31,8 +33,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credential import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired import java.util.concurrent.CompletableFuture import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt index cccaf9dd3ba..170c419d7f9 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt @@ -20,8 +20,8 @@ import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.help.HelpIds import software.amazon.q.jetbrains.ui.AsyncComboBox import software.amazon.q.jetbrains.utils.ui.selected -import software.aws.toolkits.resources.AmazonQBundle.message import software.amazon.q.resources.AwsCoreBundle +import software.aws.toolkits.resources.AmazonQBundle.message import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry import javax.swing.JComponent diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt index 47f24aeaccf..53481c9f39c 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt @@ -26,9 +26,9 @@ import software.amazon.awssdk.arns.Arn import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.tryOrNull -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.amazon.q.jetbrains.ui.AsyncComboBox import software.amazon.q.jetbrains.utils.notifyInfo +import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.resources.message import javax.swing.JComponent import javax.swing.JList diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt index 0d7de51729c..cff3d4fd853 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt @@ -21,15 +21,15 @@ import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntime import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger +import software.amazon.q.jetbrains.utils.notifyInfo +import software.amazon.q.jetbrains.utils.notifyWarn +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.calculateIfIamIdentityCenterConnection import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIntent import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.amazon.q.jetbrains.utils.notifyInfo -import software.amazon.q.jetbrains.utils.notifyWarn -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean diff --git a/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt index 2cb7160654e..2c73e5136b5 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt @@ -31,6 +31,8 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager @@ -38,8 +40,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerC import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired import java.time.Instant import java.util.concurrent.CompletableFuture @@ -113,7 +113,7 @@ class DefaultAuthCredentialsServiceTest { every { connectionStateForFeature(any()) } returns BearerTokenAuthState.AUTHORIZED } project.replaceService(ToolkitConnectionManager::class.java, mockConnectionManager, project) - mockkStatic("software.aws.toolkits.jetbrains.utils.FunctionUtilsKt") + mockkStatic("software.amazon.q.jetbrains.utils.FunctionUtilsKt") // these set so init doesn't always emit every { isQConnected(any()) } returns false every { isQExpired(any()) } returns true diff --git a/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt index e81b4823ff0..09817f8b3b3 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt @@ -28,6 +28,8 @@ import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.utils.isQConnected +import software.amazon.q.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager @@ -35,8 +37,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerC import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired import java.time.Instant import java.util.concurrent.CompletableFuture diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt index 8b746578612..30bd962a77f 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt @@ -33,11 +33,11 @@ import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection +import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator -import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings.Companion.CONTEXT_INDEX_SIZE import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings.Companion.CONTEXT_INDEX_THREADS diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt index 797638382c6..e25385dfead 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt @@ -42,11 +42,11 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestName import software.amazon.q.jetbrains.core.coroutines.EDT +import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil -import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule -import software.amazon.q.jetbrains.utils.satisfiesKt import java.net.URI import java.nio.file.Path import java.util.concurrent.CompletableFuture diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/FakeCPython.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/FakeCPython.kt new file mode 100644 index 00000000000..47646746b18 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/FakeCPython.kt @@ -0,0 +1,19 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.openapi.projectRoots.Sdk +import com.jetbrains.python.psi.LanguageLevel +import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor +import com.jetbrains.python.sdk.flavors.PyFlavorData +import org.jetbrains.annotations.NotNull + +internal class FakeCPython(private val languageLevel: LanguageLevel) : CPythonSdkFlavor() { + @NotNull + override fun getName(): String = "FakeCPython" + + override fun getVersionString(sdkHome: String?) = "$name ${languageLevel.toPythonVersion()}" + + override fun getLanguageLevel(sdk: Sdk) = languageLevel +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PyTestSdkType.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PyTestSdkType.kt new file mode 100644 index 00000000000..539b27b4685 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PyTestSdkType.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.projectRoots.SdkAdditionalData +import com.intellij.openapi.projectRoots.SdkTypeId +import com.jetbrains.python.PyNames +import com.jetbrains.python.psi.LanguageLevel +import org.jdom.Element + +class PyTestSdkType(private val level: LanguageLevel) : SdkTypeId { + override fun getName(): String = PyNames.PYTHON_SDK_ID_NAME + + override fun getVersionString(sdk: Sdk) = "FakeCPython ${level.toPythonVersion()}" + + override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) { + } + + override fun loadAdditionalData(currentSdk: Sdk, additional: Element) = null +} diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PythonCodeInsightTestFixtureRule.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PythonCodeInsightTestFixtureRule.kt new file mode 100644 index 00000000000..7e9dd76de82 --- /dev/null +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PythonCodeInsightTestFixtureRule.kt @@ -0,0 +1,150 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.ide.util.projectWizard.EmptyModuleBuilder +import com.intellij.openapi.application.runWriteActionAndWait +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleType +import com.intellij.openapi.module.ModuleTypeManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.projectRoots.SdkAdditionalData +import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.SystemInfo +import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.builders.ModuleFixtureBuilder +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.ModuleFixture +import com.intellij.testFramework.fixtures.TestFixtureBuilder +import com.intellij.testFramework.fixtures.impl.ModuleFixtureBuilderImpl +import com.intellij.testFramework.fixtures.impl.ModuleFixtureImpl +import com.intellij.testFramework.runInEdtAndWait +import com.intellij.xdebugger.XDebuggerUtil +import com.jetbrains.python.PythonModuleTypeBase +import com.jetbrains.python.psi.LanguageLevel +import com.jetbrains.python.psi.PyFile +import com.jetbrains.python.sdk.PythonSdkAdditionalData +import com.jetbrains.python.sdk.flavors.PyFlavorAndData +import com.jetbrains.python.sdk.flavors.PyFlavorData +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.attribute.PosixFilePermission + +/** + * JUnit test Rule that will create a Light [Project] and [CodeInsightTestFixture] with Python support. Projects are + * lazily created and are torn down after each test. + * + * If you wish to have just a [Project], you may use Intellij's [com.intellij.testFramework.ProjectRule] + */ +class PythonCodeInsightTestFixtureRule : CodeInsightTestFixtureRule() { + override fun createTestFixture(): CodeInsightTestFixture { + val fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory() + fixtureFactory.registerFixtureBuilder( + PythonModuleFixtureBuilder::class.java, + PythonModuleFixtureBuilder::class.java + ) + val fixtureBuilder = fixtureFactory.createFixtureBuilder(testName) + fixtureBuilder.addModule(PythonModuleFixtureBuilder::class.java) + val newFixture = fixtureFactory.createCodeInsightFixture(fixtureBuilder.fixture) + newFixture.testDataPath = testDataPath + newFixture.setUp() + + val module = newFixture.module + + val projectRoot = newFixture.tempDirFixture.getFile(".")!! + + if (SystemInfo.isUnix) { + val path = Paths.get(projectRoot.path) + + // TODO: Investigate this more. On 2020.1 this folder has strict permissions + // on code build (due to root?) that prevents it from mounting into docker for sam + Files.setPosixFilePermissions( + path, + setOf( + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_READ, + PosixFilePermission.GROUP_READ, + PosixFilePermission.GROUP_WRITE, + PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_READ, + PosixFilePermission.OTHERS_WRITE, + PosixFilePermission.OTHERS_EXECUTE + ) + ) + } + + PsiTestUtil.addContentRoot(module, projectRoot) + val sdk = PyTestSdk.create("3.12.0") + setModuleSdk(module, sdk) + + return newFixture + } + + override val fixture: CodeInsightTestFixture + get() = lazyFixture.value + + fun setModuleSdk(module: Module, sdk: Sdk) { + runWriteActionAndWait { + ProjectJdkTable.getInstance().addJdk(sdk, module) + ModuleRootModificationUtil.setModuleSdk(module, sdk) + } + } +} + +internal class PythonModuleFixtureBuilder(fixtureBuilder: TestFixtureBuilder) : + ModuleFixtureBuilderImpl(PlatformPythonModuleType(), fixtureBuilder), + ModuleFixtureBuilder { + + override fun instantiateFixture(): ModuleFixture = ModuleFixtureImpl(this) +} + +internal class PlatformPythonModuleType : PythonModuleTypeBase() { + override fun createModuleBuilder(): EmptyModuleBuilder = object : EmptyModuleBuilder() { + override fun getModuleType(): ModuleType = instance + } + + companion object { + val instance: PlatformPythonModuleType + get() = ModuleTypeManager.getInstance().findByID("PYTHON_MODULE") as PlatformPythonModuleType + } +} + +class PyTestSdk private constructor(private val version: LanguageLevel) : ProjectJdkImpl("PySdk $version", PyTestSdkType(version)) { + override fun getVersionString(): String = version.toString() + + override fun getSdkAdditionalData(): SdkAdditionalData = PythonSdkAdditionalData(PyFlavorAndData(PyFlavorData.Empty, FakeCPython(version))) + + companion object { + fun create(version: String): PyTestSdk { + val level = LanguageLevel.fromPythonVersion(version) ?: error("Invalid version") + val sdk = PyTestSdk(level) + + sdk.putUserData(Key.create("MOCK_PY_MARKER_KEY"), true) + + return sdk + } + } +} + +fun PythonCodeInsightTestFixtureRule.addBreakpoint() { + runInEdtAndWait { + val document = fixture.editor.document + val lambdaClass = fixture.file as PyFile + val lambdaBody = lambdaClass.topLevelFunctions[0].statementList.statements[0] + val lineNumber = document.getLineNumber(lambdaBody.textOffset) + + XDebuggerUtil.getInstance().toggleLineBreakpoint( + project, + fixture.file.virtualFile, + lineNumber + ) + } +} diff --git a/plugins/core-q/jetbrains-ultimate/build.gradle.kts b/plugins/core-q/jetbrains-ultimate/build.gradle.kts index b440209e626..2811805cd34 100644 --- a/plugins/core-q/jetbrains-ultimate/build.gradle.kts +++ b/plugins/core-q/jetbrains-ultimate/build.gradle.kts @@ -13,4 +13,5 @@ intellijToolkit { dependencies { compileOnly(project(":plugin-core-q:jetbrains-community")) + testFixturesApi(testFixtures(project(":plugin-core-q:jetbrains-community"))) } diff --git a/plugins/core-q/jetbrains-ultimate/tstFixtures/software/amazon/q/jetbrains/utils/rules/NodeJsCodeInsightTestFixtureRule.kt b/plugins/core-q/jetbrains-ultimate/tstFixtures/software/amazon/q/jetbrains/utils/rules/NodeJsCodeInsightTestFixtureRule.kt new file mode 100644 index 00000000000..60e0d1e89f4 --- /dev/null +++ b/plugins/core-q/jetbrains-ultimate/tstFixtures/software/amazon/q/jetbrains/utils/rules/NodeJsCodeInsightTestFixtureRule.kt @@ -0,0 +1,174 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.q.jetbrains.utils.rules + +import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager +import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterRef +import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter +import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreterManager +import com.intellij.lang.javascript.dialects.JSLanguageLevel +import com.intellij.lang.javascript.psi.JSFile +import com.intellij.lang.javascript.settings.JSRootConfiguration +import com.intellij.openapi.module.WebModuleTypeBase +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.runInEdtAndGet +import com.intellij.testFramework.runInEdtAndWait +import com.intellij.util.text.SemVer +import com.intellij.xdebugger.XDebuggerUtil +import org.intellij.lang.annotations.Language + +/** + * JUnit test Rule that will create a Light [Project] and [CodeInsightTestFixture] with NodeJs support. Projects are + * lazily created and are torn down after each test. + * + * If you wish to have just a [Project], you may use Intellij's [com.intellij.testFramework.ProjectRule] + */ +class NodeJsCodeInsightTestFixtureRule : CodeInsightTestFixtureRule(NodeJsLightProjectDescriptor()) { + override fun createTestFixture(): CodeInsightTestFixture { + val codeInsightFixture = super.createTestFixture() + PsiTestUtil.addContentRoot(codeInsightFixture.module, codeInsightFixture.tempDirFixture.getFile(".")!!) + codeInsightFixture.project.setNodeJsInterpreterVersion(SemVer("v8.10.10", 8, 10, 10)) + codeInsightFixture.project.setJsLanguageLevel(JSLanguageLevel.ES6) + + return codeInsightFixture + } + + fun addBreakpoint() { + runInEdtAndWait { + val document = fixture.editor.document + val psiFile = fixture.file as JSFile + val lineNumber = document.getLineNumber(psiFile.statements.first().textOffset) + + XDebuggerUtil.getInstance().toggleLineBreakpoint( + project, + fixture.file.virtualFile, + lineNumber + ) + } + } +} + +class NodeJsLightProjectDescriptor : LightProjectDescriptor() { + override fun getSdk(): Sdk? = null + + override fun getModuleTypeId(): String = WebModuleTypeBase.getInstance().id +} + +class MockNodeJsInterpreter(private var version: SemVer) : NodeJsLocalInterpreter("/path/to/$version/mock/node") { + init { + NodeJsLocalInterpreterManager.getInstance().interpreters = + NodeJsLocalInterpreterManager.getInstance().interpreters + listOf(this) + } + + // could differ on windows causing interpreter lookup failure during tests + override fun getPresentableName(): String = referenceName + + override fun getCachedVersion(): Ref = Ref(version) +} + +class HeavyNodeJsCodeInsightTestFixtureRule : CodeInsightTestFixtureRule() { + override fun createTestFixture(): CodeInsightTestFixture { + val fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory() + val projectFixture = fixtureFactory.createFixtureBuilder(testName) + val codeInsightFixture = fixtureFactory.createCodeInsightFixture(projectFixture.fixture) + codeInsightFixture.setUp() + codeInsightFixture.testDataPath = testDataPath + + return codeInsightFixture + } + + fun addBreakpoint() { + runInEdtAndWait { + val document = fixture.editor.document + val psiFile = fixture.file as JSFile + val lineNumber = document.getLineNumber(psiFile.statements.first().textOffset) + + XDebuggerUtil.getInstance().toggleLineBreakpoint( + project, + fixture.file.virtualFile, + lineNumber + ) + } + } +} + +fun Project.setNodeJsInterpreterVersion(version: SemVer) { + NodeJsInterpreterManager.getInstance(this).setInterpreterRef( + NodeJsInterpreterRef.create(MockNodeJsInterpreter(version)) + ) +} + +fun Project.setJsLanguageLevel(languageLevel: JSLanguageLevel) { + JSRootConfiguration.getInstance(this) + .storeLanguageLevelAndUpdateCaches(languageLevel) +} + +fun CodeInsightTestFixture.addLambdaHandler( + subPath: String = ".", + fileName: String = "app", + handlerName: String = "lambdaHandler", + @Language("JS") fileContent: String = + """ + exports.$handlerName = function (event, context, callback) { + return 'HelloWorld' + }; + """.trimIndent(), +): PsiElement { + val psiFile = this.addFileToProject("$subPath/$fileName.js", fileContent) as JSFile + + return runInEdtAndGet { + psiFile.findElementAt(fileContent.indexOf(handlerName))!! + } +} + +fun CodeInsightTestFixture.addTypeScriptLambdaHandler( + subPath: String = ".", + fileName: String = "app", + handlerName: String = "lambdaHandler", + @Language("TS") fileContent: String = + """ + export const $handlerName = (event: APIGatewayProxyEvent, context: Context, callback: Callback): APIGatewayProxyResult => { + return { statusCode: 200 } + } + """.trimIndent(), +): PsiElement { + val psiFile = this.addFileToProject("$subPath/$fileName.ts", fileContent) as JSFile + + return runInEdtAndGet { + psiFile.findElementAt(fileContent.indexOf(handlerName))!! + } +} + +fun CodeInsightTestFixture.addPackageJsonFile( + subPath: String = ".", + @Language("JSON") content: String = + """ + { + "name": "hello-world", + "version": "1.0.0" + } + """.trimIndent(), +): PsiFile = this.addFileToProject("$subPath/package.json", content) + +fun CodeInsightTestFixture.addTypeScriptPackageJsonFile( + subPath: String = ".", + @Language("JSON") content: String = + """ + { + "name": "hello-world", + "version": "1.0.0", + "devDependencies": { + "typescript": "*" + } + } + """.trimIndent(), +): PsiFile = this.addPackageJsonFile(subPath, content) diff --git a/plugins/core-q/webview/package-lock.json b/plugins/core-q/webview/package-lock.json index d0fd3258687..5d32af8fda8 100644 --- a/plugins/core-q/webview/package-lock.json +++ b/plugins/core-q/webview/package-lock.json @@ -304,8 +304,7 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "peer": true + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" }, "node_modules/@types/sanitize-html": { "version": "2.11.0", @@ -361,7 +360,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -810,7 +808,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -849,7 +846,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1032,7 +1028,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -1527,7 +1522,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2789,7 +2783,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3090,7 +3083,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, - "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3165,7 +3157,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3579,7 +3570,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3658,7 +3648,6 @@ "version": "3.4.20", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.4.20", "@vue/compiler-sfc": "3.4.20", @@ -3747,7 +3736,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3794,7 +3782,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt index 1154797ef58..b44e8637ef0 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt @@ -484,7 +484,7 @@ class AwsResourceCacheTest { whenever(mockResource.fetch(any())).then { latch.await() // exception gets thrown fast enough where the second fetchIfNeeded check occurs after the first call throws - runTest { + runTest { delay(500) } throw RuntimeException("Boom") diff --git a/plugins/core/webview/package-lock.json b/plugins/core/webview/package-lock.json index d0fd3258687..5d32af8fda8 100644 --- a/plugins/core/webview/package-lock.json +++ b/plugins/core/webview/package-lock.json @@ -304,8 +304,7 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "peer": true + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" }, "node_modules/@types/sanitize-html": { "version": "2.11.0", @@ -361,7 +360,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -810,7 +808,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -849,7 +846,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1032,7 +1028,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -1527,7 +1522,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2789,7 +2783,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3090,7 +3083,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, - "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3165,7 +3157,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3579,7 +3570,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3658,7 +3648,6 @@ "version": "3.4.20", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.4.20", "@vue/compiler-sfc": "3.4.20", @@ -3747,7 +3736,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3794,7 +3782,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", From 63747b56f3f59f7a0111a6eb8a2f8fe67d3e955b Mon Sep 17 00:00:00 2001 From: Sherry Lu Date: Tue, 6 Jan 2026 10:38:52 -0800 Subject: [PATCH 07/60] register jetbrians ultimate endpoint in core-q and disable core-q tests --- .../amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts | 1 + plugins/core-q/jetbrains-community/build.gradle.kts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts b/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts index 263483a63ee..cb295c41ec3 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts +++ b/plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { testCompileOnly(project(":plugin-core-q:jetbrains-ultimate")) testImplementation(testFixtures(project(":plugin-amazonq:codewhisperer:jetbrains-community"))) + testImplementation(testFixtures(project(":plugin-core-q:jetbrains-ultimate"))) testImplementation(project(path = ":plugin-toolkit:jetbrains-ultimate", configuration = "testArtifacts")) } diff --git a/plugins/core-q/jetbrains-community/build.gradle.kts b/plugins/core-q/jetbrains-community/build.gradle.kts index b217b48e048..fc7338e8633 100644 --- a/plugins/core-q/jetbrains-community/build.gradle.kts +++ b/plugins/core-q/jetbrains-community/build.gradle.kts @@ -135,3 +135,7 @@ tasks.prepareTestSandbox { into(intellijPlatform.projectName.map { "$it/lib" }) } } + +tasks.test { + enabled = false +} From 24618e78435c9e06e788085bc2d9dda25fe3f289 Mon Sep 17 00:00:00 2001 From: Sherry Lu Date: Wed, 7 Jan 2026 11:50:09 -0800 Subject: [PATCH 08/60] fix linter errors and delete unused tests in AwsToolkitExplorerToolWindowTest --- .../CodeWhispererEditorUtilTest.kt | 2 +- .../CodeWhispererModelConfiguratorTest.kt | 2 +- .../CodeWhispererReferenceManagerTest.kt | 2 +- .../codewhisperer/CodeWhispererTestBase.kt | 2 +- .../codescan/CodeWhispererCodeFileScanTest.kt | 2 +- .../codescan/CodeWhispererCodeScanTest.kt | 2 +- .../CodeWhispererProjectCodeScanTest.kt | 2 +- .../aws/toolkits/core/s3/BucketUtilsTest.kt | 2 +- .../AwsCredentialsExtensionsTest.kt | 2 -- .../amazon/q/core/credentials/Mocks.kt | 4 ---- .../core/credentials/SsoUrlIdentifierTest.kt | 1 - .../lambda/LambdaSampleEventProviderTest.kt | 3 --- .../amazon/q/core/region/AwsRegionTest.kt | 2 -- .../q/core/region/PartitionParserTest.kt | 1 - .../q/core/telemetry/TelemetryBatcherTest.kt | 4 ---- .../q/core/utils/CollectionUtilsTest.kt | 1 - .../amazon/q/core/utils/EitherTest.kt | 2 -- .../amazon/q/core/utils/ExceptionUtilsTest.kt | 1 - .../amazon/q/core/utils/LogUtilsTest.kt | 10 --------- .../core/utils/RemoteResourceResolverTest.kt | 8 ------- .../amazon/q/core/utils/ZipUtilsTest.kt | 2 -- .../jetbrains-community/build.gradle.kts | 1 - .../services/telemetry/otel/OTelService.kt | 2 +- .../q/jetbrains/core/AwsResourceCache.kt | 2 +- .../core/credentials/CredentialManager.kt | 4 ++-- .../q/jetbrains/telemetry/TelemetryService.kt | 12 +++++----- .../q/jetbrains/core/AwsClientManager.kt | 12 +++++----- .../q/jetbrains/core/AwsResourceCache.kt | 12 +++++----- .../DefaultRemoteResourceResolverProvider.kt | 2 +- .../core/credentials/AwsConnectionManager.kt | 12 +++++----- .../core/credentials/ConfigFilesFacade.kt | 6 ++--- .../CreateOrUpdateCredentialProfilesAction.kt | 2 +- .../core/credentials/CredentialManager.kt | 2 +- .../credentials/CredentialsRegionHandler.kt | 4 ++-- .../DefaultAwsConnectionManager.kt | 2 +- .../credentials/DefaultToolkitAuthManager.kt | 6 ++--- .../jetbrains/core/credentials/LoginUtils.kt | 10 ++++----- .../core/credentials/ToolkitAuthManager.kt | 16 +++++++------- .../credentials/ToolkitConnectionImpls.kt | 4 ++-- .../pinning/CodeCatalystConnection.kt | 4 ++-- .../pinning/ConnectionPinningManager.kt | 4 ++-- .../profiles/ProfileAssumeRoleProvider.kt | 2 +- .../ProfileCredentialProviderFactory.kt | 22 +++++++++---------- .../credentials/profiles/ProfileWatcher.kt | 2 +- .../core/credentials/sso/DiskCache.kt | 2 +- .../credentials/sso/SsoAccessTokenProvider.kt | 6 ++--- .../sso/bearer/BearerTokenProvider.kt | 12 +++++----- .../sso/bearer/ConfirmUserCodeLoginDialog.kt | 2 +- .../gettingstarted/GettingStartedAuthUtils.kt | 2 +- .../core/gettingstarted/IdcRolePopup.kt | 4 ++-- .../SetupAuthenticationDialog.kt | 8 +++---- .../editor/GettingStartedPanelUtils.kt | 4 ++-- .../editor/GettingStartedTelemetryUtils.kt | 4 ++-- .../NotificationPollingService.kt | 4 ++-- .../notifications/ProcessNotificationsBase.kt | 4 ++-- .../core/plugin/PluginUpdateManager.kt | 6 ++--- .../core/region/AwsRegionProvider.kt | 2 +- .../q/jetbrains/core/webview/LoginBrowser.kt | 10 ++++----- .../telemetry/DefaultTelemetryPublisher.kt | 6 ++--- .../services/telemetry/OpenTelemetryAction.kt | 2 +- .../services/telemetry/otel/OtelBase.kt | 4 ++-- .../amazon/q/jetbrains/utils/FunctionUtils.kt | 2 +- .../q/jetbrains/utils/NotificationUtils.kt | 2 +- .../q/jetbrains/utils/RemoteEnvUtils.kt | 2 +- .../q/jetbrains/core/LoginBrowserTest.kt | 2 +- .../DefaultToolkitAuthManagerTest.kt | 10 ++++----- .../core/gettingstarted/IdcRolePopupTest.kt | 4 ++-- .../SetupAuthenticationDialogTest.kt | 8 +++---- .../q}/jetbrains/core/BrowserMessageTest.kt | 0 .../q}/jetbrains/core/LoginBrowserTest.kt | 0 .../DefaultToolkitAuthManagerTest.kt | 0 .../core/gettingstarted/IdcRolePopupTest.kt | 0 .../SetupAuthenticationDialogTest.kt | 0 .../q/jetbrains/core/AwsClientManagerTest.kt | 10 ++++----- .../q/jetbrains/core/AwsResourceCacheTest.kt | 6 ++--- .../core/credentials/CredentialManagerTest.kt | 8 +++---- .../CredentialsRegionHandlerTest.kt | 8 +++---- .../DefaultAwsConnectionManagerTest.kt | 12 +++++----- .../DefaultConfigFilesFacadeTest.kt | 2 +- .../DefaultToolkitConnectionManagerTest.kt | 2 +- .../RefreshConnectionActionTest.kt | 4 ++-- .../profiles/Ec2MetadataConfigProviderTest.kt | 2 +- .../profiles/ProfileAssumeRoleProviderTest.kt | 2 +- .../ProfileCredentialProviderFactoryTest.kt | 16 +++++++------- .../credentials/profiles/ProfileReaderTest.kt | 2 +- .../profiles/ProfileWatcherTest.kt | 2 +- .../sso/SsoAccessTokenProviderTest.kt | 4 ++-- .../sso/bearer/BearerTokenTestUtil.kt | 2 +- .../InteractiveBearerTokenProviderTest.kt | 4 ++-- .../ProfileSdkTokenProviderWrapperTest.kt | 2 +- .../NotificationFormatUtilsTest.kt | 2 +- .../NotificationPollingServiceTest.kt | 2 +- .../core/plugin/PluginUpdateManagerTest.kt | 2 +- .../telemetry/OpenedFileTypeMetricsTest.kt | 1 - .../services/telemetry/PluginResolverTest.kt | 1 - .../telemetry/TelemetryServiceTest.kt | 14 ++++++------ .../services/telemetry/TelemetryUtilsTest.kt | 1 - .../services/telemetry/otel/OtelBaseTest.kt | 6 ++--- .../ToolkitTelemetryOTelSpanProcessorTest.kt | 2 +- .../q/jetbrains/settings/AwsSettingsTest.kt | 2 +- .../q/jetbrains/ui/AsyncComboBoxTest.kt | 1 - .../amazon/q/jetbrains/utils/MRUListTest.kt | 1 - .../amazon/q/jetbrains/utils/TextUtilsTest.kt | 4 ---- .../q/jetbrains/core/MockClientManager.kt | 2 +- .../q/jetbrains/core/MockResourceCache.kt | 2 +- .../credentials/MockAwsConnectionManager.kt | 4 ++-- .../credentials/MockCredentialsManager.kt | 2 +- .../q/jetbrains/settings/MockAwsSettings.kt | 1 - plugins/core-q/webview/package-lock.json | 15 ++++++++++++- .../SetupAuthenticationDialogTest.kt | 4 ---- .../jetbrains/core/AwsClientManagerTest.kt | 3 --- .../jetbrains/core/AwsResourceCacheTest.kt | 2 +- .../jetbrains/core/coroutines/ScopeTest.kt | 3 --- .../core/credentials/CredentialManagerTest.kt | 4 ---- .../ProfileCredentialsIdentifierSsoTest.kt | 5 ----- .../core/credentials/sso/DiskCacheTest.kt | 6 +---- .../services/telemetry/otel/OtelBaseTest.kt | 4 ---- .../ToolkitTelemetryOTelSpanProcessorTest.kt | 2 +- .../jetbrains/settings/AwsSettingsTest.kt | 1 - .../jetbrains/core/CoreTestHelper.kt.kt | 2 +- plugins/core/webview/package-lock.json | 15 ++++++++++++- .../AwsToolkitExplorerToolWindowTest.kt | 13 ----------- 122 files changed, 232 insertions(+), 299 deletions(-) rename plugins/core-q/jetbrains-community/tst-253+/software/{aws/toolkits => amazon/q}/jetbrains/core/BrowserMessageTest.kt (100%) rename plugins/core-q/jetbrains-community/tst-253+/software/{aws/toolkits => amazon/q}/jetbrains/core/LoginBrowserTest.kt (100%) rename plugins/core-q/jetbrains-community/tst-253+/software/{aws/toolkits => amazon/q}/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt (100%) rename plugins/core-q/jetbrains-community/tst-253+/software/{aws/toolkits => amazon/q}/jetbrains/core/gettingstarted/IdcRolePopupTest.kt (100%) rename plugins/core-q/jetbrains-community/tst-253+/software/{aws/toolkits => amazon/q}/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt (100%) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt index fd64e77fe4b..0b7c89e2709 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt @@ -13,12 +13,12 @@ import org.junit.Rule import org.junit.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.leftContext_success_Iac import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule class CodeWhispererEditorUtilTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt index 358c78f8737..317e58f01d7 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt @@ -10,7 +10,6 @@ import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.registerServiceInstance import com.intellij.testFramework.replaceService import com.intellij.util.xmlb.XmlSerializer -import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import org.assertj.core.api.Assertions.assertThat import org.jdom.output.XMLOutputter import org.junit.Before @@ -50,6 +49,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSe import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomizationState +import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.customization.DefaultCodeWhispererModelConfigurator import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt index df4c28892db..2053aae15f9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt @@ -11,8 +11,8 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test -import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule +import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager class CodeWhispererReferenceManagerTest { @Rule diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index 705adf33f4d..6efd06772d9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -47,6 +47,7 @@ import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQServerInstanceFacade @@ -81,7 +82,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.settings.CodeWhispererConfigurationType import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.resources.message import java.util.concurrent.CompletableFuture import java.util.concurrent.Future diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt index db7cefcb04c..4928260345a 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt @@ -29,12 +29,12 @@ import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUr import software.amazon.q.core.utils.WaiterTimeoutException import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.FileInputStream import java.lang.management.ManagementFactory diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt index 1d00cf7957f..25f7e22b71e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt @@ -26,13 +26,13 @@ import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUr import software.amazon.q.core.utils.WaiterTimeoutException import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.File import java.io.FileInputStream diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt index 73082283687..1b7c0461e7e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt @@ -12,12 +12,12 @@ import org.junit.jupiter.api.assertThrows import org.mockito.kotlin.any import org.mockito.kotlin.spy import org.mockito.kotlin.stub +import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.rules.addFileToModule import software.amazon.q.jetbrains.utils.rules.addModule import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getNormalizedRelativePath -import software.amazon.q.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import java.io.BufferedInputStream import java.nio.file.Paths diff --git a/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt b/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt index cfa827c4b9f..e465c8b3066 100644 --- a/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt +++ b/plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt @@ -11,9 +11,9 @@ import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.BucketVersioningStatus import software.amazon.awssdk.services.s3.model.PutObjectRequest +import software.amazon.q.core.rules.S3TemporaryBucketRule import software.amazon.q.core.s3.deleteBucketAndContents import software.amazon.q.core.s3.regionForBucket -import software.amazon.q.core.rules.S3TemporaryBucketRule class BucketUtilsTest { private val usEast1Client = S3Client.builder().region(Region.US_EAST_1).build() diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt index 1c49e4273cc..d9caaecdd40 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt @@ -7,8 +7,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Test import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.AwsSessionCredentials -import software.amazon.q.core.credentials.mergeWithExistingEnvironmentVariables -import software.amazon.q.core.credentials.toEnvironmentVariables import software.amazon.q.core.utils.test.aString class AwsCredentialsExtensionsTest { diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt index aa3513d96ab..aeeec611521 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt @@ -5,10 +5,6 @@ package software.amazon.q.core.credentials import org.mockito.kotlin.mock import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.credentials.CredentialIdentifierBase -import software.amazon.q.core.credentials.CredentialType -import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.utils.test.aString fun aToolkitCredentialsProvider( diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt index f02b16c115b..03cb07a4c8c 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt @@ -10,7 +10,6 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.ArgumentsProvider import org.junit.jupiter.params.provider.ArgumentsSource -import software.amazon.q.core.credentials.validatedSsoIdentifierFromUrl import java.util.stream.Stream class SsoUrlIdentifierTest { diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt index 7f98d720fba..2473c8857ee 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt @@ -10,9 +10,6 @@ import org.junit.rules.TemporaryFolder import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify -import software.amazon.q.core.lambda.LambdaSampleEventManifestResource -import software.amazon.q.core.lambda.LambdaSampleEventProvider -import software.amazon.q.core.lambda.LambdaSampleEventResource import software.amazon.q.core.utils.RemoteResourceResolver import java.util.concurrent.CompletableFuture diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt index 2d8e7633768..561abd4351d 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt @@ -8,8 +8,6 @@ import org.junit.Test import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import org.junit.runners.Parameterized -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.region.mergeWithExistingEnvironmentVariables import software.amazon.q.core.utils.test.aString import kotlin.random.Random diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt index 38eece70fea..8e2438d575e 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt @@ -5,7 +5,6 @@ package software.amazon.q.core.region import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.amazon.q.core.region.PartitionParser import software.aws.toolkits.resources.BundledResources class PartitionParserTest { diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt index 363ec48db19..91dd50c64fa 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt @@ -18,10 +18,6 @@ import org.mockito.kotlin.verifyBlocking import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.stubbing.Answer import software.amazon.awssdk.core.exception.SdkServiceException -import software.amazon.q.core.telemetry.DefaultMetricEvent -import software.amazon.q.core.telemetry.DefaultTelemetryBatcher -import software.amazon.q.core.telemetry.MetricEvent -import software.amazon.q.core.telemetry.TelemetryPublisher import java.time.Instant import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt index fe122d53b2d..3325c38ae35 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt @@ -5,7 +5,6 @@ package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.amazon.q.core.utils.replace class CollectionUtilsTest { diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt index 79fc46e3f94..fddd3e1c6dd 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt @@ -6,8 +6,6 @@ package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test -import software.amazon.q.core.utils.Either -import software.amazon.q.core.utils.xor class EitherTest { @Test diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt index 3b55969b126..9d2e6de0814 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt @@ -5,7 +5,6 @@ package software.amazon.q.core.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import software.amazon.q.core.utils.tryOrNull class ExceptionUtilsTest { @Test diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt index 65f7c9e4ade..5232170dcf6 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt @@ -17,16 +17,6 @@ import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.slf4j.Logger import org.slf4j.event.Level -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.info -import software.amazon.q.core.utils.log -import software.amazon.q.core.utils.logWhenNull -import software.amazon.q.core.utils.trace -import software.amazon.q.core.utils.tryOrNull -import software.amazon.q.core.utils.tryOrThrow -import software.amazon.q.core.utils.tryOrThrowNullable -import software.amazon.q.core.utils.warn class LogUtilsTest { diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt index 1e9acdfc504..aab23494fd8 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt @@ -15,14 +15,6 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify import software.amazon.q.core.lambda.LambdaManifestValidator -import software.amazon.q.core.utils.DefaultRemoteResourceResolver -import software.amazon.q.core.utils.RemoteResolveParser -import software.amazon.q.core.utils.RemoteResource -import software.amazon.q.core.utils.UrlFetcher -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.exists -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.writeText import java.io.InputStream import java.nio.file.Files import java.nio.file.Path diff --git a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt index 74108ca4ed4..8a62f0cbdcf 100644 --- a/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt +++ b/plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt @@ -9,8 +9,6 @@ import org.junit.After import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import software.amazon.q.core.utils.createTemporaryZipFile -import software.amazon.q.core.utils.putNextEntry import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path diff --git a/plugins/core-q/jetbrains-community/build.gradle.kts b/plugins/core-q/jetbrains-community/build.gradle.kts index fc7338e8633..8e6a3a38571 100644 --- a/plugins/core-q/jetbrains-community/build.gradle.kts +++ b/plugins/core-q/jetbrains-community/build.gradle.kts @@ -75,7 +75,6 @@ val replaceInGeneratedSources = tasks.register("replaceInGeneratedS } } - tasks.compileKotlin { dependsOn(replaceInGeneratedSources) } diff --git a/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt b/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt index 698e6e43b95..e5a8fb77965 100644 --- a/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt +++ b/plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 @file:Suppress("UnusedPrivateClass") -package software.aws.toolkits.jetbrains.services.telemetry.otel +package software.amazon.q.jetbrains.services.telemetry.otel import com.intellij.openapi.Disposable import com.intellij.openapi.components.Service diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt index 6704f34867e..c52e967afa1 100644 --- a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt @@ -4,13 +4,13 @@ package migration.software.amazon.q.jetbrains.core import com.intellij.openapi.components.service -import software.amazon.q.jetbrains.core.Resource import software.amazon.q.core.ClientConnectionSettings import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.TokenConnectionSettings import software.amazon.q.core.credentials.ToolkitBearerTokenProvider import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.core.Resource import java.time.Duration import java.util.concurrent.CompletionStage import java.util.concurrent.ExecutionException diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt index 0fdc4c0adca..532e736626d 100644 --- a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt @@ -9,8 +9,6 @@ import com.intellij.openapi.util.SimpleModificationTracker import com.intellij.util.messages.Topic import software.amazon.awssdk.auth.credentials.AwsCredentials import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider -import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded -import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.CredentialProviderFactory import software.amazon.q.core.credentials.CredentialProviderNotFoundException @@ -19,6 +17,8 @@ import software.amazon.q.core.credentials.SsoSessionIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded +import software.amazon.q.resources.AwsCoreBundle import java.util.concurrent.ConcurrentHashMap abstract class CredentialManager : SimpleModificationTracker() { diff --git a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt index e00ce64860b..41fc2f5e7b9 100644 --- a/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt +++ b/plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt @@ -7,6 +7,12 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.telemetry.DefaultMetricEvent +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.core.credentials.getConnectionSettings import software.amazon.q.jetbrains.core.getResourceIfPresent import software.amazon.q.jetbrains.services.sts.StsResources @@ -14,12 +20,6 @@ import software.amazon.q.jetbrains.services.telemetry.MetricEventMetadata import software.amazon.q.jetbrains.services.telemetry.PluginResolver import software.amazon.q.jetbrains.services.telemetry.TelemetryListener import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.core.ConnectionSettings -import software.amazon.q.core.telemetry.DefaultMetricEvent -import software.amazon.q.core.telemetry.MetricEvent -import software.amazon.q.core.telemetry.TelemetryBatcher -import software.amazon.q.core.telemetry.TelemetryPublisher -import software.amazon.q.core.utils.tryOrNull import java.util.concurrent.atomic.AtomicBoolean abstract class TelemetryService(private val publisher: TelemetryPublisher, protected val batcher: TelemetryBatcher) : Disposable { diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt index 912257e0926..2037f0f52ee 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt @@ -16,12 +16,6 @@ import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder import software.amazon.awssdk.core.SdkClient import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration import software.amazon.awssdk.http.SdkHttpClient -import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager -import software.amazon.q.jetbrains.core.credentials.CredentialManager -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.jetbrains.services.telemetry.PluginResolver -import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.core.ClientConnectionSettings import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.TokenConnectionSettings @@ -31,6 +25,12 @@ import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener import software.amazon.q.core.region.ToolkitRegionProvider import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.services.telemetry.PluginResolver +import software.amazon.q.jetbrains.settings.AwsSettings open class AwsClientManager : ToolkitClientManager(), Disposable { init { diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt index 5d55b348a2f..b4e7a372159 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt @@ -15,12 +15,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.jetbrains.annotations.VisibleForTesting import software.amazon.awssdk.core.SdkClient -import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope -import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager -import software.amazon.q.jetbrains.core.credentials.CredentialManager -import software.amazon.q.jetbrains.core.credentials.getConnectionSettingsOrThrow -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.amazon.q.core.ClientConnectionSettings import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.TokenConnectionSettings @@ -31,6 +25,12 @@ import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.getConnectionSettingsOrThrow +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import java.time.Clock import java.time.Duration import java.time.Instant diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt index 0342ee07cca..d0cc04cf7d2 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt @@ -5,9 +5,9 @@ package software.amazon.q.jetbrains.core import com.intellij.openapi.application.PathManager import com.intellij.util.io.createDirectories -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.amazon.q.core.utils.DefaultRemoteResourceResolver import software.amazon.q.core.utils.UrlFetcher +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.CompletableFuture diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt index 0e1629f7e87..5ee81a3680d 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt @@ -15,12 +15,6 @@ import com.intellij.openapi.util.SimpleModificationTracker import com.intellij.util.ExceptionUtil import com.intellij.util.messages.Topic import org.jetbrains.concurrency.AsyncPromise -import software.amazon.q.jetbrains.core.AwsResourceCache -import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.jetbrains.services.sts.StsResources -import software.amazon.q.jetbrains.utils.MRUList -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread -import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.CredentialProviderNotFoundException @@ -29,6 +23,12 @@ import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.AwsResourceCache +import software.amazon.q.jetbrains.core.region.AwsRegionProvider +import software.amazon.q.jetbrains.services.sts.StsResources +import software.amazon.q.jetbrains.utils.MRUList +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread +import software.amazon.q.resources.AwsCoreBundle import software.aws.toolkits.telemetry.AwsTelemetry import java.util.concurrent.atomic.AtomicReference diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt index a8afa4358fe..24dfa3f2add 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt @@ -8,9 +8,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager import software.amazon.awssdk.profiles.Profile import software.amazon.awssdk.profiles.ProfileFile import software.amazon.awssdk.profiles.ProfileFileLocation -import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher -import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants -import software.amazon.q.jetbrains.core.credentials.profiles.ssoSessions import software.amazon.q.core.utils.appendText import software.amazon.q.core.utils.createParentDirectories import software.amazon.q.core.utils.exists @@ -20,6 +17,9 @@ import software.amazon.q.core.utils.touch import software.amazon.q.core.utils.tryDirOp import software.amazon.q.core.utils.tryFileOp import software.amazon.q.core.utils.writeText +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher +import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants +import software.amazon.q.jetbrains.core.credentials.profiles.ssoSessions import java.nio.file.Path interface ConfigFilesFacade { diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt index cea895ac550..3719d0499b6 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt @@ -19,8 +19,8 @@ import com.intellij.openapi.vfs.LocalFileSystem import icons.AwsIcons import org.jetbrains.annotations.TestOnly import software.amazon.awssdk.profiles.ProfileFileLocation -import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.utils.exists +import software.amazon.q.resources.AwsCoreBundle import software.aws.toolkits.telemetry.AwsTelemetry import java.nio.file.Path diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt index 9f97f03b85f..def8aa0da3f 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt @@ -6,10 +6,10 @@ package software.amazon.q.jetbrains.core.credentials import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.extensions.ExtensionPointName -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.amazon.q.core.credentials.CredentialProviderFactory import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.telemetry.AwsTelemetry import java.util.concurrent.atomic.AtomicInteger diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt index 0d3b085b9dc..90ffb700c22 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt @@ -6,13 +6,13 @@ package software.amazon.q.jetbrains.core.credentials import com.intellij.notification.NotificationAction import com.intellij.openapi.components.service import com.intellij.openapi.project.Project +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.region.AwsRegion import software.amazon.q.jetbrains.core.region.AwsRegionProvider import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion import software.amazon.q.jetbrains.utils.notifyInfo import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.region.AwsRegion /** * Encapsulates logic for handling of regions when a new credential identifier is selected diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt index 5bcf7b4283f..b25ebe7f5a2 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt @@ -8,10 +8,10 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.project.Project import kotlinx.coroutines.launch +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope import software.amazon.q.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.core.utils.tryOrNull data class ConnectionSettingsState( var activeProfile: String? = null, diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt index 53e493f1319..df67db4cd92 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt @@ -9,15 +9,15 @@ import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.util.Disposer -import software.amazon.q.jetbrains.core.credentials.profiles.ProfileSsoSessionIdentifier -import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.amazon.q.core.credentials.SsoSessionIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsChangeListener import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.credentials.profiles.ProfileSsoSessionIdentifier +import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL +import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import java.util.Collections typealias ToolkitAuthManager = migration.software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt index b30d8206ad0..af7dcaadbb5 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt @@ -16,6 +16,11 @@ import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException import software.amazon.awssdk.services.ssooidc.model.InvalidRequestException import software.amazon.awssdk.services.ssooidc.model.SsoOidcException import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.core.credentials.validatedSsoIdentifierFromUrl +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.AwsClientManager import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION @@ -25,11 +30,6 @@ import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearer import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.credentials.validatedSsoIdentifierFromUrl -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn import software.aws.toolkits.telemetry.CredentialSourceId import java.io.IOException diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt index ec68c9f1f60..ffaab075e7d 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt @@ -11,6 +11,14 @@ import com.intellij.openapi.project.Project import migration.software.amazon.q.jetbrains.telemetry.TelemetryService import software.amazon.awssdk.services.ssooidc.model.SsoOidcException import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit +import software.amazon.q.core.ClientConnectionSettings +import software.amazon.q.core.ConnectionSettings +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection import software.amazon.q.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifierSso import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants.SSO_SESSION_SECTION_NAME @@ -20,14 +28,6 @@ import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvid import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.ClientConnectionSettings -import software.amazon.q.core.ConnectionSettings -import software.amazon.q.core.TokenConnectionSettings -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.credentials.ToolkitBearerTokenProvider -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.info -import software.amazon.q.core.utils.warn import software.aws.toolkits.telemetry.AuthTelemetry import software.aws.toolkits.telemetry.CredentialSourceId import software.aws.toolkits.telemetry.CredentialType diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt index 740de635fd2..6eda00ba9be 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt @@ -6,13 +6,13 @@ package software.amazon.q.jetbrains.core.credentials import com.fasterxml.jackson.annotation.JsonIgnore import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider import software.amazon.q.jetbrains.core.credentials.sso.DiskCache import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider import software.amazon.q.jetbrains.core.credentials.sso.bearer.ProfileSdkTokenProviderWrapper import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.core.TokenConnectionSettings -import software.amazon.q.core.credentials.ToolkitBearerTokenProvider /** * An SSO bearer connection created through a `sso-session` declaration in a user's ~/.aws/config diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt index 80dae1dee87..56c765975f6 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt @@ -3,11 +3,11 @@ package software.amazon.q.jetbrains.core.credentials.pinning +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.sono.CODECATALYST_SCOPES -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger class CodeCatalystConnection : FeatureWithPinnedConnection { override val featureId: String = "aws.codecatalyst" diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt index 67d8862c680..e0f7d7ceb47 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt @@ -9,11 +9,11 @@ import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.extensions.ExtensionPointName +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager import software.amazon.q.jetbrains.core.credentials.ToolkitConnection import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger import java.util.concurrent.ConcurrentHashMap typealias ConnectionPinningManager = migration.software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt index f2241c69f6e..ed0898b9d1a 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt @@ -13,9 +13,9 @@ import software.amazon.awssdk.services.sts.StsClient import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider import software.amazon.awssdk.services.sts.model.AssumeRoleRequest import software.amazon.awssdk.utils.SdkAutoCloseable +import software.amazon.q.core.region.AwsRegion import software.amazon.q.jetbrains.core.AwsClientManager import software.amazon.q.jetbrains.core.credentials.promptForMfaToken -import software.amazon.q.core.region.AwsRegion import java.util.function.Supplier class ProfileAssumeRoleProvider(@get:TestOnly internal val parentProvider: AwsCredentialsProvider, region: AwsRegion, profile: Profile) : diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt index 735c98bab78..a4c0400aba5 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt @@ -24,6 +24,17 @@ import software.amazon.awssdk.profiles.Profile import software.amazon.awssdk.profiles.ProfileProperty import software.amazon.awssdk.services.sso.model.UnauthorizedException import software.amazon.awssdk.services.ssooidc.model.SsoOidcException +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialIdentifierBase +import software.amazon.q.core.credentials.CredentialProviderFactory +import software.amazon.q.core.credentials.CredentialSourceId +import software.amazon.q.core.credentials.CredentialType +import software.amazon.q.core.credentials.CredentialsChangeEvent +import software.amazon.q.core.credentials.CredentialsChangeListener +import software.amazon.q.core.credentials.SsoSessionBackedCredentialIdentifier +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.credentials.ChangeConnectionSettingIfValid import software.amazon.q.jetbrains.core.credentials.ConnectionState import software.amazon.q.jetbrains.core.credentials.CredentialManager @@ -50,17 +61,6 @@ import software.amazon.q.jetbrains.utils.createShowMoreInfoDialogAction import software.amazon.q.jetbrains.utils.notifyError import software.amazon.q.jetbrains.utils.notifyInfo import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.credentials.CredentialIdentifierBase -import software.amazon.q.core.credentials.CredentialProviderFactory -import software.amazon.q.core.credentials.CredentialSourceId -import software.amazon.q.core.credentials.CredentialType -import software.amazon.q.core.credentials.CredentialsChangeEvent -import software.amazon.q.core.credentials.CredentialsChangeListener -import software.amazon.q.core.credentials.SsoSessionBackedCredentialIdentifier -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn const val DEFAULT_PROFILE_NAME = "default" diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt index 64b55147eaf..81d2d568028 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt @@ -12,10 +12,10 @@ import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.openapi.vfs.pointers.VirtualFilePointer import com.intellij.util.containers.ContainerUtil import software.amazon.awssdk.profiles.ProfileFileLocation -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.amazon.q.core.utils.debug import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import java.nio.file.Paths typealias ProfileWatcher = migration.software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt index 5af117bc456..256ce3bc70c 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.util.StdDateFormat import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import software.amazon.q.jetbrains.services.telemetry.scrubNames import software.amazon.q.core.utils.createParentDirectories import software.amazon.q.core.utils.deleteIfExists import software.amazon.q.core.utils.getLogger @@ -33,6 +32,7 @@ import software.amazon.q.core.utils.touch import software.amazon.q.core.utils.tryDirOp import software.amazon.q.core.utils.tryFileOp import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.services.telemetry.scrubNames import software.aws.toolkits.telemetry.AuthTelemetry import software.aws.toolkits.telemetry.Result import java.io.InputStream diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt index 4cc16623f66..cf1084042b5 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt @@ -18,6 +18,9 @@ import software.amazon.awssdk.services.ssooidc.model.CreateTokenResponse import software.amazon.awssdk.services.ssooidc.model.InvalidClientException import software.amazon.awssdk.services.ssooidc.model.InvalidRequestException import software.amazon.awssdk.services.ssooidc.model.SlowDownException +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.amazon.q.jetbrains.core.credentials.sso.pkce.PKCE_CLIENT_NAME import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService @@ -26,9 +29,6 @@ import software.amazon.q.jetbrains.services.telemetry.scrubNames import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread import software.amazon.q.jetbrains.utils.sleepWithCancellation import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.info -import software.amazon.q.core.utils.warn import software.aws.toolkits.telemetry.AuthTelemetry import software.aws.toolkits.telemetry.AuthType import software.aws.toolkits.telemetry.AwsTelemetry diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt index ed013bcec9d..fc6460f0ed1 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt @@ -23,6 +23,12 @@ import software.amazon.awssdk.utils.SdkAutoCloseable import software.amazon.awssdk.utils.cache.CachedSupplier import software.amazon.awssdk.utils.cache.NonBlocking import software.amazon.awssdk.utils.cache.RefreshResult +import software.amazon.q.core.ToolkitClientCustomizer +import software.amazon.q.core.clients.nullDefaultProfileFile +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.credentials.ToolkitBearerTokenProviderDelegate +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.AwsClientManager import software.amazon.q.jetbrains.core.credentials.diskCache import software.amazon.q.jetbrains.core.credentials.sso.AccessToken @@ -31,12 +37,6 @@ import software.amazon.q.jetbrains.core.credentials.sso.DiskCache import software.amazon.q.jetbrains.core.credentials.sso.PendingAuthorization import software.amazon.q.jetbrains.core.credentials.sso.SsoAccessTokenProvider import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener.Companion.TOPIC -import software.amazon.q.core.ToolkitClientCustomizer -import software.amazon.q.core.clients.nullDefaultProfileFile -import software.amazon.q.core.credentials.ToolkitBearerTokenProvider -import software.amazon.q.core.credentials.ToolkitBearerTokenProviderDelegate -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn import java.time.Clock import java.time.Duration import java.util.concurrent.atomic.AtomicBoolean diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt index 5e25dc7308a..c129086ae36 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt @@ -17,8 +17,8 @@ import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBFont import com.intellij.util.ui.components.BorderLayoutPanel -import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.resources.AwsCoreBundle import java.awt.datatransfer.StringSelection import javax.swing.JComponent diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt index 52b16f700c5..fd434573835 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt @@ -4,6 +4,7 @@ package software.amazon.q.jetbrains.core.gettingstarted import com.intellij.openapi.project.Project +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection import software.amazon.q.jetbrains.core.credentials.ManagedBearerSsoConnection import software.amazon.q.jetbrains.core.credentials.ProfileSsoManagedBearerSsoConnection @@ -23,7 +24,6 @@ import software.amazon.q.jetbrains.core.gettingstarted.editor.getStartupState import software.amazon.q.jetbrains.core.region.AwsRegionProvider import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.utils.tryOrNull import software.aws.toolkits.telemetry.AuthTelemetry import software.aws.toolkits.telemetry.FeatureId import software.aws.toolkits.telemetry.MetricResult diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt index d7082f75490..f992ce02f0c 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt @@ -18,6 +18,8 @@ import software.amazon.awssdk.profiles.Profile import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.sso.SsoClient import software.amazon.awssdk.services.sso.model.RoleInfo +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.AwsClientManager import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager import software.amazon.q.jetbrains.core.credentials.AwsConnectionManagerConnection @@ -29,8 +31,6 @@ import software.amazon.q.jetbrains.core.credentials.profiles.ProfileWatcher import software.amazon.q.jetbrains.ui.AsyncComboBox import software.amazon.q.jetbrains.utils.ui.selected import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn data class IdcRolePopupState( var roleInfo: RoleInfo? = null, diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt index e8088a5658a..53897830e77 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt @@ -29,6 +29,10 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.profiles.Profile import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.ToolkitPlaces import software.amazon.q.jetbrains.core.AwsClientManager import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade @@ -48,10 +52,6 @@ import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded import software.amazon.q.jetbrains.utils.ui.editorNotificationCompoundBorder import software.amazon.q.jetbrains.utils.ui.selected import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.tryOrNull import software.aws.toolkits.telemetry.CredentialSourceId import software.aws.toolkits.telemetry.FeatureId import software.aws.toolkits.telemetry.MetricResult diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt index 182d23270e7..bde4de4955e 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt @@ -5,6 +5,8 @@ package software.amazon.q.jetbrains.core.gettingstarted.editor import com.intellij.openapi.project.Project import com.intellij.ui.dsl.builder.Panel +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialType import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager import software.amazon.q.jetbrains.core.credentials.ConnectionState @@ -17,8 +19,6 @@ import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.profiles.SsoSessionConstants import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.credentials.CredentialType import java.util.Locale enum class ActiveConnectionType { diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt index 3d84a3890c1..6cc7644e650 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt @@ -5,13 +5,13 @@ package software.amazon.q.jetbrains.core.gettingstarted.editor import com.intellij.configurationStore.getPersistentStateComponentStorageLocation import com.intellij.openapi.project.Project +import software.amazon.q.core.utils.exists +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.core.credentials.CredentialManager import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager import software.amazon.q.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifierSso import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.core.utils.exists -import software.amazon.q.core.utils.tryOrNull import software.aws.toolkits.telemetry.AuthStatus import software.aws.toolkits.telemetry.StartUpState diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt index 6b9bf7de5ac..fa19af7f92d 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt @@ -12,14 +12,14 @@ import com.intellij.util.Alarm import com.intellij.util.AlarmFactory import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking -import software.amazon.q.jetbrains.core.DefaultRemoteResourceResolverProvider -import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider import software.amazon.q.core.utils.RemoteResolveParser import software.amazon.q.core.utils.RemoteResource import software.amazon.q.core.utils.UpdateCheckResult import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.DefaultRemoteResourceResolverProvider +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider import software.aws.toolkits.telemetry.Component import software.aws.toolkits.telemetry.ToolkitTelemetry import java.io.InputStream diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt index cb577a697a9..e7a1b2d677f 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt @@ -12,12 +12,12 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider -import software.amazon.q.jetbrains.utils.notifyStickyWithData import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.info import software.amazon.q.core.utils.inputStream import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider +import software.amazon.q.jetbrains.utils.notifyStickyWithData import software.aws.toolkits.telemetry.Component import software.aws.toolkits.telemetry.Result import software.aws.toolkits.telemetry.ToolkitTelemetry diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt index 0d078be5298..ed3b122f41c 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt @@ -21,15 +21,15 @@ import com.intellij.openapi.updateSettings.impl.PluginDownloader import com.intellij.openapi.updateSettings.impl.UpdateChecker import com.intellij.util.Alarm import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.info import software.amazon.q.jetbrains.AwsPlugin import software.amazon.q.jetbrains.AwsToolkit import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.jetbrains.settings.AwsSettingsSharedConfigurable import software.amazon.q.jetbrains.utils.notifyInfo import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.info import software.aws.toolkits.telemetry.Component import software.aws.toolkits.telemetry.Result import software.aws.toolkits.telemetry.ToolkitTelemetry diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt index 6abf6d81cbd..e997d03802f 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.components.service import software.amazon.awssdk.regions.providers.AwsProfileRegionProvider import software.amazon.awssdk.regions.providers.AwsRegionProviderChain import software.amazon.awssdk.regions.providers.SystemSettingsRegionProvider -import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider import software.amazon.q.core.region.AwsPartition import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.region.PartitionParser @@ -15,6 +14,7 @@ import software.amazon.q.core.region.ServiceEndpointResource import software.amazon.q.core.region.ToolkitRegionProvider import software.amazon.q.core.utils.inputStream import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider import software.aws.toolkits.resources.BundledResources class AwsRegionProvider : ToolkitRegionProvider() { diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt index 8200e203ee0..0d63a4ecac1 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt @@ -19,6 +19,11 @@ import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language import org.jetbrains.annotations.VisibleForTesting import org.slf4j.event.Level +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.utils.debug +import software.amazon.q.core.utils.error +import software.amazon.q.core.utils.getLogger +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.Login @@ -40,11 +45,6 @@ import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.amazon.q.jetbrains.utils.pollFor import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.tryOrNull import software.aws.toolkits.telemetry.AuthType import software.aws.toolkits.telemetry.AwsTelemetry import software.aws.toolkits.telemetry.CredentialSourceId diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt index 4b4a7af5d35..86f576a881d 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt @@ -13,12 +13,12 @@ import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct import software.amazon.awssdk.services.toolkittelemetry.model.MetadataEntry import software.amazon.awssdk.services.toolkittelemetry.model.MetricDatum import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment -import software.amazon.q.jetbrains.core.AwsClientManager -import software.amazon.q.jetbrains.core.AwsSdkClient -import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext import software.amazon.q.core.clients.nullDefaultProfileFile import software.amazon.q.core.telemetry.MetricEvent import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.jetbrains.core.AwsClientManager +import software.amazon.q.jetbrains.core.AwsSdkClient +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext class DefaultTelemetryPublisher( private val clientProvider: () -> ToolkitTelemetryClient, diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt index 87bb5457a2a..b89dd0410a1 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt @@ -19,8 +19,8 @@ import com.intellij.ui.JBColor import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBTextField import com.intellij.util.ui.components.BorderLayoutPanel -import software.amazon.q.jetbrains.isDeveloperMode import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.jetbrains.isDeveloperMode import java.awt.BorderLayout import javax.swing.BorderFactory import javax.swing.JComponent diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt index 850e6ba6765..f4f13f5ba69 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt @@ -17,11 +17,11 @@ import io.opentelemetry.sdk.trace.ReadWriteSpan import kotlinx.coroutines.CoroutineScope import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit -import software.amazon.q.jetbrains.isDeveloperMode -import software.amazon.q.jetbrains.services.telemetry.PluginResolver import software.amazon.q.core.utils.error import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.isDeveloperMode +import software.amazon.q.jetbrains.services.telemetry.PluginResolver import software.aws.toolkits.telemetry.impl.BaseSpan import java.time.Instant import java.util.concurrent.TimeUnit diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt index 3cd6c546c83..8a0a4b4373a 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt @@ -7,12 +7,12 @@ import com.intellij.openapi.project.Project import kotlinx.coroutines.delay import kotlinx.coroutines.withTimeoutOrNull import org.slf4j.LoggerFactory +import software.amazon.q.core.utils.debug import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager import software.amazon.q.jetbrains.core.credentials.pinning.QConnection import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.amazon.q.core.utils.debug private val LOG = LoggerFactory.getLogger("FunctionUtils") diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt index cff959edbf3..c92e0d219fa 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt @@ -15,11 +15,11 @@ import com.intellij.openapi.ui.DialogBuilder import com.intellij.openapi.util.text.StringUtil import com.intellij.ui.ScrollPaneFactory import org.slf4j.LoggerFactory +import software.amazon.q.core.utils.warn import software.amazon.q.jetbrains.core.help.HelpIds import software.amazon.q.jetbrains.core.notifications.BannerNotificationService import software.amazon.q.jetbrains.core.notifications.NotificationDismissalState import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.utils.warn import javax.swing.JLabel import javax.swing.JTextArea diff --git a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt index 7ff62a8fa0b..c28af00dc9f 100644 --- a/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt +++ b/plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt @@ -5,9 +5,9 @@ package software.amazon.q.jetbrains.utils import com.intellij.idea.AppMode import com.intellij.ui.jcef.JBCefApp -import software.amazon.q.jetbrains.isDeveloperMode import software.amazon.q.core.utils.exists import software.amazon.q.core.utils.tryOrNull +import software.amazon.q.jetbrains.isDeveloperMode import java.nio.file.Paths /** diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt index 94538bd6f60..525fdad24af 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt @@ -16,10 +16,10 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify +import software.amazon.q.core.telemetry.MetricEvent import software.amazon.q.jetbrains.core.webview.BrowserMessage import software.amazon.q.jetbrains.core.webview.BrowserState import software.amazon.q.jetbrains.core.webview.LoginBrowser -import software.amazon.q.core.telemetry.MetricEvent import software.amazon.q.jetbrains.services.telemetry.MockTelemetryServiceExtension import kotlin.test.assertNotNull diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt index f3ec390ad65..f0cb86a2796 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt @@ -28,18 +28,18 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.MockClientManagerExtension import software.amazon.q.jetbrains.core.credentials.profiles.ProfileSsoSessionIdentifier import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.amazon.q.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider +import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.core.telemetry.MetricEvent -import software.amazon.q.core.telemetry.TelemetryBatcher -import software.amazon.q.core.telemetry.TelemetryPublisher -import software.amazon.q.core.utils.test.aString -import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying import software.amazon.q.jetbrains.utils.satisfiesKt diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt index 24f35896e84..07fd2da5182 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt @@ -17,12 +17,12 @@ import org.junit.jupiter.api.extension.RegisterExtension import software.amazon.awssdk.profiles.Profile import software.amazon.awssdk.services.sso.SsoClient import software.amazon.awssdk.services.sso.model.RoleInfo +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.MockClientManagerExtension import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension -import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.satisfiesKt +import software.amazon.q.resources.AwsCoreBundle @ExtendWith(MockKExtension::class) class IdcRolePopupTest { diff --git a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt index 8db493b164a..1371a3d1076 100644 --- a/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt +++ b/plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt @@ -25,6 +25,9 @@ import software.amazon.awssdk.services.sts.StsClient import software.amazon.awssdk.services.sts.model.GetCallerIdentityRequest import software.amazon.awssdk.services.sts.model.GetCallerIdentityResponse import software.amazon.awssdk.services.sts.model.StsException +import software.amazon.q.core.region.Endpoint +import software.amazon.q.core.region.Service +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.MockClientManagerExtension import software.amazon.q.jetbrains.core.credentials.ConfigFilesFacade import software.amazon.q.jetbrains.core.credentials.UserConfigSsoSessionProfile @@ -34,11 +37,8 @@ import software.amazon.q.jetbrains.core.credentials.sono.SONO_REGION import software.amazon.q.jetbrains.core.credentials.sono.SONO_URL import software.amazon.q.jetbrains.core.gettingstarted.editor.SourceOfEntry import software.amazon.q.jetbrains.core.region.MockRegionProviderExtension -import software.amazon.q.resources.AwsCoreBundle -import software.amazon.q.core.region.Endpoint -import software.amazon.q.core.region.Service -import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.satisfiesKt +import software.amazon.q.resources.AwsCoreBundle import software.aws.toolkits.telemetry.FeatureId @ExtendWith(MockKExtension::class) diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/BrowserMessageTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/BrowserMessageTest.kt similarity index 100% rename from plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/BrowserMessageTest.kt rename to plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/BrowserMessageTest.kt diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/LoginBrowserTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/LoginBrowserTest.kt similarity index 100% rename from plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/LoginBrowserTest.kt rename to plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/LoginBrowserTest.kt diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt similarity index 100% rename from plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt rename to plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/IdcRolePopupTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt similarity index 100% rename from plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/IdcRolePopupTest.kt rename to plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt diff --git a/plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt similarity index 100% rename from plugins/core-q/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt rename to plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt index 64555c33777..d533bdb94bf 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt @@ -38,11 +38,6 @@ import software.amazon.awssdk.http.SdkHttpClient import software.amazon.awssdk.http.async.SdkAsyncHttpClient import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.lambda.LambdaClient -import software.amazon.q.jetbrains.core.AwsClientManager.Companion.CUSTOMIZER_EP -import software.amazon.q.jetbrains.core.credentials.CredentialManager -import software.amazon.q.jetbrains.core.credentials.MockAwsConnectionManager.ProjectAccountSettingsManagerRule -import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule -import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.TokenConnectionSettings import software.amazon.q.core.ToolkitClientCustomizer @@ -52,6 +47,11 @@ import software.amazon.q.core.region.Endpoint import software.amazon.q.core.region.Service import software.amazon.q.core.region.anAwsRegion import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.AwsClientManager.Companion.CUSTOMIZER_EP +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.MockAwsConnectionManager.ProjectAccountSettingsManagerRule +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import java.net.URI import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.jvm.isAccessible diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt index c9a527a13e6..4ebb6852ad4 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt @@ -26,14 +26,14 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever -import software.amazon.q.jetbrains.core.credentials.CredentialManager -import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule -import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.core.ConnectionSettings import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.utils.test.retryableAssert +import software.amazon.q.jetbrains.core.credentials.CredentialManager +import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.jetbrains.utils.hasCauseWithMessage import software.amazon.q.jetbrains.utils.hasException import software.amazon.q.jetbrains.utils.hasValue diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt index 5343b801fc0..0648045fce0 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt @@ -17,10 +17,6 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.AwsCredentials import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider -import software.amazon.q.jetbrains.core.region.MockRegionProviderRule -import software.amazon.q.jetbrains.core.region.getDefaultRegion -import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread -import software.amazon.q.jetbrains.utils.computeOnEdt import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.CredentialIdentifierBase import software.amazon.q.core.credentials.CredentialProviderFactory @@ -31,6 +27,10 @@ import software.amazon.q.core.credentials.CredentialsChangeListener import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.region.anAwsRegion import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.core.region.getDefaultRegion +import software.amazon.q.jetbrains.utils.assertIsNonDispatchThread +import software.amazon.q.jetbrains.utils.computeOnEdt import software.amazon.q.jetbrains.utils.isInstanceOf import kotlin.test.assertNotNull diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt index 6290be9fbd6..802d26df92f 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt @@ -11,14 +11,14 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test -import software.amazon.q.jetbrains.core.region.MockRegionProviderRule -import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion -import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.credentials.aCredentialsIdentifier import software.amazon.q.core.region.anAwsRegion +import software.amazon.q.jetbrains.core.region.MockRegionProviderRule +import software.amazon.q.jetbrains.settings.AwsSettings import software.amazon.q.jetbrains.settings.AwsSettingsRule +import software.amazon.q.jetbrains.settings.UseAwsCredentialRegion import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule +import software.amazon.q.resources.AwsCoreBundle class CredentialsRegionHandlerTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt index a078ac0695c..7e585dd73da 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt @@ -11,6 +11,12 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.mock +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.aCredentialsIdentifier +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.rules.EnvironmentVariableHelper +import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.core.utils.test.notNull import software.amazon.q.jetbrains.core.MockResourceCacheRule import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager.Companion.selectedPartition import software.amazon.q.jetbrains.core.credentials.profiles.DEFAULT_PROFILE_ID @@ -18,12 +24,6 @@ import software.amazon.q.jetbrains.core.region.AwsRegionProvider import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.jetbrains.core.region.getDefaultRegion import software.amazon.q.jetbrains.services.sts.StsResources -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.credentials.aCredentialsIdentifier -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.rules.EnvironmentVariableHelper -import software.amazon.q.core.rules.SystemPropertyHelper -import software.amazon.q.core.utils.test.notNull import software.amazon.q.jetbrains.utils.deserializeState import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule import software.amazon.q.jetbrains.utils.satisfiesKt diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt index 9c9cc95122e..364a93bb2cc 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt @@ -10,8 +10,8 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import software.amazon.awssdk.profiles.Profile import software.amazon.q.core.utils.createParentDirectories -import software.amazon.q.core.utils.writeText import software.amazon.q.core.utils.test.assertPosixPermissions +import software.amazon.q.core.utils.writeText import software.amazon.q.jetbrains.utils.satisfiesKt import java.nio.file.Paths diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt index b8311f53c9f..d5feb2056d3 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt @@ -15,10 +15,10 @@ import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import software.amazon.awssdk.services.ssooidc.SsoOidcClient +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.MockClientManagerRule import software.amazon.q.jetbrains.core.credentials.pinning.ConnectionPinningManager import software.amazon.q.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection -import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.isInstanceOf class DefaultToolkitConnectionManagerTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt index f715c15e462..f8b47d40d17 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt @@ -9,11 +9,11 @@ import com.intellij.testFramework.TestActionEvent import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test -import software.amazon.q.jetbrains.core.MockResourceCacheRule -import software.amazon.q.jetbrains.core.dummyResource import software.amazon.q.core.credentials.aToolkitCredentialsProvider import software.amazon.q.core.region.anAwsRegion import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.MockResourceCacheRule +import software.amazon.q.jetbrains.core.dummyResource import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt index 563a06c7112..4cf65a9368a 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt @@ -7,10 +7,10 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Rule import org.junit.Test -import software.amazon.q.jetbrains.core.credentials.profiles.Ec2MetadataConfigProvider.getEc2MedataEndpoint import software.amazon.q.core.rules.EnvironmentVariableHelper import software.amazon.q.core.rules.SystemPropertyHelper import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.profiles.Ec2MetadataConfigProvider.getEc2MedataEndpoint import software.amazon.q.jetbrains.utils.isInstanceOf class Ec2MetadataConfigProviderTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt index 66bf432a1b2..283927d15aa 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt @@ -24,9 +24,9 @@ import software.amazon.awssdk.services.sts.StsClient import software.amazon.awssdk.services.sts.model.AssumeRoleRequest import software.amazon.awssdk.services.sts.model.AssumeRoleResponse import software.amazon.awssdk.utils.SdkAutoCloseable -import software.amazon.q.jetbrains.core.MockClientManagerRule import software.amazon.q.core.region.anAwsRegion import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.MockClientManagerRule import java.time.Duration import java.time.Instant diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt index ff7f475c2d1..5dedf1515cc 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt @@ -42,6 +42,14 @@ import software.amazon.awssdk.services.sso.model.GetRoleCredentialsResponse import software.amazon.awssdk.services.sso.model.RoleCredentials import software.amazon.awssdk.services.ssooidc.SsoOidcClient import software.amazon.awssdk.services.sts.StsClient +import software.amazon.q.core.TokenConnectionSettings +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.credentials.CredentialsChangeEvent +import software.amazon.q.core.credentials.CredentialsChangeListener +import software.amazon.q.core.credentials.SsoSessionIdentifier +import software.amazon.q.core.credentials.ToolkitBearerTokenProvider +import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.MockClientManagerRule import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection import software.amazon.q.jetbrains.core.credentials.InteractiveCredential @@ -55,14 +63,6 @@ import software.amazon.q.jetbrains.core.credentials.sso.PKCEAccessTokenCacheKey import software.amazon.q.jetbrains.core.credentials.sso.PKCEAuthorizationGrantToken import software.amazon.q.jetbrains.core.credentials.sso.SsoCache import software.amazon.q.jetbrains.core.region.getDefaultRegion -import software.amazon.q.core.TokenConnectionSettings -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.credentials.CredentialsChangeEvent -import software.amazon.q.core.credentials.CredentialsChangeListener -import software.amazon.q.core.credentials.SsoSessionIdentifier -import software.amazon.q.core.credentials.ToolkitBearerTokenProvider -import software.amazon.q.core.rules.SystemPropertyHelper -import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.utils.isInstanceOf import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying import software.amazon.q.jetbrains.utils.rules.NotificationListenerRule diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt index ec2ed7aa09d..969276f7a68 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt @@ -8,8 +8,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import software.amazon.q.resources.AwsCoreBundle import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.resources.AwsCoreBundle import java.io.File class ProfileReaderTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt index a0c6453025f..007a15d6b69 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt @@ -19,8 +19,8 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.opentest4j.AssertionFailedError -import software.amazon.q.jetbrains.utils.spinUntil import software.amazon.q.core.rules.SystemPropertyHelper +import software.amazon.q.jetbrains.utils.spinUntil import java.io.File import java.io.IOException import java.time.Duration diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt index be5a13d3ec6..b05f3225844 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt @@ -37,11 +37,11 @@ import software.amazon.awssdk.services.ssooidc.model.SlowDownException import software.amazon.awssdk.services.ssooidc.model.SsoOidcException import software.amazon.awssdk.services.ssooidc.model.StartDeviceAuthorizationRequest import software.amazon.awssdk.services.ssooidc.model.StartDeviceAuthorizationResponse -import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE -import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService import software.amazon.q.core.region.aRegionId import software.amazon.q.core.utils.delegateMock import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.sono.IDENTITY_CENTER_ROLE_ACCESS_SCOPE +import software.amazon.q.jetbrains.core.credentials.sso.pkce.ToolkitOAuthService import software.amazon.q.jetbrains.utils.rules.RegistryRule import software.amazon.q.jetbrains.utils.rules.SsoLoginCallbackProviderRule import java.time.Clock diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt index 6d92e8dc29f..7b6f6e8ad49 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt @@ -3,9 +3,9 @@ package software.amazon.q.jetbrains.core.credentials.sso.bearer -import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken import software.amazon.q.core.region.aRegionId import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken import java.time.Instant fun anAccessToken(refreshToken: String? = aString(), expiresAt: Instant) = DeviceAuthorizationGrantToken( diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt index d204edd394b..c0edf6a83cf 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt @@ -35,6 +35,8 @@ import software.amazon.awssdk.services.ssooidc.model.AccessDeniedException import software.amazon.awssdk.services.ssooidc.model.CreateTokenRequest import software.amazon.awssdk.services.ssooidc.model.CreateTokenResponse import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.amazon.q.core.region.aRegionId +import software.amazon.q.core.utils.test.aString import software.amazon.q.jetbrains.core.AwsClientManager import software.amazon.q.jetbrains.core.MockClientManager import software.amazon.q.jetbrains.core.MockClientManagerRule @@ -46,8 +48,6 @@ import software.amazon.q.jetbrains.core.credentials.sso.DeviceAuthorizationGrant import software.amazon.q.jetbrains.core.credentials.sso.DeviceGrantAccessTokenCacheKey import software.amazon.q.jetbrains.core.credentials.sso.DiskCache import software.amazon.q.jetbrains.core.credentials.sso.PKCEAccessTokenCacheKey -import software.amazon.q.core.region.aRegionId -import software.amazon.q.core.utils.test.aString import java.time.Clock import java.time.Instant import java.time.temporal.ChronoUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt index ceaaac526c3..aac18361a81 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt @@ -15,12 +15,12 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import org.mockito.kotlin.verifyNoInteractions import software.amazon.awssdk.services.ssooidc.SsoOidcClient -import software.amazon.q.jetbrains.core.MockClientManagerRule import software.amazon.q.core.rules.EnvironmentVariableHelper import software.amazon.q.core.utils.createParentDirectories import software.amazon.q.core.utils.test.aString import software.amazon.q.core.utils.toHexString import software.amazon.q.core.utils.writeText +import software.amazon.q.jetbrains.core.MockClientManagerRule import java.nio.file.Path import java.security.MessageDigest import java.time.Instant diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt index cf60a49d436..08544d089d5 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -22,9 +22,9 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet import software.amazon.q.core.utils.exists import software.amazon.q.core.utils.inputStream +import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet import java.io.InputStream import java.nio.file.Paths import java.util.stream.Stream diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt index 5b3f9e69351..7b5731d3f31 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt @@ -13,9 +13,9 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider import software.amazon.q.core.utils.RemoteResourceResolver import software.amazon.q.core.utils.UpdateCheckResult +import software.amazon.q.jetbrains.core.RemoteResourceResolverProvider import java.nio.file.Path import java.util.concurrent.CompletableFuture diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt index d106937a048..c24c138753b 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt @@ -26,9 +26,9 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.stub import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import software.amazon.q.core.utils.tryOrNull import software.amazon.q.jetbrains.AwsToolkit.TOOLKIT_PLUGIN_ID import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.core.utils.tryOrNull class PluginUpdateManagerTest { val applicationRule = ApplicationRule() diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt index 41e8a8360cb..0f3e7a427fc 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt @@ -7,7 +7,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import software.amazon.q.jetbrains.services.telemetry.OpenedFileTypesMetricsService class OpenedFileTypeMetricsTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt index c3b0a0a8bab..5d95faf193e 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt @@ -15,7 +15,6 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct -import software.amazon.q.jetbrains.services.telemetry.PluginResolver @ExtendWith(MockKExtension::class) class PluginResolverTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt index 937c5a83913..7adcf688efc 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt @@ -20,6 +20,13 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.stub import org.mockito.kotlin.times import org.mockito.kotlin.verify +import software.amazon.q.core.credentials.CredentialIdentifier +import software.amazon.q.core.region.AwsRegion +import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_INVALID +import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_NOT_SET +import software.amazon.q.core.telemetry.MetricEvent +import software.amazon.q.core.telemetry.TelemetryBatcher +import software.amazon.q.core.telemetry.TelemetryPublisher import software.amazon.q.jetbrains.core.MockResourceCacheRule import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager import software.amazon.q.jetbrains.core.credentials.ConnectionState @@ -30,13 +37,6 @@ import software.amazon.q.jetbrains.core.credentials.waitUntilConnectionStateIsSt import software.amazon.q.jetbrains.core.region.MockRegionProviderRule import software.amazon.q.jetbrains.services.sts.StsResources import software.amazon.q.jetbrains.settings.AwsSettings -import software.amazon.q.core.credentials.CredentialIdentifier -import software.amazon.q.core.region.AwsRegion -import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_INVALID -import software.amazon.q.core.telemetry.DefaultMetricEvent.Companion.METADATA_NOT_SET -import software.amazon.q.core.telemetry.MetricEvent -import software.amazon.q.core.telemetry.TelemetryBatcher -import software.amazon.q.core.telemetry.TelemetryPublisher import software.amazon.q.jetbrains.utils.isInstanceOfSatisfying import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt index b5cd0989df8..8cfc6707025 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt @@ -7,7 +7,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import software.amazon.q.jetbrains.services.telemetry.scrubNames import java.util.stream.Stream class TelemetryUtilsTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt index 2c1342a2c42..f81c3aacc35 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt @@ -29,12 +29,12 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct -import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext -import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread -import software.amazon.q.jetbrains.utils.spinUntil import software.amazon.q.core.utils.getLogger import software.amazon.q.core.utils.warn +import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext +import software.amazon.q.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.amazon.q.jetbrains.utils.satisfiesKt +import software.amazon.q.jetbrains.utils.spinUntil import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry import java.time.Duration diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt index a437c49257c..afa1afeb46a 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt @@ -20,12 +20,12 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify import software.amazon.awssdk.services.toolkittelemetry.model.MetricUnit -import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.amazon.q.core.telemetry.DefaultMetricEvent import software.amazon.q.core.telemetry.DefaultMetricEvent.DefaultDatum import software.amazon.q.core.telemetry.TelemetryBatcher import software.amazon.q.core.telemetry.TelemetryPublisher import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher +import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.amazon.q.jetbrains.utils.satisfiesKt import software.aws.toolkits.telemetry.CodewhispererAutomatedTriggerType import software.aws.toolkits.telemetry.CodewhispererCompletionType diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt index 472a42110cb..7fb542c100d 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt @@ -17,10 +17,10 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.spy -import software.amazon.q.jetbrains.services.telemetry.TelemetryService import software.amazon.q.core.telemetry.TelemetryBatcher import software.amazon.q.core.telemetry.TelemetryPublisher import software.amazon.q.jetbrains.services.telemetry.NoOpPublisher +import software.amazon.q.jetbrains.services.telemetry.TelemetryService @ExtendWith(ApplicationExtension::class) class AwsSettingsTest { diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt index 3f86712fa79..52a1cda8471 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt @@ -10,7 +10,6 @@ import org.junit.Test import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify -import software.amazon.q.jetbrains.ui.AsyncComboBox import java.util.concurrent.CountDownLatch import javax.swing.DefaultComboBoxModel diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt index 3ef58077aec..36602a3ef05 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt @@ -6,7 +6,6 @@ package software.amazon.q.jetbrains.utils import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import software.amazon.q.jetbrains.utils.MRUList class MRUListTest { private lateinit var list: MRUList diff --git a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt index 68dfe6a3f1e..d383d27e05a 100644 --- a/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt +++ b/plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt @@ -10,10 +10,6 @@ import org.assertj.core.api.Assertions.assertThat import org.intellij.lang.annotations.Language import org.junit.Rule import org.junit.Test -import software.amazon.q.jetbrains.utils.applyPatch -import software.amazon.q.jetbrains.utils.formatText -import software.amazon.q.jetbrains.utils.generateUnifiedPatch -import software.amazon.q.jetbrains.utils.toHumanReadable import software.amazon.q.core.utils.convertMarkdownToHTML import software.amazon.q.core.utils.extractCodeBlockLanguage diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt index fc8507d6d91..2426bafeb38 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt @@ -16,7 +16,6 @@ import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider import software.amazon.awssdk.core.SdkClient import software.amazon.awssdk.regions.Region -import software.amazon.q.jetbrains.core.region.AwsRegionProvider import software.amazon.q.core.ToolkitClientCustomizer import software.amazon.q.core.ToolkitClientManager import software.amazon.q.core.clients.SdkClientProvider @@ -24,6 +23,7 @@ import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.region.ToolkitRegionProvider import software.amazon.q.core.utils.delegateMock +import software.amazon.q.jetbrains.core.region.AwsRegionProvider import kotlin.reflect.KClass class MockClientManager : AwsClientManager() { diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt index 1c8aa0fe590..027562604bd 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt @@ -12,12 +12,12 @@ import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.BeforeEachCallback import org.junit.jupiter.api.extension.ExtensionContext import org.junit.runner.Description -import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager import software.amazon.q.core.ClientConnectionSettings import software.amazon.q.core.credentials.ToolkitAuthenticationProvider import software.amazon.q.core.credentials.ToolkitBearerTokenProvider import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.core.credentials.AwsConnectionManager import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionStage import java.util.concurrent.ConcurrentHashMap diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt index ab446de62a6..7bd788c3b31 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt @@ -8,12 +8,12 @@ import com.intellij.openapi.project.Project import com.intellij.testFramework.ProjectRule import org.junit.rules.ExternalResource import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider -import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.jetbrains.utils.spinUntil import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion +import software.amazon.q.jetbrains.core.region.AwsRegionProvider import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule +import software.amazon.q.jetbrains.utils.spinUntil import java.time.Duration class MockAwsConnectionManager(project: Project) : AwsConnectionManager(project) { diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt index e8dbcf336ec..a802b1d5f44 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt @@ -11,7 +11,6 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.AwsCredentials import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider -import software.amazon.q.jetbrains.core.region.getDefaultRegion import software.amazon.q.core.credentials.CredentialIdentifier import software.amazon.q.core.credentials.CredentialIdentifierBase import software.amazon.q.core.credentials.CredentialProviderFactory @@ -21,6 +20,7 @@ import software.amazon.q.core.credentials.SsoSessionIdentifier import software.amazon.q.core.credentials.ToolkitCredentialsProvider import software.amazon.q.core.region.AwsRegion import software.amazon.q.core.utils.test.aString +import software.amazon.q.jetbrains.core.region.getDefaultRegion import software.amazon.q.jetbrains.utils.rules.ClearableLazy @Deprecated("Use MockCredentialManagerRule") diff --git a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt index 62ef0c67ca9..fc8634dc16a 100644 --- a/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt +++ b/plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt @@ -4,7 +4,6 @@ package software.amazon.q.jetbrains.settings import com.intellij.testFramework.ApplicationRule -import software.amazon.q.jetbrains.settings.AwsSettings import java.util.UUID class MockAwsSettings : AwsSettings { diff --git a/plugins/core-q/webview/package-lock.json b/plugins/core-q/webview/package-lock.json index 5d32af8fda8..d0fd3258687 100644 --- a/plugins/core-q/webview/package-lock.json +++ b/plugins/core-q/webview/package-lock.json @@ -304,7 +304,8 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "peer": true }, "node_modules/@types/sanitize-html": { "version": "2.11.0", @@ -360,6 +361,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -808,6 +810,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -846,6 +849,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1028,6 +1032,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -1522,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2783,6 +2789,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3083,6 +3090,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3157,6 +3165,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3570,6 +3579,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3648,6 +3658,7 @@ "version": "3.4.20", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.4.20", "@vue/compiler-sfc": "3.4.20", @@ -3736,6 +3747,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3782,6 +3794,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt b/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt index c0f0c445d43..6ac0941b2a7 100644 --- a/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt +++ b/plugins/core/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt @@ -3,11 +3,9 @@ package software.aws.toolkits.jetbrains.core.gettingstarted -import com.intellij.openapi.Disposable import com.intellij.openapi.ui.TestDialog import com.intellij.openapi.ui.TestDialogManager import com.intellij.testFramework.ProjectExtension -import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.runInEdtAndWait import io.mockk.every import io.mockk.junit5.MockKExtension @@ -15,7 +13,6 @@ import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.verify import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -31,7 +28,6 @@ import software.amazon.awssdk.services.sts.model.StsException import software.aws.toolkits.core.region.Endpoint import software.aws.toolkits.core.region.Service import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.MockClientManagerExtension import software.aws.toolkits.jetbrains.core.credentials.ConfigFilesFacade import software.aws.toolkits.jetbrains.core.credentials.UserConfigSsoSessionProfile diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt index 8bce5e64782..d532a154e39 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsClientManagerTest.kt @@ -11,7 +11,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching import com.github.tomakehurst.wiremock.core.WireMockConfiguration import com.github.tomakehurst.wiremock.junit.WireMockRule import com.github.tomakehurst.wiremock.matching.ContainsPattern -import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.use @@ -19,12 +18,10 @@ import com.intellij.testFramework.DisposableRule import com.intellij.testFramework.ExtensionTestUtil import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.RuleChain -import com.intellij.testFramework.junit5.TestDisposable import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Rule import org.junit.Test -import org.junit.jupiter.api.BeforeEach import org.junit.rules.TemporaryFolder import org.mockito.kotlin.mock import org.mockito.kotlin.whenever diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt index b44e8637ef0..1154797ef58 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/AwsResourceCacheTest.kt @@ -484,7 +484,7 @@ class AwsResourceCacheTest { whenever(mockResource.fetch(any())).then { latch.await() // exception gets thrown fast enough where the second fetchIfNeeded check occurs after the first call throws - runTest { + runTest { delay(500) } throw RuntimeException("Boom") diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt index a8944a3996b..1973530a494 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt @@ -14,7 +14,6 @@ import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.TestApplicationManager import com.intellij.testFramework.createTestOpenProjectOptions -import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.replaceService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -27,10 +26,8 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.jupiter.api.BeforeEach import org.junit.rules.TemporaryFolder import org.junit.rules.TestName -import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.utils.isInstanceOf import java.time.Duration import java.util.concurrent.CancellationException diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt index 1b975991493..e5674051460 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/CredentialManagerTest.kt @@ -3,19 +3,16 @@ package software.aws.toolkits.jetbrains.core.credentials -import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.testFramework.ApplicationRule import com.intellij.testFramework.DisposableRule import com.intellij.testFramework.ExtensionTestUtil -import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Rule import org.junit.Test -import org.junit.jupiter.api.BeforeEach import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.AwsCredentials import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider @@ -30,7 +27,6 @@ import software.aws.toolkits.core.credentials.CredentialsChangeListener import software.aws.toolkits.core.region.AwsRegion import software.aws.toolkits.core.region.anAwsRegion import software.aws.toolkits.core.utils.test.aString -import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule import software.aws.toolkits.jetbrains.core.region.getDefaultRegion import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt index 106c6343d8c..648624858df 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt @@ -9,17 +9,13 @@ import com.intellij.testFramework.ApplicationExtension import com.intellij.testFramework.junit5.TestDisposable import com.intellij.testFramework.replaceService import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.RegisterExtension import org.mockito.kotlin.mock import software.amazon.awssdk.services.ssooidc.SsoOidcClient import software.amazon.awssdk.services.ssooidc.model.SsoOidcException -import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.MockClientManager -import software.aws.toolkits.jetbrains.core.MockClientManagerExtension import software.aws.toolkits.jetbrains.core.credentials.sso.DiskCache import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException @@ -63,7 +59,6 @@ class ProfileCredentialsIdentifierSsoTest { assertThat(sut.handleValidationException(exception)).isNotNull() } - @Test fun `ignores arbitrary exception`() { assertThat(sut.handleValidationException(RuntimeException())).isNull() diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt index f84f30d6fea..e5ac374d365 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/sso/DiskCacheTest.kt @@ -3,11 +3,9 @@ package software.aws.toolkits.jetbrains.core.credentials.sso -import com.intellij.openapi.Disposable import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.NioFiles import com.intellij.testFramework.ApplicationExtension -import com.intellij.testFramework.junit5.TestDisposable import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -18,7 +16,6 @@ import org.junit.jupiter.api.io.TempDir import software.aws.toolkits.core.utils.readText import software.aws.toolkits.core.utils.test.assertPosixPermissions import software.aws.toolkits.core.utils.writeText -import software.aws.toolkits.jetbrains.core.CoreTestHelper import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -45,8 +42,7 @@ class DiskCacheTest { private lateinit var sut: DiskCache @BeforeEach - fun setUp(@TestDisposable disposable: Disposable, @TempDir tempFolder: Path) { -// CoreTestHelper.registerMissingServices(disposable) + fun setUp(@TempDir tempFolder: Path) { cacheRoot = tempFolder.toAbsolutePath() cacheLocation = Paths.get(cacheRoot.toString(), "fakehome", ".aws", "sso", "cache") Files.createDirectories(cacheLocation) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt index e4046a3b003..dfe8fd93b77 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBaseTest.kt @@ -3,10 +3,8 @@ package software.aws.toolkits.jetbrains.services.telemetry.otel -import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.testFramework.ApplicationExtension -import com.intellij.testFramework.junit5.TestDisposable import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.TraceId import io.opentelemetry.context.Context @@ -19,7 +17,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows @@ -34,7 +31,6 @@ import org.junit.jupiter.params.provider.MethodSource import software.amazon.awssdk.services.toolkittelemetry.model.AWSProduct import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.jetbrains.utils.satisfiesKt diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt index ca2bb900728..d3d2a1cb5c2 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt @@ -35,7 +35,7 @@ import software.aws.toolkits.telemetry.CodewhispererTriggerType import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry import java.time.Instant -//import software.aws.toolkits.jetbrains.core.CoreTestHelper +// import software.aws.toolkits.jetbrains.core.CoreTestHelper @ExtendWith(ApplicationExtension::class) class ToolkitTelemetryOTelSpanProcessorTest { diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt index fc2c65a2756..9c53a0f8661 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/AwsSettingsTest.kt @@ -19,7 +19,6 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.spy import software.aws.toolkits.core.telemetry.TelemetryBatcher import software.aws.toolkits.core.telemetry.TelemetryPublisher -import software.aws.toolkits.jetbrains.core.CoreTestHelper import software.aws.toolkits.jetbrains.services.telemetry.NoOpPublisher import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService diff --git a/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt b/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt index 1b9d3d00eb6..1e04f9fa657 100644 --- a/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt +++ b/plugins/core/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/core/CoreTestHelper.kt.kt @@ -7,8 +7,8 @@ import com.intellij.openapi.extensions.ExtensionPoint import com.intellij.testFramework.replaceService import org.mockito.kotlin.mock import software.aws.toolkits.jetbrains.core.credentials.MockCredentialsManager -import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider import software.aws.toolkits.jetbrains.core.credentials.sso.MockSsoLoginCallbackProvider +import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider import software.aws.toolkits.jetbrains.services.telemetry.NoOpTelemetryService import software.aws.toolkits.jetbrains.settings.MockAwsSettings /** diff --git a/plugins/core/webview/package-lock.json b/plugins/core/webview/package-lock.json index 5d32af8fda8..d0fd3258687 100644 --- a/plugins/core/webview/package-lock.json +++ b/plugins/core/webview/package-lock.json @@ -304,7 +304,8 @@ "node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "peer": true }, "node_modules/@types/sanitize-html": { "version": "2.11.0", @@ -360,6 +361,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -808,6 +810,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -846,6 +849,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1028,6 +1032,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -1522,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2783,6 +2789,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -3083,6 +3090,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -3157,6 +3165,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3570,6 +3579,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3648,6 +3658,7 @@ "version": "3.4.20", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.20.tgz", "integrity": "sha512-xF4zDKXp67NjgORFX/HOuaiaKYjgxkaToK0KWglFQEYlCw9AqgBlj1yu5xa6YaRek47w2IGiuvpvrGg/XuQFCw==", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.4.20", "@vue/compiler-sfc": "3.4.20", @@ -3736,6 +3747,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -3782,6 +3794,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/plugins/toolkit/jetbrains-core/tst-242-252/software/aws/toolkits/jetbrains/core/explorer/AwsToolkitExplorerToolWindowTest.kt b/plugins/toolkit/jetbrains-core/tst-242-252/software/aws/toolkits/jetbrains/core/explorer/AwsToolkitExplorerToolWindowTest.kt index c0d1c61ae5b..2d13c7ff11b 100644 --- a/plugins/toolkit/jetbrains-core/tst-242-252/software/aws/toolkits/jetbrains/core/explorer/AwsToolkitExplorerToolWindowTest.kt +++ b/plugins/toolkit/jetbrains-core/tst-242-252/software/aws/toolkits/jetbrains/core/explorer/AwsToolkitExplorerToolWindowTest.kt @@ -58,17 +58,4 @@ class AwsToolkitExplorerToolWindowTest { assertThat(sut.state.selectedTab).isEqualTo(AwsToolkitExplorerToolWindow.Q_TAB_ID) } } - - @Test - fun `handles loading invalid state`() { - (ToolWindowManager.getInstance(projectExtension.project) as ToolWindowHeadlessManagerImpl) - .doRegisterToolWindow(AwsToolkitExplorerFactory.TOOLWINDOW_ID) - val sut = runInEdtAndGet { AwsToolkitExplorerToolWindow(projectExtension.project) } - - sut.loadState( - AwsToolkitExplorerToolWindowState().apply { - selectedTab = aString() - } - ) - } } From 39f84026b2a08dee22441228b6d0224fd6d4e35e Mon Sep 17 00:00:00 2001 From: Sherry Lu Date: Wed, 7 Jan 2026 16:16:46 -0800 Subject: [PATCH 09/60] remove q related codes and jobs --- .github/workflows/prerelease.yml | 4 +- .../Run Amazon Q - Community [2024.3].run.xml | 25 - .../Run Amazon Q - Community [2025.1].run.xml | 25 - .../Run Amazon Q - Community [2025.2].run.xml | 25 - .run/Run Amazon Q - Rider [2024.3].run.xml | 25 - .run/Run Amazon Q - Rider [2025.1].run.xml | 25 - .run/Run Amazon Q - Rider [2025.2].run.xml | 25 - .run/Run Amazon Q - Ultimate [2024.3].run.xml | 25 - .run/Run Amazon Q - Ultimate [2025.1].run.xml | 25 - .run/Run Amazon Q - Ultimate [2025.2].run.xml | 25 - build.gradle.kts | 1 - buildspec/linuxTestsForAmazonQ.yml | 71 - buildspec/windowsTestsForAmazonQ.yml | 89 - plugins/amazonq/build.gradle.kts | 139 - plugins/amazonq/chat/build.gradle.kts | 10 - .../chat/jetbrains-community/build.gradle.kts | 36 - .../resources/META-INF/plugin-chat.xml | 90 - .../jetbrains/common/util/FileUtils.kt | 82 - .../services/amazonq/GetAmazonQLogsAction.kt | 68 - .../services/amazonq/QLoginWebview.kt | 381 -- .../amazonq/QMigrationNotificationAction.kt | 43 - .../services/amazonq/QOpenPanelAction.kt | 30 - .../services/amazonq/QRefreshPanelAction.kt | 56 - .../services/amazonq/apps/AmazonQApp.kt | 29 - .../amazonq/apps/AmazonQAppFactory.kt | 10 - .../amazonq/apps/AmazonQAppInitContext.kt | 21 - .../services/amazonq/apps/AppConnection.kt | 14 - .../services/amazonq/auth/AuthController.kt | 96 - .../services/amazonq/auth/AuthFollowUpType.kt | 15 - .../services/amazonq/auth/AuthNeededState.kt | 14 - .../amazonq/commands/MessageSerializer.kt | 52 - .../amazonq/commands/MessageTypeRegistry.kt | 24 - .../explorerActions/ReauthenticateWithQ.kt | 16 - .../explorerActions/SignInToQAction.kt | 54 - .../gettingstarted/QGettingStartedContent.kt | 249 -- .../gettingstarted/QGettingStartedEditor.kt | 48 - .../QGettingStartedEditorProvider.kt | 28 - .../gettingstarted/QGettingStartedPanel.kt | 46 - .../amazonq/messages/AmazonQMessages.kt | 12 - .../amazonq/messages/MessagePublisher.kt | 35 - .../onboarding/OnboardingPageInteraction.kt | 17 - .../amazonq/startup/AmazonQStartupActivity.kt | 56 - .../amazonq/toolwindow/AmazonQPanel.kt | 335 -- .../amazonq/toolwindow/AmazonQToolWindow.kt | 59 - .../toolwindow/AmazonQToolWindowFactory.kt | 181 - .../toolwindow/AmazonQToolWindowListener.kt | 32 - .../services/amazonq/toolwindow/AppSource.kt | 17 - .../services/amazonq/util/HighlightCommand.kt | 16 - .../services/amazonq/util/JcefBrowserUtil.kt | 24 - .../services/amazonq/util/JsonUtil.kt | 12 - .../services/amazonq/webview/Browser.kt | 577 --- .../amazonq/webview/BrowserConnector.kt | 792 ---- .../webview/FlareAdditionalFindings.kt | 75 - .../amazonq/webview/FqnWebviewAdapter.kt | 85 - .../amazonq/webview/theme/AmazonQTheme.kt | 55 - .../amazonq/webview/theme/CssVariable.kt | 66 - .../webview/theme/EditorThemeAdapter.kt | 166 - .../webview/theme/ThemeBrowserAdapter.kt | 126 - .../amazonqCodeScan/CodeScanChatApp.kt | 180 - .../amazonqCodeScan/CodeScanChatAppFactory.kt | 12 - .../amazonqCodeScan/CodeScanChatItems.kt | 165 - .../amazonqCodeScan/CodeScanConstants.kt | 6 - .../InboundAppMessagesHandler.kt | 33 - .../amazonqCodeScan/auth/CodeScanAuthUtils.kt | 11 - .../commands/CodeScanActionMessage.kt | 16 - .../commands/CodeScanCommand.kt | 8 - .../commands/CodeScanMessageListener.kt | 21 - .../controller/CodeScanChatController.kt | 197 - .../controller/CodeScanChatHelper.kt | 80 - .../messages/CodeScanMessage.kt | 182 - .../amazonqCodeScan/session/Session.kt | 9 - .../storage/ChatSessionStorage.kt | 37 - .../toolkits/jetbrains/services/cwc/App.kt | 120 - .../jetbrains/services/cwc/AppFactory.kt | 11 - .../jetbrains/services/cwc/ChatConstants.kt | 14 - .../services/cwc/InboundAppMessagesHandler.kt | 39 - .../services/cwc/clients/chat/ChatSession.kt | 17 - .../cwc/clients/chat/ChatSessionFactory.kt | 10 - .../chat/exceptions/ChatApiException.kt | 17 - .../cwc/clients/chat/model/Requests.kt | 65 - .../cwc/clients/chat/model/Responses.kt | 72 - .../clients/chat/v1/ChatSessionFactoryV1.kt | 11 - .../cwc/clients/chat/v1/ChatSessionV1.kt | 329 -- .../services/cwc/commands/ActionRegistrar.kt | 52 - .../commands/CodeScanIssueActionMessage.kt | 9 - .../cwc/commands/ContextMenuActionMessage.kt | 12 - .../services/cwc/commands/CustomAction.kt | 32 - .../cwc/commands/EditorContextCommand.kt | 41 - .../cwc/commands/ExplainCodeAction.kt | 6 - .../services/cwc/commands/FixCodeAction.kt | 6 - .../cwc/commands/GenerateUnitTestsAction.kt | 6 - .../cwc/commands/OptimizeCodeAction.kt | 6 - .../cwc/commands/RefactorCodeAction.kt | 6 - .../cwc/commands/SendToPromptAction.kt | 6 - .../cwc/commands/SendToQActionGroup.kt | 23 - .../actions/CodeScanCompleteAction.kt | 21 - .../actions/HandleIssueCommandAction.kt | 94 - .../services/cwc/controller/ChatController.kt | 483 -- .../cwc/controller/ReferenceLogController.kt | 39 - .../cwc/controller/chat/StaticPrompt.kt | 14 - .../cwc/controller/chat/StaticTextResponse.kt | 64 - .../chat/messenger/ChatPromptHandler.kt | 240 - .../chat/prompts/PromptsGenerator.kt | 11 - .../chat/telemetry/TelemetryHelper.kt | 473 -- .../chat/userIntent/UserIntentRecognizer.kt | 51 - .../cwc/editor/context/ActiveFileContext.kt | 12 - .../context/ActiveFileContextExtractor.kt | 42 - .../editor/context/ExtractionTriggerType.kt | 11 - .../cwc/editor/context/file/FileContext.kt | 12 - .../context/file/FileContextExtractor.kt | 56 - .../context/file/util/LanguageExtractor.kt | 15 - .../context/file/util/MatchPolicyExtractor.kt | 130 - .../context/focusArea/FocusAreaContext.kt | 13 - .../focusArea/FocusAreaContextExtractor.kt | 214 - .../context/focusArea/UICodeSelection.kt | 14 - .../services/cwc/exceptions/ChatException.kt | 12 - .../cwc/inline/InlineChatActionPromoter.kt | 39 - .../cwc/inline/InlineChatController.kt | 734 --- .../cwc/inline/InlineChatEditorHint.kt | 105 - .../cwc/inline/InlineChatPopupFactory.kt | 177 - .../cwc/inline/InlineChatPopupPanel.kt | 184 - .../cwc/inline/OpenChatInputAction.kt | 34 - .../listeners/InlineChatFileListener.kt | 57 - .../listeners/InlineChatSelectionListener.kt | 29 - .../services/cwc/messages/CwcMessage.kt | 298 -- .../services/cwc/storage/ChatSessionInfo.kt | 14 - .../cwc/storage/ChatSessionStorage.kt | 33 - .../services/amazonq/TelemetryHelperTest.kt | 618 --- .../services/amazonq/TelemetryHelperTest.kt | 639 --- .../services/amazonq/AmazonQTestBase.kt | 74 - .../clients/AmazonQStreamingClientTest.kt | 258 -- .../amazonq/webview/BrowserConnectorTest.kt | 271 -- .../workspace/context/WorkspaceTest.kt | 209 - .../amazonq/codetransform/build.gradle.kts | 10 - .../jetbrains-community/build.gradle.kts | 36 - .../META-INF/codetransform-ext-java.xml | 51 - .../META-INF/plugin-codetransform.xml | 6 - .../codemodernizer/ArtifactHandler.kt | 448 -- .../codemodernizer/CodeModernizerManager.kt | 945 ---- .../codemodernizer/CodeModernizerSession.kt | 656 --- .../CodeModernizerStartupActivity.kt | 20 - .../codemodernizer/CodeTransformChatApp.kt | 209 - .../CodeTransformChatAppFactory.kt | 11 - ...eTransformProjectStartupSettingListener.kt | 32 - .../CodeTransformTelemetryManager.kt | 197 - .../InboundAppMessagesHandler.kt | 57 - .../codemodernizer/TransformationSummary.kt | 6 - .../CodeModernizerShowJobStatusAction.kt | 32 - ...eModernizerShowTransformationPlanAction.kt | 34 - ...odernizerShowTransformationStatusAction.kt | 31 - ...dernizerShowTransformationSummaryAction.kt | 34 - .../CodeModernizerStopModernizerAction.kt | 47 - .../codemodernizer/client/GumbyClient.kt | 276 -- .../commands/CodeTransformActionMessage.kt | 18 - .../commands/CodeTransformCommand.kt | 18 - .../commands/CodeTransformMessageListener.kt | 65 - .../constants/CodeModernizerUIConstants.kt | 170 - .../constants/CodeTransformChatItems.kt | 781 ---- .../constants/CodeTransformFileConstants.kt | 24 - .../controller/CodeTransformChatController.kt | 1007 ----- .../controller/CodeTransformChatHelper.kt | 118 - .../codemodernizer/file/PomFileAnnotator.kt | 114 - .../ideMaven/MavenRunnerUtils.kt | 315 -- .../ideMaven/TransformMavenRunner.kt | 58 - .../ideMaven/TransformRunnable.kt | 36 - .../messages/CodeTransformMessage.kt | 269 -- .../model/BuildProgressStepTreeItem.kt | 24 - .../BuildProgressTimelineStepDetailItem.kt | 12 - .../BuildProgressTimelineStepDetailsList.kt | 39 - .../codemodernizer/model/BuildStepStatus.kt | 21 - .../model/CodeModernizerArtifact.kt | 132 - .../model/CodeModernizerException.kt | 6 - .../model/CodeModernizerJobCompletedResult.kt | 22 - .../model/CodeModernizerManifest.kt | 9 - .../model/CodeModernizerMetrics.kt | 14 - .../model/CodeModernizerSessionContext.kt | 368 -- .../model/CodeModernizerStartJobResult.kt | 15 - .../model/CodeTransformConversationState.kt | 10 - .../model/CodeTransformDownloadArtifact.kt | 9 - .../model/CodeTransformFailureBuildLog.kt | 56 - .../model/CodeTransformHilDownloadArtifact.kt | 86 - .../model/CodeTransformHilDownloadManifest.kt | 15 - .../model/CodeTransformHilUploadManifest.kt | 20 - .../codemodernizer/model/CodeTransformType.kt | 9 - .../codemodernizer/model/CustomerSelection.kt | 21 - .../model/DependencyUpdatesReport.kt | 26 - .../model/DownloadArtifactResult.kt | 12 - .../model/DownloadFailureReason.kt | 14 - .../model/InvalidTelemetryReason.kt | 8 - .../codemodernizer/model/JobHistoryItem.kt | 19 - .../model/JobHistoryTableModel.kt | 21 - .../services/codemodernizer/model/JobId.kt | 6 - .../model/MavenCopyCommandsResult.kt | 13 - .../MavenDependencyReportCommandsResult.kt | 10 - .../codemodernizer/model/MigrationStep.kt | 6 - .../model/ParseZipFailureReason.kt | 8 - .../codemodernizer/model/PlanTable.kt | 17 - .../codemodernizer/model/PlanTableRow.kt | 43 - .../codemodernizer/model/SctMetadata.kt | 79 - .../model/SqlMetadataValidationResult.kt | 13 - .../model/UnzipFailureReason.kt | 8 - .../model/UploadFailureReason.kt | 13 - .../codemodernizer/model/ValidationResult.kt | 17 - .../codemodernizer/model/ZipCreationResult.kt | 11 - .../codemodernizer/model/ZipManifest.kt | 29 - .../panels/BuildProgressStepDetailsPanel.kt | 184 - .../panels/BuildProgressTreePanel.kt | 188 - .../panels/CodeModernizerBanner.kt | 137 - .../CodeModernizerJobHistoryTablePanel.kt | 56 - .../codemodernizer/panels/LoadingPanel.kt | 117 - .../BuildProgressSplitterPanelManager.kt | 386 -- .../CodeModernizerBottomWindowPanelManager.kt | 326 -- .../plan/CodeModernizerPlanEditor.kt | 531 --- .../plan/CodeModernizerPlanEditorProvider.kt | 50 - .../plan/CodeModernizerPlanVirtualFile.kt | 20 - .../session/ChatSessionStorage.kt | 35 - .../codemodernizer/session/Session.kt | 14 - .../state/CodeModernizerSessionState.kt | 73 - .../state/CodeModernizerState.kt | 77 - .../state/CodeTransformTelemetryState.kt | 40 - .../CodeModernizerBottomToolWindowFactory.kt | 40 - .../ui/components/PanelHeaderFactory.kt | 32 - .../utils/CodeTransformApiUtils.kt | 330 -- .../utils/CodeTransformFileUtils.kt | 310 -- .../utils/CodeTransformModuleUtils.kt | 63 - .../utils/CodeTransformProjectUtils.kt | 103 - .../utils/CodeTransformUtils.kt | 78 - .../utils/CodeTransformValidationUtils.kt | 18 - .../fixtures/downloadResults/manifest.json | 7 - .../utils/fixtures/downloadResults/pom.xml | 16 - .../feedback/CodeTransformFeedbackDialog.kt | 32 - .../tst-resources/codemodernizer/diff.patch | 27 - .../tst-resources/codemodernizer/expectedFile | 1 - .../humanInTheLoop/downloadResults.zip | Bin 1073 -> 0 bytes .../humanInTheLoop/manifest.json | 7 - .../codemodernizer/humanInTheLoop/pom.xml | 19 - .../codemodernizer/min_jdk_upgrade.patch | 161 - .../codemodernizer/overwrittenFile | 1 - .../tst-resources/codemodernizer/simple.zip | Bin 24241 -> 0 bytes .../tst-resources/codemodernizer/test.txt | 1 - .../codemodernizer/CodeTransformChatTest.kt | 66 - .../CodeTransformHilDownloadArtifactTest.kt | 41 - .../CodeTransformTelemetryTest.kt | 49 - ...eWhispererCodeModernizerGumbyClientTest.kt | 279 -- .../CodeWhispererCodeModernizerSessionTest.kt | 616 --- .../CodeWhispererCodeModernizerTest.kt | 288 -- .../CodeWhispererCodeModernizerTestBase.kt | 366 -- .../CodeWhispererCodeModernizerUtilsTest.kt | 700 --- .../CodeModernizerJobHistoryPanelTest.kt | 32 - .../codemodernizer/panels/LoadingPanelTest.kt | 57 - .../codemodernizer/panels/PanelTestBase.kt | 20 - .../BuildProgressSplitterPanelManagerTest.kt | 26 - ...eModernizerBottomWindowPanelManagerTest.kt | 61 - .../ui/components/PanelHeaderFactoryTest.kt | 21 - .../utils/CodeTransformModuleUtilsTest.kt | 84 - .../utils/CodeTransformProjectUtilsTest.kt | 72 - .../amazonq/codewhisperer/build.gradle.kts | 11 - .../jetbrains-community/build.gradle.kts | 38 - .../detekt-baseline-main.xml | 19 - .../detekt-baseline-test.xml | 12 - .../jetbrains-community/detekt-baseline.xml | 16 - .../CodeWhispererCodeScanIntegrationTest.kt | 73 - ...odeWhispererCodeScanJavaIntegrationTest.kt | 28 - .../CodeWhispererCompletionIntegrationTest.kt | 60 - .../CodeWhispererIntegrationTestBase.kt | 244 - ...hispererReferenceTrackerIntegrationTest.kt | 70 - .../META-INF/plugin-codewhisperer.xml | 121 - .../resources/codewhisperer/licenses.json | 455 -- .../codewhisperer/popup/QManualCall.kt | 27 - .../codewhisperer/popup/QManualCall.kt | 23 - .../CodeWhispererExplorerActionManager.kt | 135 - .../CodeWhispererConnectOnGithubAction.kt | 25 - .../actions/CodeWhispererLearnMoreAction.kt | 26 - .../CodeWhispererProvideFeedbackAction.kt | 24 - .../CodeWhispererRecommendationAction.kt | 52 - .../CodeWhispererShowSettingsAction.kt | 25 - .../actions/CodeWhispererWhatIsAction.kt | 35 - .../codescan/AmazonQCodeFixSession.kt | 253 -- .../CodeWhispererCodeScanException.kt | 48 - ...WhispererCodeScanHighlightingFilesPanel.kt | 99 - .../CodeWhispererCodeScanIssueDetailsPanel.kt | 209 - .../codescan/CodeWhispererCodeScanManager.kt | 1134 ----- .../CodeWhispererCodeScanResultsView.kt | 423 -- .../codescan/CodeWhispererCodeScanSession.kt | 445 -- .../CodeWhispererCodeScanTreeModel.kt | 39 - .../CodeWhispererCodeScanFilterGroup.kt | 39 - ...ererCodeScanGroupingStrategyActionGroup.kt | 37 - .../CodeScanIssueDetailsDisplayType.kt | 8 - .../CodeWhispererCodeScanDocumentListener.kt | 59 - ...spererCodeScanEditorMouseMotionListener.kt | 286 -- .../CodeWhispererCodeScanFileListener.kt | 31 - .../sessionconfig/CodeScanSessionConfig.kt | 347 -- .../utils/AmazonQCodeReviewGitUtils.kt | 141 - .../utils/CodeWhispererCodeScanIssueUtils.kt | 494 --- .../codetest/CodeTestException.kt | 35 - .../sessionconfig/CodeTestSessionConfig.kt | 306 -- .../credentials/CodeWhispererClientAdaptor.kt | 634 --- .../credentials/CodeWhispererLoginType.kt | 12 - .../editor/CodeWhispererEditorManager.kt | 280 -- .../editor/CodeWhispererEditorManagerNew.kt | 256 -- .../editor/CodeWhispererEditorUtil.kt | 122 - .../CodeWhispererExplorerActionManager.kt | 56 - .../explorer/QStatusBarLoggedInActionGroup.kt | 95 - .../explorer/actions/ActionFactory.kt | 74 - .../explorer/actions/Customize.kt | 46 - .../codewhisperer/explorer/actions/Learn.kt | 23 - .../explorer/actions/OpenCodeReference.kt | 20 - .../codewhisperer/explorer/actions/Pause.kt | 21 - .../explorer/actions/PauseCodeScans.kt | 21 - .../codewhisperer/explorer/actions/Resume.kt | 21 - .../explorer/actions/ResumeCodeScans.kt | 29 - .../CodeWhispererFallbackImportAdder.kt | 61 - .../importadder/CodeWhispererImportAdder.kt | 172 - .../CodeWhispererImportAdderListener.kt | 46 - .../CodeWhispererJavaImportAdder.kt | 59 - .../CodeWhispererPythonImportAdder.kt | 106 - .../inlay/CodeWhispererInlayBlockRenderer.kt | 35 - .../inlay/CodeWhispererInlayInlineRenderer.kt | 25 - .../inlay/CodeWhispererInlayManager.kt | 92 - .../inlay/CodeWhispererInlayManagerNew.kt | 90 - .../inlay/CodeWhispererInlayRenderer.kt | 35 - .../InlineCompletionRemoteRendererFactory.kt | 37 - .../language/CodeWhispererLanguageManager.kt | 163 - .../CodeWhispererProgrammingLanguage.kt | 42 - .../CodeWhispereJavaClassResolver.kt | 35 - .../CodeWhispererClassResolver.kt | 26 - .../CodeWhispererPythonClassResolver.kt | 44 - .../language/languages/CodeWhispererAbap.kt | 19 - .../language/languages/CodeWhispererC.kt | 21 - .../language/languages/CodeWhispererCpp.kt | 21 - .../language/languages/CodeWhispererCsharp.kt | 21 - .../language/languages/CodeWhispererDart.kt | 20 - .../language/languages/CodeWhispererGo.kt | 21 - .../language/languages/CodeWhispererJava.kt | 21 - .../languages/CodeWhispererJavaScript.kt | 21 - .../language/languages/CodeWhispererJson.kt | 27 - .../language/languages/CodeWhispererJsx.kt | 21 - .../language/languages/CodeWhispererKotlin.kt | 19 - .../language/languages/CodeWhispererLua.kt | 20 - .../language/languages/CodeWhispererPhp.kt | 21 - .../languages/CodeWhispererPlainText.kt | 27 - .../languages/CodeWhispererPowershell.kt | 20 - .../language/languages/CodeWhispererPython.kt | 27 - .../language/languages/CodeWhispererR.kt | 19 - .../language/languages/CodeWhispererRuby.kt | 27 - .../language/languages/CodeWhispererRust.kt | 19 - .../language/languages/CodeWhispererScala.kt | 19 - .../language/languages/CodeWhispererShell.kt | 25 - .../language/languages/CodeWhispererSql.kt | 19 - .../language/languages/CodeWhispererSwift.kt | 20 - .../languages/CodeWhispererSystemVerilog.kt | 20 - .../language/languages/CodeWhispererTf.kt | 23 - .../language/languages/CodeWhispererTsx.kt | 21 - .../languages/CodeWhispererTypeScript.kt | 21 - .../languages/CodeWhispererUnknownLanguage.kt | 27 - .../language/languages/CodeWhispererVue.kt | 20 - .../language/languages/CodeWhispererYaml.kt | 27 - .../layout/CodeWhispererLayoutConfig.kt | 84 - .../learn/LearnCodeWhispererEditor.kt | 109 - .../learn/LearnCodeWhispererEditorProvider.kt | 50 - .../learn/LearnCodeWhispererManager.kt | 30 - .../learn/LearnCodeWhispererUIComponents.kt | 172 - .../learn/LearnCodeWhispererVirtualFile.kt | 24 - .../codewhisperer/model/CodeWhispererModel.kt | 260 -- .../popup/CodeWhispererPopupComponents.kt | 307 -- .../popup/CodeWhispererPopupListener.kt | 37 - .../popup/CodeWhispererPopupManager.kt | 707 --- .../popup/CodeWhispererPopupManagerNew.kt | 629 --- .../popup/CodeWhispererUIChangeListener.kt | 138 - .../popup/CodeWhispererUIChangeListenerNew.kt | 106 - .../popup/QInlineCompletionInsertHandler.kt | 11 - .../popup/QInlineCompletionProvider.kt | 698 --- .../popup/QInlineCompletionUtils.kt | 35 - .../CodeWhispererEditorActionHandler.kt | 11 - .../CodeWhispererPopupBackspaceHandlerNew.kt | 33 - .../CodeWhispererPopupEnterHandler.kt | 47 - .../handlers/CodeWhispererPopupEscHandler.kt | 24 - .../CodeWhispererPopupLeftArrowHandler.kt | 19 - .../CodeWhispererPopupRightArrowHandler.kt | 19 - .../handlers/CodeWhispererPopupTabHandler.kt | 21 - .../CodeWhispererPopupTypedHandler.kt | 41 - ...CodeWhispererAcceptButtonActionListener.kt | 26 - .../listeners/CodeWhispererActionListener.kt | 11 - .../CodeWhispererNextButtonActionListener.kt | 26 - .../CodeWhispererPrevButtonActionListener.kt | 26 - .../listeners/CodeWhispererScrollListener.kt | 41 - .../CodeWhispererAutoTriggerHandler.kt | 38 - .../CodeWhispererAutoTriggerService.kt | 76 - .../CodeWhispererAutomatedTriggerType.kt | 31 - .../CodeWhispererClassifierConstants.kt | 441 -- .../service/CodeWhispererInvocationStatus.kt | 103 - .../CodeWhispererInvocationStatusNew.kt | 81 - .../CodeWhispererLicenseInfoManager.kt | 30 - .../CodeWhispererRecommendationManager.kt | 41 - .../service/CodeWhispererService.kt | 660 --- .../service/CodeWhispererServiceNew.kt | 669 --- .../service/CodeWhispererUserGroupSettings.kt | 139 - .../settings/CodeWhispererConfigurable.kt | 333 -- ...hispererIntelliSenseAutoTriggerListener.kt | 59 - .../CodeWhispererProjectStartupActivity.kt | 97 - ...WhispererProjectStartupSettingsListener.kt | 66 - .../status/CodeWhispererStatusBarWidget.kt | 165 - .../CodeWhispererStatusBarWidgetFactory.kt | 31 - .../CodeWhispererTelemetryService.kt | 284 -- .../CodeWhispererTelemetryServiceNew.kt | 142 - ...odeWhispererCodeReferenceActionListener.kt | 29 - .../CodeWhispererCodeReferenceComponents.kt | 159 - .../CodeWhispererCodeReferenceManager.kt | 269 -- ...WhispererCodeReferenceToolWindowFactory.kt | 30 - .../services/codewhisperer/util/BM25.kt | 131 - .../util/CodeInsightsSettingsFacade.kt | 98 - .../util/CodeWhispererConstants.kt | 223 - .../util/CodeWhispererEndpointCustomizer.kt | 111 - .../util/CodeWhispererFileContextProvider.kt | 26 - .../util/CodeWhispererMetadata.kt | 8 - .../codewhisperer/util/CodeWhispererUtil.kt | 441 -- .../util/CodeWhispererZipUploadManager.kt | 267 -- .../util/GitIgnoreFilteringUtil.kt | 146 - .../jetbrains/utils/OffsetSuggestedFix.kt | 21 - .../util/CodeInsightsSettingsFacadeTest.kt | 137 - .../util/CodeWhispererUtilTest.kt | 58 - .../util/CodeInsightsSettingsFacadeTest.kt | 121 - .../util/CodeWhispererUtilTest.kt | 55 - .../services/codewhisperer/Bm25Test.kt | 90 - .../codewhisperer/CodeWhispererAcceptTest.kt | 148 - .../codewhisperer/CodeWhispererActionTest.kt | 97 - .../CodeWhispererBasicTestBase.kt | 18 - .../CodeWhispererClientAdaptorTest.kt | 302 -- .../CodeWhispererConfigurableTest.kt | 71 - .../CodeWhispererConstantsTest.kt | 23 - .../CodeWhispererEditorUtilTest.kt | 112 - .../CodeWhispererEndpointCustomizerTest.kt | 121 - .../CodeWhispererExplorerActionManagerTest.kt | 216 - .../CodeWhispererFeatureConfigServiceTest.kt | 202 - .../CodeWhispererFileContextProviderTest.kt | 106 - .../CodeWhispererLanguageManagerTest.kt | 298 -- .../CodeWhispererLicenseInfoManagerTest.kt | 32 - .../CodeWhispererModelConfiguratorTest.kt | 665 --- .../CodeWhispererNavigationTest.kt | 100 - .../CodeWhispererReferenceManagerTest.kt | 47 - .../codewhisperer/CodeWhispererServiceTest.kt | 129 - .../CodeWhispererSettingsTest.kt | 287 -- .../codewhisperer/CodeWhispererStateTest.kt | 79 - .../CodeWhispererTelemetryTest.kt | 123 - .../codewhisperer/CodeWhispererTestBase.kt | 344 -- .../codewhisperer/CodeWhispererTestUtil.kt | 187 - .../CodeWhispererTypeaheadTest.kt | 102 - .../CodeWhispererUserActionsTest.kt | 136 - .../CodeWhispererUserGroupSettingsTest.kt | 174 - .../CodeWhispererUserInputTest.kt | 61 - .../codewhisperer/CodeWhispererUtilTest.kt | 396 -- .../services/codewhisperer/QEndpointsTest.kt | 60 - .../QRegionProfileManagerTest.kt | 432 -- .../codescan/CodeWhispererCodeFileScanTest.kt | 474 -- .../codescan/CodeWhispererCodeScanTest.kt | 336 -- .../codescan/CodeWhispererCodeScanTestBase.kt | 466 -- .../CodeWhispererProjectCodeScanTest.kt | 391 -- .../codetest/CodeTestSessionConfigTest.kt | 312 -- .../CodeWhispererFallbackImportAdderTest.kt | 84 - .../CodeWhispererJavaImportAdderTest.kt | 139 - .../CodeWhispererPythonImportAdderTest.kt | 324 -- .../util/GitIgnoreFilteringUtilTest.kt | 93 - .../utils/OffsetSuggestedFixKtTest.kt | 114 - .../CodeWhispererImportAdderTestBase.kt | 91 - .../jetbrains-ultimate/build.gradle.kts | 36 - .../importadder/CodeWhispererJSImportAdder.kt | 80 - .../importadder/CodeWhispererJSImportUtil.kt | 13 - .../CodeWhispererJSImportAdderTest.kt | 268 -- .../amazonq/contrib/QCT-Maven-1-0-156-0.jar | Bin 151890 -> 0 bytes plugins/amazonq/mynah-ui/.eslintignore | 10 - plugins/amazonq/mynah-ui/.eslintrc.js | 121 - plugins/amazonq/mynah-ui/.gitignore | 11 - plugins/amazonq/mynah-ui/build.gradle.kts | 36 - plugins/amazonq/mynah-ui/package-lock.json | 3811 ---------------- plugins/amazonq/mynah-ui/package.json | 55 - .../mynah-ui/src/mynah-ui/connectorAdapter.ts | 98 - .../ui/apps/amazonqCommonsConnector.ts | 102 - .../mynah-ui/ui/apps/codeScanChatConnector.ts | 183 - .../ui/apps/codeTransformChatConnector.ts | 374 -- .../src/mynah-ui/ui/apps/cwChatConnector.ts | 389 -- .../mynah-ui/src/mynah-ui/ui/commands.ts | 76 - .../mynah-ui/src/mynah-ui/ui/connector.ts | 443 -- .../mynah-ui/src/mynah-ui/ui/defs.d.ts | 9 - .../src/mynah-ui/ui/diffTree/actions.ts | 61 - .../src/mynah-ui/ui/diffTree/types.ts | 8 - .../src/mynah-ui/ui/feedback/constants.ts | 31 - .../src/mynah-ui/ui/followUps/generator.ts | 67 - .../src/mynah-ui/ui/followUps/handler.ts | 139 - .../src/mynah-ui/ui/followUps/model.ts | 11 - .../src/mynah-ui/ui/forms/constants.ts | 59 - .../amazonq/mynah-ui/src/mynah-ui/ui/main.ts | 845 ---- .../src/mynah-ui/ui/messages/controller.ts | 132 - .../src/mynah-ui/ui/messages/handler.ts | 56 - .../src/mynah-ui/ui/quickActions/generator.ts | 89 - .../src/mynah-ui/ui/quickActions/handler.ts | 155 - .../src/mynah-ui/ui/storages/tabsStorage.ts | 144 - .../mynah-ui/src/mynah-ui/ui/styles.css | 13 - .../mynah-ui/src/mynah-ui/ui/styles/dark.scss | 6 - .../src/mynah-ui/ui/tabs/generator.ts | 124 - .../src/mynah-ui/ui/telemetry/actions.ts | 38 - .../src/mynah-ui/ui/texts/constants.ts | 30 - .../src/mynah-ui/ui/texts/disclaimer.ts | 20 - .../src/mynah-ui/ui/walkthrough/agent.ts | 64 - .../src/mynah-ui/ui/walkthrough/welcome.ts | 55 - plugins/amazonq/mynah-ui/tsconfig.json | 16 - .../amazonq/mynah-ui/webpack.media.config.js | 44 - plugins/amazonq/shared/build.gradle.kts | 11 - .../jetbrains-community/build.gradle.kts | 46 - .../resources/META-INF/module-amazonq.xml | 13 - .../resources/AmazonQBundle.properties | 28 - .../providers/PythonModuleUtil.kt | 9 - .../amazonq/lsp/util/FileChooserFilters.kt | 29 - .../amazonq/lsp/util/FileChooserFilters.kt | 22 - .../providers/PythonModuleUtil.kt | 9 - .../CodeWhispererModelConfigurator.kt | 48 - .../jetbrains/DefaultProblemsViewMutator.kt | 14 - .../toolkits/jetbrains/ProblemsViewMutator.kt | 16 - .../CodeWhispererFeatureConfigListener.kt | 20 - .../CodeWhispererFeatureConfigService.kt | 211 - .../jetbrains/services/amazonq/Constants.kt | 47 - .../jetbrains/services/amazonq/QUtils.kt | 65 - .../services/amazonq/RetryableOperation.kt | 53 - .../amazonq/actions/QSwitchProfilesAction.kt | 36 - .../amazonq/clients/AmazonQStreamingClient.kt | 109 - .../amazonq/gettingstarted/QActionGroups.kt | 8 - .../gettingstarted/QGettingStartedUtils.kt | 33 - .../QGettingStartedVirtualFile.kt | 22 - .../services/amazonq/lsp/AmazonQChatServer.kt | 267 -- .../amazonq/lsp/AmazonQDiffVirtualFile.kt | 37 - .../amazonq/lsp/AmazonQLanguageClient.kt | 91 - .../amazonq/lsp/AmazonQLanguageClientImpl.kt | 607 --- .../amazonq/lsp/AmazonQLanguageServer.kt | 45 - .../amazonq/lsp/AmazonQLspConfiguration.kt | 36 - .../amazonq/lsp/AmazonQLspConstants.kt | 17 - .../services/amazonq/lsp/AmazonQLspService.kt | 617 --- .../lsp/CodeWhispererLspConfiguration.kt | 17 - .../services/amazonq/lsp/NodeExePatcher.kt | 192 - .../services/amazonq/lsp/TrustChainUtil.kt | 144 - .../amazonq/lsp/artifacts/ArtifactHelper.kt | 256 -- .../amazonq/lsp/artifacts/ArtifactManager.kt | 184 - .../amazonq/lsp/artifacts/LspException.kt | 21 - .../amazonq/lsp/artifacts/LspUtils.kt | 103 - .../amazonq/lsp/artifacts/ManifestFetcher.kt | 177 - .../lsp/auth/DefaultAuthCredentialsService.kt | 212 - .../DefaultModuleDependenciesService.kt | 80 - .../dependencies/ModuleDependenciesService.kt | 10 - .../dependencies/ModuleDependencyProvider.kt | 25 - .../providers/JavaModuleDependencyProvider.kt | 35 - .../PythonModuleDependencyProvider.kt | 39 - .../lsp/encryption/JwtEncryptionManager.kt | 65 - .../flareChat/AmazonQLspTypeAdapterFactory.kt | 36 - .../lsp/flareChat/ChatCommunicationManager.kt | 248 -- .../amazonq/lsp/flareChat/FlareUiMessage.kt | 12 - .../flareChat/ProgressNotificationUtils.kt | 30 - .../model/EncryptionInitializationRequest.kt | 20 - .../amazonq/lsp/model/EnumJsonValueAdapter.kt | 42 - .../lsp/model/ExtendedClientMetadata.kt | 90 - .../lsp/model/aws/AwsServerCapabilities.kt | 57 - .../aws/GetConfigurationFromServerParams.kt | 8 - .../lsp/model/aws/InlineCompletionStates.kt | 10 - ...LogInlineCompletionSessionResultsParams.kt | 16 - .../lsp/model/aws/LspServerConfigurations.kt | 17 - .../lsp/model/aws/chat/AuthFollowUpClicked.kt | 27 - .../amazonq/lsp/model/aws/chat/ButtonClick.kt | 20 - .../amazonq/lsp/model/aws/chat/ChatMessage.kt | 114 - .../lsp/model/aws/chat/ChatNotification.kt | 9 - .../amazonq/lsp/model/aws/chat/ChatPrompt.kt | 36 - .../model/aws/chat/ChatReadyNotification.kt | 9 - .../amazonq/lsp/model/aws/chat/ChatResult.kt | 54 - .../lsp/model/aws/chat/ChatUiMessageParams.kt | 14 - .../lsp/model/aws/chat/Conversations.kt | 101 - .../lsp/model/aws/chat/CopyCodeToClipboard.kt | 20 - .../lsp/model/aws/chat/CopyFileParams.kt | 9 - .../lsp/model/aws/chat/CreatePromptParams.kt | 14 - .../amazonq/lsp/model/aws/chat/CursorState.kt | 17 - .../amazonq/lsp/model/aws/chat/ErrorParams.kt | 11 - .../amazonq/lsp/model/aws/chat/Feedback.kt | 22 - .../amazonq/lsp/model/aws/chat/FileClick.kt | 22 - .../amazonq/lsp/model/aws/chat/FileParams.kt | 8 - .../lsp/model/aws/chat/FlareChatCommands.kt | 68 - .../lsp/model/aws/chat/FollowUpClick.kt | 15 - .../model/aws/chat/GenericCommandParams.kt | 31 - .../model/aws/chat/GetSerializedChatParams.kt | 40 - .../lsp/model/aws/chat/InsertToCursor.kt | 25 - .../amazonq/lsp/model/aws/chat/LinkClick.kt | 38 - .../lsp/model/aws/chat/ListAvailableModels.kt | 20 - .../lsp/model/aws/chat/OpenFileDiffParams.kt | 11 - .../lsp/model/aws/chat/OpenSettingsParams.kt | 15 - .../aws/chat/PromptInputOptionChangeParams.kt | 15 - .../model/aws/chat/QuickChatActionRequest.kt | 20 - .../lsp/model/aws/chat/SendChatPrompt.kt | 20 - .../aws/chat/ShowOpenFileDialogParams.kt | 12 - .../aws/chat/ShowSaveFileDialogParams.kt | 13 - .../lsp/model/aws/chat/StopResponseParams.kt | 12 - .../lsp/model/aws/chat/TabBarActionParams.kt | 27 - .../amazonq/lsp/model/aws/chat/TabEvent.kt | 34 - .../aws/credentials/ConnectionMetadata.kt | 32 - .../credentials/UpdateCredentialsPayload.kt | 18 - .../DidChangeDependencyPathsParams.kt | 12 - .../textDocument/InlineCompletionContext.kt | 9 - .../aws/textDocument/InlineCompletionItem.kt | 11 - .../InlineCompletionListWithReferences.kt | 12 - .../textDocument/InlineCompletionReference.kt | 15 - .../InlineCompletionReferencePosition.kt | 9 - .../InlineCompletionTriggerKind.kt | 9 - .../InlineCompletionWithReferencesParams.kt | 13 - .../textDocument/SelectedCompletionInfo.kt | 11 - .../TextDocumentServiceHandler.kt | 260 -- .../amazonq/lsp/util/LspEditorUtil.kt | 133 - .../amazonq/lsp/util/TelemetryParsingUtil.kt | 16 - .../amazonq/lsp/util/WorkspaceFolderUtil.kt | 26 - .../lsp/workspace/WorkspaceServiceHandler.kt | 323 -- .../services/amazonq/profile/QEndpoints.kt | 44 - .../amazonq/profile/QProfileResources.kt | 59 - .../amazonq/profile/QProfileSwitchIntent.kt | 21 - .../amazonq/profile/QRegionProfile.kt | 24 - .../amazonq/profile/QRegionProfileDialog.kt | 143 - .../amazonq/profile/QRegionProfileManager.kt | 255 -- .../profile/QRegionProfileSelectedListener.kt | 16 - .../project/FeatureDevSessionContext.kt | 234 - .../services/amazonq/project/LspMessage.kt | 87 - .../amazonq/project/RelevantDocument.kt | 9 - .../services/amazonq/project/Workspace.kt | 111 - .../amazonq/toolwindow/AmazonQToolWindowId.kt | 6 - .../utils/CodeTransformSharedUtils.kt | 30 - .../actions/ManageSubscription.kt | 58 - .../CodeWhispererCustomization.kt | 19 - .../CodeWhispererCustomizationDialog.kt | 286 -- .../CodeWhispererCustomizationListener.kt | 20 - .../CodeWhispererModelConfigurator.kt | 358 -- .../services/codewhisperer/learn/QFileType.kt | 17 - .../UserModificationTrackingEntry.kt | 10 - .../util/CodeWhispererColorUtil.kt | 26 - .../util/CustomizationConstants.kt | 28 - .../InsertedCodeModificationEntry.kt | 18 - .../chat/telemetry/QTelemetryUtils.kt | 14 - .../settings/CodeWhispererSettings.kt | 204 - .../jetbrains/settings/LspSettings.kt | 46 - .../jetbrains/settings/MeetQSettings.kt | 56 - .../feedback/CodeWhispererFeedbackDialog.kt | 25 - .../ui/feedback/FeatureDevFeedbackDialog.kt | 22 - .../ui/feedback/TestGenFeedbackDialog.kt | 26 - .../aws/toolkits/resources/AmazonQBundle.kt | 18 - .../lsp/artifacts/ArtifactManagerTest.kt | 146 - .../auth/DefaultAuthCredentialsServiceTest.kt | 278 -- .../lsp/artifacts/ArtifactManagerTest.kt | 130 - .../auth/DefaultAuthCredentialsServiceTest.kt | 262 -- .../lsp/AmazonQLanguageClientImplTest.kt | 544 --- .../amazonq/lsp/NodeExePatcherTest.kt | 125 - .../amazonq/lsp/TrustChainUtilTest.kt | 471 -- .../lsp/artifacts/ArtifactHelperTest.kt | 239 - .../amazonq/lsp/artifacts/LspUtilsTest.kt | 103 - .../lsp/artifacts/ManifestFetcherTest.kt | 119 - .../DefaultModuleDependenciesServiceTest.kt | 254 -- .../encryption/JwtEncryptionManagerTest.kt | 77 - .../lsp/model/aws/chat/ChatMessageTest.kt | 73 - .../TextDocumentServiceHandlerTest.kt | 376 -- .../lsp/util/ApplyWorkspaceEditTest.kt | 212 - .../amazonq/lsp/util/FileUriUtilTest.kt | 173 - .../lsp/util/WorkspaceFolderUtilTest.kt | 110 - .../workspace/WorkspaceServiceHandlerTest.kt | 834 ---- .../jetbrains/settings/LspSettingsTest.kt | 95 - .../jetbrains-ultimate/build.gradle.kts | 28 - .../jetbrains/CwmProblemsViewMutator.kt | 19 - .../META-INF/amazonq-ext-codewithme.xml | 8 - .../resources/META-INF/amazonq-ext-java.xml | 16 - .../resources/META-INF/amazonq-ext-nodejs.xml | 12 - .../resources/META-INF/amazonq-ext-python.xml | 15 - .../src/main/resources/META-INF/plugin.xml | 202 - .../main/resources/META-INF/pluginIcon.svg | 21 - .../amazonq/src/main/resources/icons/file.svg | 7 - ...PluginAmazonQJvmBinaryCompatabilityTest.kt | 76 - .../PluginCoreJvmBinaryCompatabilityTest.kt | 62 - plugins/core-q/build.gradle.kts | 40 - plugins/core-q/core-q/build.gradle.kts | 34 - .../detekt-baseline-integrationTest.xml | 7 - .../core-q/core-q/detekt-baseline-main.xml | 11 - .../core-q/core-q/detekt-baseline-test.xml | 9 - plugins/core-q/core-q/detekt-baseline.xml | 11 - .../aws/toolkits/core/s3/BucketUtilsTest.kt | 71 - .../amazon/q/core/ToolkitClientManager.kt | 270 -- .../q/core/clients/SdkClientProvider.kt | 10 - .../q/core/region/ToolkitRegionProvider.kt | 88 - .../amazon/q/core/ConnectionSettings.kt | 41 - .../amazon/q/core/ToolkitClientCustomizer.kt | 34 - .../amazon/q/core/ToolkitClientManager.kt | 8 - .../q/core/clients/ClientBuilderUtils.kt | 25 - .../q/core/clients/SdkClientProvider.kt | 8 - .../q/core/credentials/AwsCredentials.kt | 43 - .../credentials/CredentialProviderFactory.kt | 37 - .../credentials/CredentialsChangeEvent.kt | 20 - .../q/core/credentials/SsoUrlIdentifier.kt | 38 - .../credentials/ToolkitCredentialsProvider.kt | 147 - .../ToolkitCredentialsProviderManager.kt | 23 - .../q/core/lambda/LambdaArchitecture.kt | 32 - .../amazon/q/core/lambda/LambdaRuntime.kt | 49 - .../core/lambda/LambdaSampleEventProvider.kt | 111 - .../amazon/q/core/lambda/LambdaUtils.kt | 10 - .../amazon/q/core/region/AwsPartition.kt | 8 - .../amazon/q/core/region/AwsRegion.kt | 52 - .../amazon/q/core/region/Partitions.kt | 61 - .../q/core/region/ToolkitRegionProvider.kt | 8 - .../software/amazon/q/core/s3/BucketUtils.kt | 31 - .../q/core/telemetry/CachedIdentityStorage.kt | 26 - .../amazon/q/core/telemetry/MetricEvent.kt | 192 - .../q/core/telemetry/TelemetryBatcher.kt | 143 - .../q/core/telemetry/TelemetryPublisher.kt | 12 - .../amazon/q/core/utils/AttributeBag.kt | 32 - .../amazon/q/core/utils/CollectionUtils.kt | 20 - .../software/amazon/q/core/utils/Either.kt | 15 - .../amazon/q/core/utils/ExceptionUtils.kt | 14 - .../software/amazon/q/core/utils/LogUtils.kt | 110 - .../software/amazon/q/core/utils/PathUtils.kt | 217 - .../q/core/utils/RemoteResourceResolver.kt | 168 - .../amazon/q/core/utils/SensitiveField.kt | 50 - .../amazon/q/core/utils/StringUtils.kt | 14 - .../software/amazon/q/core/utils/TextUtils.kt | 72 - .../software/amazon/q/core/utils/Waiter.kt | 133 - .../software/amazon/q/core/utils/ZipUtils.kt | 48 - .../tst-resources/jsonSampleFailure.json | 20 - .../tst-resources/jsonSampleSuccess.json | 63 - .../tst-resources/sampleLambdaEvent.json | 4 - .../core-q/tst-resources/xmlSampleFailure.xml | 13 - .../core-q/tst-resources/xmlSampleSuccess.xml | 13 - .../AwsCredentialsExtensionsTest.kt | 64 - .../amazon/q/core/credentials/Mocks.kt | 25 - .../core/credentials/SsoUrlIdentifierTest.kt | 56 - .../lambda/LambdaSampleEventProviderTest.kt | 104 - .../core/parser/EndpointsJsonValidatorTest.kt | 24 - .../parser/LambdaManifestValidatorTest.kt | 25 - .../LambdaSampleEventJsonValidatorTest.kt | 23 - .../amazon/q/core/region/AwsRegionTest.kt | 112 - .../q/core/region/PartitionParserTest.kt | 24 - .../q/core/rules/ECSTemporaryServiceRule.kt | 53 - .../core/rules/EcrTemporaryRepositoryRule.kt | 48 - .../q/core/rules/EnvironmentVariableHelper.kt | 66 - .../q/core/rules/S3TemporaryBucketRule.kt | 50 - .../q/core/rules/SystemPropertyHelper.kt | 25 - .../q/core/telemetry/TelemetryBatcherTest.kt | 203 - .../q/core/utils/CollectionUtilsTest.kt | 27 - .../q/core/utils/CompletionStageUtils.kt | 17 - .../q/core/utils/DelegateSdkConsumers.kt | 54 - .../amazon/q/core/utils/EitherTest.kt | 39 - .../amazon/q/core/utils/ExceptionUtilsTest.kt | 16 - .../core/utils/IntegrationTestCredentials.kt | 39 - .../amazon/q/core/utils/LogUtilsTest.kt | 181 - .../core/utils/RemoteResourceResolverTest.kt | 194 - .../software/amazon/q/core/utils/RuleUtils.kt | 21 - .../amazon/q/core/utils/RuleUtilsTest.kt | 27 - .../amazon/q/core/utils/ZipUtilsTest.kt | 69 - .../q/core/utils/test/AssertJAsserts.kt | 29 - .../amazon/q/core/utils/test/TestUtils.kt | 41 - .../amazon/q/core/utils/test/TestUtilsTest.kt | 36 - .../q/jetbrains/utils/AttributeBagTest.kt | 48 - .../q/jetbrains/utils/StringUtilsTest.kt | 25 - .../jetbrains-community/build.gradle.kts | 140 - .../detekt-baseline-main.xml | 20 - .../detekt-baseline-test.xml | 15 - .../detekt-baseline-testFixtures.xml | 13 - .../jetbrains-community/detekt-baseline.xml | 22 - .../resources/icons/logos/AWS.svg | 12 - .../resources/icons/logos/AWS_Q.svg | 4 - .../resources/icons/logos/AWS_Q_dark.svg | 4 - .../resources/icons/logos/AWS_dark.svg | 12 - .../resources/icons/logos/AWS_smile.svg | 41 - .../resources/icons/logos/AWS_smile_Large.svg | 41 - .../icons/logos/AWS_smile_Large_dark.svg | 41 - .../resources/icons/logos/AWS_smile_dark.svg | 41 - .../logos/Amazon-Q-Icon_Gradient_Large.svg | 17 - .../logos/Amazon-Q-Icon_Gradient_Medium.svg | 17 - .../logos/Amazon-Q-Icon_Squid-Ink_Medium.svg | 4 - .../logos/Amazon-Q-Icon_White_Medium.svg | 4 - .../logos/Amazon_CodeCatalyst_Medium.svg | 7 - .../logos/Amazon_CodeCatalyst_Medium_dark.svg | 7 - .../icons/logos/Amazon_CodeCatalyst_Small.svg | 7 - .../logos/Amazon_CodeCatalyst_Small_dark.svg | 7 - .../resources/icons/logos/Amazon_Q_grey.svg | 4 - .../icons/logos/CW_InlineSuggestions_dark.svg | 15 - .../logos/CW_InlineSuggestions_light.svg | 15 - .../icons/logos/CloudFormationTool.svg | 15 - .../icons/logos/CloudFormationTool_dark.svg | 15 - .../icons/logos/CodeWhisperer_Large.svg | 12 - .../resources/icons/logos/EventBridge.svg | 11 - .../icons/logos/EventBridge_dark.svg | 11 - .../resources/icons/logos/MynahIcon.svg | 48 - .../resources/icons/logos/MynahIcon_dark.svg | 50 - .../resources/icons/misc/csharp.svg | 10 - .../resources/icons/misc/csharp_dark.svg | 10 - .../resources/icons/misc/frown.svg | 4 - .../resources/icons/misc/frown_dark.svg | 4 - .../resources/icons/misc/java.svg | 6 - .../resources/icons/misc/javaScript.svg | 6 - .../resources/icons/misc/javaScript_dark.svg | 6 - .../resources/icons/misc/java_dark.svg | 6 - .../resources/icons/misc/learn.svg | 5 - .../resources/icons/misc/learn_dark.svg | 5 - .../resources/icons/misc/new.svg | 5 - .../resources/icons/misc/python.svg | 8 - .../resources/icons/misc/smile.svg | 4 - .../resources/icons/misc/smile_dark.svg | 4 - .../resources/icons/misc/smile_grey.svg | 4 - .../resources/icons/misc/smile_grey_dark.svg | 4 - .../resources/icons/misc/typeScript.svg | 5 - .../resources/icons/misc/typeScript_dark.svg | 5 - .../icons/resources/AppRunnerService.svg | 9 - .../icons/resources/AppRunnerService_dark.svg | 9 - .../icons/resources/CloudFormationStack.svg | 11 - .../resources/CloudFormationStack_dark.svg | 11 - .../icons/resources/CodewhispererCustom.svg | 7 - .../icons/resources/ECRRepository.svg | 11 - .../icons/resources/ECRRepository_dark.svg | 11 - .../icons/resources/LambdaFunction.svg | 12 - .../icons/resources/LambdaFunction_dark.svg | 12 - .../resources/icons/resources/Redshift.svg | 13 - .../icons/resources/Redshift_dark.svg | 13 - .../resources/icons/resources/S3Bucket.svg | 11 - .../icons/resources/S3Bucket_dark.svg | 11 - .../resources/icons/resources/Schema.svg | 13 - .../icons/resources/SchemaRegistry.svg | 11 - .../icons/resources/SchemaRegistry_dark.svg | 11 - .../resources/icons/resources/Schema_dark.svg | 13 - .../icons/resources/ServerlessApp.svg | 18 - .../icons/resources/ServerlessApp_dark.svg | 23 - .../cloudwatchlogs/CloudWatchLogs.svg | 9 - .../cloudwatchlogs/CloudWatchLogsGroup.svg | 9 - .../CloudWatchLogsGroup_dark.svg | 9 - .../CloudWatchLogsToolWindow.svg | 9 - .../CloudWatchLogsToolWindow_dark.svg | 9 - .../cloudwatchlogs/CloudWatchLogs_dark.svg | 9 - .../resources/codetransform/checkmark.svg | 3 - .../codetransform/greenCheckmark.svg | 3 - .../codetransform/transform-arrow-dark.svg | 6 - .../codetransform/transform-arrow-light.svg | 6 - .../codetransform/transform-default-dark.svg | 4 - .../codetransform/transform-default-light.svg | 4 - .../transform-dependencies-dark.svg | 7 - .../transform-dependencies-light.svg | 7 - .../codetransform/transform-file-dark.svg | 5 - .../codetransform/transform-file-light.svg | 5 - .../transform-step-into-dark.svg | 9 - .../transform-step-into-light.svg | 9 - .../transform-timeline-step-done-light.svg | 4 - .../transform-timeline-step-done.svg | 4 - .../transform-variables-dark.svg | 7 - .../transform-variables-light.svg | 7 - .../codewhisperer/severity-critical.svg | 1 - .../resources/codewhisperer/severity-high.svg | 1 - .../resources/codewhisperer/severity-info.svg | 1 - .../severity-initial-critical.svg | 4 - .../codewhisperer/severity-initial-high.svg | 4 - .../codewhisperer/severity-initial-info.svg | 4 - .../codewhisperer/severity-initial-low.svg | 4 - .../codewhisperer/severity-initial-medium.svg | 4 - .../resources/codewhisperer/severity-low.svg | 1 - .../codewhisperer/severity-medium.svg | 1 - .../resources/dynamodb/DynamoDbTable.svg | 7 - .../resources/dynamodb/DynamoDbTable_dark.svg | 7 - .../icons/resources/ecs/EcsCluster.svg | 11 - .../icons/resources/ecs/EcsCluster_dark.svg | 11 - .../icons/resources/ecs/EcsService.svg | 11 - .../icons/resources/ecs/EcsService_dark.svg | 11 - .../icons/resources/ecs/EcsTaskDefinition.svg | 11 - .../resources/ecs/EcsTaskDefinition_dark.svg | 11 - .../resources/icons/resources/rds/Mysql.svg | 9 - .../icons/resources/rds/Mysql_dark.svg | 9 - .../icons/resources/rds/Postgres.svg | 9 - .../icons/resources/rds/Postgres_dark.svg | 9 - .../icons/resources/sqs/SqsQueue.svg | 13 - .../icons/resources/sqs/SqsQueue_dark.svg | 13 - .../icons/resources/sqs/SqsToolWindow.svg | 13 - .../resources/sqs/SqsToolWindow_dark.svg | 13 - .../resources/oauthCallback/auth.css | 93 - .../resources/oauthCallback/index.html | 99 - .../resources/telemetryOverride.json | 429 -- .../services/telemetry/otel/OTelService.kt | 171 - .../services/telemetry/otel/OTelService.kt | 171 - .../impl/jcef/JBCefLocalRequestHandler.kt | 87 - .../impl/jcef/JBCefStreamResourceHandler.kt | 78 - .../jetbrains-community/src/icons/AwsIcons.kt | 191 - .../q/jetbrains/core/AwsResourceCache.kt | 155 - .../core/RemoteResourceResolverProvider.kt | 15 - .../coroutines/PluginCoroutineScopeTracker.kt | 37 - .../core/credentials/CredentialManager.kt | 139 - .../core/credentials/ToolkitAuthManager.kt | 38 - .../pinning/ConnectionPinningManager.kt | 20 - .../credentials/profiles/ProfileWatcher.kt | 15 - .../sso/SsoLoginCallbackProvider.kt | 10 - .../q/jetbrains/settings/AwsSettings.kt | 26 - .../q/jetbrains/telemetry/TelemetryService.kt | 125 - .../software/amazon/q/jetbrains/AwsToolkit.kt | 42 - .../amazon/q/jetbrains/IsDeveloperMode.kt | 8 - .../amazon/q/jetbrains/ToolkitPlaces.kt | 13 - .../q/jetbrains/core/AwsClientManager.kt | 118 - .../q/jetbrains/core/AwsResourceCache.kt | 420 -- .../amazon/q/jetbrains/core/AwsSdkClient.kt | 66 - .../q/jetbrains/core/AwsTelemetryPrompter.kt | 37 - .../DefaultRemoteResourceResolverProvider.kt | 46 - .../amazon/q/jetbrains/core/HttpUtils.kt | 33 - .../q/jetbrains/core/coroutines/contexts.kt | 37 - .../q/jetbrains/core/coroutines/scopes.kt | 74 - .../core/credentials/AwsConnectionManager.kt | 375 -- .../AwsConnectionManagerConnection.kt | 15 - .../ChangeConnectionSettingIfValid.kt | 10 - .../core/credentials/ConfigFilesFacade.kt | 291 -- .../CreateOrUpdateCredentialProfilesAction.kt | 103 - .../core/credentials/CredentialManager.kt | 86 - .../credentials/CredentialTelemetryUtil.kt | 29 - .../credentials/CredentialsRegionHandler.kt | 65 - .../DefaultAwsConnectionManager.kt | 64 - .../credentials/DefaultToolkitAuthManager.kt | 307 -- .../DefaultToolkitConnectionManager.kt | 166 - .../core/credentials/InteractiveCredential.kt | 22 - .../jetbrains/core/credentials/LoginUtils.kt | 262 -- .../MfaRequiredInteractiveCredentials.kt | 16 - .../jetbrains/core/credentials/MfaSupport.kt | 17 - .../PostValidateInteractiveCredential.kt | 11 - .../credentials/RefreshConnectionAction.kt | 38 - .../SsoRequiredInteractiveCredentials.kt | 34 - .../jetbrains/core/credentials/SsoSupport.kt | 11 - .../core/credentials/ToolkitAuthManager.kt | 423 -- .../credentials/ToolkitConnectionImpls.kt | 123 - .../ToolkitConnectionManagerListener.kt | 17 - .../ToolkitCredentialProcessProvider.kt | 112 - .../credentials/actions/SsoLogoutAction.kt | 34 - .../pinning/CodeCatalystConnection.kt | 31 - .../pinning/ConnectionPinningManager.kt | 136 - .../ConnectionPinningManagerListener.kt | 17 - .../core/credentials/pinning/QConnection.kt | 25 - .../profiles/CredentialSourceType.kt | 21 - .../profiles/DEFAULT_PROFILE_ID.kt | 6 - .../profiles/Ec2MetadataConfigProvider.kt | 63 - .../profiles/ProfileAssumeRoleProvider.kt | 84 - .../ProfileCredentialProviderFactory.kt | 522 --- .../profiles/ProfileLegacySsoProvider.kt | 57 - .../credentials/profiles/ProfileReader.kt | 125 - .../profiles/ProfileSsoSessionIdentifier.kt | 15 - .../profiles/ProfileSsoSessionProvider.kt | 61 - .../core/credentials/profiles/ProfileUtils.kt | 69 - .../credentials/profiles/ProfileWatcher.kt | 93 - .../profiles/SsoSessionConstants.kt | 10 - .../core/credentials/sono/SonoConstants.kt | 38 - .../core/credentials/sso/AccessToken.kt | 94 - .../core/credentials/sso/Authorization.kt | 26 - .../credentials/sso/ClientRegistration.kt | 77 - .../core/credentials/sso/DiskCache.kt | 322 -- .../credentials/sso/SsoAccessTokenProvider.kt | 636 --- .../core/credentials/sso/SsoCache.kt | 17 - .../credentials/sso/SsoCredentialProvider.kt | 77 - .../core/credentials/sso/SsoLoginCallback.kt | 24 - .../sso/SsoLoginCallbackProvider.kt | 139 - .../sso/bearer/BearerTokenProvider.kt | 323 -- .../sso/bearer/BearerTokenProviderListener.kt | 34 - .../sso/bearer/ConfirmUserCodeLoginDialog.kt | 72 - .../sso/pkce/ToolkitOAuthService.kt | 280 -- .../gettingstarted/GettingStartedAuthUtils.kt | 246 - .../core/gettingstarted/IdcRolePopup.kt | 158 - .../SetupAuthenticationDialog.kt | 499 --- .../editor/GettingStartedPanelUtils.kt | 212 - .../editor/GettingStartedTelemetryUtils.kt | 136 - .../q/jetbrains/core/help/HelpIdTranslator.kt | 25 - .../amazon/q/jetbrains/core/help/HelpIds.kt | 138 - .../notifications/CustomizeNotificationsUi.kt | 129 - .../DisplayToastNotifications.kt | 6 - .../NotificationCustomDeserializers.kt | 127 - .../notifications/NotificationFormatUtils.kt | 200 - .../core/notifications/NotificationPanel.kt | 44 - .../NotificationPollingService.kt | 143 - .../NotificationServiceInitializer.kt | 23 - .../notifications/NotificationStateUtils.kt | 109 - .../notifications/ProcessNotificationsBase.kt | 139 - .../core/notifications/RulesEngine.kt | 209 - .../core/plugin/PluginAutoUpdater.kt | 24 - .../core/plugin/PluginUpdateManager.kt | 232 - .../core/region/AwsRegionProvider.kt | 67 - .../jetbrains/core/webview/BrowserMessage.kt | 78 - .../webview/LocalAssetJBCefRequestHandler.kt | 83 - .../q/jetbrains/core/webview/LoginBrowser.kt | 460 -- .../core/webview/WebviewTelemetryUtils.kt | 28 - .../jetbrains/services/amazonq/QConstants.kt | 10 - .../explorerActions/QLearnMoreAction.kt | 25 - .../q/jetbrains/services/sts/StsResources.kt | 17 - .../AwsCognitoCredentialsProvider.kt | 87 - .../telemetry/AwsToolkitStartupMetrics.kt | 17 - .../services/telemetry/ClientMetadata.kt | 30 - .../telemetry/DefaultTelemetryPublisher.kt | 134 - .../services/telemetry/OpenTelemetryAction.kt | 112 - .../telemetry/OpenedFileTypesMetrics.kt | 66 - .../services/telemetry/PluginResolver.kt | 52 - .../services/telemetry/TelemetryService.kt | 31 - .../services/telemetry/TelemetryUtils.kt | 282 -- .../services/telemetry/otel/OtelBase.kt | 262 -- .../otel/ToolkitTelemetryOTelSpanProcessor.kt | 72 - .../q/jetbrains/settings/AwsSettings.kt | 140 - .../settings/AwsSettingsSharedConfigurable.kt | 51 - .../amazon/q/jetbrains/ui/AsyncComboBox.kt | 187 - .../q/jetbrains/ui/KeyValueTextField.kt | 126 - .../q/jetbrains/ui/feedback/FeedbackDialog.kt | 226 - .../amazon/q/jetbrains/utils/DevFileUtils.kt | 15 - .../amazon/q/jetbrains/utils/FunctionUtils.kt | 60 - .../amazon/q/jetbrains/utils/MRUList.kt | 26 - .../q/jetbrains/utils/NotificationUtils.kt | 185 - .../amazon/q/jetbrains/utils/PsiUtils.kt | 27 - .../q/jetbrains/utils/RemoteEnvUtils.kt | 39 - .../amazon/q/jetbrains/utils/SpinUtils.kt | 42 - .../amazon/q/jetbrains/utils/TextUtils.kt | 44 - .../q/jetbrains/utils/ThreadingUtils.kt | 90 - .../utils/actions/OpenBrowserAction.kt | 21 - .../utils/ui/ResizingColumnRenderer.kt | 52 - .../amazon/q/jetbrains/utils/ui/UiUtils.kt | 285 -- .../amazon/q/resources/AwsCoreBundle.kt | 19 - .../q/jetbrains/core/BrowserMessageTest.kt | 346 -- .../q/jetbrains/core/LoginBrowserTest.kt | 161 - .../DefaultToolkitAuthManagerTest.kt | 477 -- .../core/gettingstarted/IdcRolePopupTest.kt | 112 - .../SetupAuthenticationDialogTest.kt | 349 -- .../q/jetbrains/core/BrowserMessageTest.kt | 332 -- .../q/jetbrains/core/LoginBrowserTest.kt | 145 - .../DefaultToolkitAuthManagerTest.kt | 455 -- .../core/gettingstarted/IdcRolePopupTest.kt | 100 - .../SetupAuthenticationDialogTest.kt | 340 -- .../tst-resources/exampleNotification2.json | 124 - .../tst-resources/olderNotification.json | 115 - .../tst-resources/selfSigned.jks | Bin 2295 -> 0 bytes .../q/jetbrains/core/AwsClientManagerTest.kt | 449 -- .../q/jetbrains/core/AwsResourceCacheTest.kt | 563 --- .../q/jetbrains/core/AwsSdkClientTest.kt | 133 - .../core/coroutines/CoroutineUtilsTest.kt | 46 - .../q/jetbrains/core/coroutines/ScopeTest.kt | 201 - ...ateOrUpdateCredentialProfilesActionTest.kt | 209 - .../core/credentials/CredentialManagerTest.kt | 316 -- .../CredentialsRegionHandlerTest.kt | 188 - .../DefaultAwsConnectionManagerTest.kt | 529 --- .../DefaultConfigFilesFacadeTest.kt | 608 --- .../DefaultToolkitConnectionManagerTest.kt | 145 - .../RefreshConnectionActionTest.kt | 89 - .../pinning/ConnectionPinningManagerTest.kt | 201 - .../profiles/Ec2MetadataConfigProviderTest.kt | 102 - .../profiles/ProfileAssumeRoleProviderTest.kt | 192 - .../ProfileCredentialProviderFactoryTest.kt | 1202 ----- .../ProfileCredentialsIdentifierSsoTest.kt | 60 - .../credentials/profiles/ProfileReaderTest.kt | 335 -- .../credentials/profiles/ProfileTestUtils.kt | 12 - .../profiles/ProfileWatcherTest.kt | 177 - .../core/credentials/sso/AccessTokenTest.kt | 38 - .../credentials/sso/ClientRegistrationTest.kt | 39 - .../core/credentials/sso/DiskCacheTest.kt | 717 --- .../sso/SsoAccessTokenProviderTest.kt | 561 --- .../sso/SsoCredentialProviderTest.kt | 122 - .../sso/bearer/BearerTokenProviderTest.kt | 36 - .../sso/bearer/BearerTokenTestUtil.kt | 17 - .../InteractiveBearerTokenProviderTest.kt | 355 -- .../ProfileSdkTokenProviderWrapperTest.kt | 115 - .../core/gettingstarted/SourceOfEntryTest.kt | 20 - .../NotificationDismissalStateTest.kt | 84 - .../NotificationFormatUtilsTest.kt | 156 - .../NotificationFormatUtilsTestCases.kt | 388 -- .../notifications/NotificationManagerTest.kt | 45 - .../NotificationPollingServiceTest.kt | 83 - .../NotificationResourceResolverTest.kt | 78 - .../ProcessNotificationsBaseTest.kt | 161 - .../core/plugin/PluginUpdateManagerTest.kt | 124 - .../AwsCognitoCredentialsProviderTest.kt | 190 - .../DefaultTelemetryPublisherTest.kt | 275 -- .../telemetry/OpenedFileTypeMetricsTest.kt | 36 - .../services/telemetry/PluginResolverTest.kt | 114 - .../telemetry/TelemetryServiceTest.kt | 289 -- .../services/telemetry/TelemetryUtilsTest.kt | 66 - .../services/telemetry/otel/OtelBaseTest.kt | 364 -- .../ToolkitTelemetryOTelSpanProcessorTest.kt | 227 - .../q/jetbrains/settings/AwsSettingsTest.kt | 74 - .../q/jetbrains/ui/AsyncComboBoxTest.kt | 84 - .../amazon/q/jetbrains/utils/MRUListTest.kt | 44 - .../jetbrains/utils/NotificationUtilsTest.kt | 38 - .../amazon/q/jetbrains/utils/TextUtilsTest.kt | 203 - .../q/jetbrains/utils/ThreadingUtilsKtTest.kt | 97 - .../junit5/impl/TestDisposableExtension.kt | 76 - .../amazon/q/jetbrains/core/DummyResource.kt | 21 - .../software/amazon/q/jetbrains/core/Id.kt | 10 - .../q/jetbrains/core/MockClientManager.kt | 134 - .../q/jetbrains/core/MockResourceCache.kt | 207 - .../credentials/MockAwsConnectionManager.kt | 125 - .../credentials/MockCredentialsManager.kt | 183 - .../MockCredentialsRegionHandler.kt | 11 - .../credentials/MockToolkitAuthManagerRule.kt | 31 - .../sso/MockSsoLoginCallbackProvider.kt | 106 - .../core/region/MockRegionProvider.kt | 126 - .../telemetry/MockTelemetryService.kt | 73 - .../q/jetbrains/settings/MockAwsSettings.kt | 36 - .../q/jetbrains/utils/AssertJMatchers.kt | 58 - .../q/jetbrains/utils/SerializationUtils.kt | 29 - .../jetbrains/utils/ServiceExceptionUtils.kt | 47 - .../utils/extensions/SsoLoginExtension.kt | 46 - .../utils/rules/CodeInsightTestFixtureRule.kt | 183 - .../q/jetbrains/utils/rules/FakeCPython.kt | 19 - .../rules/JavaCodeInsightTestFixtureRule.kt | 168 - .../utils/rules/NotificationListenerRule.kt | 50 - .../q/jetbrains/utils/rules/PyTestSdkType.kt | 22 - .../rules/PythonCodeInsightTestFixtureRule.kt | 150 - .../q/jetbrains/utils/rules/RegistryRule.kt | 36 - .../q/jetbrains/utils/rules/SsoLoginRule.kt | 51 - .../jetbrains-ultimate/build.gradle.kts | 17 - .../lang/javascript/JavascriptLanguage.kt | 8 - .../lang/javascript/JavascriptLanguage.kt | 8 - .../rules/NodeJsCodeInsightTestFixtureRule.kt | 174 - plugins/core-q/resources/build.gradle.kts | 49 - .../resources/MessagesBundle.properties | 2120 --------- .../toolkits/resources/BundledResources.kt | 10 - .../aws/toolkits/resources/Localization.kt | 26 - .../resources/BundledResourcesTest.kt | 28 - plugins/core-q/sdk-codegen/README.md | 22 - plugins/core-q/sdk-codegen/build.gradle.kts | 17 - .../codewhispererruntime/paginators-1.json | 31 - .../codewhispererruntime/service-2.json | 3933 ---------------- .../codewhispererstreaming/service-2.json | 2671 ----------- .../telemetry/customization.config | 6 - .../telemetry/service-2.json | 257 -- plugins/core-q/webview/.gitignore | 3 - plugins/core-q/webview/build.gradle.kts | 35 - plugins/core-q/webview/package-lock.json | 3944 ----------------- plugins/core-q/webview/package.json | 61 - plugins/core-q/webview/src/constants.ts | 6 - plugins/core-q/webview/src/defs.d.ts | 23 - plugins/core-q/webview/src/ideClient.ts | 92 - plugins/core-q/webview/src/model.ts | 149 - .../webview/src/q-ui/assets/common.scss | 105 - .../src/q-ui/components/authenticating.vue | 74 - .../src/q-ui/components/awsProfileForm.vue | 84 - .../webview/src/q-ui/components/login.vue | 135 - .../src/q-ui/components/loginOptions.vue | 45 - .../webview/src/q-ui/components/logo.vue | 120 - .../src/q-ui/components/profileSelection.vue | 227 - .../webview/src/q-ui/components/qOptions.vue | 142 - .../webview/src/q-ui/components/reauth.vue | 108 - .../webview/src/q-ui/components/root.vue | 99 - .../src/q-ui/components/selectableItem.vue | 150 - .../src/q-ui/components/ssoLoginForm.vue | 181 - .../src/q-ui/components/toolkitOptions.vue | 156 - plugins/core-q/webview/src/q-ui/index.ts | 82 - plugins/core-q/webview/src/q-ui/toolkit.ts | 74 - plugins/core-q/webview/src/vue.shims.d.ts | 11 - .../core-q/webview/src/webviewTelemetry.ts | 68 - plugins/core-q/webview/tsconfig.json | 19 - plugins/core-q/webview/webpack.config.js | 134 - .../AwsToolkitExplorerToolWindowTest.kt | 1 - sandbox-all/build.gradle.kts | 1 - ui-tests-starter/build.gradle.kts | 2 - 1146 files changed, 2 insertions(+), 128100 deletions(-) delete mode 100644 .run/Run Amazon Q - Community [2024.3].run.xml delete mode 100644 .run/Run Amazon Q - Community [2025.1].run.xml delete mode 100644 .run/Run Amazon Q - Community [2025.2].run.xml delete mode 100644 .run/Run Amazon Q - Rider [2024.3].run.xml delete mode 100644 .run/Run Amazon Q - Rider [2025.1].run.xml delete mode 100644 .run/Run Amazon Q - Rider [2025.2].run.xml delete mode 100644 .run/Run Amazon Q - Ultimate [2024.3].run.xml delete mode 100644 .run/Run Amazon Q - Ultimate [2025.1].run.xml delete mode 100644 .run/Run Amazon Q - Ultimate [2025.2].run.xml delete mode 100644 buildspec/linuxTestsForAmazonQ.yml delete mode 100644 buildspec/windowsTestsForAmazonQ.yml delete mode 100644 plugins/amazonq/build.gradle.kts delete mode 100644 plugins/amazonq/chat/build.gradle.kts delete mode 100644 plugins/amazonq/chat/jetbrains-community/build.gradle.kts delete mode 100644 plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QRefreshPanelAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/session/Session.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/storage/ChatSessionStorage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/App.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/AppFactory.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/ChatConstants.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/InboundAppMessagesHandler.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/ChatSession.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/ChatSessionFactory.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/exceptions/ChatApiException.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/model/Requests.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/model/Responses.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/v1/ChatSessionFactoryV1.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/clients/chat/v1/ChatSessionV1.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ActionRegistrar.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/CodeScanIssueActionMessage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ContextMenuActionMessage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/CustomAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/EditorContextCommand.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ExplainCodeAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/FixCodeAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/GenerateUnitTestsAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/OptimizeCodeAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/RefactorCodeAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/SendToPromptAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/SendToQActionGroup.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/CodeScanCompleteAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/HandleIssueCommandAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/ChatController.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/ReferenceLogController.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/StaticPrompt.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/StaticTextResponse.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/prompts/PromptsGenerator.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/userIntent/UserIntentRecognizer.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContext.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContextExtractor.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ExtractionTriggerType.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContext.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/LanguageExtractor.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/MatchPolicyExtractor.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContext.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/UICodeSelection.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/exceptions/ChatException.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatActionPromoter.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatController.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatEditorHint.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupFactory.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupPanel.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/OpenChatInputAction.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatFileListener.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionInfo.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionStorage.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt delete mode 100644 plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/workspace/context/WorkspaceTest.kt delete mode 100644 plugins/amazonq/codetransform/build.gradle.kts delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/codetransform-ext-java.xml delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/plugin-codetransform.xml delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerStartupActivity.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatAppFactory.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/TransformationSummary.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowJobStatusAction.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationPlanAction.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationStatusAction.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationSummaryAction.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerStopModernizerAction.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/commands/CodeTransformActionMessage.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/commands/CodeTransformCommand.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/commands/CodeTransformMessageListener.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeModernizerUIConstants.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformFileConstants.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatHelper.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/file/PomFileAnnotator.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformMavenRunner.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformRunnable.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/messages/CodeTransformMessage.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/BuildProgressStepTreeItem.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/BuildProgressTimelineStepDetailItem.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/BuildProgressTimelineStepDetailsList.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/BuildStepStatus.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerException.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerJobCompletedResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerManifest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerMetrics.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerStartJobResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformConversationState.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformDownloadArtifact.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformFailureBuildLog.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilDownloadArtifact.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilDownloadManifest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformHilUploadManifest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformType.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CustomerSelection.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/DependencyUpdatesReport.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/DownloadArtifactResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/DownloadFailureReason.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/InvalidTelemetryReason.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobHistoryItem.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobHistoryTableModel.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobId.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/MavenCopyCommandsResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/MavenDependencyReportCommandsResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/MigrationStep.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ParseZipFailureReason.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/PlanTable.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/PlanTableRow.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SctMetadata.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SqlMetadataValidationResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/UnzipFailureReason.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/UploadFailureReason.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ValidationResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipCreationResult.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/BuildProgressStepDetailsPanel.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/BuildProgressTreePanel.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/CodeModernizerBanner.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/CodeModernizerJobHistoryTablePanel.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/LoadingPanel.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/BuildProgressSplitterPanelManager.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditor.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditorProvider.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanVirtualFile.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/session/ChatSessionStorage.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/session/Session.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerSessionState.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeTransformTelemetryState.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/toolwindow/CodeModernizerBottomToolWindowFactory.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ui/components/PanelHeaderFactory.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformProjectUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformValidationUtils.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/fixtures/downloadResults/manifest.json delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/fixtures/downloadResults/pom.xml delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeTransformFeedbackDialog.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/diff.patch delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/expectedFile delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/humanInTheLoop/downloadResults.zip delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/humanInTheLoop/manifest.json delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/humanInTheLoop/pom.xml delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/min_jdk_upgrade.patch delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/overwrittenFile delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/simple.zip delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst-resources/codemodernizer/test.txt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformHilDownloadArtifactTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTestBase.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/CodeModernizerJobHistoryPanelTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/LoadingPanelTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/PanelTestBase.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/BuildProgressSplitterPanelManagerTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManagerTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/ui/components/PanelHeaderFactoryTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtilsTest.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformProjectUtilsTest.kt delete mode 100644 plugins/amazonq/codewhisperer/build.gradle.kts delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/build.gradle.kts delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/detekt-baseline-main.xml delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/detekt-baseline-test.xml delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/detekt-baseline.xml delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanJavaIntegrationTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCompletionIntegrationTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererIntegrationTestBase.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceTrackerIntegrationTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/resources/codewhisperer/licenses.json delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src-242/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererConnectOnGithubAction.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererLearnMoreAction.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererProvideFeedbackAction.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererShowSettingsAction.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererWhatIsAction.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/AmazonQCodeFixSession.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanException.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanHighlightingFilesPanel.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanIssueDetailsPanel.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanResultsView.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTreeModel.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/actions/CodeWhispererCodeScanFilterGroup.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/actions/CodeWhispererCodeScanGroupingStrategyActionGroup.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/context/CodeScanIssueDetailsDisplayType.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanDocumentListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanEditorMouseMotionListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/listeners/CodeWhispererCodeScanFileListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/AmazonQCodeReviewGitUtils.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/utils/CodeWhispererCodeScanIssueUtils.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestException.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererLoginType.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManagerNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Learn.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/OpenCodeReference.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Pause.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/PauseCodeScans.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Resume.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ResumeCodeScans.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdder.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJavaImportAdder.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdder.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayBlockRenderer.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayInlineRenderer.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayRenderer.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/InlineCompletionRemoteRendererFactory.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/CodeWhispererLanguageManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/CodeWhispererProgrammingLanguage.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/classresolver/CodeWhispereJavaClassResolver.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/classresolver/CodeWhispererClassResolver.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/classresolver/CodeWhispererPythonClassResolver.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererAbap.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererC.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererCpp.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererCsharp.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererDart.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererGo.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererJava.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererJavaScript.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererJson.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererJsx.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererKotlin.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererLua.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPhp.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPlainText.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPowershell.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPython.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererR.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererRuby.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererRust.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererScala.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererShell.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSql.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSwift.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSystemVerilog.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererTf.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererTsx.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererTypeScript.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererUnknownLanguage.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererVue.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererYaml.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/layout/CodeWhispererLayoutConfig.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditor.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererEditorProvider.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererUIComponents.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/LearnCodeWhispererVirtualFile.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListenerNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionInsertHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionUtils.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandlerNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutomatedTriggerType.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererClassifierConstants.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererLicenseInfoManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererUserGroupSettings.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidgetFactory.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceComponents.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceToolWindowFactory.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/BM25.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacade.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererFileContextProvider.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererMetadata.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererZipUploadManager.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/GitIgnoreFilteringUtil.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/OffsetSuggestedFix.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacadeTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeInsightsSettingsFacadeTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtilTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/Bm25Test.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererAcceptTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererActionTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererBasicTestBase.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConfigurableTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererConstantsTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEndpointCustomizerTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFeatureConfigServiceTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererFileContextProviderTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLicenseInfoManagerTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererNavigationTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferenceManagerTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererStateTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTypeaheadTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserActionsTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserGroupSettingsTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserInputTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QRegionProfileManagerTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/CodeTestSessionConfigTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererFallbackImportAdderTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJavaImportAdderTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererPythonImportAdderTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/util/GitIgnoreFilteringUtilTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/utils/OffsetSuggestedFixKtTest.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderTestBase.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-ultimate/build.gradle.kts delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-ultimate/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdder.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-ultimate/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportUtil.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-ultimate/tst/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererJSImportAdderTest.kt delete mode 100644 plugins/amazonq/contrib/QCT-Maven-1-0-156-0.jar delete mode 100644 plugins/amazonq/mynah-ui/.eslintignore delete mode 100644 plugins/amazonq/mynah-ui/.eslintrc.js delete mode 100644 plugins/amazonq/mynah-ui/.gitignore delete mode 100644 plugins/amazonq/mynah-ui/build.gradle.kts delete mode 100644 plugins/amazonq/mynah-ui/package-lock.json delete mode 100644 plugins/amazonq/mynah-ui/package.json delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/connectorAdapter.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/amazonqCommonsConnector.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeScanChatConnector.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeTransformChatConnector.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/commands.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/defs.d.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/diffTree/actions.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/diffTree/types.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/feedback/constants.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/followUps/generator.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/followUps/handler.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/followUps/model.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/forms/constants.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/messages/controller.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/messages/handler.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/quickActions/generator.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/quickActions/handler.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/storages/tabsStorage.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/styles.css delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/styles/dark.scss delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/tabs/generator.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/telemetry/actions.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/texts/constants.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/texts/disclaimer.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/walkthrough/agent.ts delete mode 100644 plugins/amazonq/mynah-ui/src/mynah-ui/ui/walkthrough/welcome.ts delete mode 100644 plugins/amazonq/mynah-ui/tsconfig.json delete mode 100644 plugins/amazonq/mynah-ui/webpack.media.config.js delete mode 100644 plugins/amazonq/shared/build.gradle.kts delete mode 100644 plugins/amazonq/shared/jetbrains-community/build.gradle.kts delete mode 100644 plugins/amazonq/shared/jetbrains-community/resources/META-INF/module-amazonq.xml delete mode 100644 plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties delete mode 100644 plugins/amazonq/shared/jetbrains-community/src-242-251/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/PythonModuleUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src-242/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileChooserFilters.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileChooserFilters.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src-252+/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/PythonModuleUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/DefaultProblemsViewMutator.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ProblemsViewMutator.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigListener.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/Constants.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/RetryableOperation.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/actions/QSwitchProfilesAction.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClient.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QActionGroups.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedUtils.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedVirtualFile.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQChatServer.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQDiffVirtualFile.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClient.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageServer.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspConfiguration.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspConstants.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/CodeWhispererLspConfiguration.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcher.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspException.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtils.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcher.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependenciesService.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependencyProvider.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/JavaModuleDependencyProvider.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/PythonModuleDependencyProvider.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/encryption/JwtEncryptionManager.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/FlareUiMessage.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ProgressNotificationUtils.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EncryptionInitializationRequest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/ExtendedClientMetadata.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/AwsServerCapabilities.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/GetConfigurationFromServerParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/InlineCompletionStates.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/LogInlineCompletionSessionResultsParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/LspServerConfigurations.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ButtonClick.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatNotification.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatPrompt.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatReadyNotification.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatResult.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatUiMessageParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/CopyCodeToClipboard.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/CopyFileParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/CreatePromptParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/CursorState.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ErrorParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Feedback.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/FileClick.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/FileParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/FlareChatCommands.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/FollowUpClick.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GenericCommandParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/InsertToCursor.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/LinkClick.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ListAvailableModels.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/OpenFileDiffParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/OpenSettingsParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/PromptInputOptionChangeParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/QuickChatActionRequest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/SendChatPrompt.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ShowOpenFileDialogParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ShowSaveFileDialogParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/StopResponseParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabEvent.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/ConnectionMetadata.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/UpdateCredentialsPayload.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/dependencies/DidChangeDependencyPathsParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionContext.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionItem.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionListWithReferences.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionReference.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionReferencePosition.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionTriggerKind.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/InlineCompletionWithReferencesParams.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/textDocument/SelectedCompletionInfo.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/TelemetryParsingUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QProfileResources.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QProfileSwitchIntent.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfile.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileDialog.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileSelectedListener.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/FeatureDevSessionContext.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/LspMessage.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/RelevantDocument.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/Workspace.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowId.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformSharedUtils.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomization.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationDialog.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererCustomizationListener.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/learn/QFileType.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/UserModificationTrackingEntry.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererColorUtil.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CustomizationConstants.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/InsertedCodeModificationEntry.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/QTelemetryUtils.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/CodeWhispererSettings.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/MeetQSettings.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/CodeWhispererFeedbackDialog.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/FeatureDevFeedbackDialog.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/TestGenFeedbackDialog.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/resources/AmazonQBundle.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManagerTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManagerTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/NodeExePatcherTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtilTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelperTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/LspUtilsTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ManifestFetcherTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/encryption/JwtEncryptionManagerTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/ApplyWorkspaceEditTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileUriUtilTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/settings/LspSettingsTest.kt delete mode 100644 plugins/amazonq/shared/jetbrains-ultimate/build.gradle.kts delete mode 100644 plugins/amazonq/shared/jetbrains-ultimate/src/software/aws/toolkits/jetbrains/CwmProblemsViewMutator.kt delete mode 100644 plugins/amazonq/src/main/resources/META-INF/amazonq-ext-codewithme.xml delete mode 100644 plugins/amazonq/src/main/resources/META-INF/amazonq-ext-java.xml delete mode 100644 plugins/amazonq/src/main/resources/META-INF/amazonq-ext-nodejs.xml delete mode 100644 plugins/amazonq/src/main/resources/META-INF/amazonq-ext-python.xml delete mode 100644 plugins/amazonq/src/main/resources/META-INF/plugin.xml delete mode 100644 plugins/amazonq/src/main/resources/META-INF/pluginIcon.svg delete mode 100644 plugins/amazonq/src/main/resources/icons/file.svg delete mode 100644 plugins/amazonq/src/test/kotlin/PluginAmazonQJvmBinaryCompatabilityTest.kt delete mode 100644 plugins/amazonq/src/test/kotlin/software/aws/toolkits/jetbrains/core/PluginCoreJvmBinaryCompatabilityTest.kt delete mode 100644 plugins/core-q/build.gradle.kts delete mode 100644 plugins/core-q/core-q/build.gradle.kts delete mode 100644 plugins/core-q/core-q/detekt-baseline-integrationTest.xml delete mode 100644 plugins/core-q/core-q/detekt-baseline-main.xml delete mode 100644 plugins/core-q/core-q/detekt-baseline-test.xml delete mode 100644 plugins/core-q/core-q/detekt-baseline.xml delete mode 100644 plugins/core-q/core-q/it/software/aws/toolkits/core/s3/BucketUtilsTest.kt delete mode 100644 plugins/core-q/core-q/src/migration/software/amazon/q/core/ToolkitClientManager.kt delete mode 100644 plugins/core-q/core-q/src/migration/software/amazon/q/core/clients/SdkClientProvider.kt delete mode 100644 plugins/core-q/core-q/src/migration/software/amazon/q/core/region/ToolkitRegionProvider.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/ConnectionSettings.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientCustomizer.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/ToolkitClientManager.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/clients/ClientBuilderUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/clients/SdkClientProvider.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/AwsCredentials.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialProviderFactory.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/CredentialsChangeEvent.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/SsoUrlIdentifier.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProvider.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/credentials/ToolkitCredentialsProviderManager.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaArchitecture.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaRuntime.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaSampleEventProvider.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/lambda/LambdaUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/AwsPartition.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/AwsRegion.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/Partitions.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/region/ToolkitRegionProvider.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/s3/BucketUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/CachedIdentityStorage.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/MetricEvent.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryBatcher.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/telemetry/TelemetryPublisher.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/AttributeBag.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/CollectionUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/Either.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/ExceptionUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/LogUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/PathUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/RemoteResourceResolver.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/SensitiveField.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/StringUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/TextUtils.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/Waiter.kt delete mode 100644 plugins/core-q/core-q/src/software/amazon/q/core/utils/ZipUtils.kt delete mode 100644 plugins/core-q/core-q/tst-resources/jsonSampleFailure.json delete mode 100644 plugins/core-q/core-q/tst-resources/jsonSampleSuccess.json delete mode 100644 plugins/core-q/core-q/tst-resources/sampleLambdaEvent.json delete mode 100644 plugins/core-q/core-q/tst-resources/xmlSampleFailure.xml delete mode 100644 plugins/core-q/core-q/tst-resources/xmlSampleSuccess.xml delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/credentials/AwsCredentialsExtensionsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/credentials/Mocks.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/credentials/SsoUrlIdentifierTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/lambda/LambdaSampleEventProviderTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/parser/EndpointsJsonValidatorTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaManifestValidatorTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/parser/LambdaSampleEventJsonValidatorTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/region/AwsRegionTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/region/PartitionParserTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/rules/ECSTemporaryServiceRule.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/rules/EcrTemporaryRepositoryRule.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/rules/EnvironmentVariableHelper.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/rules/S3TemporaryBucketRule.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/rules/SystemPropertyHelper.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/telemetry/TelemetryBatcherTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/CollectionUtilsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/CompletionStageUtils.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/DelegateSdkConsumers.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/EitherTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/ExceptionUtilsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/IntegrationTestCredentials.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/LogUtilsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/RemoteResourceResolverTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtils.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/RuleUtilsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/ZipUtilsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/AssertJAsserts.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtils.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/core/utils/test/TestUtilsTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/AttributeBagTest.kt delete mode 100644 plugins/core-q/core-q/tst/software/amazon/q/jetbrains/utils/StringUtilsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/build.gradle.kts delete mode 100644 plugins/core-q/jetbrains-community/detekt-baseline-main.xml delete mode 100644 plugins/core-q/jetbrains-community/detekt-baseline-test.xml delete mode 100644 plugins/core-q/jetbrains-community/detekt-baseline-testFixtures.xml delete mode 100644 plugins/core-q/jetbrains-community/detekt-baseline.xml delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_Q_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_Large_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/AWS_smile_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Large.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon-Q-Icon_White_Medium.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Medium_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_CodeCatalyst_Small_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/Amazon_Q_grey.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CW_InlineSuggestions_light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CloudFormationTool_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/CodeWhisperer_Large.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/EventBridge_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/logos/MynahIcon_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/csharp.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/csharp_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/frown.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/frown_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/java.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/javaScript.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/javaScript_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/java_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/learn.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/learn_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/new.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/python.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/smile_grey_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/typeScript.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/misc/typeScript_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/AppRunnerService_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/CloudFormationStack_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/CodewhispererCustom.svg delete mode 100755 plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository.svg delete mode 100755 plugins/core-q/jetbrains-community/resources/icons/resources/ECRRepository_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/LambdaFunction_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Redshift.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Redshift_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/S3Bucket_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Schema.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/SchemaRegistry_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/Schema_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ServerlessApp_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsGroup_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogsToolWindow_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/cloudwatchlogs/CloudWatchLogs_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/checkmark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/greenCheckmark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-arrow-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-default-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-dependencies-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-file-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-step-into-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-timeline-step-done.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codetransform/transform-variables-light.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-critical.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-high.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-info.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-critical.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-high.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-info.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-low.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-initial-medium.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-low.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/codewhisperer/severity-medium.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/dynamodb/DynamoDbTable_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsCluster_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsService_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/ecs/EcsTaskDefinition_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Mysql_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/rds/Postgres_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsQueue_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/icons/resources/sqs/SqsToolWindow_dark.svg delete mode 100644 plugins/core-q/jetbrains-community/resources/oauthCallback/auth.css delete mode 100644 plugins/core-q/jetbrains-community/resources/oauthCallback/index.html delete mode 100644 plugins/core-q/jetbrains-community/resources/telemetryOverride.json delete mode 100644 plugins/core-q/jetbrains-community/src-242-252/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt delete mode 100644 plugins/core-q/jetbrains-community/src-253+/software/amazon/q/jetbrains/services/telemetry/otel/OTelService.kt delete mode 100644 plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefLocalRequestHandler.kt delete mode 100644 plugins/core-q/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefStreamResourceHandler.kt delete mode 100644 plugins/core-q/jetbrains-community/src/icons/AwsIcons.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/AwsResourceCache.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/RemoteResourceResolverProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/coroutines/PluginCoroutineScopeTracker.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/settings/AwsSettings.kt delete mode 100644 plugins/core-q/jetbrains-community/src/migration/software/amazon/q/jetbrains/telemetry/TelemetryService.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/AwsToolkit.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/IsDeveloperMode.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ToolkitPlaces.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsClientManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsResourceCache.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsSdkClient.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/AwsTelemetryPrompter.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/DefaultRemoteResourceResolverProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/HttpUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/contexts.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/coroutines/scopes.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/AwsConnectionManagerConnection.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ChangeConnectionSettingIfValid.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ConfigFilesFacade.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesAction.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialTelemetryUtil.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandler.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/InteractiveCredential.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/LoginUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaRequiredInteractiveCredentials.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/MfaSupport.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/PostValidateInteractiveCredential.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/RefreshConnectionAction.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoRequiredInteractiveCredentials.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/SsoSupport.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitAuthManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionImpls.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitConnectionManagerListener.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/ToolkitCredentialProcessProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/actions/SsoLogoutAction.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/CodeCatalystConnection.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerListener.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/pinning/QConnection.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/CredentialSourceType.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/DEFAULT_PROFILE_ID.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileLegacySsoProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReader.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionIdentifier.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileSsoSessionProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcher.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/profiles/SsoSessionConstants.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sono/SonoConstants.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/AccessToken.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/Authorization.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistration.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/DiskCache.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCache.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallback.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/SsoLoginCallbackProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderListener.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/bearer/ConfirmUserCodeLoginDialog.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopup.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialog.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIdTranslator.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/help/HelpIds.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/CustomizeNotificationsUi.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/DisplayToastNotifications.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationCustomDeserializers.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPanel.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationPollingService.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationServiceInitializer.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/NotificationStateUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBase.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/notifications/RulesEngine.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginAutoUpdater.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/plugin/PluginUpdateManager.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/region/AwsRegionProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/BrowserMessage.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LocalAssetJBCefRequestHandler.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/LoginBrowser.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/core/webview/WebviewTelemetryUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/QConstants.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/amazonq/explorerActions/QLearnMoreAction.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/sts/StsResources.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/AwsToolkitStartupMetrics.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/ClientMetadata.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisher.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenTelemetryAction.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypesMetrics.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/PluginResolver.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryService.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/TelemetryUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/OtelBase.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessor.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettings.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/settings/AwsSettingsSharedConfigurable.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/AsyncComboBox.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/KeyValueTextField.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/ui/feedback/FeedbackDialog.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/DevFileUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/FunctionUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/MRUList.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/NotificationUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/PsiUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/RemoteEnvUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/SpinUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/TextUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ThreadingUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/actions/OpenBrowserAction.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/ResizingColumnRenderer.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/jetbrains/utils/ui/UiUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/src/software/amazon/q/resources/AwsCoreBundle.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/BrowserMessageTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/LoginBrowserTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-242-252/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/BrowserMessageTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/LoginBrowserTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/credentials/DefaultToolkitAuthManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/gettingstarted/IdcRolePopupTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-253+/software/amazon/q/jetbrains/core/gettingstarted/SetupAuthenticationDialogTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst-resources/exampleNotification2.json delete mode 100644 plugins/core-q/jetbrains-community/tst-resources/olderNotification.json delete mode 100644 plugins/core-q/jetbrains-community/tst-resources/selfSigned.jks delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsClientManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsResourceCacheTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/AwsSdkClientTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/CoroutineUtilsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/coroutines/ScopeTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CreateOrUpdateCredentialProfilesActionTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/CredentialsRegionHandlerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultAwsConnectionManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/DefaultToolkitConnectionManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/RefreshConnectionActionTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/pinning/ConnectionPinningManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/Ec2MetadataConfigProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileAssumeRoleProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactoryTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileCredentialsIdentifierSsoTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileReaderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileTestUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/profiles/ProfileWatcherTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/AccessTokenTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/ClientRegistrationTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/DiskCacheTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoAccessTokenProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/SsoCredentialProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/BearerTokenTestUtil.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/InteractiveBearerTokenProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/credentials/sso/bearer/ProfileSdkTokenProviderWrapperTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/gettingstarted/SourceOfEntryTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationDismissalStateTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationPollingServiceTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/NotificationResourceResolverTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/notifications/ProcessNotificationsBaseTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/core/plugin/PluginUpdateManagerTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/AwsCognitoCredentialsProviderTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/DefaultTelemetryPublisherTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/OpenedFileTypeMetricsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/PluginResolverTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryServiceTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/TelemetryUtilsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/OtelBaseTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/services/telemetry/otel/ToolkitTelemetryOTelSpanProcessorTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/settings/AwsSettingsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/ui/AsyncComboBoxTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/MRUListTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/NotificationUtilsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/TextUtilsTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tst/software/amazon/q/jetbrains/utils/ThreadingUtilsKtTest.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/com/intellij/testFramework/junit5/impl/TestDisposableExtension.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/DummyResource.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/Id.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockClientManager.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/MockResourceCache.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockAwsConnectionManager.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsManager.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockCredentialsRegionHandler.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/MockToolkitAuthManagerRule.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/credentials/sso/MockSsoLoginCallbackProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/core/region/MockRegionProvider.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/services/telemetry/MockTelemetryService.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/settings/MockAwsSettings.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/AssertJMatchers.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/SerializationUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/ServiceExceptionUtils.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/extensions/SsoLoginExtension.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/CodeInsightTestFixtureRule.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/FakeCPython.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/JavaCodeInsightTestFixtureRule.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/NotificationListenerRule.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PyTestSdkType.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/PythonCodeInsightTestFixtureRule.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/RegistryRule.kt delete mode 100644 plugins/core-q/jetbrains-community/tstFixtures/software/amazon/q/jetbrains/utils/rules/SsoLoginRule.kt delete mode 100644 plugins/core-q/jetbrains-ultimate/build.gradle.kts delete mode 100644 plugins/core-q/jetbrains-ultimate/src-242-243/compat/com/intellij/lang/javascript/JavascriptLanguage.kt delete mode 100644 plugins/core-q/jetbrains-ultimate/src-251+/compat/com/intellij/lang/javascript/JavascriptLanguage.kt delete mode 100644 plugins/core-q/jetbrains-ultimate/tstFixtures/software/amazon/q/jetbrains/utils/rules/NodeJsCodeInsightTestFixtureRule.kt delete mode 100644 plugins/core-q/resources/build.gradle.kts delete mode 100644 plugins/core-q/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties delete mode 100644 plugins/core-q/resources/src/software/aws/toolkits/resources/BundledResources.kt delete mode 100644 plugins/core-q/resources/src/software/aws/toolkits/resources/Localization.kt delete mode 100644 plugins/core-q/resources/tst/software/aws/toolkits/resources/BundledResourcesTest.kt delete mode 100644 plugins/core-q/sdk-codegen/README.md delete mode 100644 plugins/core-q/sdk-codegen/build.gradle.kts delete mode 100644 plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/paginators-1.json delete mode 100644 plugins/core-q/sdk-codegen/codegen-resources/codewhispererruntime/service-2.json delete mode 100644 plugins/core-q/sdk-codegen/codegen-resources/codewhispererstreaming/service-2.json delete mode 100644 plugins/core-q/sdk-codegen/codegen-resources/telemetry/customization.config delete mode 100644 plugins/core-q/sdk-codegen/codegen-resources/telemetry/service-2.json delete mode 100644 plugins/core-q/webview/.gitignore delete mode 100644 plugins/core-q/webview/build.gradle.kts delete mode 100644 plugins/core-q/webview/package-lock.json delete mode 100644 plugins/core-q/webview/package.json delete mode 100644 plugins/core-q/webview/src/constants.ts delete mode 100644 plugins/core-q/webview/src/defs.d.ts delete mode 100644 plugins/core-q/webview/src/ideClient.ts delete mode 100644 plugins/core-q/webview/src/model.ts delete mode 100644 plugins/core-q/webview/src/q-ui/assets/common.scss delete mode 100644 plugins/core-q/webview/src/q-ui/components/authenticating.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/awsProfileForm.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/login.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/loginOptions.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/logo.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/profileSelection.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/qOptions.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/reauth.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/root.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/selectableItem.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/ssoLoginForm.vue delete mode 100644 plugins/core-q/webview/src/q-ui/components/toolkitOptions.vue delete mode 100644 plugins/core-q/webview/src/q-ui/index.ts delete mode 100644 plugins/core-q/webview/src/q-ui/toolkit.ts delete mode 100644 plugins/core-q/webview/src/vue.shims.d.ts delete mode 100644 plugins/core-q/webview/src/webviewTelemetry.ts delete mode 100644 plugins/core-q/webview/tsconfig.json delete mode 100644 plugins/core-q/webview/webpack.config.js diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 32bd1956813..39dda9f571d 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -17,8 +17,8 @@ jobs: generate_artifact: strategy: matrix: - build_target: [ ':plugin-core:buildPlugin', ':plugin-toolkit:intellij-standalone:buildPlugin', ':plugin-amazonq:buildPlugin' ] - version: [ '2024.3', '2025.1', '2025.2', '2025.3' ] + build_target: [ ':plugin-core:buildPlugin', ':plugin-toolkit:intellij-standalone:buildPlugin' ] + version: [ '2025.1', '2025.2', '2025.3' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.run/Run Amazon Q - Community [2024.3].run.xml b/.run/Run Amazon Q - Community [2024.3].run.xml deleted file mode 100644 index 4f6b86d32f1..00000000000 --- a/.run/Run Amazon Q - Community [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Community [2025.1].run.xml b/.run/Run Amazon Q - Community [2025.1].run.xml deleted file mode 100644 index ed23a45a50e..00000000000 --- a/.run/Run Amazon Q - Community [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Community [2025.2].run.xml b/.run/Run Amazon Q - Community [2025.2].run.xml deleted file mode 100644 index 7652b369b5f..00000000000 --- a/.run/Run Amazon Q - Community [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Rider [2024.3].run.xml b/.run/Run Amazon Q - Rider [2024.3].run.xml deleted file mode 100644 index 9e64b94c3e3..00000000000 --- a/.run/Run Amazon Q - Rider [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Rider [2025.1].run.xml b/.run/Run Amazon Q - Rider [2025.1].run.xml deleted file mode 100644 index e94c11fe07f..00000000000 --- a/.run/Run Amazon Q - Rider [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Rider [2025.2].run.xml b/.run/Run Amazon Q - Rider [2025.2].run.xml deleted file mode 100644 index b44257e0f62..00000000000 --- a/.run/Run Amazon Q - Rider [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Ultimate [2024.3].run.xml b/.run/Run Amazon Q - Ultimate [2024.3].run.xml deleted file mode 100644 index 6891367056b..00000000000 --- a/.run/Run Amazon Q - Ultimate [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Ultimate [2025.1].run.xml b/.run/Run Amazon Q - Ultimate [2025.1].run.xml deleted file mode 100644 index 8d363e8ad4a..00000000000 --- a/.run/Run Amazon Q - Ultimate [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Ultimate [2025.2].run.xml b/.run/Run Amazon Q - Ultimate [2025.2].run.xml deleted file mode 100644 index 95c4963df05..00000000000 --- a/.run/Run Amazon Q - Ultimate [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 778a136b053..9dc691999d5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,7 +33,6 @@ tasks.createRelease.configure { dependencies { aggregateCoverage(project(":plugin-toolkit:intellij-standalone")) aggregateCoverage(project(":plugin-core")) - aggregateCoverage(project(":plugin-amazonq")) project.findProject(":plugin-toolkit:jetbrains-gateway")?.let { aggregateCoverage(it) diff --git a/buildspec/linuxTestsForAmazonQ.yml b/buildspec/linuxTestsForAmazonQ.yml deleted file mode 100644 index 737681deb84..00000000000 --- a/buildspec/linuxTestsForAmazonQ.yml +++ /dev/null @@ -1,71 +0,0 @@ -version: 0.2 - -cache: - paths: - # - '/root/.gradle/caches/**/*' - - '/root/.gradle/wrapper/**/*' - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - - useradd codebuild-user - - dnf install -y acl - - chown -R codebuild-user:codebuild-user /codebuild/output - - chown -R codebuild-user:codebuild-user /codebuild/local-cache - - setfacl -m d:o::rwx,o::rwx /root - # (CVE-2022-24765) fatal: detected dubious ownership in repository - - su codebuild-user -c "git config --global --add safe.directory \"$CODEBUILD_SRC_DIR\"" - - build: - commands: - - | - if [ "$CODEARTIFACT_DOMAIN_NAME" ] && [ "$CODEARTIFACT_REPO_NAME" ]; then - CODEARTIFACT_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text) - # CODEARTIFACT_NUGET_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text) - CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain $CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600) - su codebuild-user -c "dotnet codeartifact-creds install" - fi - - - chmod +x gradlew - - su codebuild-user -c "./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-amazonq:check coverageReport --info --console plain --continue" - - ./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-amazonq:buildPlugin - - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') # Encode `#` in the URL because otherwise the url is clipped in the Codecov.io site - - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "$CODE_COV_TOKEN" && curl -Os https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov || true # this sometimes times out but we don't want to fail the build - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F unittest || true - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F codewhisperer || true - - post_build: - commands: - - BUILD_ARTIFACTS="/tmp/buildArtifacts" - - TEST_ARTIFACTS="/tmp/testArtifacts" - - mkdir -p $TEST_ARTIFACTS/test-reports - - mkdir -p $BUILD_ARTIFACTS - - rsync -rmq --include='*/' --include '**/build/idea-sandbox/**/log*/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/build/reports/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/test-results/**/*.xml' --exclude='*' . $TEST_ARTIFACTS/test-reports || true - - cp -r ./plugins/amazonq/build/distributions/*.zip $BUILD_ARTIFACTS/ || touch $BUILD_ARTIFACTS/build_failed - -reports: - unit-test: - files: - - "**/*" - base-directory: /tmp/testArtifacts/test-reports - discard-paths: yes - -artifacts: - files: - - "**/*" - base-directory: /tmp/testArtifacts - secondary-artifacts: - plugin: - files: - - /tmp/buildArtifacts/* - discard-paths: yes - name: plugin.zip diff --git a/buildspec/windowsTestsForAmazonQ.yml b/buildspec/windowsTestsForAmazonQ.yml deleted file mode 100644 index 70c6c2056f4..00000000000 --- a/buildspec/windowsTestsForAmazonQ.yml +++ /dev/null @@ -1,89 +0,0 @@ -version: 0.2 - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - # force install java21 while we work through path issues - - | - $javaName = "C:\Program Files\Amazon Corretto" | ForEach-Object { - ls $_ | Where-Object {$_ -Like "jdk*"} | Sort-Object -Descending -Property Name | Select-Object -first 1 -expandproperty Name - } - $JAVA_HOME = "C:\Program Files\Amazon Corretto\$javaName" - - | - if(-Not($Env:CODE_COV_TOKEN -eq $null)) { - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - } - - dotnet --list-sdks - - build: - commands: - - | - # See https://github.com/NuGet/NuGet.Client/pull/4259 - $Env:NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY = "3,1000" - - $Env:JAVA_HOME = $JAVA_HOME - - if ($Env:CODEARTIFACT_DOMAIN_NAME -and $Env:CODEARTIFACT_REPO_NAME) { - $Env:CODEARTIFACT_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text - # $Env:CODEARTIFACT_NUGET_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text - $Env:CODEARTIFACT_AUTH_TOKEN=aws codeartifact get-authorization-token --domain $Env:CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600 - } - - # Rider is very expensive (spikes our CI jobs to 50% CPU, so let it do the prep work in parallel, but run tests later - ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-amazonq:check --info --console plain --continue - if ($LastExitCode -ne 0) { - Write-Host "Command failed with exit code $LastExitCode" - exit -1 - } - # ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-toolkit:jetbrains-rider:check coverageReport --info --console plain - - post_build: - commands: - - | - $script:TEST_ARTIFACTS=Join-Path $env:TEMP testArtifacts - $script:TEST_REPORTS=Join-Path $script:TEST_ARTIFACTS test-reports - - function copyFolder($basedir, $subdir, $destdir) { - $src = Join-Path "." -ChildPath $basedir | Join-Path -ChildPath $subdir - $dest = Join-Path $destdir -ChildPath $basedir | Join-Path -ChildPath $subDir - if( (Get-ChildItem $src -ErrorAction SilentlyContinue | Measure-Object).Count -ne 0) { - Copy-Item $src $dest -Recurse -Force -ErrorAction SilentlyContinue - } - } - - function copyArtifacts($root) { - copyFolder $root "build/reports/" $script:TEST_ARTIFACTS - copyFolder $root "build/idea-sandbox/system-test/log/" $script:TEST_ARTIFACTS - copyFolder $root "build/test-results/test/" $script:TEST_REPORTS - } - - copyArtifacts "." - Get-ChildItem -Directory | ForEach-Object { copyArtifacts $_.Name } - - if(-Not($Env:CODEBUILD_BUILD_SUCCEEDING -eq "0" -Or $Env:CODE_COV_TOKEN -eq $null)) { - $env:VCS_COMMIT_ID=$Env:CODEBUILD_RESOLVED_SOURCE_VERSION; - $env:CI_BUILD_URL=[uri]::EscapeUriString($Env:CODEBUILD_BUILD_URL); - $env:CI_BUILD_ID=$Env:CODEBUILD_BUILD_ID; - .\codecov.exe -t $Env:CODE_COV_TOKEN ` - --flags unittest ` - -f "build/reports/jacoco/coverageReport/coverageReport.xml" ` - -c $Env:CODEBUILD_RESOLVED_SOURCE_VERSION - } - -reports: - unit-test: - files: - - "**/*" - base-directory: "$env:TEMP/testArtifacts/test-reports" - discard-paths: yes - -artifacts: - base-directory: "$env:TEMP/testArtifacts" - files: - - "**/*" diff --git a/plugins/amazonq/build.gradle.kts b/plugins/amazonq/build.gradle.kts deleted file mode 100644 index 65a5aaa7cd5..00000000000 --- a/plugins/amazonq/build.gradle.kts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import de.undercouch.gradle.tasks.download.Download -import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask -import software.aws.toolkits.gradle.changelog.tasks.GeneratePluginChangeLog - -plugins { - id("toolkit-publishing-conventions") - id("toolkit-publish-root-conventions") - id("toolkit-jvm-conventions") - id("toolkit-testing") - id("de.undercouch.download") -} - -buildscript { - dependencies { - classpath(libs.bundles.jackson) - } -} - -val changelog = tasks.register("pluginChangeLog") { - includeUnreleased.set(true) - changeLogFile.value(layout.buildDirectory.file("changelog/change-notes.xml")) -} - -tasks.jar { - dependsOn(changelog) - from(changelog) { - into("META-INF") - } -} - -dependencies { - implementation(project(":plugin-core-q")) - implementation(project(":plugin-amazonq:chat")) - implementation(project(":plugin-amazonq:codetransform")) - implementation(project(":plugin-amazonq:codewhisperer")) - implementation(project(":plugin-amazonq:mynah-ui")) - implementation(project(":plugin-amazonq:shared")) - implementation(libs.bundles.jackson) - implementation(libs.lsp4j) - - testImplementation(project(":plugin-core-q")) -} - -tasks.check { - val serviceSubdirs = project(":plugin-amazonq").subprojects - serviceSubdirs.forEach { serviceSubDir -> - val subDirs = serviceSubDir.subprojects - subDirs.forEach { insideService-> - dependsOn(":plugin-amazonq:${serviceSubDir.name}:${insideService.name}:check") - } - } -} - -val downloadFlareManifest by tasks.registering(Download::class) { - src("https://aws-toolkit-language-servers.amazonaws.com/qAgenticChatServer/0/manifest.json") - dest(layout.buildDirectory.file("flare/manifest.json")) - onlyIfModified(true) - useETag(true) -} - -data class FlareManifest( - val versions: List, -) - -data class FlareVersion( - val serverVersion: String, - val thirdPartyLicenses: String, - val targets: List, -) - -data class FlareTarget( - val platform: String, - val arch: String, - val contents: List -) - -data class FlareContent( - val url: String, -) - -val downloadFlareArtifacts by tasks.registering(Download::class) { - dependsOn(downloadFlareManifest) - inputs.files(downloadFlareManifest) - - val manifestFile = downloadFlareManifest.map { it.outputFiles.first() } - val manifest = manifestFile.map { jacksonObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).readValue(it.readText(), FlareManifest::class.java) } - - // use darwin-aarch64 because its the smallest and we're going to throw away everything platform specific - val latest = manifest.map { it.versions.first() } - val latestVersion = latest.map { it.serverVersion } - val licensesUrl = latest.map { it.thirdPartyLicenses } - val darwin = latest.map { it.targets.first { target -> target.platform == "darwin" && target.arch == "arm64" } } - val contentUrls = darwin.map { it.contents.map { content -> content.url } } - - val destination = layout.buildDirectory.dir(latestVersion.map { "flare/$it" }) - outputs.dir(destination) - - src(contentUrls.zip(licensesUrl) { left, right -> left + right}) - dest(destination) - onlyIfModified(true) - useETag(true) -} - -val prepareBundledFlare by tasks.registering(Copy::class) { - dependsOn(downloadFlareArtifacts) - inputs.files(downloadFlareArtifacts) - - val dest = layout.buildDirectory.dir("tmp/extractFlare") - into(dest) - from(downloadFlareArtifacts.map { it.outputFiles.filterNot { file -> file.name.endsWith(".zip") } }) - - doLast { - copy { - into(dest) - includeEmptyDirs = false - downloadFlareArtifacts.get().outputFiles.filter { it.name.endsWith(".zip") }.forEach { - dest.get().file(it.parentFile.name).asFile.createNewFile() - from(zipTree(it)) { - include("*.js") - include("*.txt") - } - } - } - } -} - -tasks.withType().configureEach { - from(file("contrib/QCT-Maven-1-0-156-0.jar")) { - into(intellijPlatform.projectName.map { "$it/lib" }) - } - from(prepareBundledFlare) { - into(intellijPlatform.projectName.map { "$it/flare" }) - } -} diff --git a/plugins/amazonq/chat/build.gradle.kts b/plugins/amazonq/chat/build.gradle.kts deleted file mode 100644 index a801996a7a6..00000000000 --- a/plugins/amazonq/chat/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -plugins { - id("toolkit-jvm-conventions") -} - -dependencies { - implementation(project(":plugin-amazonq:chat:jetbrains-community")) -} diff --git a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts b/plugins/amazonq/chat/jetbrains-community/build.gradle.kts deleted file mode 100644 index 927bbf26216..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import software.aws.toolkits.gradle.intellij.IdeFlavor - -plugins { - id("toolkit-intellij-subplugin") -} - -intellijToolkit { - ideFlavor.set(IdeFlavor.IC) -} - -dependencies { - implementation(project(":plugin-core-q")) - - implementation(project(":plugin-amazonq:shared:jetbrains-community")) - // everything references codewhisperer, which is not ideal - implementation(project(":plugin-amazonq:codewhisperer:jetbrains-community")) - implementation(libs.diff.util) - implementation(libs.commons.text) - - compileOnly(project(":plugin-core-q:jetbrains-community")) - - testImplementation(testFixtures(project(":plugin-core-q:jetbrains-community"))) -} - -// hack because our test structure currently doesn't make complete sense -tasks.prepareTestSandbox { - val pluginXmlJar = project(":plugin-amazonq").tasks.jar - - dependsOn(pluginXmlJar) - from(pluginXmlJar) { - into(intellijPlatform.projectName.map { "$it/lib" }) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml b/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml deleted file mode 100644 index d1197e69b67..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt deleted file mode 100644 index 6cf8cc24769..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.common.util - -import com.github.difflib.DiffUtils -import com.github.difflib.patch.DeltaType -import com.intellij.openapi.fileChooser.FileChooser -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.CharsetToolkit -import com.intellij.openapi.vfs.VirtualFile -import java.io.File -import java.nio.charset.Charset -import java.nio.file.Path -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteIfExists -import kotlin.io.path.writeBytes - -fun resolveAndCreateOrUpdateFile(projectRootPath: Path, relativeFilePath: String, fileContent: String) { - val filePath = projectRootPath.resolve(relativeFilePath) - filePath.parent.createDirectories() // Create directories if needed - filePath.writeBytes(fileContent.toByteArray(Charsets.UTF_8)) -} - -fun resolveAndDeleteFile(projectRootPath: Path, relativePath: String) { - val filePath = projectRootPath.resolve(relativePath) - filePath.deleteIfExists() -} - -fun selectFolder(project: Project, openOn: VirtualFile): VirtualFile? { - val fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor() - return FileChooser.chooseFile(fileChooserDescriptor, project, openOn) -} - -fun readFileToString(file: File): String { - val charsetToolkit = CharsetToolkit(file.readBytes(), Charset.forName("UTF-8"), false) - val charset = charsetToolkit.guessEncoding(4096) - return file.readText(charset) -} - -/** - * Calculates the number of added characters and lines between existing content and LLM response - * - * @param existingContent The original text content before changes - * @param llmResponse The new text content from the LLM - * @return A Map containing: - * - "addedChars": Total number of new characters added - * - "addedLines": Total number of new lines added - */ -data class DiffResult(val addedChars: Int, val addedLines: Int) - -fun getDiffCharsAndLines( - existingContent: String, - llmResponse: String, -): DiffResult { - var addedChars = 0 - var addedLines = 0 - - val existingLines = existingContent.lines() - val llmLines = llmResponse.lines() - - val patch = DiffUtils.diff(existingLines, llmLines) - - for (delta in patch.deltas) { - when (delta.type) { - DeltaType.INSERT -> { - addedChars += delta.target.lines.sumOf { it.length } - addedLines += delta.target.lines.size - } - - DeltaType.CHANGE -> { - addedChars += delta.target.lines.sumOf { it.length } - addedLines += delta.target.lines.size - } - - else -> {} // Do nothing for DELETE - } - } - - return DiffResult(addedChars, addedLines) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt deleted file mode 100644 index f9ca023248a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.icons.AllIcons -import com.intellij.ide.actions.RevealFileAction -import com.intellij.ide.logsUploader.LogPacker -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages -import com.intellij.openapi.util.IconLoader -import com.intellij.ui.ColorUtil -import com.intellij.ui.JBColor -import com.intellij.util.IconUtil -import com.intellij.util.ui.UIUtil -import kotlinx.coroutines.runBlocking -import software.amazon.q.jetbrains.utils.notifyInfo -import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded -import software.amazon.q.resources.AwsCoreBundle -import software.aws.toolkits.resources.AmazonQBundle.message - -class GetAmazonQLogsAction : DumbAwareAction(message("amazonq.getLogs.tooltip.text")) { - private val baseIcon = IconLoader.getIcon("/icons/file.svg", GetAmazonQLogsAction::class.java) - - private val lightIcon by lazy { - IconUtil.colorize(baseIcon, ColorUtil.brighter(UIUtil.getLabelForeground(), 2)) - } - - override fun update(e: AnActionEvent) { - e.presentation.icon = if (!JBColor.isBright()) { - baseIcon - } else { - lightIcon - } - } - - override fun getActionUpdateThread() = ActionUpdateThread.BGT - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - showLogCollectionWarningGetLogs(project) - } - - companion object { - fun showLogCollectionWarningGetLogs(project: Project) { - if (Messages.showOkCancelDialog( - message("amazonq.logs.warning"), - message("amazonq.getLogs"), - AwsCoreBundle.message("general.ok"), - AwsCoreBundle.message("general.cancel"), - AllIcons.General.Warning - ) == 0 - ) { - runUnderProgressIfNeeded(project, message("amazonq.getLogs"), cancelable = true) { - runBlocking { - try { - RevealFileAction.openFile(LogPacker.packLogs(project)) - } catch (_: Exception) { - notifyInfo(message("amazonq.getLogs"), message("amazonq.logs.error"), project) - } - } - } - } - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt deleted file mode 100644 index 8ac30cbf887..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.ide.BrowserUtil -import com.intellij.openapi.Disposable -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBTextArea -import com.intellij.ui.components.panels.Wrapper -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.panel -import com.intellij.ui.jcef.JBCefJSQuery -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn -import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.actions.SsoLogoutAction -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES -import software.amazon.q.jetbrains.core.credentials.sono.isSono -import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.jetbrains.core.webview.BrowserMessage -import software.amazon.q.jetbrains.core.webview.BrowserState -import software.amazon.q.jetbrains.core.webview.LocalAssetJBCefRequestHandler -import software.amazon.q.jetbrains.core.webview.LoginBrowser -import software.amazon.q.jetbrains.isDeveloperMode -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIntent -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.aws.toolkits.telemetry.FeatureId -import software.aws.toolkits.telemetry.MetricResult -import software.aws.toolkits.telemetry.Telemetry -import software.aws.toolkits.telemetry.UiTelemetry -import software.aws.toolkits.telemetry.WebviewTelemetry -import java.awt.event.ActionListener -import java.net.URI -import javax.swing.JButton -import javax.swing.JComponent - -@Service(Service.Level.PROJECT) -class QWebviewPanel private constructor(val project: Project, private val scope: CoroutineScope) : Disposable { - private val webviewContainer = Wrapper() - var browser: QWebviewBrowser? = null - private set - - val component = panel { - row { - cell(webviewContainer) - .align(Align.FILL) - }.resizableRow() - - if (isDeveloperMode()) { - row { - cell( - JButton("Show Web Debugger").apply { - addActionListener( - ActionListener { - browser?.jcefBrowser?.openDevtools() - }, - ) - }, - ) - .align(Align.FILL) - } - } - } - - init { - init() - } - - fun disposeAndRecreate() { - webviewContainer.removeAll() - val toDispose = browser - init() - if (toDispose != null) { - Disposer.dispose(toDispose) - } - } - - private fun init() { - if (!isQWebviewsAvailable()) { - // Fallback to an alternative browser-less solution - webviewContainer.add(JBTextArea("JCEF not supported")) - browser = null - } else { - browser = QWebviewBrowser(project, this).also { - webviewContainer.add(it.component()) - - val themeBrowserAdapter = ThemeBrowserAdapter() - EditorThemeAdapter().onThemeChange() - .distinctUntilChanged() - .onEach { theme -> - themeBrowserAdapter.updateLoginThemeInBrowser(it.jcefBrowser.cefBrowser, theme) - } - .launchIn(scope) - } - } - } - - companion object { - fun getInstance(project: Project) = project.service() - } - - override fun dispose() { - } -} - -class QWebviewBrowser(val project: Project, private val parentDisposable: Disposable) : - LoginBrowser( - project, - ), - Disposable { - // TODO: confirm if we need such configuration or the default is fine - override val jcefBrowser = createBrowser(parentDisposable) - private val query = JBCefJSQuery.create(jcefBrowser) - private val assetHandler = LocalAssetJBCefRequestHandler(jcefBrowser) - - init { - loadWebView(query) - - query.addHandler(jcefHandler) - } - - override fun dispose() { - Disposer.dispose(jcefBrowser) - } - - fun component(): JComponent? = jcefBrowser.component - - override fun handleBrowserMessage(message: BrowserMessage?) { - if (message == null) { - return - } - - when (message) { - is BrowserMessage.PrepareUi -> { - this.prepareBrowser(BrowserState(FeatureId.AmazonQ, false)) - WebviewTelemetry.amazonqSignInOpened( - project, - reAuth = isQExpired(project) - ) - } - - is BrowserMessage.SelectConnection -> { - this.selectionSettings[message.connectionId]?.let { settings -> - settings.onChange(settings.currentSelection) - } - } - - is BrowserMessage.LoginBuilderId -> { - loginBuilderId(Q_SCOPES) - } - - is BrowserMessage.LoginIdC -> { - val awsRegion = AwsRegionProvider.getInstance()[message.region] ?: error("unknown region returned from Q browser") - loginIdC(message.url, awsRegion, Q_SCOPES) - } - - is BrowserMessage.CancelLogin -> { - cancelLogin() - } - - is BrowserMessage.Signout -> { - ( - ToolkitConnectionManager.getInstance(project) - .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - )?.let { connection -> - runInEdt { - SsoLogoutAction(connection).actionPerformed( - AnActionEvent.createFromDataContext( - "qBrowser", - null, - DataContext.EMPTY_CONTEXT - ) - ) - } - } - } - - is BrowserMessage.Reauth -> { - reauth(ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())) - } - - is BrowserMessage.LoginIAM, is BrowserMessage.ToggleBrowser -> { - error("QBrowser doesn't support the provided command ${message::class.simpleName}") - } - - is BrowserMessage.SendUiClickTelemetry -> { - val signInOption = message.signInOptionClicked - if (signInOption.isNullOrEmpty()) { - LOG.warn { "Unknown sign in option" } - } else { - UiTelemetry.click(project, signInOption) - } - } - - is BrowserMessage.SwitchProfile -> { - QRegionProfileManager.getInstance().switchProfile( - project, - QRegionProfile(profileName = message.profileName, arn = message.arn), - intent = QProfileSwitchIntent.Auth - ) - } - - is BrowserMessage.ListProfiles -> { - handleListProfilesMessage() - } - - is BrowserMessage.PublishWebviewTelemetry -> { - publishTelemetry(message) - } - - is BrowserMessage.OpenUrl -> { - BrowserUtil.browse(URI(message.externalLink)) - } - } - } - - override fun prepareBrowser(state: BrowserState) { - // TODO: duplicate code in ToolkitLoginWebview - selectionSettings.clear() - - if (!isQConnected(project)) { - // existing connections - // TODO: filter "active"(state == 'AUTHENTICATED') connection only maybe? - val bearerCreds = ToolkitAuthManager.getInstance().listConnections().filterIsInstance().associate { - it.id to BearerConnectionSelectionSettings(it) { conn -> - if (conn.isSono()) { - loginBuilderId(Q_SCOPES) - } else { - // TODO: rewrite scope logic, it's short term solution only - AwsRegionProvider.getInstance()[conn.region]?.let { region -> - loginIdC(conn.startUrl, region, Q_SCOPES) - } - } - } - } - - selectionSettings.putAll(bearerCreds) - } - - // previous login - val lastLoginIdcInfo = ToolkitAuthManager.getInstance().getLastLoginIdcInfo().apply { - // set default option as us-east-1 - if (this.region.isBlank()) { - this.region = AwsRegionProvider.getInstance().defaultRegion().id - } - } - - // available regions - val regions = AwsRegionProvider.getInstance().allRegionsForService("sso").values.let { - writeValueAsString(it) - } - - val stage = if (isQExpired(project)) { - "REAUTH" - } else if (isQConnected(project) && QRegionProfileManager.getInstance().isPendingProfileSelection(project)) { - "PROFILE_SELECT" - } else { - "START" - } - - when (stage) { - "PROFILE_SELECT" -> { - val jsonData = """ - { - stage: '$stage', - status: 'pending' - } - """.trimIndent() - executeJS("window.ideClient.prepareUi($jsonData)") - } - - else -> { - val jsonData = """ - { - stage: '$stage', - regions: $regions, - idcInfo: { - profileName: '${lastLoginIdcInfo.profileName}', - startUrl: '${lastLoginIdcInfo.startUrl}', - region: '${lastLoginIdcInfo.region}' - }, - cancellable: ${state.browserCancellable}, - feature: '${state.feature}', - existConnections: ${writeValueAsString(selectionSettings.values.map { it.currentSelection }.toList())}, - } - """.trimIndent() - - executeJS("window.ideClient.prepareUi($jsonData)") - } - } - } - - override fun loginIAM(profileName: String, accessKey: String, secretKey: String) { - LOG.error { "IAM is not supported by Q" } - return - } - - override fun loadWebView(query: JBCefJSQuery) { - val webScriptUri = assetHandler.createResource( - WEB_SCRIPT, - QWebviewBrowser::class.java.getResourceAsStream("/webview/assets/$WEB_SCRIPT") - ) - - jcefBrowser.loadURL(assetHandler.createResource("content.html", getWebviewHTML(webScriptUri, query))) - } - - private fun handleListProfilesMessage() { - ApplicationManager.getApplication().executeOnPooledThread { - var errorMessage = "" - val profiles = try { - QRegionProfileManager.getInstance().listRegionProfiles(project) - } catch (e: Exception) { - e.message?.let { - errorMessage = it - } - LOG.warn { "Failed to call listRegionProfiles API: $errorMessage" } - val qConn = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) - Telemetry.amazonq.didSelectProfile.use { span -> - span.source(QProfileSwitchIntent.Auth.value) - .amazonQProfileRegion(QRegionProfileManager.getInstance().activeProfile(project)?.region ?: "not-set") - .ssoRegion((qConn as? AwsBearerTokenConnection)?.region) - .credentialStartUrl((qConn as? AwsBearerTokenConnection)?.startUrl) - .result(MetricResult.Failed) - .reason(e.message) - } - - null - } - - // auto-select the profile if users only have 1 and don't show the UI - if (profiles?.size == 1) { - LOG.debug { "User only have access to 1 Q profile, auto-selecting profile ${profiles.first().profileName} for ${project.name}" } - QRegionProfileManager.getInstance().switchProfile(project, profiles.first(), QProfileSwitchIntent.Update) - return@executeOnPooledThread - } - - // required EDT as this entire block is executed on thread pool - runInEdt { - val jsonData = """ - { - stage: 'PROFILE_SELECT', - status: '${if (profiles != null) "succeeded" else "failed"}', - profiles: ${writeValueAsString(profiles ?: "")}, - errorMessage: '$errorMessage' - } - """.trimIndent() - - executeJS("window.ideClient.prepareUi($jsonData)") - } - } - } - - companion object { - private val LOG = getLogger() - private const val WEB_SCRIPT = "js/getStart.js" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt deleted file mode 100644 index b438425a123..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.ui.popup.JBPopup -import com.intellij.openapi.wm.StatusBarWidget -import com.intellij.openapi.wm.WindowManager -import com.intellij.openapi.wm.impl.status.IdeStatusBarImpl -import com.intellij.openapi.wm.impl.status.TextPanel -import com.intellij.ui.awt.RelativePoint -import com.intellij.ui.popup.AbstractPopup -import icons.AwsIcons -import software.aws.toolkits.jetbrains.services.codewhisperer.status.CodeWhispererStatusBarWidget -import software.aws.toolkits.resources.message -import java.awt.Dimension -import java.awt.Point -import javax.swing.JPanel - -class QMigrationNotificationAction : AnAction(message("q.migration.notification.title"), null, AwsIcons.Misc.NEW) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val statusBar = WindowManager.getInstance().getStatusBar(project) - val widget = statusBar.getWidget(CodeWhispererStatusBarWidget.ID) ?: return - val statusBarComponent = statusBar.component as IdeStatusBarImpl? ?: return - - // This is kinda ugly, but it works. We want to display the popup at the status bar component but it's a private - // field. So we have to search from its parent all the way down that matches Q status bar characteristics. - val component = statusBarComponent.components.flatMap { - (it as JPanel).components.filterIsInstance() - }.firstOrNull { it.text?.startsWith("Amazon Q") ?: false } ?: return - val presentation = widget.getPresentation() as StatusBarWidget.MultipleTextValuesPresentation - val popup = presentation.getPopup() ?: return - val dimension = getSizeFor(popup) - val at = Point(0, -dimension.height) - popup.show(RelativePoint(component, at)) - } - - private fun getSizeFor(popup: JBPopup): Dimension = - if (popup is AbstractPopup) popup.sizeForPositioning else popup.content.preferredSize -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt deleted file mode 100644 index cab5e06d312..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.wm.ToolWindowManager -import icons.AwsIcons -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AMAZON_Q_WINDOW_ID -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.runScanKey -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.UiTelemetry - -class QOpenPanelAction : AnAction(message("action.q.openchat.text"), null, AwsIcons.Logos.AWS_Q) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.getRequiredData(CommonDataKeys.PROJECT) - UiTelemetry.click(project, "q_openChat") - ToolWindowManager.getInstance(project).getToolWindow(AMAZON_Q_WINDOW_ID)?.activate(null, true) - if (e.getData(runScanKey) == true) { - AmazonQToolWindow.openScanTab(project) - } - } - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = e.getData(CommonDataKeys.PROJECT) != null - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QRefreshPanelAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QRefreshPanelAction.kt deleted file mode 100644 index 52ed6897ec3..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QRefreshPanelAction.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.progress.currentThreadCoroutineScope -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.util.messages.Topic -import kotlinx.coroutines.launch -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow -import software.aws.toolkits.resources.AmazonQBundle -import java.util.EventListener - -class QRefreshPanelAction : DumbAwareAction(AmazonQBundle.message("amazonq.refresh.panel"), null, AllIcons.Actions.Refresh) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - - // Notify LSP server about all open tabs being removed - val chatManager = ChatCommunicationManager.getInstance(project) - currentThreadCoroutineScope().launch { - chatManager.getAllTabIds().forEach { tabId -> - AmazonQLspService.executeAsyncIfRunning(project) { - rawEndpoint.notify(CHAT_TAB_REMOVE, mapOf("tabId" to tabId)) - } - } - } - - // recreate chat browser - AmazonQToolWindow.getInstance(project).disposeAndRecreate() - // recreate signin browser - QWebviewPanel.getInstance(project).disposeAndRecreate() - RefreshQChatPanelButtonPressedListener.notifyRefresh() - } - - override fun getActionUpdateThread() = ActionUpdateThread.BGT -} - -interface RefreshQChatPanelButtonPressedListener : EventListener { - fun onRefresh() {} - - companion object { - @Topic.AppLevel - val TOPIC = Topic.create("Q Chat refreshed", RefreshQChatPanelButtonPressedListener::class.java) - - fun notifyRefresh() { - ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onRefresh() - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt deleted file mode 100644 index 814dcca1211..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import com.intellij.openapi.Disposable - -/** - * Base interface for the entry point for "apps" that are built using AmazonQ. - * - * Apps should implement this interface, and then register the implementing class in plugin.xml as an extension: - * - * - * - * - */ -interface AmazonQApp : Disposable { - - /** - * The types of tabs supported by this app. Messages will only be received by the app if they have a tabType that is contained in this list. - */ - val tabTypes: List - - /** - * This initializer function is called when the tool window is being setup. The app is passed an instance of [AmazonQAppInitContext], which contains the - * connections needed to communicate with the Amazon Q UI. - */ - fun init(context: AmazonQAppInitContext) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt deleted file mode 100644 index 1bf8143a88f..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import com.intellij.openapi.project.Project - -interface AmazonQAppFactory { - fun createApp(project: Project): AmazonQApp -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt deleted file mode 100644 index ec9405cab18..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageListener -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher -import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter - -/** - * Context object that is passed to each [AmazonQApp] during initialization. Contains the connections needed to communicate with the Amazon Q UI. - */ -data class AmazonQAppInitContext( - val project: Project, - val messagesFromAppToUi: MessagePublisher, - val messagesFromUiToApp: MessageListener, - val messageTypeRegistry: MessageTypeRegistry, - val fqnWebviewAdapter: FqnWebviewAdapter, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt deleted file mode 100644 index f3f2ea4ad96..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector - -data class AppConnection( - val app: AmazonQApp, - val messagesFromAppToUi: MessageConnector, - val messagesFromUiToApp: MessageConnector, - val messageTypeRegistry: MessageTypeRegistry, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt deleted file mode 100644 index af2bf3efd9a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.auth - -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.Project -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn -import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection -import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnectionType -import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet -import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity -import software.amazon.q.jetbrains.core.gettingstarted.reauthenticateWithQ -import software.amazon.q.jetbrains.core.gettingstarted.requestCredentialsForQ -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.CwsprChatCommandType -import software.aws.toolkits.telemetry.UiTelemetry - -class AuthController { - /** - * Check the state of the Q connection. If the connection is valid then null is returned, otherwise it returns a [AuthNeededState] - * holding a message indicating the problem and what type of authentication is needed to resolve. - */ - fun getAuthNeededStates(project: Project): AuthNeededStates { - val connectionState = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) - - // CW chat is enabled for Builder and IDC users, same for Amazon Q - return AuthNeededStates( - chat = getAuthNeededState(connectionState), - amazonQ = getAuthNeededState(connectionState) - ) - } - - private fun getAuthNeededState( - amazonqConnectionState: ActiveConnection, - onlyIamIdcConnection: Boolean = false, - ): AuthNeededState? = - when (amazonqConnectionState) { - ActiveConnection.NotConnected -> { - AuthNeededState( - message = message("q.connection.disconnected"), - authType = AuthFollowUpType.FullAuth, - ) - } - - is ActiveConnection.ValidBearer -> { - if (onlyIamIdcConnection && amazonqConnectionState.connectionType != ActiveConnectionType.IAM_IDC) { - AuthNeededState( - message = message("q.connection.need_scopes"), - authType = AuthFollowUpType.Unsupported, - ) - } else { - null - } - } - - is ActiveConnection.ExpiredBearer -> AuthNeededState( - message = message("q.connection.expired"), - authType = AuthFollowUpType.ReAuth, - ) - // Not a bearer connection. This should not happen, but if it does, we treat it as a full-auth scenario - else -> { - logger.warn { "Received non-bearer connection for Q" } - AuthNeededState( - message = message("q.connection.invalid"), - authType = AuthFollowUpType.FullAuth, - ) - } - } - - fun handleAuth(project: Project, type: AuthFollowUpType) { - when (type) { - AuthFollowUpType.MissingScopes, - AuthFollowUpType.Unsupported, - AuthFollowUpType.FullAuth, - -> runInEdt { - UiTelemetry.click(project, "amazonq_chatAuthenticate") - requestCredentialsForQ(project, connectionInitiatedFromQChatPanel = true, isReauth = false) - } - - AuthFollowUpType.ReAuth, - -> runInEdt { - reauthenticateWithQ(project) - } - } - - TelemetryHelper.recordTelemetryChatRunCommand(CwsprChatCommandType.Auth, type.name, getStartUrl(project)) - } - - companion object { - private val logger = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt deleted file mode 100644 index 9c5d8377eda..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.auth - -import com.fasterxml.jackson.annotation.JsonValue - -enum class AuthFollowUpType( - @field:JsonValue val json: String, -) { - FullAuth("full-auth"), - ReAuth("re-auth"), - MissingScopes("missing_scopes"), - Unsupported("use-supported-auth"), -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt deleted file mode 100644 index e034717adf9..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.auth - -data class AuthNeededState( - val message: String, - val authType: AuthFollowUpType, -) - -data class AuthNeededStates( - val chat: AuthNeededState? = null, - val amazonQ: AuthNeededState? = null, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt deleted file mode 100644 index 29b7213a76b..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.commands - -import com.fasterxml.jackson.annotation.JsonAutoDetect -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.PropertyAccessor -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.treeToValue -import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.amazonq.messages.UnknownMessageType -import software.aws.toolkits.jetbrains.services.amazonq.util.command - -class MessageSerializer @VisibleForTesting constructor() { - - val objectMapper = jacksonObjectMapper() - .registerModule(JavaTimeModule()) - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - - fun toNode(json: String) = objectMapper.readTree(json) - - fun deserialize(node: JsonNode, registeredTypes: MessageTypeRegistry): AmazonQMessage { - val type = registeredTypes.get(node.command) ?: return UnknownMessageType(node.asText()) - return objectMapper.treeToValue(node, type.java) - } - - fun serialize(value: Any): String = objectMapper.writeValueAsString(value) - - inline fun deserializeChatMessages(value: JsonNode): T = - objectMapper.treeToValue(value) - - inline fun deserializeChatMessages(value: JsonNode, clazz: Class): T = - objectMapper.treeToValue(value, clazz) - - // Provide singleton global access - companion object { - private val instance = MessageSerializer() - - fun getInstance() = instance - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt deleted file mode 100644 index 9547312a24d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.commands - -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import kotlin.reflect.KClass - -private typealias MessageClass = KClass - -/** - * This class allows an App to register the target class to use for deserialization of a particular command from the TypeScript code. - * Messages from TypeScript arrive as a JSON object that always has a "command" field. Apps can register the specific class an object with a particular command - * should deserialize as before they are sent out to the app's MessageListener. - */ -class MessageTypeRegistry { - private val registry = mutableMapOf() - - fun register(command: String, type: MessageClass) = registry.put(command, type) - fun register(vararg entries: Pair) = registry.putAll(entries) - - fun remove(command: String) = registry.remove(command) - fun get(command: String) = registry[command] -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt deleted file mode 100644 index 066b3bda8d0..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.explorerActions - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import software.amazon.q.jetbrains.core.gettingstarted.reauthenticateWithQ -import software.aws.toolkits.resources.message - -class ReauthenticateWithQ : AnAction(message("q.reauthenticate")) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - reauthenticateWithQ(project) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt deleted file mode 100644 index ed364f873d1..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.explorerActions - -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.wm.ToolWindowManager -import software.amazon.q.jetbrains.core.credentials.ReauthSource -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded -import software.amazon.q.jetbrains.core.gettingstarted.requestCredentialsForQ -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.UiTelemetry - -class SignInToQAction : SignInToQActionBase(message("q.sign.in")) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - UiTelemetry.click(project, "auth_start_Q") - - if (!isQWebviewsAvailable()) { - requestCredentialsForQ(project, isReauth = false) - } else { - ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID)?.show() - } - } -} - -class EnableQAction : SignInToQActionBase(message("q.enable.text")) - -abstract class SignInToQActionBase(actionName: String) : DumbAwareAction(actionName, null, AllIcons.CodeWithMe.CwmAccess) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - UiTelemetry.click(project, "auth_start_Q") - val connectionManager = ToolkitConnectionManager.getInstance(project) - connectionManager.activeConnectionForFeature(QConnection.getInstance())?.let { - reauthConnectionIfNeeded(project, it, isReAuth = true, reauthSource = ReauthSource.CODEWHISPERER_STATUSBAR) - } ?: run { - runInEdt { - if (requestCredentialsForQ(project, isReauth = false)) { - if (!openMeetQPage(project)) { - return@runInEdt - } - } - } - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt deleted file mode 100644 index 8c65f81aef7..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project -import com.intellij.ui.JBColor -import com.intellij.ui.jcef.JBCefBrowserBase -import com.intellij.ui.jcef.JBCefJSQuery -import com.intellij.ui.jcef.JCEFHtmlPanel -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import org.cef.browser.CefBrowser -import org.cef.browser.CefFrame -import org.cef.handler.CefLoadHandlerAdapter -import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope -import software.amazon.q.jetbrains.core.webview.LocalAssetJBCefRequestHandler -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.UiTelemetry -import java.util.Base64 -import java.util.function.Function - -class QGettingStartedContent(val project: Project) : Disposable { - val jcefBrowser: JBCefBrowserBase = JCEFHtmlPanel("about:blank") - val receiveMessageQuery = JBCefJSQuery.create(jcefBrowser) - - init { - jcefBrowser.jbCefClient.addLoadHandler( - object : CefLoadHandlerAdapter() { - override fun onLoadEnd(browser: CefBrowser, frame: CefFrame, httpStatusCode: Int) { - // only needs to be done once - jcefBrowser.jbCefClient.removeLoadHandler(this, browser) - - disposableCoroutineScope(this@QGettingStartedContent).launch { - EditorThemeAdapter().onThemeChange() - .distinctUntilChanged() - .onEach { - val js = if (it.darkMode) { - "document.body.classList.add('$darkThemeClass');document.body.classList.remove('$lightThemeClass');" - } else { - "document.body.classList.add('$lightThemeClass');document.body.classList.remove('$darkThemeClass');" - } - browser.executeJavaScript(js, browser.url, 0) - } - .launchIn(this) - } - } - }, - jcefBrowser.cefBrowser - ) - loadWebView() - val handler = Function { - val command = jacksonObjectMapper().readTree(it).get("command").asText() - when (command) { - "sendToQ" -> { - UiTelemetry.click(project, "amazonq_meet_askq") - AmazonQToolWindow.getStarted(project) - } - } - - JBCefJSQuery.Response(null) - } - receiveMessageQuery.addHandler(handler) - } - - fun component() = jcefBrowser.component - - private fun loadWebView() { - // load the web app - jcefBrowser.loadURL(LocalAssetJBCefRequestHandler(jcefBrowser).createResource("content.html", getWebviewHTML())) - } - - private fun getWebviewHTML(): String { - val colorMode = if (JBColor.isBright()) lightThemeClass else darkThemeClass - val bgLogoDark = getBase64EncodedImageString("/icons/logos/Amazon-Q-Icon_White_Medium.svg") - val qLogo = getBase64EncodedImageString("/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg") - val bgLogoLight = getBase64EncodedImageString("/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg") - val cwLogoDark = getBase64EncodedImageString("/icons/logos/CW_InlineSuggestions_dark.svg") - val cwLogoLight = getBase64EncodedImageString("/icons/logos/CW_InlineSuggestions_light.svg") - val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)") - - return """ - - - - - - - - - - -
-
- -

${message("q.onboarding.description")}

-
- -
-
-
- - - - """.trimIndent() - } - - private fun getBase64EncodedImageString(imageLocation: String) = QGettingStartedContent::class.java.getResourceAsStream(imageLocation).use { - Base64.getEncoder().encodeToString(it?.readAllBytes() ?: return@use null) - } - - private fun getImageSourceFromEncodedString(imageName: String?) = "data:image/svg+xml;base64,$imageName" - - override fun dispose() { - } - - companion object { - private const val darkThemeClass = "dark" - private const val lightThemeClass = "light" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt deleted file mode 100644 index 4f7e4291005..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.intellij.openapi.fileEditor.FileEditor -import com.intellij.openapi.fileEditor.FileEditorState -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.UserDataHolderBase -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.components.JBScrollPane -import java.beans.PropertyChangeListener -import javax.swing.JComponent - -class QGettingStartedEditor( - private val project: Project, - private val file: VirtualFile, -) : - UserDataHolderBase(), FileEditor { - override fun dispose() { - } - - override fun getComponent(): JComponent { - val panel = QGettingStartedPanel(project) - Disposer.register(this, panel) - - return JBScrollPane(panel.component) - } - - override fun getFile(): VirtualFile = file - - override fun getName(): String = file.name - - override fun getPreferredFocusedComponent(): JComponent? = null - - override fun setState(state: FileEditorState) {} - - override fun isModified(): Boolean = false - - override fun isValid(): Boolean = true - - override fun addPropertyChangeListener(listener: PropertyChangeListener) { - } - - override fun removePropertyChangeListener(listener: PropertyChangeListener) { - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt deleted file mode 100644 index dde52f78fe2..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.intellij.openapi.fileEditor.FileEditor -import com.intellij.openapi.fileEditor.FileEditorPolicy -import com.intellij.openapi.fileEditor.FileEditorProvider -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile - -class QGettingStartedEditorProvider : FileEditorProvider, DumbAware { - override fun accept(project: Project, file: VirtualFile) = file is QGettingStartedVirtualFile - - override fun createEditor(project: Project, file: VirtualFile): FileEditor { - file as QGettingStartedVirtualFile - return QGettingStartedEditor(project, file) - } - - override fun getEditorTypeId() = EDITOR_TYPE - - override fun getPolicy() = FileEditorPolicy.HIDE_DEFAULT_EDITOR - - companion object { - const val EDITOR_TYPE = "QGettingStartedUxMainPanel" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt deleted file mode 100644 index d5e15052f00..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBTextArea -import com.intellij.ui.components.panels.Wrapper -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.panel -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable - -class QGettingStartedPanel( - val project: Project, -) : Disposable { - private val webviewContainer = Wrapper() - var browser: QGettingStartedContent? = null - private set - - val component = panel { - row { - cell(webviewContainer) - .align(Align.FILL) - }.resizableRow() - } - - init { - if (!isQWebviewsAvailable()) { - // Fallback to an alternative browser-less solution - webviewContainer.add(JBTextArea("JCEF not supported")) - browser = null - } else { - browser = QGettingStartedContent(project).also { - webviewContainer.add(it.component()) - } - } - } - - override fun dispose() { - browser?.let { - Disposer.dispose(it) - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt deleted file mode 100644 index 70c0e1641f0..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.messages - -interface AmazonQMessage - -/** - * Message that is sent when a command is received that does not have a registered deserialization class. The content is the plain-text representation of the - * received JSON. - */ -data class UnknownMessageType(val content: String) : AmazonQMessage diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt deleted file mode 100644 index 62b350c56f7..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.messages - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow - -/** - * MessagePublisher is used for sending outbound messages - */ -interface MessagePublisher { - suspend fun publish(message: AmazonQMessage) -} - -/** - * MessageListener is used to receive inbound messages - */ -interface MessageListener { - val flow: Flow -} - -/** - * A MessageConnector is a uni-directional channel for passing messages between Amazon Q and App implementations. It is provided as either a MessagePublisher or - * MessageListener depending on the intended direction of communication. - */ -class MessageConnector : MessagePublisher, MessageListener { - private val _messages = MutableSharedFlow(extraBufferCapacity = 10, replay = 10) - override val flow = _messages.asSharedFlow() - - override suspend fun publish(message: AmazonQMessage) { - _messages.emit(message) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt deleted file mode 100644 index 6a57c9ec73a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.onboarding - -import com.fasterxml.jackson.annotation.JsonValue -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage - -enum class OnboardingPageInteractionType( - @field:JsonValue val json: String, -) { - CwcButtonClick("onboarding-page-cwc-button-clicked"), -} - -data class OnboardingPageInteraction( - val type: OnboardingPageInteractionType, -) : AmazonQMessage diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt deleted file mode 100644 index 6dd45965158..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.startup - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.ProjectActivity -import com.intellij.openapi.wm.ToolWindowManager -import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.gettingstarted.emitUserState -import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory -import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager -import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatController -import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import java.util.concurrent.atomic.AtomicBoolean - -class AmazonQStartupActivity : ProjectActivity { - private val runOnce = AtomicBoolean(false) - - override suspend fun execute(project: Project) { - if (ApplicationManager.getApplication().isUnitTestMode) return - - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { - if (it is AwsBearerTokenConnection && CodeWhispererFeatureConfigService.getInstance().getChatWSContext()) { - CodeWhispererSettings.getInstance().toggleProjectContextEnabled(value = true, passive = true) - } - } - - // initialize html contents in BGT so users don't have to wait when they open the tool window - AmazonQToolWindow.getInstance(project) - InlineChatController.getInstance(project) - - if (CodeWhispererExplorerActionManager.getInstance().getIsFirstRestartAfterQInstall()) { - runInEdt { - val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID) ?: return@runInEdt - toolWindow.show() - CodeWhispererExplorerActionManager.getInstance().setIsFirstRestartAfterQInstall(false) - } - } - - QRegionProfileManager.getInstance().validateProfile(project) - - AmazonQLspService.getInstance(project) - if (runOnce.get()) return - emitUserState(project) - runOnce.set(true) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt deleted file mode 100644 index cc09349dcca..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBLoadingPanel -import com.intellij.ui.components.JBTextArea -import com.intellij.ui.components.ProgressBarLoadingDecorator -import com.intellij.ui.components.panels.Wrapper -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.AlignX -import com.intellij.ui.dsl.builder.AlignY -import com.intellij.ui.dsl.builder.panel -import com.intellij.ui.jcef.JBCefApp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.jetbrains.core.coroutines.EDT -import software.amazon.q.jetbrains.isDeveloperMode -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext -import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry -import software.aws.toolkits.jetbrains.services.amazonq.isQSupportedInThisVersion -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.util.highlightCommand -import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser -import software.aws.toolkits.jetbrains.services.amazonq.webview.BrowserConnector -import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable -import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import software.aws.toolkits.resources.message -import java.awt.datatransfer.DataFlavor -import java.awt.dnd.DropTarget -import java.awt.dnd.DropTargetDragEvent -import java.awt.dnd.DropTargetDropEvent -import java.awt.dnd.DropTargetEvent -import java.io.File -import java.util.concurrent.CompletableFuture -import javax.imageio.ImageIO.read -import javax.swing.JButton - -class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Disposable { - private val browser = CompletableFuture() - private val webviewContainer = Wrapper() - private val appSource = AppSource() - private val browserConnector = BrowserConnector(project = project) - private val editorThemeAdapter = EditorThemeAdapter() - private val appConnections = mutableListOf() - - val component = panel { - row { - cell(webviewContainer) - .align(Align.FILL) - }.resizableRow() - - // Button to show the web debugger for debugging the UI: - if (isDeveloperMode()) { - row { - cell( - JButton("Show Web Debugger").apply { - addActionListener { - // Code to be executed when the button is clicked - // Add your logic here - - browser.get().jcefBrowser.openDevtools() - } - }, - ) - .align(AlignX.CENTER) - .align(AlignY.BOTTOM) - } - } - } - - init { - if (!JBCefApp.isSupported()) { - // Fallback to an alternative browser-less solution - if (isRunningOnRemoteBackend()) { - webviewContainer.add(JBTextArea("Amazon Q chat is not supported in this remote dev environment because it lacks JCEF webview support.")) - } else { - webviewContainer.add(JBTextArea("JCEF not supported")) - } - browser.complete(null) - } else if (!isQSupportedInThisVersion()) { - webviewContainer.add(JBTextArea("${message("q.unavailable")}\n ${message("q.unavailable.node")}")) - browser.complete(null) - } else { - val loadingPanel = if (isRunningOnRemoteBackend()) { - JBLoadingPanel(null) { - ProgressBarLoadingDecorator(it, this, -1) - } - } else { - JBLoadingPanel(null, this) - } - - val wrapper = Wrapper() - loadingPanel.startLoading() - - webviewContainer.add(wrapper) - wrapper.setContent(loadingPanel) - - scope.launch { - val mynahAsset = service().fetchArtifact(project).resolve("amazonq-ui.js") - // wait for server to be running - AmazonQLspService.getInstance(project).instanceFlow.first() - - withContext(EDT) { - browser.complete( - Browser(this@AmazonQPanel, mynahAsset, project).also { browserInstance -> - wrapper.setContent(browserInstance.component()) - - // Register direct callback instead of using message bus - ChatCommunicationManager.getInstance(project).setChatUpdateCallback { message -> - browserInstance.postChat(message) - } - - // Add DropTarget to the browser component - // JCEF does not propagate OS-level dragenter, dragOver and drop into DOM. - // As an alternative, enabling the native drag in JCEF, - // and let the native handling the drop event, and update the UI through JS bridge. - val dropTarget = object : DropTarget() { - override fun dragEnter(dtde: DropTargetDragEvent) { - setDragAndDropOverlayVisible(browserInstance, true) - } - override fun dragOver(dtde: DropTargetDragEvent) { - setDragAndDropOverlayVisible(browserInstance, true) - } - override fun dragExit(dte: DropTargetEvent) { - setDragAndDropOverlayVisible(browserInstance, false) - } - override fun drop(dtde: DropTargetDropEvent) { - try { - dtde.acceptDrop(dtde.dropAction) - val transferable = dtde.transferable - if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { - val fileList = transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<*> - - val errorMessages = mutableListOf() - val validImages = mutableListOf() - val allowedTypes = setOf("jpg", "jpeg", "png", "gif", "webp") - val maxFileSize = 3.75 * 1024 * 1024 // 3.75MB in bytes - val maxDimension = 8000 - - for (file in fileList as List) { - val validationResult = validateImageFile(file, allowedTypes, maxFileSize, maxDimension) - if (validationResult != null) { - errorMessages.add(validationResult) - } else { - validImages.add(file) - } - } - - // File count restriction - if (validImages.size > 20) { - errorMessages.add("A maximum of 20 images can be added to a single message.") - validImages.subList(20, validImages.size).clear() - } - - executeJavaScript(browserInstance, "window.resetTopBarClicked()") - - setDragAndDropOverlayVisible(browserInstance, false) - - val json = OBJECT_MAPPER.writeValueAsString(validImages) - executeJavaScript(browserInstance, "window.handleNativeDrop('$json')") - - if (errorMessages.isNotEmpty()) { - val errorJson = OBJECT_MAPPER.writeValueAsString(errorMessages) - executeJavaScript(browserInstance, "window.handleNativeNotify('$errorJson')") - } - - dtde.dropComplete(true) - } else { - dtde.dropComplete(false) - } - } catch (e: Exception) { - LOG.error { "Failed to handle file drop operation: ${e.message}" } - dtde.dropComplete(false) - } - } - } - - // Set DropTarget on the browser component and its children - browserInstance.component()?.let { component -> - component.dropTarget = dropTarget - // Also try setting on parent if needed - component.parent?.dropTarget = dropTarget - } - - initConnections() - connectUi(browserInstance) - connectApps(browserInstance) - - loadingPanel.stopLoading() - } - ) - } - } - } - } - - fun sendMessage(message: AmazonQMessage, tabType: String) { - appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach { - scope.launch { - it.messagesFromUiToApp.publish(message) - } - } - } - - fun sendMessageAppToUi(message: AmazonQMessage, tabType: String) { - appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach { - scope.launch { - it.messagesFromAppToUi.publish(message) - } - } - } - - private fun initConnections() { - val apps = appSource.getApps(project) - apps.forEach { app -> - appConnections += AppConnection( - app = app, - messagesFromAppToUi = MessageConnector(), - messagesFromUiToApp = MessageConnector(), - messageTypeRegistry = MessageTypeRegistry(), - ) - } - } - - private fun connectApps(browser: Browser) { - val fqnWebviewAdapter = FqnWebviewAdapter(browser.jcefBrowser, browserConnector) - - appConnections.forEach { connection -> - val initContext = AmazonQAppInitContext( - project = project, - messagesFromAppToUi = connection.messagesFromAppToUi, - messagesFromUiToApp = connection.messagesFromUiToApp, - messageTypeRegistry = connection.messageTypeRegistry, - fqnWebviewAdapter = fqnWebviewAdapter, - ) - // Connect the app to the UI - connection.app.init(initContext) - // Dispose of the app when the tool window is disposed. - Disposer.register(this, connection.app) - } - } - - private fun connectUi(browser: Browser) { - browser.init( - isCodeTransformAvailable = isCodeTransformAvailable(project), - isCodeScanAvailable = isCodeScanAvailable(project), - highlightCommand = highlightCommand(), - activeProfile = QRegionProfileManager.getInstance().takeIf { it.shouldDisplayProfileInfo(project) }?.activeProfile(project) - ) - - scope.launch { - // Pipe messages from the UI to the relevant apps and vice versa - browserConnector.connect( - browser = browser, - connections = appConnections, - ) - } - - scope.launch { - // Update the theme in the UI when the IDE theme changes - browserConnector.connectTheme( - chatBrowser = browser.jcefBrowser.cefBrowser, - themeSource = editorThemeAdapter.onThemeChange(), - ) - } - } - - private fun executeJavaScript(browserInstance: Browser, jsCommand: String) { - try { - browserInstance.jcefBrowser.cefBrowser.executeJavaScript( - jsCommand, - browserInstance.jcefBrowser.cefBrowser.url, - 0 - ) - } catch (e: Exception) { - LOG.error { "Failed to execute JavaScript: $jsCommand - ${e.message}" } - } - } - - private fun setDragAndDropOverlayVisible(browserInstance: Browser, visible: Boolean) { - executeJavaScript(browserInstance, "window.setDragAndDropVisible('$visible')") - } - - private fun validateImageFile(file: File, allowedTypes: Set, maxFileSize: Double, maxDimension: Int): String? { - val fileName = file.name - val ext = fileName.substringAfterLast('.', "").lowercase() - - if (ext !in allowedTypes) { - return "$fileName: File must be an image in JPEG, PNG, GIF, or WebP format." - } - - if (file.length() > maxFileSize) { - return "$fileName: Image must be no more than 3.75MB in size." - } - - return try { - val img = read(file) - when { - img == null -> "$fileName: File could not be read as an image." - img.width > maxDimension -> "$fileName: Image must be no more than 8,000px in width." - img.height > maxDimension -> "$fileName: Image must be no more than 8,000px in height." - else -> null - } - } catch (e: Exception) { - "$fileName: File could not be read as an image." - } - } - - companion object { - private val LOG = getLogger() - private val OBJECT_MAPPER = jacksonObjectMapper() - } - - override fun dispose() { - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt deleted file mode 100644 index 5df972bba09..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.wm.ToolWindowManager -import kotlinx.coroutines.CoroutineScope -import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteraction -import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteractionType -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.runCodeScanMessage - -@Service(Service.Level.PROJECT) -class AmazonQToolWindow private constructor( - private val project: Project, - private val scope: CoroutineScope, -) : Disposable { - private var chatPanel = AmazonQPanel(project, scope) - - val component - get() = chatPanel.component - fun disposeAndRecreate() { - Disposer.dispose(chatPanel) - chatPanel = AmazonQPanel(project, scope) - } - - companion object { - fun getInstance(project: Project): AmazonQToolWindow = project.service() - - private fun showChatWindow(project: Project) = runInEdt { - val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID) - toolWindow?.show() - } - - fun getStarted(project: Project) { - // Make sure the window is shown - showChatWindow(project) - - // Send the interaction message - val window = getInstance(project) - window.chatPanel.sendMessage(OnboardingPageInteraction(OnboardingPageInteractionType.CwcButtonClick), "cwc") - } - - fun openScanTab(project: Project) { - showChatWindow(project) - val window = getInstance(project) - window.chatPanel.sendMessageAppToUi(runCodeScanMessage, tabType = "codescan") - } - } - - override fun dispose() { - // Nothing to do - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt deleted file mode 100644 index 6a8e02dfdfa..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowFactory -import com.intellij.openapi.wm.ex.ToolWindowEx -import com.intellij.ui.components.panels.Wrapper -import com.intellij.util.ui.components.BorderLayoutPanel -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger -import software.amazon.q.jetbrains.core.credentials.ToolkitConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.amazon.q.jetbrains.core.notifications.BannerContent -import software.amazon.q.jetbrains.core.notifications.NotificationDismissalState -import software.amazon.q.jetbrains.core.notifications.NotificationPanel -import software.amazon.q.jetbrains.core.notifications.ProcessNotificationsBase -import software.amazon.q.jetbrains.core.webview.BrowserState -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend -import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel -import software.aws.toolkits.jetbrains.services.amazonq.RefreshQChatPanelButtonPressedListener -import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.resources.AmazonQBundle -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.FeatureId -import java.awt.event.ComponentAdapter -import java.awt.event.ComponentEvent - -class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware { - - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - val mainPanel = BorderLayoutPanel() - val qPanel = Wrapper() - val notificationPanel = NotificationPanel().apply { - if (isRunningOnRemoteBackend() && !NotificationDismissalState.getInstance().isDismissed(REMOTE_RESIZE_NOTIFICATION_ID)) { - updateNotificationPanel( - BannerContent( - REMOTE_RESIZE_NOTIFICATION_ID, - AmazonQBundle.message("amazonq.resize.panel"), - // message is not used for banner - "", - ) - ) - } - } - - mainPanel.addToTop(notificationPanel) - mainPanel.add(qPanel) - val notifListener = ProcessNotificationsBase.getInstance(project) - notifListener.addListenerForNotification { bannerContent -> - runInEdt { - notificationPanel.updateNotificationPanel(bannerContent) - } - } - - if (toolWindow is ToolWindowEx) { - val actionManager = ActionManager.getInstance() - toolWindow.setTitleActions(listOf(actionManager.getAction("aws.q.toolwindow.titleBar"))) - } - val contentManager = toolWindow.contentManager - - project.messageBus.connect(toolWindow.disposable).subscribe( - ToolkitConnectionManagerListener.TOPIC, - object : ToolkitConnectionManagerListener { - override fun activeConnectionChanged(newConnection: ToolkitConnection?) { - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { qConn -> - openMeetQPage(project) - } - preparePanelContent(project, qPanel) - } - } - ) - - project.messageBus.connect(toolWindow.disposable).subscribe( - RefreshQChatPanelButtonPressedListener.TOPIC, - object : RefreshQChatPanelButtonPressedListener { - override fun onRefresh() { - preparePanelContent(project, qPanel) - } - } - ) - - project.messageBus.connect(toolWindow.disposable).subscribe( - BearerTokenProviderListener.TOPIC, - object : BearerTokenProviderListener { - override fun onProviderChange(providerId: String, newScopes: List?) { - if (ToolkitConnectionManager.getInstance(project).connectionStateForFeature(QConnection.getInstance()) == BearerTokenAuthState.AUTHORIZED) { - preparePanelContent(project, qPanel) - } - } - } - ) - - project.messageBus.connect(toolWindow.disposable).subscribe( - QRegionProfileSelectedListener.TOPIC, - object : QRegionProfileSelectedListener { - // note we name myProject intentionally ow it will shadow the "project" provided by the IDE - override fun onProfileSelected(myProject: Project, profile: QRegionProfile?) { - if (project.isDisposed) return - preparePanelContent(project, qPanel) - } - } - ) - - preparePanelContent(project, qPanel) - - val content = contentManager.factory.createContent(mainPanel, null, false).also { - it.isCloseable = true - it.isPinnable = true - } - toolWindow.activate(null) - contentManager.addContent(content) - } - - private fun preparePanelContent( - project: Project, - qPanel: Wrapper, - ) { - /** - * only render Q Chat when - * 1. There is a Q connection - * 2. Q connection is not expired - * 3. User is not pending region profile selection - */ - val component = if (isQConnected(project) && !isQExpired(project) && !QRegionProfileManager.getInstance().isPendingProfileSelection(project)) { - AmazonQToolWindow.getInstance(project).component - } else { - QWebviewPanel.getInstance(project).browser?.prepareBrowser(BrowserState(FeatureId.AmazonQ)) - QWebviewPanel.getInstance(project).component - } - runInEdt { - qPanel.setContent(component) - } - } - - /** - * Only applies to local - * On remote, since we are using PROJECTOR_INSTANCING, this will never run - */ - override fun init(toolWindow: ToolWindow) { - toolWindow.stripeTitle = message("toolwindow.stripe.amazon.q.window") - toolWindow.component.addComponentListener( - object : ComponentAdapter() { - override fun componentResized(e: ComponentEvent) { - val newWidth = e.component.width - if (newWidth >= MINIMUM_TOOLWINDOW_WIDTH) return - LOG.debug { - "Amazon Q Tool window stretched to a width less than the minimum allowed width, resizing to the minimum allowed width" - } - - // can't implement equivalent on remote as stretchWidth impl is noop - (toolWindow as ToolWindowEx).stretchWidth(MINIMUM_TOOLWINDOW_WIDTH - newWidth) - } - } - ) - } - - override fun shouldBeAvailable(project: Project): Boolean = isQWebviewsAvailable() - - companion object { - private val LOG = getLogger() - const val WINDOW_ID = AMAZON_Q_WINDOW_ID - private const val MINIMUM_TOOLWINDOW_WIDTH = 325 - private const val REMOTE_RESIZE_NOTIFICATION_ID = "resize.amazon.q.toolwindow.if.remote" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt deleted file mode 100644 index b201519e6f1..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowManager -import com.intellij.openapi.wm.ex.ToolWindowManagerListener -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper - -class AmazonQToolWindowListener : ToolWindowManagerListener { - - override fun toolWindowShown(toolWindow: ToolWindow) { - if (toolWindow.id == AmazonQToolWindowFactory.WINDOW_ID) { - TelemetryHelper.recordOpenChat(toolWindow.project) - } - } - - override fun stateChanged( - toolWindowManager: ToolWindowManager, - toolWindow: ToolWindow, - changeType: ToolWindowManagerListener.ToolWindowManagerEventType, - ) { - if (toolWindow.id != AmazonQToolWindowFactory.WINDOW_ID) { - return - } - - if (changeType == ToolWindowManagerListener.ToolWindowManagerEventType.HideToolWindow) { - TelemetryHelper.recordCloseChat(toolWindow.project) - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt deleted file mode 100644 index d54fed75552..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.extensions.ExtensionPointName -import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppFactory - -class AppSource { - private val extensionPointName = ExtensionPointName.create("amazon.q.appFactory") - fun getApps(project: Project) = buildList { - extensionPointName.forEachExtensionSafe { - add(it.createApp(project)) - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt deleted file mode 100644 index 5019d051c1b..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.util - -import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService - -data class HighlightCommand(val command: String, val description: String) - -fun highlightCommand(): HighlightCommand? { - val feature = CodeWhispererFeatureConfigService.getInstance().getHighlightCommandFeature() - - if (feature == null || feature.value.stringValue().isEmpty()) return null - - return HighlightCommand(feature.value.stringValue(), feature.variation) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt deleted file mode 100644 index fd86513a863..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.util - -import com.intellij.openapi.Disposable -import com.intellij.openapi.util.Disposer -import com.intellij.ui.jcef.JBCefApp -import com.intellij.ui.jcef.JBCefBrowserBase -import com.intellij.ui.jcef.JBCefBrowserBuilder -import com.intellij.ui.jcef.JBCefClient - -fun createBrowser(parent: Disposable): JBCefBrowserBase { - val client = JBCefApp.getInstance().createClient().apply { - setProperty(JBCefClient.Properties.JS_QUERY_POOL_SIZE, 5) - } - - Disposer.register(parent, client) - - return JBCefBrowserBuilder() - .setClient(client) - .setOffScreenRendering(true) - .build() -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt deleted file mode 100644 index 673396fa7e9..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.util - -import com.fasterxml.jackson.databind.JsonNode - -val JsonNode.command - get() = get("command").asText() - -val JsonNode.tabType - get() = get("tabType")?.asText() diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt deleted file mode 100644 index ee3053b1c2e..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -@file:Suppress("BannedImports") -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.google.gson.Gson -import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.jcef.JBCefJSQuery -import software.amazon.q.core.utils.inputStream -import software.amazon.q.jetbrains.core.webview.LocalAssetJBCefRequestHandler -import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand -import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.aws.toolkits.jetbrains.settings.MeetQSettings -import java.nio.file.Path -import java.nio.file.Paths - -/** - * Displays the web view for the Amazon Q tool window - */ -class Browser(parent: Disposable, private val mynahAsset: Path, val project: Project) : Disposable { - - val jcefBrowser = createBrowser(parent) - - val receiveMessageQuery = JBCefJSQuery.create(jcefBrowser) - - private val assetRequestHandler = LocalAssetJBCefRequestHandler(jcefBrowser) - - init { - assetRequestHandler.addWildcardHandler("mynah") { path -> - val asset = path.replaceFirst("mynah/", "/mynah-ui/assets/") - Paths.get(asset).normalize().toString().replace("\\", "/").let { - this::class.java.getResourceAsStream(it) - } - } - } - - fun init( - isCodeTransformAvailable: Boolean, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ) { - loadWebView( - isCodeTransformAvailable, - isCodeScanAvailable, - highlightCommand, - activeProfile - ) - } - - override fun dispose() { - Disposer.dispose(jcefBrowser) - } - - fun component() = jcefBrowser.component - - fun postChat(command: FlareUiMessage) = postChat(Gson().toJson(command)) - - @Deprecated("shouldn't need this version") - fun postChat(message: String) { - jcefBrowser - .cefBrowser - .executeJavaScript("window.postMessage($message)", jcefBrowser.cefBrowser.url, 0) - } - - // Load the chat web app into the jcefBrowser - private fun loadWebView( - isCodeTransformAvailable: Boolean, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ) { - // setup empty state. The message request handlers use this for storing state - // that's persistent between page loads. - jcefBrowser.setProperty("state", "") - jcefBrowser.jbCefClient.addDragHandler({ browser, dragData, mask -> - true // Allow drag operations - }, jcefBrowser.cefBrowser) - // load the web app - jcefBrowser.loadURL( - assetRequestHandler.createResource( - "webview/chat.html", - getWebviewHTML( - isCodeTransformAvailable, - isCodeScanAvailable, - highlightCommand, - activeProfile, - ) - ) - ) - } - - /** - * Generate index.html for the web view - * @return HTML source - */ - private fun getWebviewHTML( - isCodeTransformAvailable: Boolean, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ): String { - val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)") - val connectorAdapterPath = "${LocalAssetJBCefRequestHandler.PROTOCOL}://${LocalAssetJBCefRequestHandler.AUTHORITY}/mynah/js/connectorAdapter.js" - val mynahResource = assetRequestHandler.createResource(mynahAsset.fileName.toString(), mynahAsset.inputStream()) - - // https://github.com/highlightjs/highlight.js/issues/1387 - // language=HTML - val jsScripts = """ - - - - """.trimIndent() - - // language=HTML - return """ - - - - - AWS Q - $jsScripts - - -
Amazon Q is loading...
- - - - - """.trimIndent() - } - - companion object { - private const val MAX_ONBOARDING_PAGE_COUNT = 3 - private val OBJECT_MAPPER = jacksonObjectMapper() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt deleted file mode 100644 index 21c6a9dbb83..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt +++ /dev/null @@ -1,792 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -@file:Suppress("BannedImports") -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.module.kotlin.readValue -import com.fasterxml.jackson.module.kotlin.treeToValue -import com.google.gson.Gson -import com.intellij.ide.BrowserUtil -import com.intellij.ide.util.RunOnceUtil -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.application.runReadAction -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.options.ShowSettingsUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.jcef.JBCefJSQuery.Response -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import org.cef.browser.CefBrowser -import org.eclipse.lsp4j.TextDocumentIdentifier -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException -import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode -import org.intellij.lang.annotations.Language -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.info -import software.amazon.q.core.utils.warn -import software.aws.toolkits.jetbrains.services.amazonq.GetAmazonQLogsAction -import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageSerializer -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQChatServer -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcMethod -import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcNotification -import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcRequest -import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.AUTH_FOLLOW_UP_CLICKED -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.AuthFollowUpClickNotification -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickResult -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_BUTTON_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CONVERSATION_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_COPY_CODE_TO_CLIPBOARD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CREATE_PROMPT -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_DISCLAIMER_ACKNOWLEDGED -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FEEDBACK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FILE_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FOLLOW_UP_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INFO_LINK_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INSERT_TO_CURSOR -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LINK_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LIST_CONVERSATIONS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_OPEN_TAB -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_PINNED_CONTEXT_ADD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_PINNED_CONTEXT_REMOVE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_PROMPT_OPTION_ACKNOWLEDGED -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_QUICK_ACTION -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_READY -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SOURCE_LINK_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_ADD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResponse -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_AVAILABLE_MODELS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_MCP_SERVERS_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_RULES_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.MCP_SERVER_CLICK_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_SETTINGS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_WORKSPACE_SETTINGS_KEY -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenSettingsNotification -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResponse -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResult -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResultError -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResultSuccess -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.PROMPT_INPUT_OPTIONS_CHANGE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.QuickChatActionRequest -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.RULE_CLICK_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_CHAT_COMMAND_PROMPT -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.STOP_CHAT_RESPONSE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendChatPromptRequest -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.StopResponseMessage -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TELEMETRY_EVENT -import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil -import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil.toUriString -import software.aws.toolkits.jetbrains.services.amazonq.util.command -import software.aws.toolkits.jetbrains.services.amazonq.util.tabType -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.AmazonQTheme -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable -import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager -import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.aws.toolkits.jetbrains.settings.MeetQSettings -import software.aws.toolkits.telemetry.MetricResult -import software.aws.toolkits.telemetry.Telemetry -import java.nio.file.Path -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionException -import java.util.function.Function - -class BrowserConnector( - private val serializer: MessageSerializer = MessageSerializer.getInstance(), - private val themeBrowserAdapter: ThemeBrowserAdapter = ThemeBrowserAdapter(), - private val project: Project, -) { - val uiReady = CompletableDeferred() - private val chatCommunicationManager = ChatCommunicationManager.getInstance(project) - - suspend fun connect( - browser: Browser, - connections: List, - ) = coroutineScope { - // Send browser messages to the outbound publisher - addMessageHook(browser) - .onEach { json -> - val node = serializer.toNode(json) - when (node.command) { - // this is sent when the named agents UI is ready - "ui-is-ready" -> { - uiReady.complete(true) - chatCommunicationManager.setUiReady() - RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") { - MeetQSettings.getInstance().reinvent2024OnboardingCount += 1 - } - } - CHAT_DISCLAIMER_ACKNOWLEDGED -> { - MeetQSettings.getInstance().disclaimerAcknowledged = true - } - - // some weird issue preventing deserialization from working - "open-user-guide" -> { - BrowserUtil.browse(node.get("userGuideLink").asText()) - } - "send-telemetry" -> { - val source = node.get("source") - val module = node.get("module") - val trigger = node.get("trigger") - - if (source != null) { - Telemetry.ui.click.use { - it.elementId(source.asText()) - } - } else if (module != null && trigger != null) { - Telemetry.toolkit.willOpenModule.use { - it.module(module.asText()) - it.source(trigger.asText()) - it.result(MetricResult.Succeeded) - } - } - } - } - - val tabType = node.tabType - if (tabType == null || tabType == "cwc") { - handleFlareChatMessages(browser, node) - } else { - connections.filter { connection -> connection.app.tabTypes.contains(tabType) }.forEach { connection -> - launch { - val message = serializer.deserialize(node, connection.messageTypeRegistry) - connection.messagesFromUiToApp.publish(message) - } - } - } - } - .launchIn(this) - - // Wait for UI ready before starting to send messages to the UI. - uiReady.await() - - // Chat options including history and quick actions need to be sent in once UI is ready - updateQuickActionsInBrowser(browser) - - // Send inbound messages to the browser - val inboundMessages = connections.map { it.messagesFromAppToUi.flow }.merge() - inboundMessages - .onEach { browser.postChat(serializer.serialize(it)) } - .launchIn(this) - } - - suspend fun connectTheme( - chatBrowser: CefBrowser, - themeSource: Flow, - ) = coroutineScope { - themeSource - .distinctUntilChanged() - .onEach { - uiReady.await() - themeBrowserAdapter.updateThemeInBrowser(chatBrowser, it) - } - .launchIn(this) - } - - private fun addMessageHook(browser: Browser) = callbackFlow { - val handler = Function { - trySend(it) - Response(null) - } - - browser.receiveMessageQuery.addHandler(handler) - - awaitClose { - browser.receiveMessageQuery.removeHandler(handler) - } - } - - private suspend fun handleFlareChatMessages(browser: Browser, node: JsonNode) { - when (node.command) { - SEND_CHAT_COMMAND_PROMPT -> { - val requestFromUi = serializer.deserializeChatMessages(node) - val editor = FileEditorManager.getInstance(project).selectedTextEditor - val textDocumentIdentifier = editor?.let { e -> - e.virtualFile?.let { - TextDocumentIdentifier(toUriString(it)) - } - } - val cursorState = editor?.let { LspEditorUtil.getCursorState(it) } - - val enrichmentParams = mapOf( - "textDocument" to textDocumentIdentifier, - "cursorState" to cursorState?.let { listOf(it) }, - ) - - val serializedEnrichmentParams = serializer.objectMapper.valueToTree(enrichmentParams) - val chatParams: ObjectNode = (node.params as ObjectNode) - .setAll(serializedEnrichmentParams) - - val tabId = requestFromUi.params.tabId - val partialResultToken = chatCommunicationManager.addPartialChatMessage(tabId) - - var encryptionManager: JwtEncryptionManager? = null - val result = AmazonQLspService.executeAsyncIfRunning(project) { server -> - encryptionManager = this.encryptionManager - - val encryptedParams = EncryptedChatParams(this.encryptionManager.encrypt(chatParams), partialResultToken) - rawEndpoint.request(SEND_CHAT_COMMAND_PROMPT, encryptedParams) as CompletableFuture - } ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) - - // We assume there is only one outgoing request per tab because the input is - // blocked when there is an outgoing request - chatCommunicationManager.setInflightRequestForTab(tabId, result) - showResult(result, partialResultToken, tabId, encryptionManager, browser) - } - - CHAT_QUICK_ACTION -> { - val requestFromUi = serializer.deserializeChatMessages(node) - val tabId = requestFromUi.params.tabId - val quickActionParams = node.params ?: error("empty payload") - val partialResultToken = chatCommunicationManager.addPartialChatMessage(tabId) - var encryptionManager: JwtEncryptionManager? = null - val result = AmazonQLspService.executeAsyncIfRunning(project) { server -> - encryptionManager = this.encryptionManager - - val encryptedParams = EncryptedQuickActionChatParams(this.encryptionManager.encrypt(quickActionParams), partialResultToken) - rawEndpoint.request(CHAT_QUICK_ACTION, encryptedParams) as CompletableFuture - } ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) - - // We assume there is only one outgoing request per tab because the input is - // blocked when there is an outgoing request - chatCommunicationManager.setInflightRequestForTab(tabId, result) - - showResult(result, partialResultToken, tabId, encryptionManager, browser) - } - - CHAT_LIST_CONVERSATIONS -> { - handleChat(AmazonQChatServer.listConversations, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = CHAT_LIST_CONVERSATIONS, - params = response - ) - ) - } - } - - CHAT_CONVERSATION_CLICK -> { - handleChat(AmazonQChatServer.conversationClick, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = CHAT_CONVERSATION_CLICK, - params = response - ) - ) - } - } - - CHAT_FEEDBACK -> { - handleChat(AmazonQChatServer.feedback, node) - } - - CHAT_READY -> { - LOG.info { "Amazon Q Chat UI loaded and ready for input" } - handleChat(AmazonQChatServer.chatReady, node) { params, invoke -> - uiReady.complete(true) - chatCommunicationManager.setUiReady() - RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") { - MeetQSettings.getInstance().reinvent2024OnboardingCount += 1 - } - - invoke() - } - } - - CHAT_TAB_ADD -> { - handleChat(AmazonQChatServer.tabAdd, node) { params, invoke -> - // Track the tab ID when a tab is added - chatCommunicationManager.addTabId(params.tabId) - invoke() - } - } - - CHAT_TAB_REMOVE -> { - handleChat(AmazonQChatServer.tabRemove, node) { params, invoke -> - chatCommunicationManager.removePartialChatMessage(params.tabId) - cancelInflightRequests(params.tabId) - // Remove the tab ID from tracking when a tab is removed - chatCommunicationManager.removeTabId(params.tabId) - invoke() - } - } - - CHAT_TAB_CHANGE -> { - handleChat(AmazonQChatServer.tabChange, node) - } - - CHAT_OPEN_TAB -> { - val response = serializer.deserializeChatMessages(node) - val future = chatCommunicationManager.removeTabOpenRequest(response.requestId) ?: return - try { - val id = serializer.deserializeChatMessages(node.params).result.tabId - future.complete(OpenTabResult(id)) - } catch (e: Exception) { - try { - val err = serializer.deserializeChatMessages(node.params) - future.complete(err.error) - } catch (_: Exception) { - future.completeExceptionally(e) - } - } - } - - CHAT_INSERT_TO_CURSOR -> { - val editor = FileEditorManager.getInstance(project).selectedTextEditor - val textDocumentIdentifier = editor?.let { e -> - e.virtualFile?.let { - TextDocumentIdentifier(toUriString(it)) - } - } - val cursorPosition = editor?.let { LspEditorUtil.getCursorPosition(it) } - - val enrichmentParams = mapOf( - "textDocument" to textDocumentIdentifier, - "cursorPosition" to cursorPosition, - ) - - val insertToCursorPositionParams: ObjectNode = (node.params as ObjectNode) - .setAll(serializer.objectMapper.valueToTree(enrichmentParams)) - val enrichedNode = (node as ObjectNode).apply { - set("params", insertToCursorPositionParams) - } - - handleChat(AmazonQChatServer.insertToCursorPosition, enrichedNode) - } - - CHAT_LINK_CLICK -> { - handleChat(AmazonQChatServer.linkClick, node) - } - - CHAT_INFO_LINK_CLICK -> { - handleChat(AmazonQChatServer.infoLinkClick, node) - } - - CHAT_SOURCE_LINK_CLICK -> { - handleChat(AmazonQChatServer.sourceLinkClick, node) - } - - CHAT_FILE_CLICK -> { - handleChat(AmazonQChatServer.fileClick, node) - } - - PROMPT_INPUT_OPTIONS_CHANGE -> { - handleChat(AmazonQChatServer.promptInputOptionsChange, node) - } - - CHAT_FOLLOW_UP_CLICK -> { - handleChat(AmazonQChatServer.followUpClick, node) - } - - CHAT_BUTTON_CLICK -> { - handleChat(AmazonQChatServer.buttonClick, node).thenApply { response -> - if (response is ButtonClickResult && !response.success) { - LOG.warn { "Failed to execute action associated with button with reason: ${response.failureReason}" } - } - } - } - - CHAT_COPY_CODE_TO_CLIPBOARD -> { - handleChat(AmazonQChatServer.copyCodeToClipboard, node) - } - - GET_SERIALIZED_CHAT_REQUEST_METHOD -> { - val response = serializer.deserializeChatMessages(node) - chatCommunicationManager.completeSerializedChatResponse( - response.requestId, - response.params.result.content - ) - } - - CHAT_TAB_BAR_ACTIONS -> { - val action = node.params.get("action") - if (action.textValue() == "show_logs") { - runInEdt { - GetAmazonQLogsAction.showLogCollectionWarningGetLogs(project) - } - } else { - handleChat(AmazonQChatServer.tabBarActions, node) { params, invoke -> - invoke() - .whenComplete { actions, error -> - try { - if (error != null) { - throw error - } - - browser.postChat( - FlareUiMessage( - command = CHAT_TAB_BAR_ACTIONS, - params = actions - ) - ) - } catch (e: Exception) { - val cause = if (e is CompletionException) e.cause else e - - // dont post error to UI if user cancels export - if (cause is ResponseErrorException && cause.responseError.code == ResponseErrorCode.RequestCancelled.getValue()) { - return@whenComplete - } - LOG.error { "Failed to perform chat tab bar action $e" } - params.tabId?.let { - browser.postChat(chatCommunicationManager.getErrorUiMessage(it, e, null)) - } - } - } - } - } - } - - CHAT_CREATE_PROMPT -> { - handleChat(AmazonQChatServer.createPrompt, node) - } - - STOP_CHAT_RESPONSE -> { - val stopResponseRequest = serializer.deserializeChatMessages(node) - if (!chatCommunicationManager.hasInflightRequest(stopResponseRequest.params.tabId)) { - return - } - cancelInflightRequests(stopResponseRequest.params.tabId) - chatCommunicationManager.removePartialChatMessage(stopResponseRequest.params.tabId) - } - - AUTH_FOLLOW_UP_CLICKED -> { - val message = serializer.deserializeChatMessages(node) - chatCommunicationManager.handleAuthFollowUpClicked( - project, - message.params - ) - } - - CHAT_PROMPT_OPTION_ACKNOWLEDGED -> { - val acknowledgedMessage = node.params?.get("messageId") - if (acknowledgedMessage?.asText() == "programmerModeCardId") { - MeetQSettings.getInstance().pairProgrammingAcknowledged = true - } - } - - OPEN_SETTINGS -> { - val openSettingsNotification = serializer.deserializeChatMessages(node) - if (openSettingsNotification.params.settingKey != OPEN_WORKSPACE_SETTINGS_KEY) return - runInEdt { - ShowSettingsUtil.getInstance().showSettingsDialog(browser.project, CodeWhispererConfigurable::class.java) - } - } - TELEMETRY_EVENT -> { - handleChat(AmazonQChatServer.telemetryEvent, node) - } - LIST_MCP_SERVERS_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.listMcpServers, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = LIST_MCP_SERVERS_REQUEST_METHOD, - params = response - ) - ) - } - } - MCP_SERVER_CLICK_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.mcpServerClick, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = MCP_SERVER_CLICK_REQUEST_METHOD, - params = response - ) - ) - } - } - - OPEN_FILE_DIALOG -> { - handleChat(AmazonQChatServer.showOpenFileDialog, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = OPEN_FILE_DIALOG_REQUEST_METHOD, - params = response - ) - ) - } - } - - LIST_RULES_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.listRules, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = LIST_RULES_REQUEST_METHOD, - params = response - ) - ) - } - } - RULE_CLICK_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.ruleClick, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = RULE_CLICK_REQUEST_METHOD, - params = response - ) - ) - } - } - CHAT_PINNED_CONTEXT_ADD -> { - handleChat(AmazonQChatServer.pinnedContextAdd, node) - } - CHAT_PINNED_CONTEXT_REMOVE -> { - handleChat(AmazonQChatServer.pinnedContextRemove, node) - } - LIST_AVAILABLE_MODELS -> { - handleChat(AmazonQChatServer.listAvailableModels, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = LIST_AVAILABLE_MODELS, - params = response - ) - ) - } - } - } - } - - private fun showResult( - result: CompletableFuture, - partialResultToken: String, - tabId: String, - encryptionManager: JwtEncryptionManager?, - browser: Browser, - ) { - result.whenComplete { value, error -> - try { - if (error != null) { - throw error - } - chatCommunicationManager.removePartialChatMessage(partialResultToken) - val decryptedMessage = value?.let { encryptionManager?.decrypt(it) }.orEmpty() - val filteredMessage = parseFindingsMessages(decryptedMessage) - - val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( - SEND_CHAT_COMMAND_PROMPT, - tabId, - filteredMessage, - isPartialResult = false - ) - browser.postChat(messageToChat) - chatCommunicationManager.removeInflightRequestForTab(tabId) - } catch (e: CancellationException) { - LOG.warn { "Cancelled chat generation" } - } catch (e: Exception) { - LOG.warn(e) { "Failed to send chat message" } - browser.postChat(chatCommunicationManager.getErrorUiMessage(tabId, e, partialResultToken)) - } - } - } - - fun deserializeFindings(@Language("JSON") responsePayload: String): List { - val additionalMessages = serializer.objectMapper.readValue(responsePayload).additionalMessages - ?: return emptyList() - - return additionalMessages - } - - fun parseFindingsMessages(@Language("JSON") responsePayload: String): String { - try { - val findings = deserializeFindings(responsePayload) - val scannedFiles = mutableListOf() - val mappedFindings = buildList { - for (finding in findings) { - for (aggregatedIssue in finding.body) { - val file = LocalFileSystem.getInstance().findFileByIoFile( - Path.of(aggregatedIssue.filePath).toFile() - ) - if (file?.isDirectory == false) { - scannedFiles.add(file) - runReadAction { - FileDocumentManager.getInstance().getDocument(file) - }?.let { document -> - for (issue in aggregatedIssue.issues) { - val endLineInDocument = minOf(maxOf(0, issue.endLine - 1), document.lineCount - 1) - val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1 - val isIssueIgnored = CodeWhispererCodeScanManager.getInstance(project) - .isIgnoredIssue(issue.title, document, file, issue.startLine - 1) - if (isIssueIgnored) { - continue - } - - add( - CodeWhispererCodeScanIssue( - startLine = issue.startLine, - startCol = 1, - endLine = issue.endLine, - endCol = endCol, - file = file, - project = project, - title = issue.title, - description = issue.description, - detectorId = issue.detectorId, - detectorName = issue.detectorName, - findingId = issue.findingId, - ruleId = issue.ruleId, - relatedVulnerabilities = issue.relatedVulnerabilities, - severity = issue.severity, - recommendation = issue.recommendation, - suggestedFixes = issue.suggestedFixes, - codeSnippet = emptyList(), - isVisible = true, - autoDetected = issue.autoDetected, - scanJobId = issue.scanJobId, - ), - ) - } - } - } - } - } - } - - if (mappedFindings.isNotEmpty()) { - CodeWhispererCodeScanManager.getInstance(project) - .addOnDemandIssues( - mappedFindings, - scannedFiles, - CodeWhispererConstants.CodeAnalysisScope.AGENTIC - ) - CodeWhispererCodeScanManager.getInstance(project).showCodeScanUI() - } - // Remove findings messages from response payload - val rootNode = serializer.objectMapper.readTree(responsePayload) as ObjectNode - rootNode.remove("additionalMessages") - return serializer.objectMapper.writeValueAsString(rootNode) - } catch (e: Exception) { - LOG.error(e) { "Failed to parse findings message" } - return responsePayload - } - } - - private suspend fun updateQuickActionsInBrowser(browser: Browser) { - val isCodeTransformAvailable = isCodeTransformAvailable(project) - val isCodeScanAvailable = isCodeScanAvailable(project) - - val serverCapabilities = AmazonQLspService.getInstance(project).instanceFlow.first().initializeResult.await().awsServerCapabilities - - // language=JavaScript - val script = """ - try { - // hack to create the list of actions across all tab types - const tempConnector = connectorAdapter.initiateAdapter( - false, - true, // the two values are not used here, needed for constructor - $isCodeTransformAvailable, - $isCodeScanAvailable, - { postMessage: () => {} }, - ); - - const commands = tempConnector.initialQuickActions?.slice(0, 2) || []; - const options = ${Gson().toJson(serverCapabilities.chatOptions)}; - options.quickActions.quickActionsCommandGroups = [ - ...commands, - ...options.quickActions.quickActionsCommandGroups - ]; - - window.postMessage({ - command: "chatOptions", - params: options - }); - } catch (e) { - console.error("Error updating quick actions:", e); - } - """.trimIndent() - - browser.jcefBrowser.cefBrowser.executeJavaScript(script, browser.jcefBrowser.cefBrowser.url, 0) - } - - private fun cancelInflightRequests(tabId: String) { - chatCommunicationManager.getInflightRequestForTab(tabId)?.let { request -> - request.cancel(true) - chatCommunicationManager.removeInflightRequestForTab(tabId) - } - } - - private suspend inline fun handleChat( - lspMethod: JsonRpcMethod, - node: JsonNode, - crossinline serverAction: (params: Request, invokeService: () -> CompletableFuture) -> CompletableFuture, - ): CompletableFuture { - val requestFromUi = if (node.params == null) { - Unit as Request - } else { - serializer.deserializeChatMessages(node.params, lspMethod.params) - } - - return AmazonQLspService.executeAsyncIfRunning(project) { _ -> - val invokeService = when (lspMethod) { - is JsonRpcNotification -> { - // notify is Unit - { CompletableFuture.completedFuture(rawEndpoint.notify(lspMethod.name, node.params?.let { serializer.objectMapper.treeToValue(it) })) } - } - - is JsonRpcRequest -> { - { - rawEndpoint.request(lspMethod.name, node.params?.let { serializer.objectMapper.treeToValue(it) }).thenApply { - serializer.objectMapper.readValue( - Gson().toJson(it), - lspMethod.response - ) - } - } - } - } as () -> CompletableFuture - serverAction(requestFromUi, invokeService) - } ?: CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")) - } - - private suspend inline fun handleChat( - lspMethod: JsonRpcMethod, - node: JsonNode, - ): CompletableFuture = handleChat( - lspMethod, - node, - ) { _, invokeService -> invokeService() } - - private val JsonNode.params - get() = get("params") - - companion object { - private val LOG = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt deleted file mode 100644 index 6485c0e285d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// TODO: move to software.aws.toolkits.jetbrains.services.amazonq.lsp.model -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Description -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix - -/** - * Solely used to extract the aggregated findings from the response - */ -data class FlareAdditionalMessages( - @get:JsonDeserialize(contentUsing = FlareAggregatedFindingsDeserializer::class) - @get:JsonSetter(contentNulls = Nulls.SKIP) - val additionalMessages: List?, -) - -data class FlareAggregatedFindings( - val messageId: String, - val body: List, -) - -data class FlareCodeScanIssue( - val startLine: Int, - val endLine: Int, - val comment: String?, - val title: String, - val description: Description, - val detectorId: String, - val detectorName: String, - val findingId: String, - val ruleId: String?, - val relatedVulnerabilities: List, - val severity: String, - val recommendation: Recommendation, - val suggestedFixes: List, - val scanJobId: String, - val language: String, - val autoDetected: Boolean, - val filePath: String, - val findingContext: String?, -) - -data class AggregatedCodeScanIssue( - val filePath: String, - val issues: List, -) - -class FlareAggregatedFindingsDeserializer : JsonDeserializer() { - private val objectMapper = jacksonObjectMapper() - - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): FlareAggregatedFindings? = - try { - val node = p.readValueAsTree() - val messageId = node.get("messageId")?.asText() ?: return null - val bodyNode = node.get("body") ?: return null - - val body = objectMapper.readValue>(bodyNode.asText()) - - FlareAggregatedFindings(messageId, body) - } catch (_: Exception) { - null - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt deleted file mode 100644 index beb808b9eff..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.intellij.ui.jcef.JBCefBrowserBase -import com.intellij.ui.jcef.JBCefJSQuery -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.time.withTimeout -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn -import java.time.Duration - -/** - * Exposes the FQN (Fully Qualified Name) and import reading capabilities that are implemented in TypeScript via a Kotlin API. - */ -class FqnWebviewAdapter( - private val jcefBrowser: JBCefBrowserBase, - private val browserConnector: BrowserConnector, -) { - - private val namesExtractionResponses: Channel = Channel() - private val receiveNames: String - - init { - val receiveNamesFromBrowser = JBCefJSQuery.create(jcefBrowser) - receiveNamesFromBrowser.addHandler { names: String -> - namesExtractionResponses.trySend(names) - null - } - receiveNames = receiveNamesFromBrowser.inject("names") - } - - @Throws(TimeoutCancellationException::class) - suspend fun readImports(args: String): String { - browserConnector.uiReady.await() - - return try { - withTimeout(Duration.ofMillis(1250)) { - jcefBrowser.cefBrowser.executeJavaScript( - """ - window.fqnExtractor.readImports($args.fileContent, $args.language).then(result => { - const names = JSON.stringify(Array.from(result)); - $receiveNames - }); - """.trimIndent(), - jcefBrowser.cefBrowser.url, - 0, - ) - namesExtractionResponses.receive() - } - } catch (e: TimeoutCancellationException) { - logger.warn(e) { "Failed to read imports" } - "[]" - } - } - - suspend fun extractNames(args: String): String { - browserConnector.uiReady.await() - - return try { - withTimeout(Duration.ofMillis(1250)) { - jcefBrowser.cefBrowser.executeJavaScript( - """ - window.fqnExtractor.extractCodeQuery($args.fileContent, $args.language, $args.codeSelection).then(({codeQuery, namesWereTruncated}) => { - const names = codeQuery === undefined ? `{"simpleNames": [], "fullyQualifiedNames": {"used": []}}` : JSON.stringify(codeQuery); - $receiveNames - }); - """.trimIndent(), - jcefBrowser.cefBrowser.url, - 0, - ) - namesExtractionResponses.receive() - } - } catch (e: TimeoutCancellationException) { - logger.warn(e) { "Failed to extract fully qualified names" } - "{\"simpleNames\": [], \"fullyQualifiedNames\": {\"used\": []}}" - } - } - - companion object { - private val logger = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt deleted file mode 100644 index bbf449eac59..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -import java.awt.Color -import java.awt.Font - -/** - * Data class that encapsulates the theme values we extract from the IDE. - */ -data class AmazonQTheme( - val darkMode: Boolean, - val font: Font, - - val defaultText: Color, - val inactiveText: Color, - val linkText: Color, - val lightText: Color, - val emptyText: Color, - - val background: Color, - val border: Color, - val activeTab: Color, - - val checkboxBackground: Color, - val checkboxForeground: Color, - - val textFieldBackground: Color, - val textFieldForeground: Color, - - val buttonForeground: Color, - val buttonBackground: Color, - val secondaryButtonForeground: Color, - val secondaryButtonBackground: Color, - val inputBorderFocused: Color, - val inputBorderUnfocused: Color, - - val info: Color, - val success: Color, - val warning: Color, - val error: Color, - - val editorFont: Font, - val editorBackground: Color, - val editorForeground: Color, - val editorVariable: Color, - val editorOperator: Color, - val editorFunction: Color, - val editorComment: Color, - val editorKeyword: Color, - val editorString: Color, - val editorProperty: Color, - val editorClassName: Color, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt deleted file mode 100644 index 793a75052fb..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -/** - * Enumeration of CSS variables that used by MynahUi to theme the chat experience. - */ -enum class CssVariable( - val varName: String, -) { - FontSize("--vscode-font-size"), - FontFamily("--mynah-font-family"), - - TextColorDefault("--mynah-color-text-default"), - TextColorAlt("--mynah-color-text-alternate"), - TextColorStrong("--mynah-color-text-strong"), - TextColorWeak("--mynah-color-text-weak"), - TextColorLight("--mynah-color-light"), - TextColorLink("--mynah-color-text-link"), - TextColorInput("--mynah-color-text-input"), - TextColorDisabled("--mynah-color-text-disabled"), - - Background("--mynah-color-bg"), - BackgroundAlt("--mynah-color-bg-alt"), - TabActive("--mynah-color-tab-active"), - - ColorDeep("--mynah-color-deep"), - ColorDeepReverse("--mynah-color-deep-reverse"), - BorderDefault("--mynah-color-border-default"), - BorderFocused("--mynah-color-text-input-border-focused"), - BorderUnfocused("--mynah-color-text-input-border"), - InputBackground("--mynah-input-bg"), - - SyntaxBackground("--mynah-color-syntax-bg"), - SyntaxVariable("--mynah-color-syntax-variable"), - SyntaxFunction("--mynah-color-syntax-function"), - SyntaxOperator("--mynah-color-syntax-operator"), - SyntaxAttributeValue("--mynah-color-syntax-attr-value"), - SyntaxAttribute("--mynah-color-syntax-attr"), - SyntaxProperty("--mynah-color-syntax-property"), - SyntaxComment("--mynah-color-syntax-comment"), - SyntaxCode("--mynah-color-syntax-code"), - SyntaxKeyword("--mynah-color-syntax-keyword"), - SyntaxString("--mynah-color-syntax-string"), - SyntaxClassName("--mynah-color-syntax-class-name"), - SyntaxCodeFontFamily("--mynah-syntax-code-font-family"), - SyntaxCodeFontSize("--mynah-syntax-code-font-size"), - - StatusInfo("--mynah-color-status-info"), - StatusSuccess("--mynah-color-status-success"), - StatusWarning("--mynah-color-status-warning"), - StatusError("--mynah-color-status-error"), - - ButtonBackground("--mynah-color-button"), - ButtonForeground("--mynah-color-button-reverse"), - - SecondaryButtonBackground("--mynah-color-alternate"), - SecondaryButtonForeground("--mynah-color-alternate-reverse"), - - MainBackground("--mynah-color-main"), - MainForeground("--mynah-color-main-reverse"), - - CardBackground("--mynah-card-bg"), - CardBackgroundAlt("--mynah-card-bg-alternate"), -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt deleted file mode 100644 index 6aa7ad9729a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -import com.intellij.ide.ui.LafManagerListener -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.DefaultLanguageHighlighterColors -import com.intellij.openapi.editor.colors.ColorKey -import com.intellij.openapi.editor.colors.EditorColorsListener -import com.intellij.openapi.editor.colors.EditorColorsManager -import com.intellij.openapi.editor.colors.EditorColorsScheme -import com.intellij.openapi.editor.colors.EditorFontType -import com.intellij.openapi.editor.colors.TextAttributesKey -import com.intellij.ui.JBColor -import com.intellij.util.ui.UIUtil -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import java.awt.Color - -/** - * Helper class that returns a Flow of [AmazonQTheme] instances based on the current IDE theme. - */ -class EditorThemeAdapter { - private val logger = getLogger() - - /** - * Returns a flow of [AmazonQTheme] instances. The current theme is emitted immediately, - * and a new theme is emitted whenever the look-and-feel of the IDE changes. - */ - fun onThemeChange() = callbackFlow { - // Register a listener for changes to the IDE LaF - val messageBus = ApplicationManager.getApplication().messageBus - val connection = messageBus.connect() - - // Listen to LaF changes (the overall IDE theme changes) - connection.subscribe( - LafManagerListener.TOPIC, - LafManagerListener { - // It's important to not throw exceptions from this listener. Throwing here will prevent the user's theme from being properly applied in the IDE - try { - trySend(getThemeFromIde()) - } catch (e: Exception) { - logger.error(e) { "Cannot construct Amazon Q theme from IDE colors" } - } - }, - ) - - // Also listen to EditorColors changes. This will be triggered if the editor's colors or fonts change. - connection.subscribe( - EditorColorsManager.TOPIC, - EditorColorsListener { - // It's important to not throw exceptions from this listener. Throwing here will prevent the user's theme from being properly applied in the IDE - try { - trySend(getThemeFromIde()) - } catch (e: Exception) { - logger.error(e) { "Cannot construct Amazon Q theme from IDE colors" } - } - }, - ) - - // Send an initial value for the current theme - send(getThemeFromIde()) - // Disconnect from the message bus when the flow collection is cancelled - awaitClose { connection.disconnect() } - } - - companion object { - // Returns a theme constructed from the current look-and-feel of the IDE - fun getThemeFromIde(): AmazonQTheme { - val currentScheme = EditorColorsManager.getInstance().schemeForCurrentUITheme - - val cardBackground = currentScheme.defaultBackground - val text = currentScheme.defaultForeground - val chatBackground = tryFindDifferentColor( - cardBackground, - "Panel.background", - "EditorPane.background", - "EditorPane.inactiveBackground", - "Editor.background", - "Content.background", - default = 0xF2F2F2, - darkDefault = 0x3C3F41, - ) - - return AmazonQTheme( - darkMode = !JBColor.isBright(), - font = UIUtil.getFont(UIUtil.FontSize.NORMAL, null), - - defaultText = text, - inactiveText = themeColor("TextField.inactiveForeground", default = 0x8C8C8C, darkDefault = 0x808080), - linkText = themeColor("link.foreground", "link", "Link.activeForeground", default = 0x589DF6), - - background = chatBackground, - border = getBorderColor(currentScheme), - activeTab = themeColor("EditorTabs.underlinedTabBackground", default = 0xFFFFFF, darkDefault = 0x4E5254), - - checkboxBackground = themeColor("CheckBox.background", default = 0xF2F2F2, darkDefault = 0x3C3F41), - checkboxForeground = themeColor("CheckBox.foreground", default = 0x000000, darkDefault = 0xBBBBBB), - - textFieldBackground = themeColor("TextField.background", default = 0xFFFFFF, darkDefault = 0x45494A), - textFieldForeground = themeColor("TextField.foreground", default = 0x000000, darkDefault = 0xBBBBBB), - - buttonBackground = themeColor("Button.default.startBackground", default = 0x528CC7, darkDefault = 0x365880), - buttonForeground = themeColor("Button.default.foreground", default = 0xFFFFFF, darkDefault = 0xBBBBBB), - secondaryButtonBackground = themeColor("Button.startBackground", default = 0xFFFFFF, darkDefault = 0x4C5052), - secondaryButtonForeground = themeColor("Button.foreground", default = 0x000000, darkDefault = 0xBBBBBB), - - info = themeColor("ProgressBar.progressColor", default = 0x1E82E6, darkDefault = 0x1E82E6), - success = themeColor("ProgressBar.passedColor", default = 0x34B171, darkDefault = 0x008F50), - warning = themeColor("Component.warningFocusColor", default = 0xE2A53A), - error = themeColor("ProgressBar.failedColor", default = 0xD64F4F, darkDefault = 0xE74848), - - editorFont = currentScheme.getFont(EditorFontType.PLAIN), - editorBackground = currentScheme.defaultBackground, - editorForeground = currentScheme.defaultForeground, - editorVariable = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.LOCAL_VARIABLE), - editorOperator = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.OPERATION_SIGN), - editorFunction = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.FUNCTION_DECLARATION), - editorComment = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.LINE_COMMENT), - editorKeyword = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.KEYWORD), - editorString = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.STRING), - editorProperty = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.INSTANCE_FIELD), - editorClassName = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.CLASS_NAME), - lightText = themeColor("TextField.inactiveForeground", default = 0xA8ADBD, darkDefault = 0x5A5D63), - emptyText = themeColor("TextField.inactiveForeground", default = 0xA8ADBD, darkDefault = 0x5A5D63), - inputBorderFocused = themeColor("ActionButton.focusedBorderColor", default = 0x4682FA, darkDefault = 0x3574f0), - inputBorderUnfocused = themeColor("TextField.borderColor", default = 0xEBECF0, darkDefault = 0x4E5157), - ) - } - - private fun themeColor(name: String, default: Int, darkDefault: Int = default) = JBColor.namedColor(name, JBColor(default, darkDefault)) - - private fun themeColor(name: String, vararg backups: String, default: Int, darkDefault: Int = default): Color { - var defaultColor = JBColor(default, darkDefault) - for (i in backups.indices.reversed()) { - defaultColor = JBColor.namedColor(backups[i], defaultColor) - } - return JBColor.namedColor(name, defaultColor) - } - - private fun getBorderColor(currentScheme: EditorColorsScheme) = currentScheme.getColor(ColorKey.find("INDENT_GUIDE")) ?: themeColor( - "Borders.color", - "Component.borderColor", - "EditorTabs.borderColor", - default = 0xC4C4C4, - darkDefault = 0x646464, - ) - - private fun tryFindDifferentColor(color: Color, vararg choices: String, default: Int, darkDefault: Int): Color { - for (choice in choices) { - val themeColor = JBColor.namedColor(choice) - if (themeColor != color) { - return themeColor - } - } - // None of them are different so just take the first defined value - return themeColor(choices.first(), *choices, default = default, darkDefault = darkDefault) - } - - // Not all values may be set in the current scheme. Use the default foreground color if not specified. - private fun EditorColorsScheme.foregroundColor(key: TextAttributesKey) = getAttributes(key).foregroundColor ?: defaultForeground - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt deleted file mode 100644 index b42c96a01e8..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -import org.cef.browser.CefBrowser -import java.awt.Color -import java.awt.Font - -// The className must match what's in the mynah-ui package. -const val DARK_MODE_CLASS = "vscode-dark" - -/** - * Takes a [AmazonQTheme] instance and uses it to update CSS variables in the Webview UI. - */ -class ThemeBrowserAdapter { - fun updateLoginThemeInBrowser(browser: CefBrowser, theme: AmazonQTheme) { - browser.executeJavaScript("window.changeTheme(${theme.darkMode})", browser.url, 0) - } - - fun updateThemeInBrowser(browser: CefBrowser, theme: AmazonQTheme) { - val codeToUpdateTheme = buildJsCodeToUpdateTheme(theme) - browser.executeJavaScript(codeToUpdateTheme, browser.url, 0) - } - - fun buildJsCodeToUpdateTheme(theme: AmazonQTheme) = buildString { - val (bg, altBg, inputBg) = determineInputAndBgColor(theme) - appendDarkMode(theme.darkMode) - - append("{\n") - append("const rootElement = document.querySelector(':root');\n") - - append(CssVariable.FontSize, theme.font.toCssSize()) - append(CssVariable.FontFamily, theme.font.toCssFontFamily()) - - append(CssVariable.TextColorDefault, theme.defaultText) - append(CssVariable.TextColorAlt, theme.defaultText) - append(CssVariable.TextColorStrong, theme.textFieldForeground) - append(CssVariable.TextColorInput, theme.textFieldForeground) - append(CssVariable.TextColorLink, theme.linkText) - append(CssVariable.TextColorWeak, theme.emptyText) - append(CssVariable.TextColorLight, theme.emptyText) - append(CssVariable.TextColorDisabled, theme.inactiveText) - - append(CssVariable.Background, theme.background) - append(CssVariable.BackgroundAlt, altBg) - append(CssVariable.CardBackground, bg) - append(CssVariable.CardBackgroundAlt, theme.editorBackground) - append(CssVariable.BorderDefault, theme.border) - append(CssVariable.BorderFocused, theme.inputBorderFocused) - append(CssVariable.BorderUnfocused, theme.inputBorderUnfocused) - append(CssVariable.TabActive, theme.activeTab) - - append(CssVariable.InputBackground, inputBg) - - append(CssVariable.ButtonBackground, theme.buttonBackground) - append(CssVariable.ButtonForeground, theme.buttonForeground) - append(CssVariable.SecondaryButtonBackground, theme.secondaryButtonBackground) - append(CssVariable.SecondaryButtonForeground, theme.secondaryButtonForeground) - - append(CssVariable.StatusInfo, theme.info) - append(CssVariable.StatusSuccess, theme.success) - append(CssVariable.StatusWarning, theme.warning) - append(CssVariable.StatusError, theme.error) - - append(CssVariable.ColorDeep, theme.checkboxBackground) - append(CssVariable.ColorDeepReverse, theme.checkboxForeground) - - append(CssVariable.SyntaxCodeFontFamily, theme.editorFont.toCssFontFamily("monospace")) - append(CssVariable.SyntaxCodeFontSize, theme.editorFont.toCssSize()) - append(CssVariable.SyntaxCode, theme.editorForeground) - append(CssVariable.SyntaxBackground, theme.editorBackground) - append(CssVariable.SyntaxVariable, theme.editorVariable) - append(CssVariable.SyntaxOperator, theme.editorOperator) - append(CssVariable.SyntaxFunction, theme.editorFunction) - append(CssVariable.SyntaxComment, theme.editorComment) - append(CssVariable.SyntaxAttributeValue, theme.editorKeyword) - append(CssVariable.SyntaxAttribute, theme.editorString) - append(CssVariable.SyntaxProperty, theme.editorProperty) - append(CssVariable.SyntaxKeyword, theme.editorKeyword) - append(CssVariable.SyntaxString, theme.editorString) - append(CssVariable.SyntaxClassName, theme.editorClassName) - - append(CssVariable.MainBackground, theme.buttonBackground) - append(CssVariable.MainForeground, theme.buttonForeground) - - append("}") - } - - private fun StringBuilder.append(variable: CssVariable, value: Color) = append(variable, value.toCss()) - - private fun StringBuilder.append(variable: CssVariable, value: String) { - append("rootElement.style.setProperty('") - append(variable.varName) - append("', '") - append(value) - append("');\n") - } - - private fun StringBuilder.appendDarkMode(isDarkMode: Boolean) { - if (isDarkMode) { - // classList acts as a set, so we don't need to worry about calling add multiple times - append("document.body.classList.add('$DARK_MODE_CLASS');\n") - } else { - append("document.body.classList.remove('$DARK_MODE_CLASS');\n") - } - } - - private fun Color.toCss() = "rgba($red,$green,$blue,$alpha)" - - private fun Font.toCssSize() = "${size}px" - - // Some font names have characters that require them to be wrapped in quotes in the CSS variable, for example if they have spaces or a period. - private fun Font.toCssFontFamily(fallback: String = "system-ui") = "\"$family\", $fallback" - - // darkest = bg, second darkest is alt bg, lightest is input bg - private fun determineInputAndBgColor(theme: AmazonQTheme): Triple { - val colors = arrayOf(theme.editorBackground, theme.background, theme.textFieldBackground).sortedWith( - Comparator.comparing { - // luma calculation for brightness - (0.2126 * it.red) + (0.7152 * it.green) + (0.0722 * it.blue) - } - ) - return Triple(colors[0], colors[1], colors[2]) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt deleted file mode 100644 index 3b5b9c0be7c..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.launch -import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQApp -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext -import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanActionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanMessageListener -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.controller.CodeScanChatController -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.AuthenticationNeededExceptionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.AuthenticationUpdateMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CODE_SCAN_TAB_NAME -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.IncomingCodeScanMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.storage.ChatSessionStorage -import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import java.util.concurrent.atomic.AtomicBoolean - -private enum class CodeScanMessageTypes(val type: String) { - ClearChat("clear"), - Help("help"), - TabCreated("new-tab-was-created"), - TabRemoved("tab-was-removed"), - Scan("scan"), - StartProjectScan("codescan_start_project_scan"), - StartFileScan("codescan_start_file_scan"), - StopFileScan("codescan_stop_file_scan"), - StopProjectScan("codescan_stop_project_scan"), - OpenIssuesPanel("codescan_open_issues"), - ResponseBodyLinkClicked("response-body-link-click"), -} - -class CodeScanChatApp(private val scope: CoroutineScope) : AmazonQApp { - private val isProcessingAuthChanged = AtomicBoolean(false) - override val tabTypes = listOf(CODE_SCAN_TAB_NAME) - override fun init(context: AmazonQAppInitContext) { - val chatSessionStorage = ChatSessionStorage() - val inboundAppMessagesHandler: InboundAppMessagesHandler = CodeScanChatController(context, chatSessionStorage) - - context.messageTypeRegistry.register( - CodeScanMessageTypes.ClearChat.type to IncomingCodeScanMessage.ClearChat::class, - CodeScanMessageTypes.Help.type to IncomingCodeScanMessage.Help::class, - CodeScanMessageTypes.TabCreated.type to IncomingCodeScanMessage.TabCreated::class, - CodeScanMessageTypes.TabRemoved.type to IncomingCodeScanMessage.TabRemoved::class, - CodeScanMessageTypes.Scan.type to IncomingCodeScanMessage.Scan::class, - CodeScanMessageTypes.StartProjectScan.type to IncomingCodeScanMessage.StartProjectScan::class, - CodeScanMessageTypes.StartFileScan.type to IncomingCodeScanMessage.StartFileScan::class, - CodeScanMessageTypes.StopProjectScan.type to IncomingCodeScanMessage.StopProjectScan::class, - CodeScanMessageTypes.StopFileScan.type to IncomingCodeScanMessage.StopFileScan::class, - CodeScanMessageTypes.ResponseBodyLinkClicked.type to IncomingCodeScanMessage.ResponseBodyLinkClicked::class, - CodeScanMessageTypes.OpenIssuesPanel.type to IncomingCodeScanMessage.OpenIssuesPanel::class - ) - - scope.launch { - merge(service().flow, context.messagesFromUiToApp.flow).collect { message -> - // Launch a new coroutine to handle each message - scope.launch { handleMessage(message, inboundAppMessagesHandler) } - } - } - - fun authChanged() { - val isAnotherThreadProcessing = !isProcessingAuthChanged.compareAndSet(false, true) - if (isAnotherThreadProcessing) return - scope.launch { - val authController = AuthController() - val credentialState = authController.getAuthNeededStates(context.project).amazonQ - if (credentialState == null) { - // Notify tabs about restoring authentication - context.messagesFromAppToUi.publish( - AuthenticationUpdateMessage( - codeTransformEnabled = isCodeTransformAvailable(context.project), - codeScanEnabled = isCodeScanAvailable(context.project), - authenticatingTabIDs = chatSessionStorage.getAuthenticatingSessions().map { it.tabId } - ) - ) - - chatSessionStorage.changeAuthenticationNeeded(false) - chatSessionStorage.changeAuthenticationNeededNotified(false) - } else { - chatSessionStorage.changeAuthenticationNeeded(true) - - // Ask for reauth - chatSessionStorage.getAuthenticatingSessions().filter { !it.authNeededNotified }.forEach { - context.messagesFromAppToUi.publish( - AuthenticationNeededExceptionMessage( - tabId = it.tabId, - authType = credentialState.authType, - message = credentialState.message - ) - ) - } - - // Prevent multiple calls to activeConnectionChanged - chatSessionStorage.changeAuthenticationNeededNotified(true) - } - isProcessingAuthChanged.set(false) - } - } - - ApplicationManager.getApplication().messageBus.connect(this).subscribe( - BearerTokenProviderListener.TOPIC, - object : BearerTokenProviderListener { - override fun onProviderChange(providerId: String, newScopes: List?) { - val qProvider = getQTokenProvider(context.project) - val isQ = qProvider?.id == providerId - val isAuthorized = qProvider?.state() == BearerTokenAuthState.AUTHORIZED - if (!isQ || !isAuthorized) return - authChanged() - } - } - ) - - ApplicationManager.getApplication().messageBus.connect(this).subscribe( - ToolkitConnectionManagerListener.TOPIC, - object : ToolkitConnectionManagerListener { - override fun activeConnectionChanged(newConnection: ToolkitConnection?) { - authChanged() - } - } - ) - - context.project.messageBus.connect(this).subscribe( - QRegionProfileSelectedListener.TOPIC, - object : QRegionProfileSelectedListener { - override fun onProfileSelected(project: Project, profile: QRegionProfile?) { - chatSessionStorage.deleteAllSessions() - } - } - ) - } - - private fun getQTokenProvider(project: Project) = ( - ToolkitConnectionManager - .getInstance(project) - .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - ) - ?.getConnectionSettings() - ?.tokenProvider - ?.delegate as? BearerTokenProvider - - private suspend fun handleMessage(message: AmazonQMessage, inboundAppMessagesHandler: InboundAppMessagesHandler) { - when (message) { - is IncomingCodeScanMessage.ClearChat -> inboundAppMessagesHandler.processClearQuickAction(message) - is IncomingCodeScanMessage.Help -> inboundAppMessagesHandler.processHelpQuickAction(message) - is IncomingCodeScanMessage.TabCreated -> inboundAppMessagesHandler.processTabCreated(message) - is IncomingCodeScanMessage.TabRemoved -> inboundAppMessagesHandler.processTabRemoved(message) - is IncomingCodeScanMessage.Scan -> inboundAppMessagesHandler.processScanQuickAction(message) - is IncomingCodeScanMessage.StartProjectScan -> inboundAppMessagesHandler.processStartProjectScan(message) - is IncomingCodeScanMessage.StartFileScan -> inboundAppMessagesHandler.processStartFileScan(message) - is CodeScanActionMessage -> inboundAppMessagesHandler.processCodeScanCommand(message) - is IncomingCodeScanMessage.StopProjectScan -> inboundAppMessagesHandler.processStopProjectScan(message) - is IncomingCodeScanMessage.StopFileScan -> inboundAppMessagesHandler.processStopFileScan(message) - is IncomingCodeScanMessage.ResponseBodyLinkClicked -> inboundAppMessagesHandler.processResponseBodyLinkClicked(message) - is IncomingCodeScanMessage.OpenIssuesPanel -> inboundAppMessagesHandler.processOpenIssuesPanel(message) - } - } - - override fun dispose() { - // nothing to do - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt deleted file mode 100644 index 2ac70b38b0d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppFactory - -class CodeScanChatAppFactory(private val cs: CoroutineScope) : AmazonQAppFactory { - override fun createApp(project: Project) = CodeScanChatApp(cs) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt deleted file mode 100644 index ee2dc09e3c5..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.Button -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanButtonId -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessageContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.ProgressField -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.PromptProgressMessage -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.IssueSeverity -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.StaticPrompt -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.StaticTextResponse -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import software.aws.toolkits.resources.message -import java.util.UUID - -private val projectScanButton = Button( - id = CodeScanButtonId.StartProjectScan.id, - text = message("codescan.chat.message.button.projectScan") -) - -private val fileScanButton = Button( - id = CodeScanButtonId.StartFileScan.id, - text = message("codescan.chat.message.button.fileScan") -) - -private val openIssuesPanelButton = Button( - id = CodeScanButtonId.OpenIssuesPanel.id, - text = message("codescan.chat.message.button.openIssues"), - keepCardAfterClick = true -) - -fun buildStartNewScanChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = message("codescan.chat.new_scan.input.message"), - buttons = listOf( - fileScanButton, - projectScanButton - ), - canBeVoted = false -) - -// TODO: Replace StaticPrompt and StaticTextResponse message according to Fnf -fun buildHelpChatPromptContent() = CodeScanChatMessageContent( - type = ChatMessageType.Prompt, - message = StaticPrompt.Help.message, - canBeVoted = false -) - -fun buildHelpChatAnswerContent() = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = StaticTextResponse.Help.message, - canBeVoted = false -) - -fun buildUserSelectionProjectScanChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Prompt, - message = message("codescan.chat.message.button.projectScan"), - canBeVoted = false -) - -fun buildUserSelectionFileScanChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Prompt, - message = message("codescan.chat.message.button.fileScan"), - canBeVoted = false -) - -fun buildNotInGitRepoChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = message("codescan.chat.message.not_git_repo"), - canBeVoted = false -) - -fun buildScanInProgressChatContent(currentStep: Int, isProject: Boolean) = CodeScanChatMessageContent( - type = ChatMessageType.AnswerPart, - message = buildString { - appendLine(if (isProject) message("codescan.chat.message.scan_begin_project") else message("codescan.chat.message.scan_begin_file")) - appendLine("") - appendLine(message("codescan.chat.message.scan_begin_wait_time")) - appendLine("") - appendLine("${getIconForStep(0, currentStep)} " + message("codescan.chat.message.scan_step_1")) - appendLine("${getIconForStep(1, currentStep)} " + message("codescan.chat.message.scan_step_2")) - appendLine("${getIconForStep(2, currentStep)} " + message("codescan.chat.message.scan_step_3")) - } -) - -val cancellingProgressField = ProgressField( - status = "warning", - text = message("general.canceling"), - value = -1, - actions = emptyList() -) - -fun buildScanCompleteChatContent(issues: List, isProject: Boolean = false): CodeScanChatMessageContent { - val issueCountMap = IssueSeverity.entries.associate { it.displayName to 0 }.toMutableMap() - val aggregatedIssues = issues.groupBy { it.severity } - aggregatedIssues.forEach { (key, list) -> if (list.isNotEmpty()) issueCountMap[key] = list.size } - - val message = buildString { - appendLine(if (isProject) message("codewhisperer.codescan.scan_complete_project") else message("codewhisperer.codescan.scan_complete_file")) - issueCountMap.entries.forEach { (severity, count) -> - appendLine(message("codewhisperer.codescan.scan_complete_count", severity, count)) - } - } - - return CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = message, - buttons = listOf( - openIssuesPanelButton - ), - ) -} - -fun buildPromptProgressMessage(tabId: String, isProject: Boolean = false, isCanceling: Boolean = false) = PromptProgressMessage( - progressField = when { - isCanceling -> cancellingProgressField - isProject -> projectScanProgressField - else -> fileScanProgressField - }, - tabId = tabId -) - -fun buildClearPromptProgressMessage(tabId: String) = PromptProgressMessage( - tabId = tabId -) - -val runCodeScanMessage - get() = CodeScanChatMessage(messageType = ChatMessageType.Prompt, command = "review", tabId = UUID.randomUUID().toString()) - -val cancelFileScanButton = Button( - id = CodeScanButtonId.StopFileScan.id, - text = message("general.cancel"), - icon = "cancel" -) - -val cancelProjectScanButton = cancelFileScanButton.copy( - id = CodeScanButtonId.StopProjectScan.id -) - -val fileScanProgressField = ProgressField( - status = "default", - text = message("codescan.chat.message.scan_file_in_progress"), - value = -1, - actions = listOf(cancelFileScanButton) -) - -val projectScanProgressField = fileScanProgressField.copy( - text = message("codescan.chat.message.scan_project_in_progress"), - actions = listOf(cancelProjectScanButton) -) - -fun buildProjectScanFailedChatContent(errorMessage: String?) = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = errorMessage ?: message("codescan.chat.message.project_scan_failed") -) - -fun getIconForStep(targetStep: Int, currentStep: Int) = when { - currentStep == targetStep -> "☐" - currentStep > targetStep -> "☑" - else -> "☐" -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt deleted file mode 100644 index c27b7ef4169..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -const val FEATURE_NAME = "Amazon Q Code Scan" diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt deleted file mode 100644 index 31ca09503df..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanActionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.IncomingCodeScanMessage - -interface InboundAppMessagesHandler { - suspend fun processScanQuickAction(message: IncomingCodeScanMessage.Scan) - - suspend fun processStartProjectScan(message: IncomingCodeScanMessage.StartProjectScan) - - suspend fun processStartFileScan(message: IncomingCodeScanMessage.StartFileScan) - - suspend fun processStopProjectScan(message: IncomingCodeScanMessage.StopProjectScan) - - suspend fun processStopFileScan(message: IncomingCodeScanMessage.StopFileScan) - - suspend fun processTabCreated(message: IncomingCodeScanMessage.TabCreated) - - suspend fun processClearQuickAction(message: IncomingCodeScanMessage.ClearChat) - - suspend fun processHelpQuickAction(message: IncomingCodeScanMessage.Help) - - suspend fun processTabRemoved(message: IncomingCodeScanMessage.TabRemoved) - - suspend fun processCodeScanCommand(message: CodeScanActionMessage) - - suspend fun processResponseBodyLinkClicked(message: IncomingCodeScanMessage.ResponseBodyLinkClicked) - - suspend fun processOpenIssuesPanel(message: IncomingCodeScanMessage.OpenIssuesPanel) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt deleted file mode 100644 index 8720f036d45..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth - -import com.intellij.openapi.project.Project -import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection -import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet -import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity - -fun isCodeScanAvailable(project: Project): Boolean = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) is ActiveConnection.ValidBearer diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt deleted file mode 100644 index 01532c154f3..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands - -import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeScanResponse -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants - -data class CodeScanActionMessage( - val command: CodeScanCommand, - val project: Project, - val scanResult: CodeScanResponse? = null, - val scope: CodeWhispererConstants.CodeAnalysisScope, -) : AmazonQMessage diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt deleted file mode 100644 index f7b16705b14..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands - -enum class CodeScanCommand { - ScanComplete, -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt deleted file mode 100644 index 832741a1fc4..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands - -import com.intellij.openapi.components.Service -import com.intellij.openapi.project.Project -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeScanResponse -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants - -@Service -class CodeScanMessageListener { - private val _messages by lazy { MutableSharedFlow(extraBufferCapacity = 10) } - val flow = _messages.asSharedFlow() - - fun onScanResult(result: CodeScanResponse?, scope: CodeWhispererConstants.CodeAnalysisScope, project: Project) { - _messages.tryEmit(CodeScanActionMessage(CodeScanCommand.ScanComplete, scanResult = result, scope = scope, project = project)) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt deleted file mode 100644 index 8349a218979..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.controller - -import com.intellij.ide.BrowserUtil -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext -import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.FEATURE_NAME -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.InboundAppMessagesHandler -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildHelpChatAnswerContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildHelpChatPromptContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildNotInGitRepoChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildProjectScanFailedChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildScanCompleteChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildScanInProgressChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildStartNewScanChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildUserSelectionFileScanChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildUserSelectionProjectScanChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanActionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanCommand -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.AuthenticationNeededExceptionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.IncomingCodeScanMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.storage.ChatSessionStorage -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeScanResponse -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager -import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.aws.toolkits.jetbrains.services.codewhisperer.util.getAuthType -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import software.aws.toolkits.resources.message - -class CodeScanChatController( - private val context: AmazonQAppInitContext, - private val chatSessionStorage: ChatSessionStorage, - private val authController: AuthController = AuthController(), -) : InboundAppMessagesHandler { - - private val messenger = context.messagesFromAppToUi - private val codeScanManager = CodeWhispererCodeScanManager.getInstance(context.project) - private val codeScanChatHelper = CodeScanChatHelper(context.messagesFromAppToUi, chatSessionStorage) - private val scanInProgressMessageId = "scanProgressMessage" - - override suspend fun processScanQuickAction(message: IncomingCodeScanMessage.Scan) { - // TODO: telemetry - - if (!checkForAuth(message.tabId)) { - return - } - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - - codeScanChatHelper.setActiveCodeScanTabId(message.tabId) - codeScanChatHelper.addNewMessage(buildStartNewScanChatContent()) - codeScanChatHelper.sendChatInputEnabledMessage(false) - } - - override suspend fun processStartProjectScan(message: IncomingCodeScanMessage.StartProjectScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.addNewMessage(buildUserSelectionProjectScanChatContent()) - if (!codeScanManager.isInsideWorkTree()) { - codeScanChatHelper.addNewMessage(buildNotInGitRepoChatContent()) - } - codeScanChatHelper.addNewMessage(buildScanInProgressChatContent(currentStep = 1, isProject = true), messageIdOverride = scanInProgressMessageId) - codeScanManager.runCodeScan(CodeWhispererConstants.CodeAnalysisScope.PROJECT, initiatedByChat = true) - codeScanChatHelper.updateProgress(isProject = true, isCanceling = false) - } - - override suspend fun processStopProjectScan(message: IncomingCodeScanMessage.StopProjectScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.updateProgress(isProject = true, isCanceling = true) - codeScanManager.stopCodeScan(CodeWhispererConstants.CodeAnalysisScope.PROJECT) - } - - override suspend fun processStopFileScan(message: IncomingCodeScanMessage.StopFileScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.updateProgress(isProject = false, isCanceling = true) - codeScanManager.stopCodeScan(CodeWhispererConstants.CodeAnalysisScope.FILE) - } - - override suspend fun processStartFileScan(message: IncomingCodeScanMessage.StartFileScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.addNewMessage(buildUserSelectionFileScanChatContent()) - codeScanChatHelper.addNewMessage(buildScanInProgressChatContent(currentStep = 1, isProject = false), messageIdOverride = scanInProgressMessageId) - codeScanManager.runCodeScan(CodeWhispererConstants.CodeAnalysisScope.FILE, initiatedByChat = true) - codeScanChatHelper.updateProgress(isProject = false, isCanceling = false) - } - - override suspend fun processTabCreated(message: IncomingCodeScanMessage.TabCreated) { - logger.debug { "$FEATURE_NAME: New tab created: $message" } - codeScanChatHelper.setActiveCodeScanTabId(message.tabId) - CodeWhispererTelemetryService.getInstance().sendCodeScanNewTabEvent(getAuthType(context.project)) - } - - override suspend fun processClearQuickAction(message: IncomingCodeScanMessage.ClearChat) { - chatSessionStorage.deleteSession(message.tabId) - } - - override suspend fun processHelpQuickAction(message: IncomingCodeScanMessage.Help) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.addNewMessage(buildHelpChatPromptContent()) - codeScanChatHelper.addNewMessage(buildHelpChatAnswerContent()) - } - - override suspend fun processTabRemoved(message: IncomingCodeScanMessage.TabRemoved) { - chatSessionStorage.deleteSession(message.tabId) - } - - override suspend fun processResponseBodyLinkClicked(message: IncomingCodeScanMessage.ResponseBodyLinkClicked) { - BrowserUtil.browse(message.link) - } - - override suspend fun processCodeScanCommand(message: CodeScanActionMessage) { - if (message.project != context.project) return - val isProject = message.scope == CodeWhispererConstants.CodeAnalysisScope.PROJECT - when (message.command) { - CodeScanCommand.ScanComplete -> { - codeScanChatHelper.addNewMessage( - buildScanInProgressChatContent(currentStep = 2, isProject = isProject), - messageIdOverride = scanInProgressMessageId - ) - val result = message.scanResult - if (result != null) { - handleCodeScanResult(result, message.scope) - } else { - codeScanChatHelper.addNewMessage(buildProjectScanFailedChatContent("Cancelled")) - codeScanChatHelper.clearProgress() - } - } - } - } - - private suspend fun handleCodeScanResult(result: CodeScanResponse, scope: CodeWhispererConstants.CodeAnalysisScope) { - val isProject = scope == CodeWhispererConstants.CodeAnalysisScope.PROJECT - when (result) { - is CodeScanResponse.Success -> { - codeScanChatHelper.addNewMessage( - buildScanInProgressChatContent(currentStep = 3, isProject = isProject), - messageIdOverride = scanInProgressMessageId - ) - codeScanChatHelper.addNewMessage(buildScanCompleteChatContent(result.issues, isProject = isProject)) - codeScanChatHelper.clearProgress() - } - is CodeScanResponse.Failure -> { - codeScanChatHelper.addNewMessage(buildScanInProgressChatContent(3, isProject = isProject), messageIdOverride = scanInProgressMessageId) - codeScanChatHelper.addNewMessage(buildProjectScanFailedChatContent(result.failureReason.message)) - codeScanChatHelper.clearProgress() - } - } - } - - /** - * Return true if authenticated, else show authentication message and return false - * // TODO: Refactor this to avoid code duplication with other controllers - */ - private suspend fun checkForAuth(tabId: String): Boolean { - try { - val session = chatSessionStorage.getSession(tabId) - logger.debug { "$FEATURE_NAME: Session created with id: ${session.tabId}" } - - val credentialState = authController.getAuthNeededStates(context.project).amazonQ - if (credentialState != null) { - messenger.publish( - AuthenticationNeededExceptionMessage( - tabId = session.tabId, - authType = credentialState.authType, - message = credentialState.message - ) - ) - session.isAuthenticating = true - return false - } - } catch (err: Exception) { - messenger.publish( - CodeScanChatMessage( - tabId = tabId, - messageType = ChatMessageType.Answer, - message = message("codescan.chat.message.error_request") - ) - ) - return false - } - - return true - } - - override suspend fun processOpenIssuesPanel(message: IncomingCodeScanMessage.OpenIssuesPanel) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanManager.showCodeScanUI() - } - - companion object { - private val logger = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt deleted file mode 100644 index 920bebb5109..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.controller - -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildClearPromptProgressMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildPromptProgressMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.ChatInputEnabledMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessageContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.UpdatePlaceholderMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.storage.ChatSessionStorage -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import java.util.UUID - -class CodeScanChatHelper( - private val messagePublisher: MessagePublisher, - private val chatSessionStorage: ChatSessionStorage, -) { - private var activeCodeScanTabId: String? = null - - fun setActiveCodeScanTabId(tabId: String) { - activeCodeScanTabId = tabId - } - - fun getActiveCodeScanTabId(): String? = activeCodeScanTabId - - private fun isInValidSession() = activeCodeScanTabId == null || chatSessionStorage.getSession(activeCodeScanTabId as String).isAuthenticating - - suspend fun addNewMessage( - content: CodeScanChatMessageContent, - messageIdOverride: String? = null, - clearPreviousItemButtons: Boolean? = false, - ) { - if (isInValidSession()) return - messagePublisher.publish( - CodeScanChatMessage( - tabId = activeCodeScanTabId as String, - messageId = messageIdOverride ?: UUID.randomUUID().toString(), - messageType = content.type, - message = content.message, - buttons = content.buttons, - formItems = content.formItems, - followUps = content.followUps, - canBeVoted = content.canBeVoted, - isLoading = content.type == ChatMessageType.AnswerPart, - clearPreviousItemButtons = clearPreviousItemButtons as Boolean - ) - ) - } - - suspend fun updateProgress(isProject: Boolean = false, isCanceling: Boolean = false) { - if (isInValidSession()) return - messagePublisher.publish(buildPromptProgressMessage(activeCodeScanTabId as String, isProject, isCanceling)) - sendChatInputEnabledMessage(false) - } - - suspend fun clearProgress() { - if (isInValidSession()) return - messagePublisher.publish(buildClearPromptProgressMessage(activeCodeScanTabId as String)) - sendChatInputEnabledMessage(true) - } - - suspend fun sendChatInputEnabledMessage(isEnabled: Boolean) { - if (isInValidSession()) return - messagePublisher.publish(ChatInputEnabledMessage(activeCodeScanTabId as String, enabled = isEnabled)) - } - - suspend fun updatePlaceholder(newPlaceholder: String) { - if (isInValidSession()) return - - messagePublisher.publish( - UpdatePlaceholderMessage( - tabId = activeCodeScanTabId as String, - newPlaceholder = newPlaceholder - ) - ) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt deleted file mode 100644 index 51fffcdb53d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages - -import com.fasterxml.jackson.annotation.JsonProperty -import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthFollowUpType -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import software.aws.toolkits.jetbrains.services.cwc.messages.FollowUp -import java.time.Instant -import java.util.UUID - -const val CODE_SCAN_TAB_NAME = "codescan" - -enum class CodeScanButtonId(val id: String) { - StartProjectScan("codescan_start_project_scan"), - StartFileScan("codescan_start_file_scan"), - StopProjectScan("codescan_stop_project_scan"), - StopFileScan("codescan_stop_file_scan"), - OpenIssuesPanel("codescan_open_issues"), -} - -data class Button( - val id: String, - val text: String, - val description: String? = null, - val icon: String? = null, - val keepCardAfterClick: Boolean? = false, - val disabled: Boolean? = false, - val waitMandatoryFormItems: Boolean? = false, -) -data class ProgressField( - val title: String? = null, - val value: Int? = null, - val status: String? = null, - val actions: List