Skip to content

Commit 354dede

Browse files
evanliu048aws-toolkit-automationrli
authored
fix(amazonq): Invalidate Obsolete Customization on Profile Switch (#5575)
When a user switches profiles, the active customization is now verified against the updated list of available customizations. If the active customization is not present in the newly returned list, it is automatically invalidated. --------- Co-authored-by: aws-toolkit-automation <43144436+aws-toolkit-automation@users.noreply.github.com> Co-authored-by: Richard Li <742829+rli@users.noreply.github.com>
1 parent 40b2d4e commit 354dede

File tree

5 files changed

+80
-3
lines changed

5 files changed

+80
-3
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+
}

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/profile/QRegionProfileManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ class QRegionProfileManager : PersistentStateComponent<QProfileState>, Disposabl
144144
}
145145
}
146146

147-
project.messageBus
147+
ApplicationManager.getApplication().messageBus
148148
.syncPublisher(QRegionProfileSelectedListener.TOPIC)
149149
.onProfileSelected(project, newProfile)
150150
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileSelectedListener.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.intellij.util.messages.Topic
88

99
interface QRegionProfileSelectedListener {
1010
companion object {
11-
@Topic.ProjectLevel
11+
@Topic.AppLevel
1212
val TOPIC = Topic.create("QRegionProfileSelected", QRegionProfileSelectedListener::class.java)
1313
}
1414

0 commit comments

Comments
 (0)