Skip to content

Commit 25dab9a

Browse files
committed
Merge remote-tracking branch 'upstream/main' into multi-window
2 parents 46f6b45 + 3357e88 commit 25dab9a

File tree

5 files changed

+90
-4
lines changed

5 files changed

+90
-4
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Amazon Q: Customization now resets with a warning if unavailable in the selected profile."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Fix infinite loop when workspace indexing server fails to initialize"
4+
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/customization/CodeWhispererModelConfigurator.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import software.aws.toolkits.core.utils.debug
2222
import software.aws.toolkits.core.utils.getLogger
2323
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
2424
import software.aws.toolkits.jetbrains.services.amazonq.calculateIfIamIdentityCenterConnection
25+
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
26+
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener
2527
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
2628
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
2729
import software.aws.toolkits.jetbrains.utils.notifyInfo
@@ -48,6 +50,7 @@ private fun notifyInvalidSelectedCustomization(project: Project) {
4850
}
4951

5052
private fun notifyNewCustomization(project: Project) {
53+
if (ApplicationManager.getApplication().isUnitTestMode) return
5154
notifyInfo(
5255
title = message("codewhisperer.custom.dialog.title"),
5356
content = message("codewhisperer.notification.custom.new_customization"),
@@ -81,6 +84,18 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
8184

8285
private var customizationArnOverrideV2: String? = null
8386

87+
init {
88+
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
89+
QRegionProfileSelectedListener.TOPIC,
90+
object : QRegionProfileSelectedListener {
91+
override fun onProfileSelected(project: Project, profile: QRegionProfile?) {
92+
pluginAwareExecuteOnPooledThread {
93+
CodeWhispererModelConfigurator.getInstance().listCustomizations(project, passive = true)
94+
}
95+
}
96+
}
97+
)
98+
}
8499
override fun showConfigDialog(project: Project) {
85100
runInEdt {
86101
calculateIfIamIdentityCenterConnection(project) {

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import com.intellij.openapi.application.ApplicationManager
77
import com.intellij.testFramework.ApplicationRule
88
import com.intellij.testFramework.DisposableRule
99
import com.intellij.testFramework.ProjectRule
10+
import com.intellij.testFramework.registerServiceInstance
1011
import com.intellij.testFramework.replaceService
1112
import com.intellij.util.xmlb.XmlSerializer
13+
import migration.software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
1214
import org.assertj.core.api.Assertions.assertThat
1315
import org.jdom.output.XMLOutputter
1416
import org.junit.Before
@@ -40,10 +42,14 @@ import software.aws.toolkits.jetbrains.core.credentials.sono.isSono
4042
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
4143
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
4244
import software.aws.toolkits.jetbrains.services.amazonq.FeatureContext
45+
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener
46+
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
4347
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
4448
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomizationState
4549
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.DefaultCodeWhispererModelConfigurator
4650
import software.aws.toolkits.jetbrains.utils.xmlElement
51+
import java.util.concurrent.CountDownLatch
52+
import java.util.concurrent.TimeUnit
4753
import kotlin.reflect.full.memberProperties
4854
import kotlin.reflect.jvm.isAccessible
4955

@@ -75,6 +81,7 @@ class CodeWhispererModelConfiguratorTest {
7581
private lateinit var sut: DefaultCodeWhispererModelConfigurator
7682
private lateinit var mockClient: CodeWhispererRuntimeClient
7783
private lateinit var abManager: CodeWhispererFeatureConfigService
84+
private lateinit var mockClintAdaptor: CodeWhispererClientAdaptor
7885

7986
@Before
8087
fun setup() {
@@ -83,7 +90,11 @@ class CodeWhispererModelConfiguratorTest {
8390
regionProvider.addRegion(Region.US_EAST_1)
8491
regionProvider.addRegion(Region.US_EAST_2)
8592

86-
sut = DefaultCodeWhispererModelConfigurator()
93+
sut = spy(CodeWhispererModelConfigurator.getInstance() as DefaultCodeWhispererModelConfigurator).also { spyInstance ->
94+
ApplicationManager.getApplication().replaceService(
95+
DefaultCodeWhispererModelConfigurator::class.java, spyInstance, disposableRule.disposable
96+
)
97+
}
8798

8899
(ToolkitConnectionManager.getInstance(projectRule.project) as DefaultToolkitConnectionManager).loadState(ToolkitConnectionManagerState())
89100
mockClient.stub {
@@ -110,6 +121,9 @@ class CodeWhispererModelConfiguratorTest {
110121
abManager,
111122
disposableRule.disposable
112123
)
124+
125+
mockClintAdaptor = mock()
126+
projectRule.project.registerServiceInstance(CodeWhispererClientAdaptor::class.java, mockClintAdaptor)
113127
}
114128

115129
@Test
@@ -550,4 +564,48 @@ class CodeWhispererModelConfiguratorTest {
550564
assertThat(actual.previousAvailableCustomizations).hasSize(1)
551565
assertThat(actual.previousAvailableCustomizations["fake-sso-url"]).isEqualTo(listOf("arn_1", "arn_2", "arn_3"))
552566
}
567+
568+
@Test
569+
fun `profile switch should keep using existing customization if new list still contains that arn`() {
570+
val ssoConn = spy(LegacyManagedBearerSsoConnection(region = "us-east-1", startUrl = "url 1", scopes = Q_SCOPES))
571+
ToolkitConnectionManager.getInstance(projectRule.project).switchConnection(ssoConn)
572+
val oldCustomization = CodeWhispererCustomization("oldArn", "oldName", "oldDescription")
573+
sut.switchCustomization(projectRule.project, oldCustomization)
574+
575+
assertThat(sut.activeCustomization(projectRule.project)).isEqualTo(oldCustomization)
576+
577+
val fakeCustomizations = listOf(
578+
CodeWhispererCustomization("oldArn", "oldName", "oldDescription")
579+
)
580+
mockClintAdaptor.stub { on { listAvailableCustomizations() } doReturn fakeCustomizations }
581+
582+
ApplicationManager.getApplication().messageBus
583+
.syncPublisher(QRegionProfileSelectedListener.TOPIC)
584+
.onProfileSelected(projectRule.project, null)
585+
586+
assertThat(sut.activeCustomization(projectRule.project)).isEqualTo(oldCustomization)
587+
}
588+
589+
@Test
590+
fun `profile switch should invalidate obsolete customization if it's not in the new list`() {
591+
val ssoConn = spy(LegacyManagedBearerSsoConnection(region = "us-east-1", startUrl = "url 1", scopes = Q_SCOPES))
592+
ToolkitConnectionManager.getInstance(projectRule.project).switchConnection(ssoConn)
593+
val oldCustomization = CodeWhispererCustomization("oldArn", "oldName", "oldDescription")
594+
sut.switchCustomization(projectRule.project, oldCustomization)
595+
assertThat(sut.activeCustomization(projectRule.project)).isEqualTo(oldCustomization)
596+
val fakeCustomizations = listOf(
597+
CodeWhispererCustomization("newArn", "newName", "newDescription")
598+
)
599+
mockClintAdaptor.stub { on { listAvailableCustomizations() } doReturn fakeCustomizations }
600+
601+
val latch = CountDownLatch(1)
602+
603+
ApplicationManager.getApplication().messageBus
604+
.syncPublisher(QRegionProfileSelectedListener.TOPIC)
605+
.onProfileSelected(projectRule.project, null)
606+
607+
latch.await(2, TimeUnit.SECONDS)
608+
609+
assertThat(sut.activeCustomization(projectRule.project)).isNull()
610+
}
553611
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/ProjectContextProvider.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,14 @@ class ProjectContextProvider(val project: Project, private val encoderServer: En
116116
logger.info { "project context index starting" }
117117
delay(300)
118118
val isIndexSuccess = index()
119-
if (isIndexSuccess) isIndexComplete.set(true)
119+
if (isIndexSuccess) {
120+
isIndexComplete.set(true)
121+
}
120122
return
121123
}
124+
retryCount.incrementAndGet()
122125
} catch (e: Exception) {
126+
logger.warn(e) { "failed to init project context" }
123127
if (e.stackTraceToString().contains("Connection refused")) {
124128
retryCount.incrementAndGet()
125129
delay(10000)
@@ -133,6 +137,7 @@ class ProjectContextProvider(val project: Project, private val encoderServer: En
133137
private suspend fun initEncryption(): Boolean {
134138
val request = encoderServer.getEncryptionRequest()
135139
val response = sendMsgToLsp(LspMessage.Initialize, request)
140+
logger.info { "received response to init encryption: $response" }
136141
return response?.responseCode == 200
137142
}
138143

@@ -315,12 +320,12 @@ class ProjectContextProvider(val project: Project, private val encoderServer: En
315320
}
316321

317322
private suspend fun sendMsgToLsp(msgType: LspMessage, request: String?): LspResponse? {
318-
logger.info { "sending message: ${msgType.endpoint} to lsp on port ${encoderServer.port}" }
319-
val url = URI("http://127.0.0.1:${encoderServer.port}/${msgType.endpoint}").toURL()
320323
if (!encoderServer.isNodeProcessRunning()) {
321324
logger.warn { "language server for ${project.name} is not running" }
322325
return null
323326
}
327+
logger.info { "sending message: ${msgType.endpoint} to lsp on port ${encoderServer.port}" }
328+
val url = URI("http://127.0.0.1:${encoderServer.port}/${msgType.endpoint}").toURL()
324329
// use 1h as timeout for index, 5 seconds for other APIs
325330
val timeoutMs = if (msgType is LspMessage.Index) 60.minutes.inWholeMilliseconds.toInt() else 5000
326331
// dedicate single thread to index operation because it can be long running

0 commit comments

Comments
 (0)