Skip to content

Commit 841e708

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

File tree

13 files changed

+241
-17
lines changed

13 files changed

+241
-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: 62 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,26 @@ 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+
})
7087
})
7188

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

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

@@ -158,7 +184,7 @@ describe('TabBarController', () => {
158184
items: [{ id: 'history1' }, { id: 'history2' }],
159185
},
160186
]
161-
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns(mockSearchResults)
187+
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns({ results: mockSearchResults, searchTime: 100 })
162188

163189
const promise = tabBarController.onListConversations({
164190
filter: {
@@ -186,7 +212,7 @@ describe('TabBarController', () => {
186212
const mockSearchResults: ConversationItemGroup[] = [
187213
{ items: [{ id: 'empty', description: 'No matches found' }] },
188214
]
189-
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns(mockSearchResults)
215+
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns({ results: mockSearchResults, searchTime: 100 })
190216

191217
const promise = tabBarController.onListConversations({
192218
filter: {
@@ -218,6 +244,10 @@ describe('TabBarController', () => {
218244
await tabBarController.onConversationClick({ id: historyId })
219245

220246
sinon.assert.calledWith(openTabStub, { tabId: openTabId })
247+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
248+
action: ChatHistoryActionType.Open,
249+
languageServerVersion: testFeatures.runtime.serverInfo.version,
250+
})
221251
})
222252

223253
it('should restore tab when conversation is not already open', async () => {
@@ -242,6 +272,10 @@ describe('TabBarController', () => {
242272
const result = await tabBarController.onConversationClick({ id: historyId, action: 'delete' })
243273

244274
sinon.assert.calledWith(chatHistoryDb.deleteHistory as sinon.SinonStub, historyId)
275+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
276+
action: ChatHistoryActionType.Delete,
277+
languageServerVersion: testFeatures.runtime.serverInfo.version,
278+
})
245279
assert.strictEqual(result.success, true)
246280
})
247281

@@ -313,6 +347,12 @@ describe('TabBarController', () => {
313347
// Write serialized content to file
314348
sinon.assert.calledWith(fsWriteFileStub, '/testworkspace/test.md', 'Test Serialized Content')
315349

350+
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
351+
action: ChatHistoryActionType.Export,
352+
languageServerVersion: testFeatures.runtime.serverInfo.version,
353+
filenameExt: 'markdown',
354+
})
355+
316356
assert.strictEqual(result.success, true)
317357
})
318358

@@ -381,6 +421,11 @@ describe('TabBarController', () => {
381421
// Write serialized content to file
382422
sinon.assert.calledWith(fsWriteFileStub, '/testworkspace/test.md', 'Test Serialized Content')
383423

424+
sinon.assert.calledWith(telemetryService.emitExportTab as sinon.SinonStub, {
425+
filenameExt: 'markdown',
426+
languageServerVersion: testFeatures.runtime.serverInfo.version,
427+
})
428+
384429
assert.strictEqual(result.success, true)
385430
})
386431
})
@@ -436,6 +481,12 @@ describe('TabBarController', () => {
436481
sinon.assert.calledTwice(restoreTabStub)
437482
sinon.assert.calledWith(restoreTabStub.firstCall, mockTabs[0])
438483
sinon.assert.calledWith(restoreTabStub.secondCall, mockTabs[1])
484+
sinon.assert.calledWith(telemetryService.emitLoadHistory as sinon.SinonStub, {
485+
openTabCount: 2,
486+
amazonqTimeToLoadHistory: -1,
487+
amazonQHistoryFileSize: -1,
488+
languageServerVersion: testFeatures.runtime.serverInfo.version,
489+
})
439490
})
440491

441492
it('should only load chats once', async () => {
@@ -448,6 +499,13 @@ describe('TabBarController', () => {
448499
await tabBarController.loadChats() // Second call should be ignored
449500

450501
sinon.assert.calledOnce(restoreTabStub)
502+
sinon.assert.calledOnce(telemetryService.emitLoadHistory as sinon.SinonStub)
503+
sinon.assert.calledWith(telemetryService.emitLoadHistory as sinon.SinonStub, {
504+
openTabCount: 1,
505+
amazonqTimeToLoadHistory: -1,
506+
amazonQHistoryFileSize: -1,
507+
languageServerVersion: testFeatures.runtime.serverInfo.version,
508+
})
451509
})
452510

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

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

Lines changed: 48 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,19 @@ 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+
})
90+
7791
resolve(results)
7892
}, this.#DebounceTime)
7993
})
@@ -145,8 +159,16 @@ export class TabBarController {
145159
const selectedTab = this.#chatHistoryDb.getTab(historyID)
146160
await this.restoreTab(selectedTab)
147161
}
162+
this.#telemetryService.emitChatHistoryAction({
163+
action: ChatHistoryActionType.Open,
164+
languageServerVersion: this.#features.runtime.serverInfo.version,
165+
})
148166
} else if (params.action === 'delete') {
149167
this.#chatHistoryDb.deleteHistory(historyID)
168+
this.#telemetryService.emitChatHistoryAction({
169+
action: ChatHistoryActionType.Delete,
170+
languageServerVersion: this.#features.runtime.serverInfo.version,
171+
})
150172
} else if (params.action === 'export') {
151173
let openTabID = this.#chatHistoryDb.getOpenTabId(historyID)
152174

@@ -164,7 +186,13 @@ export class TabBarController {
164186
return { ...params, success: false }
165187
}
166188

167-
await this.onExportTab(openTabID)
189+
const format = await this.onExportTab(openTabID)
190+
191+
this.#telemetryService.emitChatHistoryAction({
192+
action: ChatHistoryActionType.Export,
193+
languageServerVersion: this.#features.runtime.serverInfo.version,
194+
filenameExt: format,
195+
})
168196
} else {
169197
this.#features.logging.error(`Unsupported action: ${params.action}`)
170198
return { ...params, success: false }
@@ -175,7 +203,12 @@ export class TabBarController {
175203

176204
async onTabBarAction(params: TabBarActionParams) {
177205
if (params.action === 'export' && params.tabId) {
178-
await this.onExportTab(params.tabId)
206+
const format = await this.onExportTab(params.tabId)
207+
208+
this.#telemetryService.emitExportTab({
209+
filenameExt: format,
210+
languageServerVersion: this.#features.runtime.serverInfo.version,
211+
})
179212

180213
return { ...params, success: true }
181214
}
@@ -210,6 +243,8 @@ export class TabBarController {
210243
})
211244

212245
await this.#features.workspace.fs.writeFile(targetPath.path, content)
246+
247+
return format
213248
}
214249

215250
/**
@@ -235,13 +270,22 @@ export class TabBarController {
235270
return
236271
}
237272
this.#loadedChats = true
273+
274+
const dbSize = this.#chatHistoryDb.getDatabaseFileSize()
275+
238276
const openConversations = this.#chatHistoryDb.getOpenTabs()
239277
if (openConversations) {
240278
for (const conversation of openConversations) {
241279
if (conversation.conversations && conversation.conversations.length > 0) {
242280
await this.restoreTab(conversation)
243281
}
244282
}
283+
this.#telemetryService.emitLoadHistory({
284+
amazonqTimeToLoadHistory: this.#chatHistoryDb.getLoadTime() || -1,
285+
amazonQHistoryFileSize: dbSize || -1,
286+
openTabCount: openConversations.length,
287+
languageServerVersion: this.#features.runtime.serverInfo.version,
288+
})
245289
}
246290
}
247291

0 commit comments

Comments
 (0)