Skip to content

Commit 234639a

Browse files
authored
Merge branch 'master' into selective_transform
2 parents 75d16a7 + 7389d24 commit 234639a

File tree

43 files changed

+669
-238
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+669
-238
lines changed

docs/telemetry.md

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,23 @@ Finally, if `setupStep2()` was the thing that failed we would see a metric like:
142142

143143
## Adding a "Stack Trace" to your metric
144144

145-
### Problem
145+
When errors are thrown we do not attach the stack trace in telemetry. We only know about the error itself, but
146+
not the path it took to get there. We sometimes need this stack trace to debug, and only have telemetry to get insight on what happened since we do not have access to logs.
147+
148+
### Scenario
146149

147150
Common example: _"I have a function, `thisFailsSometimes()` that is called in multiple places. The function sometimes fails, I know from telemetry, but I do not know if it is failing when it is a specific caller. If I knew the call stack/trace that it took to call my function that would help me debug."_
148151

149152
```typescript
150-
function outerA() {
153+
function runsSuccessfully() {
151154
thisFailsSometimes(1) // this succeeds
152155
}
153156

154-
function outerB() {
157+
function thisThrows() {
155158
thisFailsSometimes(0) // this fails
156159
}
157160

158-
function thisFailsSometimes(num: number) {
161+
function failsDependingOnInput(num: number) {
159162
return telemetry.my_Metric.run(() => {
160163
if (number === 0) {
161164
throw Error('Cannot be 0')
@@ -167,31 +170,61 @@ function thisFailsSometimes(num: number) {
167170

168171
### Solution
169172

170-
Add a value to `function` in the options of a `run()`. This will result in a stack of functions identifiers that were previously called
171-
before `thisFailsSometimes()` was run. You can then retrieve the stack in the `run()` of your final metric using `getFunctionStack()`.
173+
On class methods, use the `@withTelemetryContext()` decorator to add context to the execution. Depending on the args set, it provides features like emitting the result, or adding it's context to errors.
174+
175+
> NOTE: Decorators are currently only supported for methods and not functions
176+
177+
```typescript
178+
class MyClass {
179+
@withTelemetryContext({ name: 'runsSuccessfully', class: 'MyClass' })
180+
public runsSuccessfully() {
181+
failsDependingOnInput(1)
182+
}
183+
184+
@withTelemetryContext({ name: 'thisThrows', class: 'MyClass', errorCtx: true })
185+
public thisThrows() {
186+
failsDependingOnInput(0)
187+
}
188+
189+
@withTelemetryContext({ name: 'failsDependingOnInput' class: 'MyClass', emit: true, errorCtx: true})
190+
private failsDependingOnInput(num: number) {
191+
if (number === 0) {
192+
throw Error('Cannot be 0')
193+
}
194+
...
195+
}
196+
}
197+
198+
// Results in a metric: { source: 'MyClass#thisThrows,failsDependingOnInput', result: 'Failed' }
199+
// Results in an error that has context about the methods that lead up to it.
200+
new MyClass().thisThrows()
201+
```
202+
203+
Separately if you must use a function, add a value to `function` in the options of a `run()`. This will result in a stack of functions identifiers that were previously called
204+
before `failsDependingOnInput()` was run. You can then retrieve the stack in the `run()` of your final metric using `getFunctionStack()`.
172205

173206
```typescript
174-
function outerA() {
175-
telemetry.my_Metric.run(() => thisFailsSometimes(1), { functionId: { name: 'outerA' }})
207+
function runsSuccessfully() {
208+
telemetry.my_Metric.run(() => failsDependingOnInput(1), { functionId: { name: 'runsSuccessfully' }})
176209
}
177210

178-
function outerB() {
179-
telemetry.my_Metric.run(() => thisFailsSometimes(0), { functionId: { source: 'outerB' }})
211+
function thisThrows() {
212+
telemetry.my_Metric.run(() => failsDependingOnInput(0), { functionId: { source: 'thisThrows' }})
180213
}
181214

182-
function thisFailsSometimes(num: number) {
215+
function failsDependingOnInput(num: number) {
183216
return telemetry.my_Metric.run(() => {
184217
telemetry.record({ theCallStack: asStringifiedStack(telemetry.getFunctionStack())})
185218
if (number === 0) {
186219
throw Error('Cannot be 0')
187220
}
188221
...
189-
}, { functionId: { name: 'thisFailsSometimes' }})
222+
}, { functionId: { name: 'failsDependingOnInput' }})
190223
}
191224

192-
// Results in a metric: { theCallStack: 'outerB:thisFailsSometimes', result: 'Failed' }
193-
// { theCallStack: 'outerB:thisFailsSometimes' } implies 'outerB' was run first, then 'thisFailsSometimes'. See docstrings for more info.
194-
outerB()
225+
// Results in a metric: { theCallStack: 'thisThrows:failsDependingOnInput', result: 'Failed' }
226+
// { theCallStack: 'thisThrows:failsDependingOnInput' } implies 'thisThrows' was run first, then 'failsDependingOnInput'. See docstrings for more info.
227+
thisThrows()
195228
```
196229

197230
### Important Notes
@@ -216,25 +249,6 @@ outerB()
216249
c() // result: 'a:c', note that 'b' is not included
217250
```
218251

219-
- If you are using `run()` with a class method, you can also add the class to the entry for more context
220-
221-
```typescript
222-
class A {
223-
a() {
224-
return telemetry.my_Metric.run(() => this.b(), { functionId: { name: 'a', class: 'A' } })
225-
}
226-
227-
b() {
228-
return telemetry.my_Metric.run(() => asStringifiedStack(telemetry.getFunctionStack()), {
229-
functionId: { name: 'b', class: 'A' },
230-
})
231-
}
232-
}
233-
234-
const inst = new A()
235-
inst.a() // 'A#a,b'
236-
```
237-
238252
- If you do not want your `run()` to emit telemetry, set `emit: false` in the options
239253

240254
```typescript
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Amazon Q /dev: update diff window behavior after a change is accepted"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Feature(Amazon Q Code Transformation): support conversions of embedded SQL from Oracle to PostgreSQL"
4+
}

packages/core/src/amazonq/commons/diff.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,3 @@ export function createAmazonQUri(path: string, tabId: string) {
3333
// TODO change the featureDevScheme to a more general amazon q scheme
3434
return vscode.Uri.from({ scheme: featureDevScheme, path, query: `tabID=${tabId}` })
3535
}
36-
37-
export async function openFile(path: string) {
38-
if (!(await fs.exists(path))) {
39-
return
40-
}
41-
const fileUri = vscode.Uri.file(path)
42-
await vscode.commands.executeCommand('vscode.diff', fileUri, fileUri)
43-
}

packages/core/src/amazonq/index.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,7 @@ export { amazonQHelpUrl } from '../shared/constants'
2727
export { listCodeWhispererCommandsWalkthrough } from '../codewhisperer/ui/statusBarMenu'
2828
export { focusAmazonQPanel, focusAmazonQPanelKeybinding } from '../codewhispererChat/commands/registerCommands'
2929
export { TryChatCodeLensProvider, tryChatCodeLensCommand } from '../codewhispererChat/editor/codelens'
30-
export {
31-
createAmazonQUri,
32-
openDiff,
33-
openDeletedDiff,
34-
getOriginalFileUri,
35-
getFileDiffUris,
36-
openFile,
37-
} from './commons/diff'
30+
export { createAmazonQUri, openDiff, openDeletedDiff, getOriginalFileUri, getFileDiffUris } from './commons/diff'
3831
export { CodeReference } from '../codewhispererChat/view/connector/connector'
3932
export { AuthMessageDataMap, AuthFollowUpType } from './auth/model'
4033
export { extractAuthFollowUp } from './util/authUtils'

packages/core/src/amazonq/webview/ui/tabs/constants.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
import { isSQLTransformReady } from '../../../../dev/config'
65
import { TabType } from '../storages/tabsStorage'
76
import { QuickActionCommandGroup } from '@aws/mynah-ui'
87

@@ -47,14 +46,6 @@ What would you like to work on?`,
4746
gumby: {
4847
title: 'Q - Code Transformation',
4948
placeholder: 'Open a new tab to chat with Q',
50-
welcome: isSQLTransformReady
51-
? `Welcome to code transformation!
52-
53-
I can help you with the following tasks:
54-
- Upgrade your Java 8 and Java 11 codebases to Java 17
55-
- Convert embedded SQL from Oracle databases to PostgreSQL
56-
57-
What would you like to do? You can enter 'language upgrade' or 'SQL conversion'.`
58-
: `Welcome to code transformation!`,
49+
welcome: 'Welcome to Code Transformation!',
5950
},
6051
}

packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { openUrl } from '../../../shared/utilities/vsCodeUtils'
4343
import { getPathsFromZipFilePath } from '../../util/files'
4444
import { examples, messageWithConversationId } from '../../userFacingText'
4545
import { getWorkspaceFoldersByPrefixes } from '../../../shared/utilities/workspaceUtils'
46-
import { openDeletedDiff, openDiff, openFile } from '../../../amazonq/commons/diff'
46+
import { openDeletedDiff, openDiff } from '../../../amazonq/commons/diff'
4747
import { i18n } from '../../../shared/i18n-helper'
4848
import globals from '../../../shared/extensionGlobals'
4949
import { randomUUID } from '../../../shared'
@@ -750,7 +750,7 @@ export class FeatureDevController {
750750
this.sendAcceptCodeTelemetry(session, 1)
751751
await session.insertNewFiles([session.state.filePaths[filePathIndex]])
752752
await session.insertCodeReferenceLogs(session.state.references ?? [])
753-
await this.openFile(session.state.filePaths[filePathIndex])
753+
await this.openFile(session.state.filePaths[filePathIndex], tabId)
754754
} else {
755755
session.state.filePaths[filePathIndex].rejected = !session.state.filePaths[filePathIndex].rejected
756756
}
@@ -828,9 +828,10 @@ export class FeatureDevController {
828828
}
829829
}
830830

831-
private async openFile(filePath: NewFileInfo) {
832-
const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
833-
await openFile(absolutePath)
831+
private async openFile(filePath: NewFileInfo, tabId: string) {
832+
const leftPath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
833+
const rightPath = filePath.virtualMemoryUri.path
834+
await openDiff(leftPath, rightPath, tabId)
834835
}
835836

836837
private async stopResponse(message: any) {

packages/core/src/amazonqGumby/chat/controller/controller.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
getValidSQLConversionCandidateProjects,
3434
validateSQLMetadataFile,
3535
} from '../../../codewhisperer/commands/startTransformByQ'
36-
import { JDKVersion, transformByQState } from '../../../codewhisperer/models/model'
36+
import { JDKVersion, TransformationCandidateProject, transformByQState } from '../../../codewhisperer/models/model'
3737
import {
3838
AbsolutePathDetectedError,
3939
AlternateDependencyVersionsNotFoundError,
@@ -62,7 +62,7 @@ import { getStringHash } from '../../../shared/utilities/textUtilities'
6262
import { getVersionData } from '../../../codewhisperer/service/transformByQ/transformMavenHandler'
6363
import AdmZip from 'adm-zip'
6464
import { AuthError } from '../../../auth/sso/server'
65-
import { isSelectiveTransformationReady, isSQLTransformReady } from '../../../dev/config'
65+
import { isSelectiveTransformationReady } from '../../../dev/config'
6666

6767
// These events can be interactions within the chat,
6868
// or elsewhere in the IDE
@@ -190,12 +190,31 @@ export class GumbyController {
190190
}
191191

192192
private async transformInitiated(message: any) {
193-
// feature flag for SQL transformations
194-
if (!isSQLTransformReady) {
193+
// silently check for projects eligible for SQL conversion
194+
let embeddedSQLProjects: TransformationCandidateProject[] = []
195+
try {
196+
embeddedSQLProjects = await getValidSQLConversionCandidateProjects()
197+
} catch (err) {
198+
getLogger().error(`CodeTransformation: error validating SQL conversion projects: ${err}`)
199+
}
200+
201+
if (embeddedSQLProjects.length === 0) {
195202
await this.handleLanguageUpgrade(message)
196203
return
197204
}
198205

206+
let javaUpgradeProjects: TransformationCandidateProject[] = []
207+
try {
208+
javaUpgradeProjects = await getValidLanguageUpgradeCandidateProjects()
209+
} catch (err) {
210+
getLogger().error(`CodeTransformation: error validating Java upgrade projects: ${err}`)
211+
}
212+
213+
if (javaUpgradeProjects.length === 0) {
214+
await this.handleSQLConversion(message)
215+
return
216+
}
217+
199218
// if previous transformation was already running, show correct message to user
200219
switch (this.sessionStorage.getSession().conversationState) {
201220
case ConversationState.JOB_SUBMITTED:
@@ -224,7 +243,10 @@ export class GumbyController {
224243
this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE
225244
this.messenger.sendStaticTextResponse('choose-transformation-objective', message.tabID)
226245
this.messenger.sendChatInputEnabled(message.tabID, true)
227-
this.messenger.sendUpdatePlaceholder(message.tabID, "Enter 'language upgrade' or 'SQL conversion'")
246+
this.messenger.sendUpdatePlaceholder(
247+
message.tabID,
248+
CodeWhispererConstants.chooseTransformationObjectivePlaceholder
249+
)
228250
}
229251

230252
private async beginTransformation(message: any) {
@@ -310,13 +332,7 @@ export class GumbyController {
310332

311333
private async validateSQLConversionProjects(message: any) {
312334
try {
313-
const validProjects = await telemetry.codeTransform_validateProject.run(async () => {
314-
telemetry.record({
315-
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
316-
})
317-
const validProjects = await getValidSQLConversionCandidateProjects()
318-
return validProjects
319-
})
335+
const validProjects = await getValidSQLConversionCandidateProjects()
320336
return validProjects
321337
} catch (e: any) {
322338
if (e instanceof NoJavaProjectsFoundError) {
@@ -649,6 +665,14 @@ export class GumbyController {
649665

650666
case ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE: {
651667
const objective = data.message.trim().toLowerCase()
668+
// since we're prompting the user, their project(s) must be eligible for both types of transformations, so track how often this happens here
669+
if (objective === 'language upgrade' || objective === 'sql conversion') {
670+
telemetry.codeTransform_submitSelection.emit({
671+
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
672+
userChoice: objective,
673+
result: 'Succeeded',
674+
})
675+
}
652676
if (objective === 'language upgrade') {
653677
await this.handleLanguageUpgrade(data)
654678
} else if (objective === 'sql conversion') {

packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -295,15 +295,15 @@ export class Messenger {
295295
formItems.push({
296296
id: 'GumbyTransformSQLConversionProjectForm',
297297
type: 'select',
298-
title: 'Choose a project to transform',
298+
title: CodeWhispererConstants.chooseProjectFormTitle,
299299
mandatory: true,
300300
options: projectFormOptions,
301301
})
302302

303303
formItems.push({
304304
id: 'GumbyTransformSQLSchemaForm',
305305
type: 'select',
306-
title: 'Choose the schema of the database',
306+
title: CodeWhispererConstants.chooseSchemaFormTitle,
307307
mandatory: true,
308308
options: Array.from(transformByQState.getSchemaOptions()).map((schema) => ({
309309
value: schema,
@@ -314,7 +314,7 @@ export class Messenger {
314314
this.dispatcher.sendAsyncEventProgress(
315315
new AsyncEventProgressMessage(tabID, {
316316
inProgress: true,
317-
message: 'I can convert your embedded SQL, but I need some more info from you first.',
317+
message: CodeWhispererConstants.chooseProjectSchemaFormMessage,
318318
})
319319
)
320320

@@ -432,7 +432,7 @@ export class Messenger {
432432
message = 'I will continue transforming your code without upgrading this dependency.'
433433
break
434434
case 'choose-transformation-objective':
435-
message = 'Choose your transformation objective.'
435+
message = CodeWhispererConstants.chooseTransformationObjective
436436
break
437437
}
438438

@@ -464,6 +464,7 @@ export class Messenger {
464464
message = CodeWhispererConstants.noJavaProjectsFoundChatMessage
465465
break
466466
case 'no-maven-java-project-found':
467+
// shown when user has no pom.xml, but at this point also means they have no eligible SQL conversion projects
467468
message = CodeWhispererConstants.noPomXmlFoundChatMessage
468469
break
469470
case 'could-not-compile-project':
@@ -489,23 +490,7 @@ export class Messenger {
489490
break
490491
}
491492

492-
const buttons: ChatItemButton[] = []
493-
buttons.push({
494-
keepCardAfterClick: false,
495-
text: CodeWhispererConstants.startTransformationButtonText,
496-
id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW,
497-
})
498-
499-
this.dispatcher.sendChatMessage(
500-
new ChatMessage(
501-
{
502-
message,
503-
messageType: 'ai-prompt',
504-
buttons,
505-
},
506-
tabID
507-
)
508-
)
493+
this.sendJobFinishedMessage(tabID, message)
509494
}
510495

511496
/**

packages/core/src/amazonqGumby/models/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
export const gumbyChat = 'gumbyChat'
88

99
// This sets the tab name
10-
export const featureName = 'Q - Code Transform'
10+
export const featureName = 'Q - Code Transformation'
1111

1212
export const dependencyNoAvailableVersions = 'no available versions'

0 commit comments

Comments
 (0)