Skip to content

Commit

Permalink
feat(amazonq): reword settings + import old CW settings (aws#4783)
Browse files Browse the repository at this point in the history
* feat(amazonq): reword settings + import old CW settings

Problem: Some of the wording of the settings was off. Also, changing the name of the settings = a brand new setting in VS Code.

Solution: In order to preserve trust, we will try to import old settings users may have had. Extensions leave settings behind when they are uinstalled, so we can detect the old CW settings and import them into the new ones named after Amazon Q. This creates a seamless setting experience for migrating users.

* add .

* disable lint for long line

* update config change events

* use and update already existing migration function
  • Loading branch information
hayemaxi authored Apr 23, 2024
1 parent 4b56115 commit af6a62c
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 48 deletions.
15 changes: 5 additions & 10 deletions packages/amazonq/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,26 +118,21 @@
},
"additionalProperties": false
},
"aws.amazonQ.includeSuggestionsWithCodeReferences": {
"aws.amazonQ.showInlineCodeSuggestionsWithCodeReferences": {
"type": "boolean",
"markdownDescription": "%AWS.configuration.description.codewhisperer%",
"markdownDescription": "%AWS.configuration.description.amazonq%",
"default": true
},
"aws.amazonQ.importRecommendation": {
"type": "boolean",
"description": "%AWS.configuration.description.codewhisperer.importRecommendation%",
"description": "%AWS.configuration.description.amazonq.importRecommendation%",
"default": true
},
"aws.amazonQ.shareCodeWhispererContentWithAWS": {
"aws.amazonQ.shareContentWithAWS": {
"type": "boolean",
"markdownDescription": "%AWS.configuration.description.codewhisperer.shareCodeWhispererContentWithAWS%",
"markdownDescription": "%AWS.configuration.description.amazonq.shareContentWithAWS%",
"default": true,
"scope": "application"
},
"aws.amazonQ.javaCompilationOutput": {
"type": "string",
"default": "",
"description": "Provide the ABSOLUTE path which is used to store java project compilation results."
}
}
},
Expand Down
15 changes: 5 additions & 10 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,26 +261,21 @@
},
"additionalProperties": false
},
"aws.amazonQ.includeSuggestionsWithCodeReferences": {
"aws.amazonQ.showInlineCodeSuggestionsWithCodeReferences": {
"type": "boolean",
"markdownDescription": "%AWS.configuration.description.codewhisperer%",
"markdownDescription": "%AWS.configuration.description.amazonq%",
"default": true
},
"aws.amazonQ.importRecommendation": {
"type": "boolean",
"description": "%AWS.configuration.description.codewhisperer.importRecommendation%",
"description": "%AWS.configuration.description.amazonq.importRecommendation%",
"default": true
},
"aws.amazonQ.shareCodeWhispererContentWithAWS": {
"aws.amazonQ.shareContentWithAWS": {
"type": "boolean",
"markdownDescription": "%AWS.configuration.description.codewhisperer.shareCodeWhispererContentWithAWS%",
"markdownDescription": "%AWS.configuration.description.amazonq.shareContentWithAWS%",
"default": true,
"scope": "application"
},
"aws.amazonQ.javaCompilationOutput": {
"type": "string",
"default": "",
"description": "Provide the ABSOLUTE path which is used to store java project compilation results."
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@
"AWS.configuration.description.awssam.debug.snippets.api.description": "A new configuration for invoking an AWS Lambda in a CloudFormation template, simulating API Gateway",
"AWS.configuration.description.awssam.debug.snippets.api.label.cn": "Amazon SAM: API Gateway lambda invoke",
"AWS.configuration.description.awssam.debug.snippets.api.description.cn": "A new configuration for invoking an Amazon Lambda in a CloudFormation template, simulating API Gateway",
"AWS.configuration.description.codewhisperer": "Amazon Q creates a code reference when you insert a code suggestion from Amazon Q that is similar to training data. When unchecked, Amazon Q will not show code suggestions that have code references. If you authenticate through IAM Identity Center, this setting is controlled by your Amazon Q administrator. [Learn More](https://docs.aws.amazon.com/codewhisperer/latest/userguide/code-reference.html#opt-out-code-reference)",
"AWS.configuration.description.codewhisperer.shareCodeWhispererContentWithAWS": "When checked, your content processed by Amazon Q may be used for service improvement (except for content processed by the Amazon Q Enterprise service tier). Unchecking this box will cause AWS to delete any of your content used for that purpose. The information used to provide the Amazon Q service to you will not be affected. See the [Service Terms](https://aws.amazon.com/service-terms) for more detail.",
"AWS.configuration.description.codewhisperer.importRecommendation": "Amazon Q will add import statements with code suggestions when necessary",
"AWS.configuration.description.amazonq": "Amazon Q creates a code reference when you insert a code suggestion from Amazon Q that is similar to training data. When unchecked, Amazon Q will not show code suggestions that have code references. If you authenticate through IAM Identity Center, this setting is controlled by your Amazon Q administrator. [Learn More](https://docs.aws.amazon.com/codewhisperer/latest/userguide/code-reference.html#opt-out-code-reference)",
"AWS.configuration.description.amazonq.shareContentWithAWS": "When checked, your content processed by Amazon Q may be used for service improvement (except for content processed by the Amazon Q Enterprise service tier). Unchecking this box will cause AWS to delete any of your content used for that purpose. The information used to provide the Amazon Q service to you will not be affected. See the [Service Terms](https://aws.amazon.com/service-terms) for more detail.",
"AWS.configuration.description.amazonq.importRecommendation": "Amazon Q will add import statements with inline code suggestions when necessary.",
"AWS.command.apig.copyUrl": "Copy URL",
"AWS.command.apig.invokeRemoteRestApi": "Invoke on AWS",
"AWS.command.apig.invokeRemoteRestApi.cn": "Invoke on Amazon",
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/codewhisperer/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ import { switchToAmazonQCommand, switchToAmazonQSignInCommand } from '../amazonq

export async function activate(context: ExtContext): Promise<void> {
const codewhispererSettings = CodeWhispererSettings.instance

// Import old CodeWhisperer settings into Amazon Q
await CodeWhispererSettings.instance.importSettings()

// initialize AuthUtil earlier to make sure it can listen to connection change events.
const auth = AuthUtil.instance
auth.initCodeWhispererHooks()
Expand Down Expand Up @@ -117,7 +121,9 @@ export async function activate(context: ExtContext): Promise<void> {
EditorContext.updateTabSize(getTabSizeSetting())
}

if (configurationChangeEvent.affectsConfiguration('aws.amazonQ.includeSuggestionsWithCodeReferences')) {
if (
configurationChangeEvent.affectsConfiguration('aws.amazonQ.showInlineCodeSuggestionsWithCodeReferences')
) {
ReferenceLogViewProvider.instance.update()
if (auth.isEnterpriseSsoInUse()) {
await vscode.window
Expand All @@ -133,7 +139,7 @@ export async function activate(context: ExtContext): Promise<void> {
}
}

if (configurationChangeEvent.affectsConfiguration('aws.amazonQ.shareCodeWhispererContentWithAWS')) {
if (configurationChangeEvent.affectsConfiguration('aws.amazonQ.shareContentWithAWS')) {
if (auth.isEnterpriseSsoInUse()) {
await vscode.window
.showInformationMessage(
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/codewhisperer/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ export const validStatesForCheckingDownloadUrl = [
'REJECTED',
]

export const codewhispererSettingsImportedKey = 'aws.amazonq.codewhispererSettingsImported'
export const amazonQDismissedKey = 'aws.toolkit.amazonq.dismissed'
export const amazonQInstallDismissedKey = 'aws.toolkit.amazonqInstall.dismissed'

Expand Down
34 changes: 29 additions & 5 deletions packages/core/src/codewhisperer/util/codewhispererSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,47 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { fromExtensionManifest } from '../../shared/settings'
import { fromExtensionManifest, migrateSetting } from '../../shared/settings'
import globals from '../../shared/extensionGlobals'
import { codewhispererSettingsImportedKey } from '../models/constants'

const description = {
includeSuggestionsWithCodeReferences: Boolean,
showInlineCodeSuggestionsWithCodeReferences: Boolean, // eslint-disable-line id-length
importRecommendation: Boolean,
shareCodeWhispererContentWithAWS: Boolean,
shareContentWithAWS: Boolean,
}

export class CodeWhispererSettings extends fromExtensionManifest('aws.amazonQ', description) {
public async importSettings() {
if (globals.context.globalState.get<boolean>(codewhispererSettingsImportedKey)) {
return
}

await migrateSetting(
{ key: 'aws.codeWhisperer.includeSuggestionsWithCodeReferences', type: Boolean },
{ key: 'aws.amazonQ.showInlineCodeSuggestionsWithCodeReferences' }
)
await migrateSetting(
{ key: 'aws.codeWhisperer.importRecommendation', type: Boolean },
{ key: 'aws.amazonQ.importRecommendation' }
)
await migrateSetting(
{ key: 'aws.codeWhisperer.shareCodeWhispererContentWithAWS', type: Boolean },
{ key: 'aws.amazonQ.shareContentWithAWS' }
)

await globals.context.globalState.update(codewhispererSettingsImportedKey, true)
}

public isSuggestionsWithCodeReferencesEnabled(): boolean {
return this.get(`includeSuggestionsWithCodeReferences`, false)
return this.get(`showInlineCodeSuggestionsWithCodeReferences`, false)
}
public isImportRecommendationEnabled(): boolean {
return this.get(`importRecommendation`, false)
}

public isOptoutEnabled(): boolean {
const value = this.get('shareCodeWhispererContentWithAWS', true)
const value = this.get('shareContentWithAWS', true)
return !value
}

Expand Down
40 changes: 22 additions & 18 deletions packages/core/src/shared/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,34 +860,38 @@ export class DevSettings extends Settings.define('aws.dev', devSettings) {
* Simple utility function to 'migrate' a setting from one key to another.
*
* Currently only used for simple migrations where we are not concerned about maintaining the
* legacy definition. Only migrates to global settings.
* legacy definition.
*/
export async function migrateSetting<T, U = T>(
from: { key: string; type: TypeConstructor<T> },
to: { key: string; transform?: (value: T) => U }
to: { key: keyof SettingsProps; transform?: (value: T) => U }
) {
// TODO(sijaden): we should handle other targets besides 'global'
const config = vscode.workspace.getConfiguration()
const hasLatest = config.inspect(to.key)?.globalValue !== undefined
const logPrefix = `Settings migration ("${from.key}" -> "${to.key}")`

if (hasLatest || !config.has(from.key)) {
return true
}

try {
const oldVal = cast(config.get(from.key), from.type)
const newVal = to.transform?.(oldVal) ?? oldVal
const migrateForScope = async (scope: vscode.ConfigurationTarget) => {
const valueProp = scope === vscode.ConfigurationTarget.Global ? 'globalValue' : 'workspaceValue'
const hasLatest = config.inspect(to.key)![valueProp] !== undefined
const logPrefix = `Settings migration ("${from.key}" -> "${to.key}"), (scope: ${valueProp})`

await config.update(to.key, newVal, vscode.ConfigurationTarget.Global)
getLogger().debug(`${logPrefix}: succeeded`)
const oldSettingProps = config.inspect(from.key)
if (hasLatest || !oldSettingProps || oldSettingProps[valueProp] === undefined) {
getLogger().debug(`${logPrefix}: skipping, no migration needed`)
return
}

return true
} catch (error) {
getLogger().verbose(`${logPrefix}: failed: %s`, error)
try {
const oldVal = cast(config.get(from.key), from.type)
const newVal = to.transform?.(oldVal) ?? oldVal

return false
await config.update(to.key, newVal, scope)
getLogger().info(`${logPrefix}: succeeded`)
} catch (error) {
getLogger().verbose(`${logPrefix}: failed: %s`, error)
}
}

await migrateForScope(vscode.ConfigurationTarget.Workspace)
await migrateForScope(vscode.ConfigurationTarget.Global)
}

/**
Expand Down

0 comments on commit af6a62c

Please sign in to comment.