Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Bug Fix",
"description": "User-selected customizations are sometimes not being persisted."
}
3 changes: 2 additions & 1 deletion packages/core/src/codewhisperer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type {
SendTelemetryEventResponse,
TelemetryEvent,
InlineChatEvent,
Customization,
} from './client/codewhispereruserclient.d.ts'
export type { default as CodeWhispererUserClient } from './client/codewhispereruserclient.d.ts'
export { SecurityPanelViewProvider } from './views/securityPanelViewProvider'
Expand Down Expand Up @@ -98,6 +99,6 @@ export * as diagnosticsProvider from './service/diagnosticsProvider'
export * from './ui/codeWhispererNodes'
export { SecurityScanError } from '../codewhisperer/models/errors'
export * as CodeWhispererConstants from '../codewhisperer/models/constants'
export { getSelectedCustomization } from './util/customizationUtil'
export { getSelectedCustomization, setSelectedCustomization, baseCustomization } from './util/customizationUtil'
export { Container } from './service/serviceContainer'
export * from './util/gitUtil'
35 changes: 21 additions & 14 deletions packages/core/src/codewhisperer/util/customizationUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export const baseCustomization = {
),
}

/**
* Gets the customization that should be used for user requests. If a user has manually selected
* a customization, always respect that choice. If not, check if the user is part of an AB
* group assigned a specific customization. If so, use that customization. If not, use the
* base customization.
*/
export const getSelectedCustomization = (): Customization => {
if (
!AuthUtil.instance.isCustomizationFeatureEnabled ||
Expand All @@ -105,21 +111,22 @@ export const getSelectedCustomization = (): Customization => {
Object,
{}
)
const result = selectedCustomizationArr[AuthUtil.instance.conn.label] || baseCustomization

// A/B case
const customizationFeature = FeatureConfigProvider.getFeature(Features.customizationArnOverride)
const arnOverride = customizationFeature?.value.stringValue
const customizationOverrideName = customizationFeature?.variation
if (arnOverride === undefined || arnOverride === '') {
return result
const selectedCustomization = selectedCustomizationArr[AuthUtil.instance.conn.label]

if (selectedCustomization && selectedCustomization.name !== baseCustomization.name) {
return selectedCustomization
} else {
// A trick to prioritize arn from A/B over user's currently selected(for request and telemetry)
// but still shows customization info of user's currently selected.
return {
arn: arnOverride,
name: customizationOverrideName,
description: result.description,
const customizationFeature = FeatureConfigProvider.getFeature(Features.customizationArnOverride)
const arnOverride = customizationFeature?.value.stringValue
const customizationOverrideName = customizationFeature?.variation
if (arnOverride === undefined) {
return baseCustomization
} else {
return {
arn: arnOverride,
name: customizationOverrideName,
description: baseCustomization.description,
}
}
}
}
Expand Down
94 changes: 94 additions & 0 deletions packages/core/src/test/amazonq/customizationUtil.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as sinon from 'sinon'
import assert from 'assert'
import { tryRegister } from '../testUtil'
import {
amazonQScopes,
AuthUtil,
baseCustomization,
Customization,
FeatureConfigProvider,
getSelectedCustomization,
refreshStatusBar,
setSelectedCustomization,
} from '../../codewhisperer'
import { FeatureContext, globals } from '../../shared'
import { resetCodeWhispererGlobalVariables } from '../codewhisperer/testUtil'
import { createSsoProfile, createTestAuth } from '../credentials/testUtil'
import { SsoConnection } from '../../auth'

const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start'

describe('CodeWhisperer-customizationUtils', function () {
let auth: ReturnType<typeof createTestAuth>
let ssoConn: SsoConnection
let featureCustomization: FeatureContext

before(async function () {
createTestAuth(globals.globalState)
tryRegister(refreshStatusBar)
})

beforeEach(async function () {
auth = createTestAuth(globals.globalState)
ssoConn = await auth.createInvalidSsoConnection(
createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes })
)
featureCustomization = {
name: 'featureCustomizationName',
value: {
stringValue: 'featureCustomizationArn',
},
variation: 'featureCustomizationName',
}
sinon.stub(FeatureConfigProvider, 'getFeature').returns(featureCustomization)

sinon.stub(AuthUtil.instance, 'isConnectionExpired').returns(false)
sinon.stub(AuthUtil.instance, 'isConnected').returns(true)
sinon.stub(AuthUtil.instance, 'isCustomizationFeatureEnabled').value(true)
sinon.stub(AuthUtil.instance, 'conn').value(ssoConn)

await resetCodeWhispererGlobalVariables()
})

afterEach(function () {
sinon.restore()
})

it('Returns baseCustomization when not SSO', async function () {
sinon.stub(AuthUtil.instance, 'isValidEnterpriseSsoInUse').returns(false)
const customization = getSelectedCustomization()

assert.strictEqual(customization.name, baseCustomization.name)
})

it('Returns selectedCustomization when customization manually selected', async function () {
sinon.stub(AuthUtil.instance, 'isValidEnterpriseSsoInUse').returns(true)

const selectedCustomization: Customization = {
arn: 'selectedCustomizationArn',
name: 'selectedCustomizationName',
description: 'selectedCustomizationDescription',
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel it's better to have sinon.stub(FeatureConfigProvider, 'getFeature').returns(featureCustomization) here as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to simulate the real case when there is an override and user pre-selected customization

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to beforeEach so it's present for all tests

await setSelectedCustomization(selectedCustomization)

const actualCustomization = getSelectedCustomization()

assert.strictEqual(actualCustomization.name, selectedCustomization.name)
})

it('Returns AB customization', async function () {
sinon.stub(AuthUtil.instance, 'isValidEnterpriseSsoInUse').returns(true)

await setSelectedCustomization(baseCustomization)

const returnedCustomization = getSelectedCustomization()

assert.strictEqual(returnedCustomization.name, featureCustomization.name)
})
})
Loading