Skip to content

Commit db8a720

Browse files
authored
Merge branch 'master' into selective_transform
2 parents ee488f6 + 5f012c8 commit db8a720

File tree

49 files changed

+1158
-224
lines changed

Some content is hidden

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

49 files changed

+1158
-224
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

package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
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/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ describe('session', () => {
109109
it('only insert non rejected files', async () => {
110110
const fsSpyWriteFile = sinon.spy(fs, 'writeFile')
111111
const session = await createCodeGenState()
112+
sinon.stub(session, 'sendLinesOfCodeAcceptedTelemetry').resolves()
112113
await sessionWriteFile(session, uri, encodedContent)
113114
await session.insertChanges()
114115

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@
435435
"c8": "^9.0.0",
436436
"circular-dependency-plugin": "^5.2.2",
437437
"css-loader": "^6.10.0",
438-
"diff": "^5.1.0",
439438
"esbuild-loader": "2.20.0",
440439
"file-loader": "^6.2.0",
441440
"jsdom": "^23.0.1",
@@ -488,6 +487,7 @@
488487
"bytes": "^3.1.2",
489488
"cross-fetch": "^4.0.0",
490489
"cross-spawn": "^7.0.3",
490+
"diff": "^5.1.0",
491491
"fast-json-patch": "^3.1.1",
492492
"glob": "^10.3.10",
493493
"got": "^11.8.5",

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as vscode from 'vscode'
77
import { featureDevScheme } from '../../amazonqFeatureDev/constants'
88
import { fs } from '../../shared'
9+
import { diffLines } from 'diff'
910

1011
export async function openDiff(leftPath: string, rightPath: string, tabId: string) {
1112
const { left, right } = await getFileDiffUris(leftPath, rightPath, tabId)
@@ -29,15 +30,35 @@ export async function getFileDiffUris(leftPath: string, rightPath: string, tabId
2930
return { left, right }
3031
}
3132

33+
export async function computeDiff(leftPath: string, rightPath: string, tabId: string) {
34+
const { left, right } = await getFileDiffUris(leftPath, rightPath, tabId)
35+
const leftFile = await vscode.workspace.openTextDocument(left)
36+
const rightFile = await vscode.workspace.openTextDocument(right)
37+
38+
const changes = diffLines(leftFile.getText(), rightFile.getText(), {
39+
ignoreWhitespace: true,
40+
})
41+
42+
let charsAdded = 0
43+
let charsRemoved = 0
44+
let linesAdded = 0
45+
let linesRemoved = 0
46+
changes.forEach((change) => {
47+
const lines = change.value.split('\n')
48+
const charCount = lines.reduce((sum, line) => sum + line.length, 0)
49+
const lineCount = change.count ?? lines.length - 1 // ignoring end-of-file empty line
50+
if (change.added) {
51+
charsAdded += charCount
52+
linesAdded += lineCount
53+
} else if (change.removed) {
54+
charsRemoved += charCount
55+
linesRemoved += lineCount
56+
}
57+
})
58+
return { changes, charsAdded, linesAdded, charsRemoved, linesRemoved }
59+
}
60+
3261
export function createAmazonQUri(path: string, tabId: string) {
3362
// TODO change the featureDevScheme to a more general amazon q scheme
3463
return vscode.Uri.from({ scheme: featureDevScheme, path, query: `tabID=${tabId}` })
3564
}
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export {
3333
openDeletedDiff,
3434
getOriginalFileUri,
3535
getFileDiffUris,
36-
openFile,
36+
computeDiff,
3737
} from './commons/diff'
3838
export { CodeReference } from '../codewhispererChat/view/connector/connector'
3939
export { AuthMessageDataMap, AuthFollowUpType } from './auth/model'

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/client/codewhispererruntime-2022-11-11.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,44 @@
873873
"max": 100,
874874
"min": 0
875875
},
876+
"FeatureDevCodeAcceptanceEvent": {
877+
"type": "structure",
878+
"required": ["conversationId", "linesOfCodeAccepted", "charactersOfCodeAccepted"],
879+
"members": {
880+
"conversationId": { "shape": "ConversationId" },
881+
"linesOfCodeAccepted": { "shape": "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger" },
882+
"charactersOfCodeAccepted": { "shape": "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger" },
883+
"programmingLanguage": { "shape": "ProgrammingLanguage" }
884+
}
885+
},
886+
"FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger": {
887+
"type": "integer",
888+
"min": 0
889+
},
890+
"FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger": {
891+
"type": "integer",
892+
"min": 0
893+
},
894+
"FeatureDevCodeGenerationEvent": {
895+
"type": "structure",
896+
"required": ["conversationId", "linesOfCodeGenerated", "charactersOfCodeGenerated"],
897+
"members": {
898+
"conversationId": { "shape": "ConversationId" },
899+
"linesOfCodeGenerated": { "shape": "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger" },
900+
"charactersOfCodeGenerated": {
901+
"shape": "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger"
902+
},
903+
"programmingLanguage": { "shape": "ProgrammingLanguage" }
904+
}
905+
},
906+
"FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger": {
907+
"type": "integer",
908+
"min": 0
909+
},
910+
"FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger": {
911+
"type": "integer",
912+
"min": 0
913+
},
876914
"FeatureDevEvent": {
877915
"type": "structure",
878916
"required": ["conversationId"],
@@ -1741,6 +1779,8 @@
17411779
"chatUserModificationEvent": { "shape": "ChatUserModificationEvent" },
17421780
"terminalUserInteractionEvent": { "shape": "TerminalUserInteractionEvent" },
17431781
"featureDevEvent": { "shape": "FeatureDevEvent" },
1782+
"featureDevCodeGenerationEvent": { "shape": "FeatureDevCodeGenerationEvent" },
1783+
"featureDevCodeAcceptanceEvent": { "shape": "FeatureDevCodeAcceptanceEvent" },
17441784
"inlineChatEvent": { "shape": "InlineChatEvent" }
17451785
},
17461786
"union": true

0 commit comments

Comments
 (0)