Skip to content

fix(amazonq): adjust feature config customization arn override logic #5406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 26, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ interface CodeWhispererModelConfigurator {
*/
fun getNewUpdate(connectionId: String): Collection<CustomizationUiItem>?

/**
* Get any current persisted customization arn override config
*/
fun getPersistedCustomizationOverride(): String?

companion object {
fun getInstance(): CodeWhispererModelConfigurator = service()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe

private var serviceDefaultArn: String? = null

private var customizationArnOverrideV2: String? = null

override fun showConfigDialog(project: Project) {
runInEdt {
calculateIfIamIdentityCenterConnection(project) {
Expand Down Expand Up @@ -181,7 +183,7 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
*/
override fun switchCustomization(project: Project, newCustomization: CodeWhispererCustomization?, isOverride: Boolean) {
calculateIfIamIdentityCenterConnection(project) {
if (isOverride && (newCustomization == null || newCustomization.arn.isEmpty() || serviceDefaultArn == newCustomization.arn)) {
if (isOverride && (newCustomization == null || newCustomization.arn.isEmpty() || customizationArnOverrideV2 == newCustomization.arn)) {
return@calculateIfIamIdentityCenterConnection
}
val oldCus = connectionIdToActiveCustomizationArn[it.id]
Expand All @@ -197,7 +199,7 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
CodeWhispererCustomizationListener.notifyCustomUiUpdate()
}
if (isOverride) {
serviceDefaultArn = newCustomization?.arn
customizationArnOverrideV2 = newCustomization?.arn
}
}
}
Expand Down Expand Up @@ -249,6 +251,7 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
state.connectionIdToActiveCustomizationArn.putAll(this.connectionIdToActiveCustomizationArn)
state.previousAvailableCustomizations.putAll(this.connectionToCustomizationsShownLastTime)
state.serviceDefaultArn = this.serviceDefaultArn
state.customizationArnOverrideV2 = this.customizationArnOverrideV2

return state
}
Expand All @@ -261,8 +264,12 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
connectionToCustomizationsShownLastTime.putAll(state.previousAvailableCustomizations)

this.serviceDefaultArn = state.serviceDefaultArn
this.customizationArnOverrideV2 = state.customizationArnOverrideV2
}

// current latest field is customizationArnOverrideV2
override fun getPersistedCustomizationOverride() = customizationArnOverrideV2

override fun dispose() {}

private fun invalidateSelectedAndNotify(project: Project) {
Expand Down Expand Up @@ -294,6 +301,9 @@ class CodeWhispererCustomizationState : BaseState() {

@get:Property
var serviceDefaultArn by string()

@get:Property
var customizationArnOverrideV2 by string()
}

data class CustomizationUiItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,18 @@ class CodeWhispererProjectStartupActivity : StartupActivity.DumbAware {
runOnce = true
}

// Start a job that runs every 30 mins
// Start a job that runs every 180 minutes
private fun initFeatureConfigPollingJob(project: Project) {
projectCoroutineScope(project).launch {
while (isActive) {
CodeWhispererFeatureConfigService.getInstance().fetchFeatureConfigs(project)
CodeWhispererFeatureConfigService.getInstance().getCustomizationFeature()?.let { customization ->
val persistedCustomizationOverride = CodeWhispererModelConfigurator.getInstance().getPersistedCustomizationOverride()
val latestCustomizationOverride = customization.value.stringValue()
if (persistedCustomizationOverride == latestCustomizationOverride) return@let

// latest is different from the currently persisted, need update
CodeWhispererFeatureConfigService.getInstance().validateCustomizationOverride(project, customization)
CodeWhispererModelConfigurator.getInstance().switchCustomization(
project,
CodeWhispererCustomization(arn = customization.value.stringValue(), name = customization.variation),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ object CodeWhispererConstants {
const val TOTAL_MILLIS_IN_SECOND = 1000
const val TOTAL_SECONDS_IN_MINUTE: Long = 60L
const val ACCOUNTLESS_START_URL = "accountless"
const val FEATURE_CONFIG_POLL_INTERVAL_IN_MS: Long = 30 * 60 * 1000L // 30 mins
const val FEATURE_CONFIG_POLL_INTERVAL_IN_MS: Long = 180 * 60 * 1000L // 180 mins
const val USING: String = "using"
const val GLOBAL_USING: String = "global using"
const val STATIC: String = "static"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,47 +42,8 @@ class CodeWhispererFeatureConfigService {
featureConfigs[it.feature()] = FeatureContext(it.feature(), it.variation(), it.value())
}

// Only apply new auto-trigger UX to BID users
val isNewAutoTriggerUX = getNewAutoTriggerUX()
if (isNewAutoTriggerUX) {
calculateIfIamIdentityCenterConnection(project) {
featureConfigs.remove(NEW_AUTO_TRIGGER_UX)
}
}

val customizationArnOverride = featureConfigs[CUSTOMIZATION_ARN_OVERRIDE_NAME]?.value?.stringValue()
if (customizationArnOverride != null) {
// Double check if server-side wrongly returns a customizationArn to BID users
calculateIfBIDConnection(project) {
featureConfigs.remove(CUSTOMIZATION_ARN_OVERRIDE_NAME)
}
val availableCustomizations =
calculateIfIamIdentityCenterConnection(project) {
try {
connection.getConnectionSettings().awsClient<CodeWhispererRuntimeClient>().listAvailableCustomizationsPaginator(
ListAvailableCustomizationsRequest.builder().build()
)
.stream()
.toList()
.flatMap { resp ->
resp.customizations().map {
it.arn()
}
}
} catch (e: Exception) {
LOG.debug(e) { "Failed to list available customizations" }
null
}
}
validateNewAutoTriggerUX(project)

// If customizationArn from A/B is not available in listAvailableCustomizations response, don't use this value
if (availableCustomizations?.contains(customizationArnOverride) == false) {
LOG.debug {
"Customization arn $customizationArnOverride not available in listAvailableCustomizations, not using"
}
featureConfigs.remove(CUSTOMIZATION_ARN_OVERRIDE_NAME)
}
}
CodeWhispererFeatureConfigListener.notifyUiFeatureConfigsAvailable()
} catch (e: Exception) {
LOG.debug(e) { "Error when fetching feature configs" }
Expand Down Expand Up @@ -132,6 +93,53 @@ class CodeWhispererFeatureConfigService {
private fun connection(project: Project) =
ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())

private fun validateNewAutoTriggerUX(project: Project) {
// Only apply new auto-trigger UX to BID users
val isNewAutoTriggerUX = getNewAutoTriggerUX()
if (isNewAutoTriggerUX) {
calculateIfIamIdentityCenterConnection(project) {
featureConfigs.remove(NEW_AUTO_TRIGGER_UX)
}
}
}

fun validateCustomizationOverride(project: Project, customization: FeatureContext) {
val customizationArnOverride = customization.value.stringValue()
val connection = connection(project) ?: return
if (customizationArnOverride != null) {
// Double check if server-side wrongly returns a customizationArn to BID users
calculateIfBIDConnection(project) {
featureConfigs.remove(CUSTOMIZATION_ARN_OVERRIDE_NAME)
}
val availableCustomizations =
calculateIfIamIdentityCenterConnection(project) {
try {
connection.getConnectionSettings().awsClient<CodeWhispererRuntimeClient>().listAvailableCustomizationsPaginator(
ListAvailableCustomizationsRequest.builder().build()
)
.stream()
.toList()
.flatMap { resp ->
resp.customizations().map {
it.arn()
}
}
} catch (e: Exception) {
LOG.debug(e) { "Failed to list available customizations" }
null
}
}

// If customizationArn from A/B is not available in listAvailableCustomizations response, don't use this value
if (availableCustomizations?.contains(customizationArnOverride) == false) {
LOG.debug {
"Customization arn $customizationArnOverride not available in listAvailableCustomizations, not using"
}
featureConfigs.remove(CUSTOMIZATION_ARN_OVERRIDE_NAME)
}
}
}

companion object {
fun getInstance(): CodeWhispererFeatureConfigService = service()
private const val TEST_FEATURE_NAME = "testFeature"
Expand Down
Loading