Skip to content

Commit 2f76fdf

Browse files
committed
feat(amazonq): telemetry for chat history and export
1 parent c8a9044 commit 2f76fdf

File tree

13 files changed

+254
-17
lines changed

13 files changed

+254
-17
lines changed

chat-client/src/client/messager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
ENTER_FOCUS,
5252
ERROR_MESSAGE_TELEMETRY_EVENT,
5353
EXIT_FOCUS,
54+
HISTORY_BUTTON_CLICK_TELEMETRY_EVENT,
5455
INFO_LINK_CLICK_TELEMETRY_EVENT,
5556
INSERT_TO_CURSOR_POSITION_TELEMETRY_EVENT,
5657
LINK_CLICK_TELEMETRY_EVENT,
@@ -205,8 +206,11 @@ export class Messager {
205206
this.chatApi.fileClick(params)
206207
}
207208

208-
onListConversations = (filter?: Record<string, FilterValue>): void => {
209+
onListConversations = (filter?: Record<string, FilterValue>, tabButtonClicked?: boolean): void => {
209210
this.chatApi.listConversations({ filter })
211+
if (tabButtonClicked) {
212+
this.chatApi.telemetry({ triggerType: 'click', name: HISTORY_BUTTON_CLICK_TELEMETRY_EVENT })
213+
}
210214
}
211215

212216
onConversationClick = (conversationId: string, action?: ConversationAction): void => {

chat-client/src/client/mynahUi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ export const createMynahUi = (
444444
},
445445
onTabBarButtonClick: (tabId: string, buttonId: string) => {
446446
if (buttonId === ChatHistory.TabBarButtonId) {
447-
messager.onListConversations()
447+
messager.onListConversations(undefined, true)
448448
return
449449
}
450450

chat-client/src/contracts/telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const LINK_CLICK_TELEMETRY_EVENT = 'linkClick'
1212
export const INFO_LINK_CLICK_TELEMETRY_EVENT = 'infoLinkClick'
1313
export const SOURCE_LINK_CLICK_TELEMETRY_EVENT = 'sourceLinkClick'
1414
export const AUTH_FOLLOW_UP_CLICKED_TELEMETRY_EVENT = 'authFollowupClicked'
15+
export const HISTORY_BUTTON_CLICK_TELEMETRY_EVENT = 'historyButtonClick'
1516

1617
export enum RelevancyVoteType {
1718
UP = 'upvote',

server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ describe('AgenticChatController', () => {
176176
readFile: sinon.stub().resolves(),
177177
writeFile: fsWriteFileStub.resolves(),
178178
rm: sinon.stub().resolves(),
179+
getFileSize: sinon.stub().resolves(),
179180
}
180181

181182
// Add agent with runTool method to testFeatures

server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export class AgenticChatController implements ChatHandlers {
174174
this.#telemetryService = telemetryService
175175
this.#serviceManager = serviceManager
176176
this.#chatHistoryDb = new ChatDatabase(features)
177-
this.#tabBarController = new TabBarController(features, this.#chatHistoryDb)
177+
this.#tabBarController = new TabBarController(features, this.#chatHistoryDb, telemetryService)
178178
this.#additionalContextProvider = new AdditionalContextProvider(features.workspace, features.lsp)
179179
this.#contextCommandsProvider = new ContextCommandsProvider(
180180
this.#features.logging,

server/aws-lsp-codewhisperer/src/language-server/agenticChat/qAgenticChatServer.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('QAgenticChatServer', () => {
3131
readFile: sinon.stub().resolves(),
3232
writeFile: sinon.stub().resolves(),
3333
rm: sinon.stub().resolves(),
34+
getFileSize: sinon.stub().resolves(),
3435
}
3536

3637
// @ts-ignore

server/aws-lsp-codewhisperer/src/language-server/agenticChat/tabBarController.test.ts

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import { ChatDatabase, EMPTY_CONVERSATION_LIST_ID } from './tools/chatDb/chatDb'
1111
import { Tab } from './tools/chatDb/util'
1212
import { ConversationItemGroup, OpenTabParams, OpenTabResult } from '@aws/language-server-runtimes-types'
1313
import { InitializeParams } from '@aws/language-server-runtimes/protocol'
14+
import { ChatHistoryActionType } from '../../shared/telemetry/types'
15+
import { TelemetryService } from '../../shared/telemetry/telemetryService'
1416

1517
describe('TabBarController', () => {
1618
let testFeatures: TestFeatures
1719
let chatHistoryDb: ChatDatabase
1820
let tabBarController: TabBarController
1921
let clock: sinon.SinonFakeTimers
22+
let telemetryService: TelemetryService
2023

2124
beforeEach(() => {
2225
testFeatures = new TestFeatures()
@@ -29,9 +32,17 @@ describe('TabBarController', () => {
2932
setHistoryIdMapping: sinon.stub(),
3033
getOpenTabs: sinon.stub().returns([]),
3134
updateTabOpenState: sinon.stub(),
35+
getDatabaseFileSize: sinon.stub(),
36+
getLoadTime: sinon.stub(),
3237
} as unknown as ChatDatabase
3338

34-
tabBarController = new TabBarController(testFeatures, chatHistoryDb)
39+
telemetryService = {
40+
emitChatHistoryAction: sinon.stub(),
41+
emitExportTab: sinon.stub(),
42+
emitLoadHistory: sinon.stub(),
43+
} as any
44+
45+
tabBarController = new TabBarController(testFeatures, chatHistoryDb, telemetryService)
3546
clock = sinon.useFakeTimers()
3647
})
3748

@@ -56,7 +67,7 @@ describe('TabBarController', () => {
5667

5768
it('should perform debounced search when search filter is provided', async () => {
5869
const mockSearchResults = [{ id: 'result1' }]
59-
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns(mockSearchResults)
70+
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns({ results: mockSearchResults, searchTime: 100 })
6071

6172
const promise = tabBarController.onListConversations({ filter: { search: 'test query' } })
6273

@@ -67,11 +78,27 @@ describe('TabBarController', () => {
6778

6879
assert.deepStrictEqual(result.list, mockSearchResults)
6980
sinon.assert.calledWith(chatHistoryDb.searchMessages as sinon.SinonStub, 'test query')
81+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
82+
action: ChatHistoryActionType.Search,
83+
languageServerVersion: testFeatures.runtime.serverInfo.version,
84+
amazonqHistoryFileSize: undefined,
85+
amazonqTimeToSearchHistory: 100,
86+
result: 'Succeeded',
87+
})
7088
})
7189

7290
it('should clear previous timeout when multiple search requests are made', async () => {
7391
const clearTimeoutSpy = sinon.spy(global, 'clearTimeout')
7492

93+
// Setup mock return values for searchMessages
94+
const mockSearchResults1 = [{ id: 'result1' }]
95+
const mockSearchResults2 = [{ id: 'result2' }]
96+
;(chatHistoryDb.searchMessages as sinon.SinonStub)
97+
.onFirstCall()
98+
.returns({ results: mockSearchResults1, searchTime: 100 })
99+
.onSecondCall()
100+
.returns({ results: mockSearchResults2, searchTime: 100 })
101+
75102
// First search request
76103
const promise1 = tabBarController.onListConversations({ filter: { search: 'first query' } })
77104

@@ -158,7 +185,7 @@ describe('TabBarController', () => {
158185
items: [{ id: 'history1' }, { id: 'history2' }],
159186
},
160187
]
161-
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns(mockSearchResults)
188+
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns({ results: mockSearchResults, searchTime: 100 })
162189

163190
const promise = tabBarController.onListConversations({
164191
filter: {
@@ -186,7 +213,7 @@ describe('TabBarController', () => {
186213
const mockSearchResults: ConversationItemGroup[] = [
187214
{ items: [{ id: 'empty', description: 'No matches found' }] },
188215
]
189-
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns(mockSearchResults)
216+
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns({ results: mockSearchResults, searchTime: 100 })
190217

191218
const promise = tabBarController.onListConversations({
192219
filter: {
@@ -218,6 +245,11 @@ describe('TabBarController', () => {
218245
await tabBarController.onConversationClick({ id: historyId })
219246

220247
sinon.assert.calledWith(openTabStub, { tabId: openTabId })
248+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
249+
action: ChatHistoryActionType.Open,
250+
languageServerVersion: testFeatures.runtime.serverInfo.version,
251+
result: 'Succeeded',
252+
})
221253
})
222254

223255
it('should restore tab when conversation is not already open', async () => {
@@ -242,6 +274,11 @@ describe('TabBarController', () => {
242274
const result = await tabBarController.onConversationClick({ id: historyId, action: 'delete' })
243275

244276
sinon.assert.calledWith(chatHistoryDb.deleteHistory as sinon.SinonStub, historyId)
277+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
278+
action: ChatHistoryActionType.Delete,
279+
languageServerVersion: testFeatures.runtime.serverInfo.version,
280+
result: 'Succeeded',
281+
})
245282
assert.strictEqual(result.success, true)
246283
})
247284

@@ -313,6 +350,13 @@ describe('TabBarController', () => {
313350
// Write serialized content to file
314351
sinon.assert.calledWith(fsWriteFileStub, '/testworkspace/test.md', 'Test Serialized Content')
315352

353+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
354+
action: ChatHistoryActionType.Export,
355+
languageServerVersion: testFeatures.runtime.serverInfo.version,
356+
filenameExt: 'markdown',
357+
result: 'Succeeded',
358+
})
359+
316360
assert.strictEqual(result.success, true)
317361
})
318362

@@ -381,6 +425,12 @@ describe('TabBarController', () => {
381425
// Write serialized content to file
382426
sinon.assert.calledWith(fsWriteFileStub, '/testworkspace/test.md', 'Test Serialized Content')
383427

428+
sinon.assert.calledWith(telemetryService.emitExportTab as sinon.SinonStub, {
429+
filenameExt: 'markdown',
430+
languageServerVersion: testFeatures.runtime.serverInfo.version,
431+
result: 'Succeeded',
432+
})
433+
384434
assert.strictEqual(result.success, true)
385435
})
386436
})
@@ -436,6 +486,13 @@ describe('TabBarController', () => {
436486
sinon.assert.calledTwice(restoreTabStub)
437487
sinon.assert.calledWith(restoreTabStub.firstCall, mockTabs[0])
438488
sinon.assert.calledWith(restoreTabStub.secondCall, mockTabs[1])
489+
sinon.assert.calledWith(telemetryService.emitLoadHistory as sinon.SinonStub, {
490+
openTabCount: 2,
491+
amazonqTimeToLoadHistory: -1,
492+
amazonqHistoryFileSize: -1,
493+
languageServerVersion: testFeatures.runtime.serverInfo.version,
494+
result: 'Succeeded',
495+
})
439496
})
440497

441498
it('should only load chats once', async () => {
@@ -448,6 +505,14 @@ describe('TabBarController', () => {
448505
await tabBarController.loadChats() // Second call should be ignored
449506

450507
sinon.assert.calledOnce(restoreTabStub)
508+
sinon.assert.calledOnce(telemetryService.emitLoadHistory as sinon.SinonStub)
509+
sinon.assert.calledWith(telemetryService.emitLoadHistory as sinon.SinonStub, {
510+
openTabCount: 1,
511+
amazonqTimeToLoadHistory: -1,
512+
amazonqHistoryFileSize: -1,
513+
languageServerVersion: testFeatures.runtime.serverInfo.version,
514+
result: 'Succeeded',
515+
})
451516
})
452517

453518
it('should not restore tabs with empty conversations', async () => {

server/aws-lsp-codewhisperer/src/language-server/agenticChat/tabBarController.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
} from '@aws/language-server-runtimes-types'
1818
import { URI, Utils } from 'vscode-uri'
1919
import { InitializeParams } from '@aws/language-server-runtimes/server-interface'
20+
import { TelemetryService } from '../../shared/telemetry/telemetryService'
21+
import { ChatHistoryActionType } from '../../shared/telemetry/types'
2022

2123
/**
2224
* Controller for managing chat history and export functionality.
@@ -35,10 +37,12 @@ export class TabBarController {
3537
readonly #DebounceTime = 300 // milliseconds
3638
#features: Features
3739
#chatHistoryDb: ChatDatabase
40+
#telemetryService: TelemetryService
3841

39-
constructor(features: Features, chatHistoryDb: ChatDatabase) {
42+
constructor(features: Features, chatHistoryDb: ChatDatabase, telemetryService: TelemetryService) {
4043
this.#features = features
4144
this.#chatHistoryDb = chatHistoryDb
45+
this.#telemetryService = telemetryService
4246
}
4347

4448
/**
@@ -71,9 +75,20 @@ export class TabBarController {
7175
}
7276

7377
if (searchFilter) {
78+
const dbSize = this.#chatHistoryDb.getDatabaseFileSize()
79+
7480
let list: ConversationItemGroup[] = await new Promise<any[]>(resolve => {
7581
this.#searchTimeout = setTimeout(() => {
76-
const results = this.#chatHistoryDb.searchMessages(searchFilter)
82+
const { results, searchTime } = this.#chatHistoryDb.searchMessages(searchFilter)
83+
84+
this.#telemetryService.emitChatHistoryAction({
85+
action: ChatHistoryActionType.Search,
86+
languageServerVersion: this.#features.runtime.serverInfo.version,
87+
amazonqHistoryFileSize: dbSize,
88+
amazonqTimeToSearchHistory: searchTime,
89+
result: 'Succeeded',
90+
})
91+
7792
resolve(results)
7893
}, this.#DebounceTime)
7994
})
@@ -145,8 +160,18 @@ export class TabBarController {
145160
const selectedTab = this.#chatHistoryDb.getTab(historyID)
146161
await this.restoreTab(selectedTab)
147162
}
163+
this.#telemetryService.emitChatHistoryAction({
164+
action: ChatHistoryActionType.Open,
165+
languageServerVersion: this.#features.runtime.serverInfo.version,
166+
result: 'Succeeded',
167+
})
148168
} else if (params.action === 'delete') {
149169
this.#chatHistoryDb.deleteHistory(historyID)
170+
this.#telemetryService.emitChatHistoryAction({
171+
action: ChatHistoryActionType.Delete,
172+
languageServerVersion: this.#features.runtime.serverInfo.version,
173+
result: 'Succeeded',
174+
})
150175
} else if (params.action === 'export') {
151176
let openTabID = this.#chatHistoryDb.getOpenTabId(historyID)
152177

@@ -164,7 +189,14 @@ export class TabBarController {
164189
return { ...params, success: false }
165190
}
166191

167-
await this.onExportTab(openTabID)
192+
const format = await this.onExportTab(openTabID)
193+
194+
this.#telemetryService.emitChatHistoryAction({
195+
action: ChatHistoryActionType.Export,
196+
languageServerVersion: this.#features.runtime.serverInfo.version,
197+
filenameExt: format,
198+
result: 'Succeeded',
199+
})
168200
} else {
169201
this.#features.logging.error(`Unsupported action: ${params.action}`)
170202
return { ...params, success: false }
@@ -175,7 +207,13 @@ export class TabBarController {
175207

176208
async onTabBarAction(params: TabBarActionParams) {
177209
if (params.action === 'export' && params.tabId) {
178-
await this.onExportTab(params.tabId)
210+
const format = await this.onExportTab(params.tabId)
211+
212+
this.#telemetryService.emitExportTab({
213+
filenameExt: format,
214+
languageServerVersion: this.#features.runtime.serverInfo.version,
215+
result: 'Succeeded',
216+
})
179217

180218
return { ...params, success: true }
181219
}
@@ -210,6 +248,8 @@ export class TabBarController {
210248
})
211249

212250
await this.#features.workspace.fs.writeFile(targetPath.path, content)
251+
252+
return format
213253
}
214254

215255
/**
@@ -242,6 +282,13 @@ export class TabBarController {
242282
await this.restoreTab(conversation)
243283
}
244284
}
285+
this.#telemetryService.emitLoadHistory({
286+
amazonqTimeToLoadHistory: this.#chatHistoryDb.getLoadTime() ?? -1,
287+
amazonqHistoryFileSize: this.#chatHistoryDb.getDatabaseFileSize() ?? -1,
288+
openTabCount: openConversations.length,
289+
languageServerVersion: this.#features.runtime.serverInfo.version,
290+
result: 'Succeeded',
291+
})
245292
}
246293
}
247294

0 commit comments

Comments
 (0)