Skip to content

Commit 694937a

Browse files
committed
telemetry: add docgeneration V2 telemetry
1 parent cf700b6 commit 694937a

File tree

6 files changed

+723
-193
lines changed

6 files changed

+723
-193
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,18 @@ export function createAmazonQUri(path: string, tabId: string, scheme: string) {
3333
return vscode.Uri.from({ scheme: scheme, path, query: `tabID=${tabId}` })
3434
}
3535

36-
export async function computeDiff(leftPath: string, rightPath: string, tabId: string, scheme: string) {
36+
export async function computeDiff(
37+
leftPath: string,
38+
rightPath: string,
39+
tabId: string,
40+
scheme: string,
41+
reportedChanges?: string
42+
) {
3743
const { left, right } = await getFileDiffUris(leftPath, rightPath, tabId, scheme)
3844
const leftFile = await vscode.workspace.openTextDocument(left)
3945
const rightFile = await vscode.workspace.openTextDocument(right)
4046

41-
const changes = diffLines(leftFile.getText(), rightFile.getText(), {
47+
const changes = diffLines(reportedChanges ?? leftFile.getText(), rightFile.getText(), {
4248
ignoreWhitespace: true,
4349
})
4450

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
import { getPathsFromZipFilePath } from '../../../amazonqFeatureDev/util/files'
4545
import { FollowUpTypes } from '../../../amazonq/commons/types'
4646
import { DocGenerationTask } from '../docGenerationTask'
47+
import { DevPhase } from '../../types'
4748

4849
export interface ChatControllerEventEmitters {
4950
readonly processHumanChatMessage: EventEmitter<any>
@@ -227,8 +228,6 @@ export class DocController {
227228
return
228229
}
229230

230-
this.docGenerationTask.userIdentity = AuthUtil.instance.conn?.id
231-
232231
const sendFolderConfirmationMessage = (message: string) => {
233232
this.messenger.sendFolderConfirmationMessage(
234233
data.tabID,
@@ -288,12 +287,12 @@ export class DocController {
288287
break
289288
case FollowUpTypes.AcceptChanges:
290289
this.docGenerationTask.userDecision = 'ACCEPT'
291-
await this.sendDocGenerationEvent(data)
290+
await this.sendDocAcceptanceEvent(data)
292291
await this.insertCode(data)
293292
return
294293
case FollowUpTypes.RejectChanges:
295294
this.docGenerationTask.userDecision = 'REJECT'
296-
await this.sendDocGenerationEvent(data)
295+
await this.sendDocAcceptanceEvent(data)
297296
this.messenger.sendAnswer({
298297
type: 'answer',
299298
tabID: data?.tabID,
@@ -323,7 +322,7 @@ export class DocController {
323322
}
324323
break
325324
case FollowUpTypes.CancelFolderSelection:
326-
this.docGenerationTask.reset()
325+
this.docGenerationTask.folderLevel = 'ENTIRE_WORKSPACE'
327326
return this.tabOpened(data)
328327
}
329328
})
@@ -488,7 +487,7 @@ export class DocController {
488487
session.isAuthenticating = true
489488
return
490489
}
491-
this.docGenerationTask.numberOfNavigation += 1
490+
this.docGenerationTask.numberOfNavigations += 1
492491
this.messenger.sendAnswer({
493492
type: 'answer',
494493
tabID: message.tabID,
@@ -595,6 +594,17 @@ export class DocController {
595594
tabID: tabID,
596595
})
597596
}
597+
if (session?.state.phase === DevPhase.CODEGEN) {
598+
const { totalGeneratedChars, totalGeneratedLines, totalGeneratedFiles } =
599+
await session.countGeneratedContent(this.docGenerationTask.interactionType)
600+
this.docGenerationTask.conversationId = session.conversationId
601+
this.docGenerationTask.numberOfGeneratedChars = totalGeneratedChars
602+
this.docGenerationTask.numberOfGeneratedLines = totalGeneratedLines
603+
this.docGenerationTask.numberOfGeneratedFiles = totalGeneratedFiles
604+
const docGenerationEvent = this.docGenerationTask.docGenerationEventBase()
605+
606+
await session.sendDocGenerationTelemetryEvent(docGenerationEvent)
607+
}
598608
} finally {
599609
if (session?.state?.tokenSource?.token.isCancellationRequested) {
600610
await this.newTask({ tabID })
@@ -652,18 +662,18 @@ export class DocController {
652662
)
653663
}
654664
}
655-
private async sendDocGenerationEvent(message: any) {
665+
private async sendDocAcceptanceEvent(message: any) {
656666
const session = await this.sessionStorage.getSession(message.tabID)
657667
this.docGenerationTask.conversationId = session.conversationId
658668
const { totalAddedChars, totalAddedLines, totalAddedFiles } = await session.countAddedContent(
659669
this.docGenerationTask.interactionType
660670
)
661-
this.docGenerationTask.numberOfAddChars = totalAddedChars
662-
this.docGenerationTask.numberOfAddLines = totalAddedLines
663-
this.docGenerationTask.numberOfAddFiles = totalAddedFiles
664-
const docGenerationEvent = this.docGenerationTask.docGenerationEventBase()
671+
this.docGenerationTask.numberOfAddedChars = totalAddedChars
672+
this.docGenerationTask.numberOfAddedLines = totalAddedLines
673+
this.docGenerationTask.numberOfAddedFiles = totalAddedFiles
674+
const docAcceptanceEvent = this.docGenerationTask.docAcceptanceEventBase()
665675

666-
await session.sendDocGenerationTelemetryEvent(docGenerationEvent)
676+
await session.sendDocAcceptanceTelemetryEvent(docAcceptanceEvent)
667677
}
668678
private processLink(message: any) {
669679
void openUrl(vscode.Uri.parse(message.link))

packages/core/src/amazonqDoc/controllers/docGenerationTask.ts

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,27 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
import {
6-
DocGenerationEvent,
7-
DocGenerationFolderLevel,
8-
DocGenerationInteractionType,
9-
DocGenerationUserDecision,
6+
DocFolderLevel,
7+
DocInteractionType,
8+
DocUserDecision,
9+
DocV2AcceptanceEvent,
10+
DocV2GenerationEvent,
1011
} from '../../codewhisperer/client/codewhispereruserclient'
1112
import { getLogger } from '../../shared'
1213

1314
export class DocGenerationTask {
1415
// Telemetry fields
1516
public conversationId?: string
16-
public numberOfAddChars?: number
17-
public numberOfAddLines?: number
18-
public numberOfAddFiles?: number
19-
public userDecision?: DocGenerationUserDecision
20-
public interactionType?: DocGenerationInteractionType
21-
public userIdentity?: string
22-
public numberOfNavigation = 0
23-
public folderLevel: DocGenerationFolderLevel = 'ENTIRE_WORKSPACE'
17+
public numberOfAddedChars?: number
18+
public numberOfAddedLines?: number
19+
public numberOfAddedFiles?: number
20+
public numberOfGeneratedChars?: number
21+
public numberOfGeneratedLines?: number
22+
public numberOfGeneratedFiles?: number
23+
public userDecision?: DocUserDecision
24+
public interactionType?: DocInteractionType
25+
public numberOfNavigations = 0
26+
public folderLevel: DocFolderLevel = 'ENTIRE_WORKSPACE'
2427

2528
constructor(conversationId?: string) {
2629
this.conversationId = conversationId
@@ -32,31 +35,52 @@ export class DocGenerationTask {
3235
.map(([key]) => key)
3336

3437
if (undefinedProps.length > 0) {
35-
getLogger().debug(`DocGenerationEvent has undefined properties: ${undefinedProps.join(', ')}`)
38+
getLogger().debug(`DocV2GenerationEvent has undefined properties: ${undefinedProps.join(', ')}`)
3639
}
37-
const event: DocGenerationEvent = {
40+
const event: DocV2GenerationEvent = {
3841
conversationId: this.conversationId ?? '',
39-
numberOfAddChars: this.numberOfAddChars,
40-
numberOfAddLines: this.numberOfAddLines,
41-
numberOfAddFiles: this.numberOfAddFiles,
42-
userDecision: this.userDecision,
42+
numberOfGeneratedChars: this.numberOfGeneratedChars ?? 0,
43+
numberOfGeneratedLines: this.numberOfGeneratedLines ?? 0,
44+
numberOfGeneratedFiles: this.numberOfGeneratedFiles ?? 0,
4345
interactionType: this.interactionType,
44-
userIdentity: this.userIdentity,
45-
numberOfNavigation: this.numberOfNavigation,
46+
numberOfNavigations: this.numberOfNavigations,
47+
folderLevel: this.folderLevel,
48+
}
49+
return event
50+
}
51+
52+
public docAcceptanceEventBase() {
53+
const undefinedProps = Object.entries(this)
54+
.filter(([key, value]) => value === undefined)
55+
.map(([key]) => key)
56+
57+
if (undefinedProps.length > 0) {
58+
getLogger().debug(`DocV2AcceptanceEvent has undefined properties: ${undefinedProps.join(', ')}`)
59+
}
60+
const event: DocV2AcceptanceEvent = {
61+
conversationId: this.conversationId ?? '',
62+
numberOfAddedChars: this.numberOfAddedChars ?? 0,
63+
numberOfAddedLines: this.numberOfAddedLines ?? 0,
64+
numberOfAddedFiles: this.numberOfAddedFiles ?? 0,
65+
userDecision: this.userDecision ?? 'ACCEPTED',
66+
interactionType: this.interactionType ?? 'GENERATE_README',
67+
numberOfNavigations: this.numberOfNavigations ?? 0,
4668
folderLevel: this.folderLevel,
4769
}
4870
return event
4971
}
5072

5173
public reset() {
5274
this.conversationId = undefined
53-
this.numberOfAddChars = undefined
54-
this.numberOfAddLines = undefined
55-
this.numberOfAddFiles = undefined
75+
this.numberOfAddedChars = undefined
76+
this.numberOfAddedLines = undefined
77+
this.numberOfAddedFiles = undefined
78+
this.numberOfGeneratedChars = undefined
79+
this.numberOfGeneratedLines = undefined
80+
this.numberOfGeneratedFiles = undefined
5681
this.userDecision = undefined
5782
this.interactionType = undefined
58-
this.userIdentity = undefined
59-
this.numberOfNavigation = 0
83+
this.numberOfNavigations = 0
6084
this.folderLevel = 'ENTIRE_WORKSPACE'
6185
}
6286
}

packages/core/src/amazonqDoc/session/session.ts

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { featureName, Mode } from '../constants'
6+
import { docScheme, featureName, Mode } from '../constants'
77
import { DeletedFileInfo, Interaction, NewFileInfo, SessionState, SessionStateConfig } from '../types'
88
import { PrepareCodeGenState } from './sessionState'
99
import { telemetry } from '../../shared/telemetry/telemetry'
@@ -19,13 +19,14 @@ import { logWithConversationId } from '../../amazonqFeatureDev/userFacingText'
1919
import { ConversationIdNotFoundError } from '../../amazonqFeatureDev/errors'
2020
import { referenceLogText } from '../../amazonqFeatureDev/constants'
2121
import {
22-
DocGenerationEvent,
23-
DocGenerationInteractionType,
22+
DocInteractionType,
23+
DocV2AcceptanceEvent,
24+
DocV2GenerationEvent,
2425
SendTelemetryEventRequest,
2526
} from '../../codewhisperer/client/codewhispereruserclient'
26-
import { getDiffCharsAndLines } from '../../shared/utilities/diffUtils'
2727
import { getClientId, getOperatingSystem, getOptOutPreference } from '../../shared/telemetry/util'
2828
import { DocMessenger } from '../messenger'
29+
import { computeDiff } from '../../amazonq/commons/diff'
2930

3031
export class Session {
3132
private _state?: SessionState | Omit<SessionState, 'uploadId'>
@@ -38,6 +39,7 @@ export class Session {
3839

3940
// Used to keep track of whether or not the current session is currently authenticating/needs authenticating
4041
public isAuthenticating: boolean
42+
private _reportedDocChanges: string | undefined = undefined
4143

4244
constructor(
4345
public readonly config: SessionConfig,
@@ -177,41 +179,109 @@ export class Session {
177179
}
178180
}
179181

180-
public async countAddedContent(interactionType?: DocGenerationInteractionType) {
182+
public async countGeneratedContent(interactionType?: DocInteractionType) {
183+
let totalGeneratedChars = 0
184+
let totalGeneratedLines = 0
185+
let totalGeneratedFiles = 0
186+
const filePaths = this.state.filePaths ?? []
187+
188+
for (const filePath of filePaths) {
189+
if (interactionType === 'GENERATE_README') {
190+
if (this._reportedDocChanges) {
191+
const { charsAdded, linesAdded } = await this.computeFilePathDiff(
192+
filePath,
193+
this._reportedDocChanges
194+
)
195+
totalGeneratedChars += charsAdded
196+
totalGeneratedLines += linesAdded
197+
} else {
198+
const fileContent = filePath.fileContent
199+
totalGeneratedChars += fileContent.length
200+
totalGeneratedLines += fileContent.split('\n').length
201+
}
202+
} else {
203+
const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath, this._reportedDocChanges)
204+
totalGeneratedChars += charsAdded
205+
totalGeneratedLines += linesAdded
206+
}
207+
this._reportedDocChanges = filePath.fileContent
208+
totalGeneratedFiles += 1
209+
}
210+
return {
211+
totalGeneratedChars,
212+
totalGeneratedLines,
213+
totalGeneratedFiles,
214+
}
215+
}
216+
217+
public async countAddedContent(interactionType?: DocInteractionType) {
181218
let totalAddedChars = 0
182219
let totalAddedLines = 0
183220
let totalAddedFiles = 0
184-
185-
for (const filePath of this.state.filePaths?.filter((i) => !i.rejected) ?? []) {
186-
const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
187-
const uri = filePath.virtualMemoryUri
188-
const content = await this.config.fs.readFile(uri)
189-
const decodedContent = new TextDecoder().decode(content)
190-
totalAddedFiles += 1
191-
192-
if ((await fs.exists(absolutePath)) && interactionType === 'UPDATE_README') {
193-
const existingContent = await fs.readFileText(absolutePath)
194-
const { addedChars, addedLines } = getDiffCharsAndLines(existingContent, decodedContent)
195-
totalAddedChars += addedChars
196-
totalAddedLines += addedLines
221+
const newFilePaths =
222+
this.state.filePaths?.filter((filePath) => !filePath.rejected && !filePath.changeApplied) ?? []
223+
224+
for (const filePath of newFilePaths) {
225+
if (interactionType === 'GENERATE_README') {
226+
const fileContent = filePath.fileContent
227+
totalAddedChars += fileContent.length
228+
totalAddedLines += fileContent.split('\n').length
197229
} else {
198-
totalAddedChars += decodedContent.length
199-
totalAddedLines += decodedContent.split('\n').length
230+
const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath)
231+
totalAddedChars += charsAdded
232+
totalAddedLines += linesAdded
200233
}
234+
totalAddedFiles += 1
201235
}
202-
203236
return {
204237
totalAddedChars,
205238
totalAddedLines,
206239
totalAddedFiles,
207240
}
208241
}
209-
public async sendDocGenerationTelemetryEvent(docGenerationEvent: DocGenerationEvent) {
242+
243+
public async computeFilePathDiff(filePath: NewFileInfo, reportedChanges?: string) {
244+
const leftPath = `${filePath.workspaceFolder.uri.fsPath}/${filePath.relativePath}`
245+
const rightPath = filePath.virtualMemoryUri.path
246+
const diff = await computeDiff(leftPath, rightPath, this.tabID, docScheme, reportedChanges)
247+
return { leftPath, rightPath, ...diff }
248+
}
249+
250+
public async sendDocGenerationTelemetryEvent(docV2GenerationEvent: DocV2GenerationEvent) {
251+
const client = await this.proxyClient.getClient()
252+
try {
253+
const params: SendTelemetryEventRequest = {
254+
telemetryEvent: {
255+
docV2GenerationEvent,
256+
},
257+
optOutPreference: getOptOutPreference(),
258+
userContext: {
259+
ideCategory: 'VSCODE',
260+
operatingSystem: getOperatingSystem(),
261+
product: 'DocGeneration', // Should be the same as in JetBrains
262+
clientId: getClientId(globals.globalState),
263+
ideVersion: extensionVersion,
264+
},
265+
}
266+
const response = await client.sendTelemetryEvent(params).promise()
267+
getLogger().debug(
268+
`${featureName}: successfully sent docV2GenerationEvent: ConversationId: ${docV2GenerationEvent.conversationId} RequestId: ${response.$response.requestId}`
269+
)
270+
} catch (e) {
271+
getLogger().error(
272+
`${featureName}: failed to send doc generation telemetry: ${(e as Error).name}: ${
273+
(e as Error).message
274+
} RequestId: ${(e as any).requestId}`
275+
)
276+
}
277+
}
278+
279+
public async sendDocAcceptanceTelemetryEvent(docV2AcceptanceEvent: DocV2AcceptanceEvent) {
210280
const client = await this.proxyClient.getClient()
211281
try {
212282
const params: SendTelemetryEventRequest = {
213283
telemetryEvent: {
214-
docGenerationEvent,
284+
docV2AcceptanceEvent,
215285
},
216286
optOutPreference: getOptOutPreference(),
217287
userContext: {
@@ -224,7 +294,7 @@ export class Session {
224294
}
225295
const response = await client.sendTelemetryEvent(params).promise()
226296
getLogger().debug(
227-
`${featureName}: successfully sent docGenerationEvent: ConversationId: ${docGenerationEvent.conversationId} RequestId: ${response.$response.requestId}`
297+
`${featureName}: successfully sent docV2AcceptanceEvent: ConversationId: ${docV2AcceptanceEvent.conversationId} RequestId: ${response.$response.requestId}`
228298
)
229299
} catch (e) {
230300
getLogger().error(

0 commit comments

Comments
 (0)