Skip to content

chore: emit telemetry for inline chat result #1131

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Will be deleted or merged.
*/

import * as path from 'path'

Check warning on line 6 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test

Do not import Node.js builtin module "path"

Check warning on line 6 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

Do not import Node.js builtin module "path"
import {
ChatTriggerType,
GenerateAssistantResponseCommandInput,
Expand Down Expand Up @@ -1190,7 +1190,9 @@
}
}

async onInlineChatResult(handler: InlineChatResultParams) {}
async onInlineChatResult(params: InlineChatResultParams) {
await this.#telemetryService.emitInlineChatResultLog(params)
}

async onCodeInsertToCursorPosition(params: InsertToCursorPositionParams) {
// Implementation based on https://github.com/aws/aws-toolkit-vscode/blob/1814cc84228d4bf20270574c5980b91b227f31cf/packages/core/src/amazonq/commons/controllers/contentController.ts#L38
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ export const QAgenticChatServer =
amazonQServiceManager
)

/*
/*
Calling handleDidChangeConfiguration once to ensure we get configuration atleast once at start up

TODO: TODO: consider refactoring such responsibilities to common service manager config/initialisation server
*/
await amazonQServiceManager.handleDidChangeConfiguration()
Expand Down Expand Up @@ -161,6 +161,10 @@ export const QAgenticChatServer =
return chatController.onButtonClick(params)
})

chat.onInlineChatResult(params => {
return chatController.onInlineChatResult(params)
})

logging.log('Q Chat server has been initialized')

return () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,9 @@ export class ChatController implements ChatHandlers {
}
}

async onInlineChatResult(handler: InlineChatResultParams) {}
async onInlineChatResult(params: InlineChatResultParams) {
await this.#telemetryService.emitInlineChatResultLog(params)
}

async onCodeInsertToCursorPosition(params: InsertToCursorPositionParams) {
// Implementation based on https://github.com/aws/aws-toolkit-vscode/blob/1814cc84228d4bf20270574c5980b91b227f31cf/packages/core/src/amazonq/commons/controllers/contentController.ts#L38
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ export const QChatServerFactory =
amazonQServiceManager
)

/*
/*
Calling handleDidChangeConfiguration once to ensure we get configuration atleast once at start up

TODO: TODO: consider refactoring such responsibilities to common service manager config/initialisation server
*/
await amazonQServiceManager.handleDidChangeConfiguration()
Expand Down Expand Up @@ -150,6 +150,10 @@ export const QChatServerFactory =
return chatController.onCodeInsertToCursorPosition(params)
})

chat.onInlineChatResult(params => {
return chatController.onInlineChatResult(params)
})

logging.log('Q Chat server has been initialized')

return () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -982,4 +982,134 @@ describe('TelemetryService', () => {
sinon.assert.notCalled(codeWhisperServiceStub.sendTelemetryEvent)
})
})

describe('Inline chat result notification', () => {
let telemetryService: TelemetryService
let mockCredentialsProvider: MockCredentialsProvider

beforeEach(() => {
mockCredentialsProvider = new MockCredentialsProvider()
mockCredentialsProvider.setConnectionMetadata({
sso: {
startUrl: 'idc-start-url',
},
})

codeWhisperServiceStub.getCredentialsType.returns('bearer')
telemetryService = new TelemetryService(
baseAmazonQServiceManagerStub,
mockCredentialsProvider,
telemetry,
logging
)
})

afterEach(() => {
sinon.restore()
})

it('should send InlineChatEvent with correct parameters', () => {
const timestamp = new Date()
telemetryService.emitInlineChatResultLog({
requestId: 'mock-request-id',
inputLength: 10,
selectedLines: 2,
suggestionAddedChars: 20,
suggestionAddedLines: 3,
suggestionDeletedChars: 10,
suggestionDeletedLines: 2,
codeIntent: true,
userDecision: 'ACCEPT',
responseStartLatency: 1250,
responseEndLatency: 1500,
programmingLanguage: {
languageName: 'typescript',
},
})

const expectedEvent = {
telemetryEvent: {
inlineChatEvent: {
requestId: 'mock-request-id',
timestamp: timestamp,
inputLength: 10,
numSelectedLines: 2,
numSuggestionAddChars: 20,
numSuggestionAddLines: 3,
numSuggestionDelChars: 10,
numSuggestionDelLines: 2,
codeIntent: true,
userDecision: 'ACCEPT',
responseStartLatency: 1250,
responseEndLatency: 1500,
programmingLanguage: {
languageName: 'typescript',
},
},
},
}
sinon.assert.calledOnceWithExactly(codeWhisperServiceStub.sendTelemetryEvent, expectedEvent)
})

it('should not send InlineChatEvent when credentialsType is IAM', () => {
codeWhisperServiceStub.getCredentialsType.returns('iam')
telemetryService = new TelemetryService(
baseAmazonQServiceManagerStub,
mockCredentialsProvider,
telemetry,
logging
)
const timestamp = new Date()
telemetryService.emitInlineChatResultLog({
requestId: 'mock-request-id',
inputLength: 10,
selectedLines: 2,
suggestionAddedChars: 20,
suggestionAddedLines: 3,
suggestionDeletedChars: 10,
suggestionDeletedLines: 2,
codeIntent: true,
userDecision: 'ACCEPT',
responseStartLatency: 1250,
responseEndLatency: 1500,
programmingLanguage: {
languageName: 'typescript',
},
})
sinon.assert.notCalled(codeWhisperServiceStub.sendTelemetryEvent)
})

it('should not send InlineChatEvent when login is BuilderID, but user chose OPTOUT option', () => {
mockCredentialsProvider.setConnectionMetadata({
sso: {
startUrl: BUILDER_ID_START_URL,
},
})
telemetryService = new TelemetryService(
baseAmazonQServiceManagerStub,
mockCredentialsProvider,
telemetry,
logging
)
telemetryService.updateOptOutPreference('OPTOUT')
const timestamp = new Date()
telemetryService.emitInlineChatResultLog({
requestId: 'mock-request-id',
inputLength: 10,
selectedLines: 2,
suggestionAddedChars: 20,
suggestionAddedLines: 3,
suggestionDeletedChars: 10,
suggestionDeletedLines: 2,
codeIntent: true,
userDecision: 'ACCEPT',
responseStartLatency: 1250,
responseEndLatency: 1500,
programmingLanguage: {
languageName: 'typescript',
},
})
sinon.assert.notCalled(codeWhisperServiceStub.sendTelemetryEvent)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ import {
TelemetryEvent,
ChatAddMessageEvent,
UserIntent,
InlineChatEvent,
UUID,
Timestamp,
PrimitiveInteger,
Boolean,
InlineChatUserDecision,
Double,
ProgrammingLanguage,
} from '../../client/token/codewhispererbearertokenclient'
import { getCompletionType, getSsoConnectionType, isAwsError } from '../utils'
import {
Expand All @@ -30,6 +38,7 @@ import {
import { CodewhispererLanguage, getRuntimeLanguage } from '../languageDetection'
import { CONVERSATION_ID_METRIC_KEY } from '../../language-server/chat/telemetry/chatTelemetryController'
import { AmazonQBaseServiceManager } from '../amazonQServiceManager/BaseAmazonQServiceManager'
import { InlineChatResultParams } from '@aws/language-server-runtimes/protocol'

export class TelemetryService {
// Using Base service manager here to support fallback cases such as in codeWhispererServer
Expand Down Expand Up @@ -453,4 +462,29 @@ export class TelemetryService {
chatAddMessageEvent: event,
})
}

public emitInlineChatResultLog(params: InlineChatResultParams) {
const event: InlineChatEvent = {
requestId: params.requestId,
timestamp: new Date(),
inputLength: params.inputLength,
numSelectedLines: params.selectedLines,
numSuggestionAddChars: params.suggestionAddedChars,
numSuggestionAddLines: params.suggestionAddedLines,
numSuggestionDelChars: params.suggestionDeletedChars,
numSuggestionDelLines: params.suggestionDeletedLines,
codeIntent: params.codeIntent,
userDecision: params.userDecision,
responseStartLatency: params.responseStartLatency,
responseEndLatency: params.responseEndLatency,
}
if (params.programmingLanguage) {
event.programmingLanguage = {
languageName: getRuntimeLanguage(params.programmingLanguage.languageName as CodewhispererLanguage),
}
}
return this.invokeSendTelemetryEvent({
inlineChatEvent: event,
})
}
}
Loading