Skip to content

Commit 38b3ec8

Browse files
Merge master into feature/emr
2 parents f527b79 + 9d7f445 commit 38b3ec8

File tree

9 files changed

+90
-249
lines changed

9 files changed

+90
-249
lines changed

packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('RegionProfileManager', function () {
6565
const mockClient = {
6666
listAvailableProfiles: listProfilesStub,
6767
}
68-
const createClientStub = sinon.stub(sut, '_createQClient').resolves(mockClient)
68+
const createClientStub = sinon.stub(sut, 'createQClient').resolves(mockClient)
6969

7070
const r = await sut.listRegionProfile()
7171

@@ -234,65 +234,13 @@ describe('RegionProfileManager', function () {
234234
})
235235

236236
describe('createQClient', function () {
237-
it(`should configure the endpoint and region from a profile`, async function () {
238-
await setupConnection('idc')
239-
240-
const iadClient = await sut.createQClient({
241-
name: 'foo',
242-
region: 'us-east-1',
243-
arn: 'arn',
244-
description: 'description',
245-
})
246-
247-
assert.deepStrictEqual(iadClient.config.region, 'us-east-1')
248-
assert.deepStrictEqual(iadClient.endpoint.href, 'https://q.us-east-1.amazonaws.com/')
249-
250-
const fraClient = await sut.createQClient({
251-
name: 'bar',
252-
region: 'eu-central-1',
253-
arn: 'arn',
254-
description: 'description',
255-
})
256-
257-
assert.deepStrictEqual(fraClient.config.region, 'eu-central-1')
258-
assert.deepStrictEqual(fraClient.endpoint.href, 'https://q.eu-central-1.amazonaws.com/')
259-
})
260-
261-
it(`should throw if the region is not supported or recognizable by Q`, async function () {
262-
await setupConnection('idc')
263-
264-
await assert.rejects(
265-
async () => {
266-
await sut.createQClient({
267-
name: 'foo',
268-
region: 'ap-east-1',
269-
arn: 'arn',
270-
description: 'description',
271-
})
272-
},
273-
{ message: /trying to initiatize Q client with unrecognizable region/ }
274-
)
275-
276-
await assert.rejects(
277-
async () => {
278-
await sut.createQClient({
279-
name: 'foo',
280-
region: 'unknown-somewhere',
281-
arn: 'arn',
282-
description: 'description',
283-
})
284-
},
285-
{ message: /trying to initiatize Q client with unrecognizable region/ }
286-
)
287-
})
288-
289237
it(`should configure the endpoint and region correspondingly`, async function () {
290238
await setupConnection('idc')
291239
await sut.switchRegionProfile(profileFoo, 'user')
292240
assert.deepStrictEqual(sut.activeRegionProfile, profileFoo)
293241
const conn = authUtil.conn as SsoConnection
294242

295-
const client = await sut._createQClient('eu-central-1', 'https://amazon.com/', conn)
243+
const client = await sut.createQClient('eu-central-1', 'https://amazon.com/', conn)
296244

297245
assert.deepStrictEqual(client.config.region, 'eu-central-1')
298246
assert.deepStrictEqual(client.endpoint.href, 'https://amazon.com/')

packages/core/src/codewhisperer/activation.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,12 @@ import { AuthUtil } from './util/authUtil'
7272
import { ImportAdderProvider } from './service/importAdderProvider'
7373
import { TelemetryHelper } from './util/telemetryHelper'
7474
import { openUrl } from '../shared/utilities/vsCodeUtils'
75-
import { notifyNewCustomizations, onProfileChangedListener } from './util/customizationUtil'
75+
import {
76+
getAvailableCustomizationsList,
77+
getSelectedCustomization,
78+
notifyNewCustomizations,
79+
switchToBaseCustomizationAndNotify,
80+
} from './util/customizationUtil'
7681
import { CodeWhispererCommandBackend, CodeWhispererCommandDeclarations } from './commands/gettingStartedPageCommands'
7782
import { SecurityIssueHoverProvider } from './service/securityIssueHoverProvider'
7883
import { SecurityIssueCodeActionProvider } from './service/securityIssueCodeActionProvider'
@@ -338,7 +343,26 @@ export async function activate(context: ExtContext): Promise<void> {
338343
SecurityIssueCodeActionProvider.instance
339344
),
340345
vscode.commands.registerCommand('aws.amazonq.openEditorAtRange', openEditorAtRange),
341-
auth.regionProfileManager.onDidChangeRegionProfile(onProfileChangedListener)
346+
auth.regionProfileManager.onDidChangeRegionProfile(() => {
347+
// Validate user still has access to the selected customization.
348+
const selectedCustomization = getSelectedCustomization()
349+
// No need to validate base customization which has empty arn.
350+
if (selectedCustomization.arn.length > 0) {
351+
getAvailableCustomizationsList()
352+
.then(async (customizations) => {
353+
const r = customizations.find((it) => it.arn === selectedCustomization.arn)
354+
if (!r) {
355+
await switchToBaseCustomizationAndNotify()
356+
}
357+
})
358+
.catch((e) => {
359+
getLogger().error(
360+
`encounter error while validating selected customization on profile change: %s`,
361+
(e as Error).message
362+
)
363+
})
364+
}
365+
})
342366
)
343367

344368
// run the auth startup code with context for telemetry

packages/core/src/codewhisperer/client/codewhisperer.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import { AWSError, Credentials, Service } from 'aws-sdk'
77
import globals from '../../shared/extensionGlobals'
88
import * as CodeWhispererClient from './codewhispererclient'
99
import * as CodeWhispererUserClient from './codewhispereruserclient'
10-
import { SendTelemetryEventRequest } from './codewhispereruserclient'
10+
import { ListAvailableCustomizationsResponse, SendTelemetryEventRequest } from './codewhispereruserclient'
1111
import { ServiceOptions } from '../../shared/awsClientBuilder'
1212
import { hasVendedIamCredentials } from '../../auth/auth'
1313
import { CodeWhispererSettings } from '../util/codewhispererSettings'
1414
import { PromiseResult } from 'aws-sdk/lib/request'
1515
import { AuthUtil } from '../util/authUtil'
1616
import { isSsoConnection } from '../../auth/connection'
17+
import { pageableToCollection } from '../../shared/utilities/collectionUtils'
1718
import apiConfig = require('./service-2.json')
1819
import userApiConfig = require('./user-service-2.json')
1920
import { session } from '../util/codeWhispererSession'
2021
import { getLogger } from '../../shared/logger/logger'
22+
import { indent } from '../../shared/utilities/textUtilities'
2123
import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util'
2224
import { extensionVersion, getServiceEnvVarConfig } from '../../shared/vscode/env'
2325
import { DevSettings } from '../../shared/settings'
@@ -217,6 +219,28 @@ export class DefaultCodeWhispererClient {
217219
.promise()
218220
}
219221

222+
public async listAvailableCustomizations(): Promise<ListAvailableCustomizationsResponse[]> {
223+
const client = await this.createUserSdkClient()
224+
const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile
225+
const requester = async (request: CodeWhispererUserClient.ListAvailableCustomizationsRequest) =>
226+
client.listAvailableCustomizations(request).promise()
227+
return pageableToCollection(requester, { profileArn: profile?.arn }, 'nextToken')
228+
.promise()
229+
.then((resps) => {
230+
let logStr = 'amazonq: listAvailableCustomizations API request:'
231+
for (const resp of resps) {
232+
const requestId = resp.$response.requestId
233+
logStr += `\n${indent('RequestID: ', 4)}${requestId},\n${indent('Customizations:', 4)}`
234+
for (const [index, c] of resp.customizations.entries()) {
235+
const entry = `${index.toString().padStart(2, '0')}: ${c.name?.trim()}`
236+
logStr += `\n${indent(entry, 8)}`
237+
}
238+
}
239+
getLogger().debug(logStr)
240+
return resps
241+
})
242+
}
243+
220244
public async sendTelemetryEvent(request: SendTelemetryEventRequest) {
221245
const requestWithCommonFields: SendTelemetryEventRequest = {
222246
...request,

packages/core/src/codewhisperer/index.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,7 @@ export * as diagnosticsProvider from './service/diagnosticsProvider'
9999
export * from './ui/codeWhispererNodes'
100100
export { SecurityScanError, SecurityScanTimedOutError } from '../codewhisperer/models/errors'
101101
export * as CodeWhispererConstants from '../codewhisperer/models/constants'
102-
export {
103-
getSelectedCustomization,
104-
setSelectedCustomization,
105-
baseCustomization,
106-
onProfileChangedListener,
107-
CustomizationProvider,
108-
} from './util/customizationUtil'
102+
export { getSelectedCustomization, setSelectedCustomization, baseCustomization } from './util/customizationUtil'
109103
export { Container } from './service/serviceContainer'
110104
export * from './util/gitUtil'
111105
export * from './ui/prompters'

packages/core/src/codewhisperer/region/regionProfileManager.ts

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,12 @@ const endpoints = createConstantMap({
4747
* 'update' -> plugin auto select the profile on users' behalf as there is only 1 profile
4848
* 'reload' -> on plugin restart, plugin will try to reload previous selected profile
4949
*/
50-
export type ProfileSwitchIntent = 'user' | 'auth' | 'update' | 'reload' | 'customization'
51-
52-
export type ProfileChangedEvent = {
53-
profile: RegionProfile | undefined
54-
intent: ProfileSwitchIntent
55-
}
50+
export type ProfileSwitchIntent = 'user' | 'auth' | 'update' | 'reload'
5651

5752
export class RegionProfileManager {
5853
private static logger = getLogger()
5954
private _activeRegionProfile: RegionProfile | undefined
60-
private _onDidChangeRegionProfile = new vscode.EventEmitter<ProfileChangedEvent>()
55+
private _onDidChangeRegionProfile = new vscode.EventEmitter<RegionProfile | undefined>()
6156
public readonly onDidChangeRegionProfile = this._onDidChangeRegionProfile.event
6257

6358
// Store the last API results (for UI propuse) so we don't need to call service again if doesn't require "latest" result
@@ -117,7 +112,7 @@ export class RegionProfileManager {
117112
}
118113
const availableProfiles: RegionProfile[] = []
119114
for (const [region, endpoint] of endpoints.entries()) {
120-
const client = await this._createQClient(region, endpoint, conn as SsoConnection)
115+
const client = await this.createQClient(region, endpoint, conn as SsoConnection)
121116
const requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) =>
122117
client.listAvailableProfiles(request).promise()
123118
const request: CodeWhispererUserClient.ListAvailableProfilesRequest = {}
@@ -167,7 +162,7 @@ export class RegionProfileManager {
167162
const ssoConn = this.connectionProvider() as SsoConnection
168163

169164
// only prompt to users when users switch from A profile to B profile
170-
if (source !== 'customization' && this.activeRegionProfile !== undefined && regionProfile !== undefined) {
165+
if (this.activeRegionProfile !== undefined && regionProfile !== undefined) {
171166
const response = await showConfirmationMessage({
172167
prompt: localize(
173168
'AWS.amazonq.profile.confirmation',
@@ -209,16 +204,13 @@ export class RegionProfileManager {
209204
})
210205
}
211206

212-
await this._switchRegionProfile(regionProfile, source)
207+
await this._switchRegionProfile(regionProfile)
213208
}
214209

215-
private async _switchRegionProfile(regionProfile: RegionProfile | undefined, source: ProfileSwitchIntent) {
210+
private async _switchRegionProfile(regionProfile: RegionProfile | undefined) {
216211
this._activeRegionProfile = regionProfile
217212

218-
this._onDidChangeRegionProfile.fire({
219-
profile: regionProfile,
220-
intent: source,
221-
})
213+
this._onDidChangeRegionProfile.fire(regionProfile)
222214
// dont show if it's a default (fallback)
223215
if (regionProfile && this.profiles.length > 1) {
224216
void vscode.window.showInformationMessage(`You are using the ${regionProfile.name} profile for Q.`).then()
@@ -351,21 +343,7 @@ export class RegionProfileManager {
351343
}
352344
}
353345

354-
// TODO: Should maintain sdk client in a better way
355-
async createQClient(profile: RegionProfile): Promise<CodeWhispererUserClient> {
356-
const conn = this.connectionProvider()
357-
if (conn === undefined || !isSsoConnection(conn)) {
358-
throw new Error('No valid SSO connection')
359-
}
360-
const endpoint = endpoints.get(profile.region)
361-
if (!endpoint) {
362-
throw new Error(`trying to initiatize Q client with unrecognizable region ${profile.region}`)
363-
}
364-
return this._createQClient(profile.region, endpoint, conn)
365-
}
366-
367-
// Visible for testing only, do not use this directly, please use createQClient(profile)
368-
async _createQClient(region: string, endpoint: string, conn: SsoConnection): Promise<CodeWhispererUserClient> {
346+
async createQClient(region: string, endpoint: string, conn: SsoConnection): Promise<CodeWhispererUserClient> {
369347
const token = (await conn.getToken()).accessToken
370348
const serviceOption: ServiceOptions = {
371349
apiConfig: userApiConfig,

0 commit comments

Comments
 (0)