Skip to content

Commit f948f0b

Browse files
Merge master into feature/emr
2 parents 9797f00 + 5f012c8 commit f948f0b

File tree

10 files changed

+513
-10
lines changed

10 files changed

+513
-10
lines changed

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.

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 & 0 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,6 +30,34 @@ 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}` })

packages/core/src/amazonq/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,14 @@ 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 { createAmazonQUri, openDiff, openDeletedDiff, getOriginalFileUri, getFileDiffUris } from './commons/diff'
30+
export {
31+
createAmazonQUri,
32+
openDiff,
33+
openDeletedDiff,
34+
getOriginalFileUri,
35+
getFileDiffUris,
36+
computeDiff,
37+
} from './commons/diff'
3138
export { CodeReference } from '../codewhispererChat/view/connector/connector'
3239
export { AuthMessageDataMap, AuthFollowUpType } from './auth/model'
3340
export { extractAuthFollowUp } from './util/authUtils'

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

packages/core/src/amazonqFeatureDev/client/featureDev.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { createCodeWhispererChatStreamingClient } from '../../shared/clients/cod
2525
import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util'
2626
import { extensionVersion } from '../../shared/vscode/env'
2727
import apiConfig = require('./codewhispererruntime-2022-11-11.json')
28+
import { FeatureDevCodeAcceptanceEvent, FeatureDevCodeGenerationEvent, TelemetryEvent } from './featuredevproxyclient'
2829

2930
// Re-enable once BE is able to handle retries.
3031
const writeAPIRetryOptions = {
@@ -260,13 +261,34 @@ export class FeatureDevClient {
260261
* @param conversationId
261262
*/
262263
public async sendFeatureDevTelemetryEvent(conversationId: string) {
264+
await this.sendFeatureDevEvent('featureDevEvent', {
265+
conversationId,
266+
})
267+
}
268+
269+
public async sendFeatureDevCodeGenerationEvent(event: FeatureDevCodeGenerationEvent) {
270+
getLogger().debug(
271+
`featureDevCodeGenerationEvent: conversationId: ${event.conversationId} charactersOfCodeGenerated: ${event.charactersOfCodeGenerated} linesOfCodeGenerated: ${event.linesOfCodeGenerated}`
272+
)
273+
await this.sendFeatureDevEvent('featureDevCodeGenerationEvent', event)
274+
}
275+
276+
public async sendFeatureDevCodeAcceptanceEvent(event: FeatureDevCodeAcceptanceEvent) {
277+
getLogger().debug(
278+
`featureDevCodeAcceptanceEvent: conversationId: ${event.conversationId} charactersOfCodeAccepted: ${event.charactersOfCodeAccepted} linesOfCodeAccepted: ${event.linesOfCodeAccepted}`
279+
)
280+
await this.sendFeatureDevEvent('featureDevCodeAcceptanceEvent', event)
281+
}
282+
283+
public async sendFeatureDevEvent<T extends keyof TelemetryEvent>(
284+
eventName: T,
285+
event: NonNullable<TelemetryEvent[T]>
286+
) {
263287
try {
264288
const client = await this.getClient()
265289
const params: FeatureDevProxyClient.SendTelemetryEventRequest = {
266290
telemetryEvent: {
267-
featureDevEvent: {
268-
conversationId,
269-
},
291+
[eventName]: event,
270292
},
271293
optOutPreference: getOptOutPreference(),
272294
userContext: {
@@ -279,11 +301,11 @@ export class FeatureDevClient {
279301
}
280302
const response = await client.sendTelemetryEvent(params).promise()
281303
getLogger().debug(
282-
`${featureName}: successfully sent featureDevEvent: ConversationId: ${conversationId} RequestId: ${response.$response.requestId}`
304+
`${featureName}: successfully sent ${eventName} telemetryEvent:${'conversationId' in event ? ' ConversationId: ' + event.conversationId : ''} RequestId: ${response.$response.requestId}`
283305
)
284306
} catch (e) {
285307
getLogger().error(
286-
`${featureName}: failed to send feature dev telemetry: ${(e as Error).name}: ${
308+
`${featureName}: failed to send ${eventName} telemetry: ${(e as Error).name}: ${
287309
(e as Error).message
288310
} RequestId: ${(e as any).requestId}`
289311
)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ export class FeatureDevController {
481481
messageId,
482482
})
483483
await session.updateChatAnswer(tabID, i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges'))
484+
await session.sendLinesOfCodeGeneratedTelemetry()
484485
}
485486
this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.selectOption'))
486487
} finally {

packages/core/src/amazonqFeatureDev/session/session.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { CodeReference } from '../../amazonq/webview/ui/connector'
3232
import { UpdateAnswerMessage } from '../views/connector/connector'
3333
import { MynahIcons } from '@aws/mynah-ui'
3434
import { i18n } from '../../shared/i18n-helper'
35+
import { computeDiff } from '../../amazonq/commons/diff'
3536
export class Session {
3637
private _state?: SessionState | Omit<SessionState, 'uploadId'>
3738
private task: string = ''
@@ -44,6 +45,7 @@ export class Session {
4445
private _codeResultMessageId: string | undefined = undefined
4546
private _acceptCodeMessageId: string | undefined = undefined
4647
private _acceptCodeTelemetrySent = false
48+
private _reportedCodeChanges: Set<string>
4749

4850
// Used to keep track of whether or not the current session is currently authenticating/needs authenticating
4951
public isAuthenticating: boolean
@@ -62,6 +64,7 @@ export class Session {
6264

6365
this._telemetry = new TelemetryHelper()
6466
this.isAuthenticating = false
67+
this._reportedCodeChanges = new Set()
6568
}
6669

6770
/**
@@ -209,6 +212,7 @@ export class Session {
209212
}
210213

211214
public async insertNewFiles(newFilePaths: NewFileInfo[]) {
215+
await this.sendLinesOfCodeAcceptedTelemetry(newFilePaths)
212216
for (const filePath of newFilePaths) {
213217
const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
214218

@@ -273,6 +277,50 @@ export class Session {
273277
return i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges')
274278
}
275279

280+
public async computeFilePathDiff(filePath: NewFileInfo) {
281+
const leftPath = `${filePath.workspaceFolder.uri.fsPath}/${filePath.relativePath}`
282+
const rightPath = filePath.virtualMemoryUri.path
283+
const diff = await computeDiff(leftPath, rightPath, this.tabID)
284+
return { leftPath, rightPath, ...diff }
285+
}
286+
287+
public async sendLinesOfCodeGeneratedTelemetry() {
288+
let charactersOfCodeGenerated = 0
289+
let linesOfCodeGenerated = 0
290+
// deleteFiles are currently not counted because the number of lines added is always 0
291+
const filePaths = this.state.filePaths ?? []
292+
for (const filePath of filePaths) {
293+
const { leftPath, changes, charsAdded, linesAdded } = await this.computeFilePathDiff(filePath)
294+
const codeChangeKey = `${leftPath}#@${JSON.stringify(changes)}`
295+
if (this._reportedCodeChanges.has(codeChangeKey)) {
296+
continue
297+
}
298+
charactersOfCodeGenerated += charsAdded
299+
linesOfCodeGenerated += linesAdded
300+
this._reportedCodeChanges.add(codeChangeKey)
301+
}
302+
await this.proxyClient.sendFeatureDevCodeGenerationEvent({
303+
conversationId: this.conversationId,
304+
charactersOfCodeGenerated,
305+
linesOfCodeGenerated,
306+
})
307+
}
308+
309+
public async sendLinesOfCodeAcceptedTelemetry(filePaths: NewFileInfo[]) {
310+
let charactersOfCodeAccepted = 0
311+
let linesOfCodeAccepted = 0
312+
for (const filePath of filePaths) {
313+
const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath)
314+
charactersOfCodeAccepted += charsAdded
315+
linesOfCodeAccepted += linesAdded
316+
}
317+
await this.proxyClient.sendFeatureDevCodeAcceptanceEvent({
318+
conversationId: this.conversationId,
319+
charactersOfCodeAccepted,
320+
linesOfCodeAccepted,
321+
})
322+
}
323+
276324
get state() {
277325
if (!this._state) {
278326
throw new Error("State should be initialized before it's read")

0 commit comments

Comments
 (0)