Skip to content
Merged
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
6 changes: 5 additions & 1 deletion chat-client/src/client/messager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
ENTER_FOCUS,
ERROR_MESSAGE_TELEMETRY_EVENT,
EXIT_FOCUS,
HISTORY_BUTTON_CLICK_TELEMETRY_EVENT,
INFO_LINK_CLICK_TELEMETRY_EVENT,
INSERT_TO_CURSOR_POSITION_TELEMETRY_EVENT,
LINK_CLICK_TELEMETRY_EVENT,
Expand Down Expand Up @@ -205,8 +206,11 @@ export class Messager {
this.chatApi.fileClick(params)
}

onListConversations = (filter?: Record<string, FilterValue>): void => {
onListConversations = (filter?: Record<string, FilterValue>, tabButtonClicked?: boolean): void => {
this.chatApi.listConversations({ filter })
if (tabButtonClicked) {
this.chatApi.telemetry({ triggerType: 'click', name: HISTORY_BUTTON_CLICK_TELEMETRY_EVENT })
}
}

onConversationClick = (conversationId: string, action?: ConversationAction): void => {
Expand Down
2 changes: 1 addition & 1 deletion chat-client/src/client/mynahUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export const createMynahUi = (
},
onTabBarButtonClick: (tabId: string, buttonId: string) => {
if (buttonId === ChatHistory.TabBarButtonId) {
messager.onListConversations()
messager.onListConversations(undefined, true)
return
}

Expand Down
1 change: 1 addition & 0 deletions chat-client/src/contracts/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const LINK_CLICK_TELEMETRY_EVENT = 'linkClick'
export const INFO_LINK_CLICK_TELEMETRY_EVENT = 'infoLinkClick'
export const SOURCE_LINK_CLICK_TELEMETRY_EVENT = 'sourceLinkClick'
export const AUTH_FOLLOW_UP_CLICKED_TELEMETRY_EVENT = 'authFollowupClicked'
export const HISTORY_BUTTON_CLICK_TELEMETRY_EVENT = 'historyButtonClick'

export enum RelevancyVoteType {
UP = 'upvote',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ describe('AgenticChatController', () => {
readFile: sinon.stub().resolves(),
writeFile: fsWriteFileStub.resolves(),
rm: sinon.stub().resolves(),
getFileSize: sinon.stub().resolves(),
}

// Add agent with runTool method to testFeatures
Expand Down
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,
CodeWhispererStreamingServiceException,
Expand Down Expand Up @@ -174,7 +174,7 @@
this.#telemetryService = telemetryService
this.#serviceManager = serviceManager
this.#chatHistoryDb = new ChatDatabase(features)
this.#tabBarController = new TabBarController(features, this.#chatHistoryDb)
this.#tabBarController = new TabBarController(features, this.#chatHistoryDb, telemetryService)
this.#additionalContextProvider = new AdditionalContextProvider(features.workspace, features.lsp)
this.#contextCommandsProvider = new ContextCommandsProvider(
this.#features.logging,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('QAgenticChatServer', () => {
readFile: sinon.stub().resolves(),
writeFile: sinon.stub().resolves(),
rm: sinon.stub().resolves(),
getFileSize: sinon.stub().resolves(),
}

// @ts-ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import { ChatDatabase, EMPTY_CONVERSATION_LIST_ID } from './tools/chatDb/chatDb'
import { Tab } from './tools/chatDb/util'
import { ConversationItemGroup, OpenTabParams, OpenTabResult } from '@aws/language-server-runtimes-types'
import { InitializeParams } from '@aws/language-server-runtimes/protocol'
import { ChatHistoryActionType } from '../../shared/telemetry/types'
import { TelemetryService } from '../../shared/telemetry/telemetryService'

describe('TabBarController', () => {
let testFeatures: TestFeatures
let chatHistoryDb: ChatDatabase
let tabBarController: TabBarController
let clock: sinon.SinonFakeTimers
let telemetryService: TelemetryService

beforeEach(() => {
testFeatures = new TestFeatures()
Expand All @@ -29,9 +32,17 @@ describe('TabBarController', () => {
setHistoryIdMapping: sinon.stub(),
getOpenTabs: sinon.stub().returns([]),
updateTabOpenState: sinon.stub(),
getDatabaseFileSize: sinon.stub(),
getLoadTime: sinon.stub(),
} as unknown as ChatDatabase

tabBarController = new TabBarController(testFeatures, chatHistoryDb)
telemetryService = {
emitChatHistoryAction: sinon.stub(),
emitExportTab: sinon.stub(),
emitLoadHistory: sinon.stub(),
} as any

tabBarController = new TabBarController(testFeatures, chatHistoryDb, telemetryService)
clock = sinon.useFakeTimers()
})

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

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

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

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

assert.deepStrictEqual(result.list, mockSearchResults)
sinon.assert.calledWith(chatHistoryDb.searchMessages as sinon.SinonStub, 'test query')
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
action: ChatHistoryActionType.Search,
languageServerVersion: testFeatures.runtime.serverInfo.version,
amazonqHistoryFileSize: undefined,
amazonqTimeToSearchHistory: 100,
result: 'Succeeded',
})
})

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

// Setup mock return values for searchMessages
const mockSearchResults1 = [{ id: 'result1' }]
const mockSearchResults2 = [{ id: 'result2' }]
;(chatHistoryDb.searchMessages as sinon.SinonStub)
.onFirstCall()
.returns({ results: mockSearchResults1, searchTime: 100 })
.onSecondCall()
.returns({ results: mockSearchResults2, searchTime: 100 })

// First search request
const promise1 = tabBarController.onListConversations({ filter: { search: 'first query' } })

Expand Down Expand Up @@ -158,7 +185,7 @@ describe('TabBarController', () => {
items: [{ id: 'history1' }, { id: 'history2' }],
},
]
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns(mockSearchResults)
;(chatHistoryDb.searchMessages as sinon.SinonStub).returns({ results: mockSearchResults, searchTime: 100 })

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

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

sinon.assert.calledWith(openTabStub, { tabId: openTabId })
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
action: ChatHistoryActionType.Open,
languageServerVersion: testFeatures.runtime.serverInfo.version,
result: 'Succeeded',
})
})

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

sinon.assert.calledWith(chatHistoryDb.deleteHistory as sinon.SinonStub, historyId)
sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
action: ChatHistoryActionType.Delete,
languageServerVersion: testFeatures.runtime.serverInfo.version,
result: 'Succeeded',
})
assert.strictEqual(result.success, true)
})

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

sinon.assert.calledWith(telemetryService.emitChatHistoryAction as sinon.SinonStub, {
action: ChatHistoryActionType.Export,
languageServerVersion: testFeatures.runtime.serverInfo.version,
filenameExt: 'markdown',
result: 'Succeeded',
})

assert.strictEqual(result.success, true)
})

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

sinon.assert.calledWith(telemetryService.emitExportTab as sinon.SinonStub, {
filenameExt: 'markdown',
languageServerVersion: testFeatures.runtime.serverInfo.version,
result: 'Succeeded',
})

assert.strictEqual(result.success, true)
})
})
Expand Down Expand Up @@ -436,6 +486,13 @@ describe('TabBarController', () => {
sinon.assert.calledTwice(restoreTabStub)
sinon.assert.calledWith(restoreTabStub.firstCall, mockTabs[0])
sinon.assert.calledWith(restoreTabStub.secondCall, mockTabs[1])
sinon.assert.calledWith(telemetryService.emitLoadHistory as sinon.SinonStub, {
openTabCount: 2,
amazonqTimeToLoadHistory: -1,
amazonqHistoryFileSize: -1,
languageServerVersion: testFeatures.runtime.serverInfo.version,
result: 'Succeeded',
})
})

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

sinon.assert.calledOnce(restoreTabStub)
sinon.assert.calledOnce(telemetryService.emitLoadHistory as sinon.SinonStub)
sinon.assert.calledWith(telemetryService.emitLoadHistory as sinon.SinonStub, {
openTabCount: 1,
amazonqTimeToLoadHistory: -1,
amazonqHistoryFileSize: -1,
languageServerVersion: testFeatures.runtime.serverInfo.version,
result: 'Succeeded',
})
})

it('should not restore tabs with empty conversations', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from '@aws/language-server-runtimes-types'
import { URI, Utils } from 'vscode-uri'
import { InitializeParams } from '@aws/language-server-runtimes/server-interface'
import { TelemetryService } from '../../shared/telemetry/telemetryService'
import { ChatHistoryActionType } from '../../shared/telemetry/types'

/**
* Controller for managing chat history and export functionality.
Expand All @@ -35,10 +37,12 @@ export class TabBarController {
readonly #DebounceTime = 300 // milliseconds
#features: Features
#chatHistoryDb: ChatDatabase
#telemetryService: TelemetryService

constructor(features: Features, chatHistoryDb: ChatDatabase) {
constructor(features: Features, chatHistoryDb: ChatDatabase, telemetryService: TelemetryService) {
this.#features = features
this.#chatHistoryDb = chatHistoryDb
this.#telemetryService = telemetryService
}

/**
Expand Down Expand Up @@ -71,9 +75,20 @@ export class TabBarController {
}

if (searchFilter) {
const dbSize = this.#chatHistoryDb.getDatabaseFileSize()

let list: ConversationItemGroup[] = await new Promise<any[]>(resolve => {
this.#searchTimeout = setTimeout(() => {
const results = this.#chatHistoryDb.searchMessages(searchFilter)
const { results, searchTime } = this.#chatHistoryDb.searchMessages(searchFilter)

this.#telemetryService.emitChatHistoryAction({
action: ChatHistoryActionType.Search,
languageServerVersion: this.#features.runtime.serverInfo.version,
amazonqHistoryFileSize: dbSize,
amazonqTimeToSearchHistory: searchTime,
result: 'Succeeded',
})

resolve(results)
}, this.#DebounceTime)
})
Expand Down Expand Up @@ -145,8 +160,18 @@ export class TabBarController {
const selectedTab = this.#chatHistoryDb.getTab(historyID)
await this.restoreTab(selectedTab)
}
this.#telemetryService.emitChatHistoryAction({
action: ChatHistoryActionType.Open,
languageServerVersion: this.#features.runtime.serverInfo.version,
result: 'Succeeded',
})
} else if (params.action === 'delete') {
this.#chatHistoryDb.deleteHistory(historyID)
this.#telemetryService.emitChatHistoryAction({
action: ChatHistoryActionType.Delete,
languageServerVersion: this.#features.runtime.serverInfo.version,
result: 'Succeeded',
})
} else if (params.action === 'export') {
let openTabID = this.#chatHistoryDb.getOpenTabId(historyID)

Expand All @@ -164,7 +189,14 @@ export class TabBarController {
return { ...params, success: false }
}

await this.onExportTab(openTabID)
const format = await this.onExportTab(openTabID)

this.#telemetryService.emitChatHistoryAction({
action: ChatHistoryActionType.Export,
languageServerVersion: this.#features.runtime.serverInfo.version,
filenameExt: format,
result: 'Succeeded',
})
} else {
this.#features.logging.error(`Unsupported action: ${params.action}`)
return { ...params, success: false }
Expand All @@ -175,7 +207,13 @@ export class TabBarController {

async onTabBarAction(params: TabBarActionParams) {
if (params.action === 'export' && params.tabId) {
await this.onExportTab(params.tabId)
const format = await this.onExportTab(params.tabId)

this.#telemetryService.emitExportTab({
filenameExt: format,
languageServerVersion: this.#features.runtime.serverInfo.version,
result: 'Succeeded',
})

return { ...params, success: true }
}
Expand Down Expand Up @@ -210,6 +248,8 @@ export class TabBarController {
})

await this.#features.workspace.fs.writeFile(targetPath.path, content)

return format
}

/**
Expand Down Expand Up @@ -242,6 +282,13 @@ export class TabBarController {
await this.restoreTab(conversation)
}
}
this.#telemetryService.emitLoadHistory({
amazonqTimeToLoadHistory: this.#chatHistoryDb.getLoadTime() ?? -1,
amazonqHistoryFileSize: this.#chatHistoryDb.getDatabaseFileSize() ?? -1,
openTabCount: openConversations.length,
languageServerVersion: this.#features.runtime.serverInfo.version,
result: 'Succeeded',
})
}
}

Expand Down
Loading
Loading