@@ -17,12 +17,20 @@ import com.intellij.openapi.project.Project
17
17
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
18
18
import com.intellij.util.xmlb.annotations.MapAnnotation
19
19
import com.intellij.util.xmlb.annotations.Property
20
+ import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient
20
21
import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException
22
+ import software.amazon.awssdk.services.codewhispererruntime.model.ListAvailableCustomizationsRequest
21
23
import software.aws.toolkits.core.utils.debug
22
24
import software.aws.toolkits.core.utils.getLogger
25
+ import software.aws.toolkits.core.utils.tryOrNull
26
+ import software.aws.toolkits.jetbrains.core.AwsClientManager
27
+ import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
28
+ import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
29
+ import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider
23
30
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
24
31
import software.aws.toolkits.jetbrains.services.amazonq.calculateIfIamIdentityCenterConnection
25
32
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
33
+ import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
26
34
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener
27
35
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
28
36
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
@@ -71,6 +79,7 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
71
79
private val connectionIdToActiveCustomizationArn = Collections .synchronizedMap<String , CodeWhispererCustomization >(mutableMapOf ())
72
80
73
81
// Map to store connectionId to its listAvailableCustomizations result last time
82
+ // the customization has format profileArn::customizationArn
74
83
private val connectionToCustomizationsShownLastTime = mutableMapOf<String , MutableList <String >>()
75
84
76
85
private val connectionIdToIsAllowlisted = Collections .synchronizedMap<String , Boolean >(mutableMapOf ())
@@ -107,70 +116,89 @@ class DefaultCodeWhispererModelConfigurator : CodeWhispererModelConfigurator, Pe
107
116
108
117
@RequiresBackgroundThread
109
118
override fun listCustomizations (project : Project , passive : Boolean ): List <CustomizationUiItem >? =
110
- calculateIfIamIdentityCenterConnection(project) {
119
+ calculateIfIamIdentityCenterConnection(project) { it ->
111
120
// 1. invoke API and get result
112
- val listAvailableCustomizationsResult = try {
113
- CodeWhispererClientAdaptor .getInstance(project).listAvailableCustomizations()
114
- } catch (e: Exception ) {
115
- val requestId = (e as ? CodeWhispererRuntimeException )?.requestId()
116
- val logMessage = if (CodeWhispererConstants .Customization .noAccessToCustomizationExceptionPredicate(e)) {
117
- // TODO: not required for non GP users
118
- " ListAvailableCustomizations: connection ${it.id} is not allowlisted, requestId: ${requestId.orEmpty()} "
119
- } else {
120
- " ListAvailableCustomizations: failed due to unknown error ${e.message} , requestId: ${requestId.orEmpty()} "
121
- }
121
+ val listAvailableProfilesResult = tryOrNull { QRegionProfileManager .getInstance().listRegionProfiles(project) } ? : emptyList()
122
+
123
+ val aggregatedCustomizations = mutableListOf<CodeWhispererCustomization >()
122
124
123
- LOG .debug { logMessage }
124
- null
125
+ for (profile in listAvailableProfilesResult) {
126
+ try {
127
+ val setting = AwsRegionProvider .getInstance()[profile.region]?.let { it1 ->
128
+ ToolkitConnectionManager .getInstance(project).activeConnectionForFeature(QConnection .getInstance())?.getConnectionSettings()
129
+ ?.withRegion(it1)
130
+ }
131
+ val client = setting?.let { it1 -> AwsClientManager .getInstance().getClient(CodeWhispererRuntimeClient ::class , it1) }
132
+ val perProfileCustomizations =
133
+ client?.listAvailableCustomizationsPaginator(ListAvailableCustomizationsRequest .builder().profileArn(profile.arn).build())
134
+ ?.customizations()?.stream()?.toList()?.map {
135
+ CodeWhispererCustomization (
136
+ arn = it.arn(),
137
+ name = it.name(),
138
+ description = it.description(),
139
+ profile = profile
140
+ )
141
+ }
142
+ if (perProfileCustomizations != null ) {
143
+ aggregatedCustomizations.addAll(perProfileCustomizations)
144
+ }
145
+ } catch (e: Exception ) {
146
+ val requestId = (e as ? CodeWhispererRuntimeException )?.requestId()
147
+ val logMessage = if (CodeWhispererConstants .Customization .noAccessToCustomizationExceptionPredicate(e)) {
148
+ // TODO: not required for non GP users
149
+ " ListAvailableCustomizations: connection ${it.id} is not allowlisted, requestId: ${requestId.orEmpty()} "
150
+ } else {
151
+ " ListAvailableCustomizations: failed due to unknown error ${e.message} , requestId: ${requestId.orEmpty()} "
152
+ }
153
+
154
+ LOG .debug { logMessage }
155
+ }
125
156
}
126
157
127
158
// 2. get diff
128
159
val previousCustomizationsShapshot = connectionToCustomizationsShownLastTime.getOrElse(it.id) { emptyList() }
129
- val diff = listAvailableCustomizationsResult?.filterNot { customization -> previousCustomizationsShapshot.contains( customization.arn) }?.toSet()
130
-
160
+ // calculate new profile+ customization list using profile.arn :: customization.arn as the key
161
+ val diff = aggregatedCustomizations.filterNot { customization -> previousCustomizationsShapshot.contains( " ${customization.profile?.arn} :: ${customization.arn} " ) }.toSet()
131
162
// 3 if passive,
132
163
// (1) update allowlisting
133
164
// (2) prompt "You have New Customizations" toast notification (only show once)
134
165
//
135
166
// if not passive,
136
167
// (1) update the customization list snapshot (seen by users last time) if it will be displayed
137
168
if (passive) {
138
- connectionIdToIsAllowlisted[it.id] = listAvailableCustomizationsResult != null
169
+ connectionIdToIsAllowlisted[it.id] = aggregatedCustomizations.isNotEmpty()
139
170
if (diff?.isNotEmpty() == true && ! hasShownNewCustomizationNotification.getAndSet(true )) {
140
171
notifyNewCustomization(project)
141
172
}
142
173
} else {
143
- listAvailableCustomizationsResult? .let { customizations ->
144
- connectionToCustomizationsShownLastTime[it.id] = customizations.map { customization -> customization.arn }.toMutableList()
174
+ aggregatedCustomizations .let { customizations ->
175
+ connectionToCustomizationsShownLastTime[it.id] = customizations.map { customization -> " ${ customization.profile?. arn} :: ${customization.arn} " }.toMutableList()
145
176
}
146
177
}
147
178
148
179
// 4. invalidate selected customization if
149
180
// (1) the API call failed
150
181
// (2) the selected customization is not in the resultset of API call
151
182
activeCustomization(project)?.let { activeCustom ->
152
- if (listAvailableCustomizationsResult == null ) {
183
+ if (aggregatedCustomizations.isEmpty() ) {
153
184
invalidateSelectedAndNotify(project)
154
- } else if (! listAvailableCustomizationsResult.any { latestCustom -> latestCustom.arn == activeCustom.arn }) {
185
+ } else if (! aggregatedCustomizations.any { latestCustom -> " ${latestCustom.profile?.arn} ::${latestCustom.arn} " == " ${activeCustom.profile?.arn} ::${activeCustom.arn} " }
186
+ || activeCustom.profile!= QRegionProfileManager .getInstance().activeProfile(project)){
155
187
invalidateSelectedAndNotify(project)
156
188
}
157
189
}
158
190
159
191
// 5. transform result to UI items and return
160
- val customizationUiItems = if (diff != null ) {
161
- listAvailableCustomizationsResult.let { customizations ->
162
- val nameToCount = customizations.groupingBy { customization -> customization.name }.eachCount()
163
-
164
- customizations.map { customization ->
165
- CustomizationUiItem (
166
- customization,
167
- isNew = diff.contains(customization),
168
- shouldPrefixAccountId = (nameToCount[customization.name] ? : 0 ) > 1
169
- )
170
- }
192
+ val customizationUiItems = aggregatedCustomizations.let { customizations ->
193
+ val nameToCount = customizations.groupingBy { customization -> customization.name }.eachCount()
194
+
195
+ customizations.map { customization ->
196
+ CustomizationUiItem (
197
+ customization,
198
+ isNew = diff.contains(customization),
199
+ shouldPrefixAccountId = (nameToCount[customization.name] ? : 0 ) > 1
200
+ )
171
201
}
172
- } else {
173
- null
174
202
}
175
203
connectionToCustomizationUiItems[it.id] = customizationUiItems
176
204
0 commit comments