From 6363f66aac4432bd66e5629b7884be435bb656bb Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Tue, 10 Dec 2024 10:05:31 -0800 Subject: [PATCH 01/19] reduce system status poll freq --- .../Bug Fix-c685f18f-0c30-49a0-b4d8-e4eab0cf3bc6.json | 4 ++++ packages/core/src/amazonq/lsp/lspController.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-c685f18f-0c30-49a0-b4d8-e4eab0cf3bc6.json diff --git a/packages/amazonq/.changes/next-release/Bug Fix-c685f18f-0c30-49a0-b4d8-e4eab0cf3bc6.json b/packages/amazonq/.changes/next-release/Bug Fix-c685f18f-0c30-49a0-b4d8-e4eab0cf3bc6.json new file mode 100644 index 00000000000..ee36eb1703d --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-c685f18f-0c30-49a0-b4d8-e4eab0cf3bc6.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Reduce frequency of system status poll" +} diff --git a/packages/core/src/amazonq/lsp/lspController.ts b/packages/core/src/amazonq/lsp/lspController.ts index 1b948625e76..5fed82b7bd9 100644 --- a/packages/core/src/amazonq/lsp/lspController.ts +++ b/packages/core/src/amazonq/lsp/lspController.ts @@ -58,7 +58,7 @@ export interface Manifest { } const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json' // this LSP client in Q extension is only going to work with these LSP server versions -const supportedLspServerVersions = ['0.1.29'] +const supportedLspServerVersions = ['0.1.32'] const nodeBinName = process.platform === 'win32' ? 'node.exe' : 'node' From ce1733afa85ea21a9dc342dd968e1c2e90559f6a Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Tue, 12 Nov 2024 14:35:07 -0800 Subject: [PATCH 02/19] ai gen --- .../codewhisperer/tracker/qCodeGenTracker.ts | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts diff --git a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts new file mode 100644 index 00000000000..0e6e95cf661 --- /dev/null +++ b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts @@ -0,0 +1,163 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { getLogger } from '../../shared/logger/logger' +import * as CodeWhispererConstants from '../models/constants' +import globals from '../../shared/extensionGlobals' +import { vsCodeState } from '../models/model' +import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry' +import { runtimeLanguageContext } from '../util/runtimeLanguageContext' +import { TelemetryHelper } from '../util/telemetryHelper' +import { AuthUtil } from '../util/authUtil' +import { getSelectedCustomization } from '../util/customizationUtil' +import { codeWhispererClient as client } from '../client/codewhisperer' +import { isAwsError } from '../../shared/errors' + +/** + * This singleton class is mainly used for calculating the total code written by Amazon Q and user + * It is meant to replace `CodeWhispererCodeCoverageTracker` + */ +export class QCodeGenTracker { + private _totalNewCodeCharacterCount: number + private _totalNewCodeLineCount: number + private _timer?: NodeJS.Timer + private _serviceInvocationCount: number + + static #instance: QCodeGenTracker + + private constructor() { + this._totalNewCodeLineCount = 0 + this._totalNewCodeCharacterCount = 0 + this._serviceInvocationCount = 0 + } + + public static get instance() { + return (this.#instance ??= new this()) + } + + public get serviceInvocationCount(): number { + return this._serviceInvocationCount + } + + public isActive(): boolean { + return TelemetryHelper.instance.isTelemetryEnabled() && AuthUtil.instance.isConnected() + } + + public onQFeatureInvoked() { + this._serviceInvocationCount += 1 + } + + public flush() { + if (!this.isActive()) { + this._totalNewCodeLineCount = 0 + this._totalNewCodeCharacterCount = 0 + this.closeTimer() + return + } + try { + this.emitCodeContribution() + } catch (error) { + getLogger().error(`Encountered ${error} when emitting code contribution metric`) + } + } + + public emitCodeContribution() { + const selectedCustomization = getSelectedCustomization() + if (this._serviceInvocationCount <= 0) { + getLogger().debug(`Skip emiting code contribution metric. There is no Amazon Q active usage. `) + return + } + client + .sendTelemetryEvent({ + telemetryEvent: { + codeCoverageEvent: { + customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(this._language), + }, + acceptedCharacterCount: 0, + totalCharacterCount: 0, + timestamp: new Date(Date.now()), + totalNewCodeCharacterCount: 0, + totalNewCodeLineCount: 0, + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + + getLogger().debug( + `Failed to sendTelemetryEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) + } + + private tryStartTimer() { + if (this._timer !== undefined) { + return + } + const currentDate = new globals.clock.Date() + const startTime = performance.now() + this._timer = setTimeout(() => { + try { + const currentTime = new globals.clock.Date().getTime() + const delay: number = CodeWhispererConstants.defaultCheckPeriodMillis + const diffTime: number = startTime + delay + if (diffTime <= currentTime) { + if (this._totalNewCodeCharacterCount > 0) { + this.flush() + } else { + getLogger().debug( + `CodeWhispererCodeCoverageTracker: skipped telemetry due to empty tokens array` + ) + } + } + } catch (e) { + getLogger().verbose(`Exception Thrown from CodeWhispererCodeCoverageTracker: ${e}`) + } finally { + this.resetTracker() + this.closeTimer() + } + }, CodeWhispererConstants.defaultCheckPeriodMillis) + } + + private resetTracker() { + this._totalTokens = {} + this._acceptedTokens = {} + this._startTime = 0 + this._serviceInvocationCount = 0 + } + + private closeTimer() { + if (this._timer !== undefined) { + clearTimeout(this._timer) + this._timer = undefined + } + } + + public onTextDocumentChange(e: vscode.TextDocumentChangeEvent) { + if ( + !runtimeLanguageContext.isLanguageSupported(e.document.languageId) || + vsCodeState.isCodeWhispererEditing || + e.contentChanges.length === 0 + ) { + return + } + const contentChange = e.contentChanges[0] + if (contentChange.text.length > 50) { + return + } + this._totalNewCodeCharacterCount += contentChange.text.length + // start 5 min data reporting once valid user input is detected + this.tryStartTimer() + } +} From 0036757fc1842b515c7b5d22ecee24f28461f3ad Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Tue, 12 Nov 2024 16:22:16 -0800 Subject: [PATCH 03/19] ai code gen --- packages/core/src/codewhisperer/activation.ts | 3 +- .../service/recommendationHandler.ts | 2 + .../codewhisperer/tracker/qCodeGenTracker.ts | 67 ++++++++----------- .../codewhispererChat/clients/chat/v0/chat.ts | 3 + 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index 0ed50296da4..b9f980ad0f0 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -99,6 +99,7 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr import { setContext } from '../shared/vscode/setContext' import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' import { detectCommentAboveLine } from '../shared/utilities/commentUtils' +import { QCodeGenTracker } from './tracker/qCodeGenTracker' let localize: nls.LocalizeFunc @@ -555,7 +556,7 @@ export async function activate(context: ExtContext): Promise { } CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e) - + QCodeGenTracker.instance.onTextDocumentChange(e) /** * Handle this keystroke event only when * 1. It is not a backspace diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index bbcd177a03b..0cd76c527f9 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -44,6 +44,7 @@ import { openUrl } from '../../shared/utilities/vsCodeUtils' import { indent } from '../../shared/utilities/textUtilities' import path from 'path' import { isIamConnection } from '../../auth/connection' +import { QCodeGenTracker } from '../tracker/qCodeGenTracker' /** * This class is for getRecommendation/listRecommendation API calls and its states @@ -316,6 +317,7 @@ export class RecommendationHandler { getLogger().debug(msg) if (invocationResult === 'Succeeded') { CodeWhispererCodeCoverageTracker.getTracker(session.language)?.incrementServiceInvocationCount() + QCodeGenTracker.instance.onQFeatureInvoked() } else { if ( (errorMessage?.includes(invalidCustomizationMessage) && errorCode === 'AccessDeniedException') || diff --git a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts index 0e6e95cf661..3fa8e831384 100644 --- a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts +++ b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts @@ -6,9 +6,7 @@ import * as vscode from 'vscode' import { getLogger } from '../../shared/logger/logger' import * as CodeWhispererConstants from '../models/constants' -import globals from '../../shared/extensionGlobals' import { vsCodeState } from '../models/model' -import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { TelemetryHelper } from '../util/telemetryHelper' import { AuthUtil } from '../util/authUtil' @@ -27,6 +25,7 @@ export class QCodeGenTracker { private _serviceInvocationCount: number static #instance: QCodeGenTracker + static copySnippetThreshold = 50 private constructor() { this._totalNewCodeLineCount = 0 @@ -50,39 +49,21 @@ export class QCodeGenTracker { this._serviceInvocationCount += 1 } - public flush() { - if (!this.isActive()) { - this._totalNewCodeLineCount = 0 - this._totalNewCodeCharacterCount = 0 - this.closeTimer() - return - } - try { - this.emitCodeContribution() - } catch (error) { - getLogger().error(`Encountered ${error} when emitting code contribution metric`) - } - } - public emitCodeContribution() { const selectedCustomization = getSelectedCustomization() - if (this._serviceInvocationCount <= 0) { - getLogger().debug(`Skip emiting code contribution metric. There is no Amazon Q active usage. `) - return - } client .sendTelemetryEvent({ telemetryEvent: { codeCoverageEvent: { customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, programmingLanguage: { - languageName: runtimeLanguageContext.toRuntimeLanguage(this._language), + languageName: 'plaintext', }, acceptedCharacterCount: 0, totalCharacterCount: 0, timestamp: new Date(Date.now()), - totalNewCodeCharacterCount: 0, - totalNewCodeLineCount: 0, + totalNewCodeCharacterCount: this._totalNewCodeCharacterCount, + totalNewCodeLineCount: this._totalNewCodeLineCount, }, }, }) @@ -92,11 +73,8 @@ export class QCodeGenTracker { if (isAwsError(error)) { requestId = error.requestId } - getLogger().debug( - `Failed to sendTelemetryEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${ - error.message - }` + `Failed to sendTelemetryEvent, requestId: ${requestId ?? ''}, message: ${error.message}` ) }) } @@ -105,24 +83,31 @@ export class QCodeGenTracker { if (this._timer !== undefined) { return } - const currentDate = new globals.clock.Date() + if (!this.isActive()) { + getLogger().debug(`Skip emiting code contribution metric. Telemetry disabled or not logged in. `) + this.resetTracker() + this.closeTimer() + return + } const startTime = performance.now() this._timer = setTimeout(() => { try { - const currentTime = new globals.clock.Date().getTime() + const currentTime = performance.now() const delay: number = CodeWhispererConstants.defaultCheckPeriodMillis const diffTime: number = startTime + delay if (diffTime <= currentTime) { - if (this._totalNewCodeCharacterCount > 0) { - this.flush() - } else { - getLogger().debug( - `CodeWhispererCodeCoverageTracker: skipped telemetry due to empty tokens array` - ) + if (this._serviceInvocationCount <= 0) { + getLogger().debug(`Skip emiting code contribution metric. There is no active Amazon Q usage. `) + return + } + if (this._totalNewCodeCharacterCount === 0) { + getLogger().debug(`Skip emiting code contribution metric. There is no new code added. `) + return } + this.emitCodeContribution() } } catch (e) { - getLogger().verbose(`Exception Thrown from CodeWhispererCodeCoverageTracker: ${e}`) + getLogger().verbose(`Exception Thrown from QCodeGenTracker: ${e}`) } finally { this.resetTracker() this.closeTimer() @@ -131,9 +116,8 @@ export class QCodeGenTracker { } private resetTracker() { - this._totalTokens = {} - this._acceptedTokens = {} - this._startTime = 0 + this._totalNewCodeLineCount = 0 + this._totalNewCodeCharacterCount = 0 this._serviceInvocationCount = 0 } @@ -153,10 +137,13 @@ export class QCodeGenTracker { return } const contentChange = e.contentChanges[0] - if (contentChange.text.length > 50) { + // if user copies code into the editor for more than 50 characters + // do not count this as total new code, this will skew the data. + if (contentChange.text.length > QCodeGenTracker.copySnippetThreshold) { return } this._totalNewCodeCharacterCount += contentChange.text.length + this._totalNewCodeLineCount += contentChange.text.split('\n').length - 1 // start 5 min data reporting once valid user input is detected this.tryStartTimer() } diff --git a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts index fc164ebb95c..22040720bb1 100644 --- a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts +++ b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts @@ -9,6 +9,7 @@ import * as vscode from 'vscode' import { ToolkitError } from '../../../../shared/errors' import { createCodeWhispererChatStreamingClient } from '../../../../shared/clients/codewhispererChatClient' import { createQDeveloperStreamingClient } from '../../../../shared/clients/qDeveloperChatClient' +import { QCodeGenTracker } from '../../../../codewhisperer/tracker/qCodeGenTracker' export class ChatSession { private sessionId?: string @@ -67,6 +68,8 @@ export class ChatSession { this.sessionId = response.conversationId + QCodeGenTracker.instance.onQFeatureInvoked() + return response } } From dd72f012305c51333876b9194eddb021607ca43c Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Tue, 12 Nov 2024 17:16:09 -0800 Subject: [PATCH 04/19] more code --- .../commands/onInlineAcceptance.ts | 2 + .../codewhisperer/tracker/qCodeGenTracker.ts | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts index 3fd91d0f996..bb0fe597dca 100644 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts @@ -32,6 +32,7 @@ import { RecommendationService } from '../service/recommendationService' import { Container } from '../service/serviceContainer' import { telemetry } from '../../shared/telemetry' import { TelemetryHelper } from '../util/telemetryHelper' +import { QCodeGenTracker } from '../tracker/qCodeGenTracker' export const acceptSuggestion = Commands.declare( 'aws.amazonq.accept', @@ -126,6 +127,7 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept acceptanceEntry.editor.document.getText(insertedCoderange), acceptanceEntry.editor.document.fileName ) + QCodeGenTracker.instance.onInlineCompletionAcceptance(acceptanceEntry) if (acceptanceEntry.references !== undefined) { const referenceLog = ReferenceLogViewProvider.getReferenceLog( acceptanceEntry.recommendation, diff --git a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts index 3fa8e831384..c223c5d6c50 100644 --- a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts +++ b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode' import { getLogger } from '../../shared/logger/logger' import * as CodeWhispererConstants from '../models/constants' -import { vsCodeState } from '../models/model' +import { OnRecommendationAcceptanceEntry, vsCodeState } from '../models/model' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { TelemetryHelper } from '../util/telemetryHelper' import { AuthUtil } from '../util/authUtil' @@ -45,6 +45,8 @@ export class QCodeGenTracker { return TelemetryHelper.instance.isTelemetryEnabled() && AuthUtil.instance.isConnected() } + // this should be invoked whenever there is a successful Q feature invocation + // for all Q features public onQFeatureInvoked() { this._serviceInvocationCount += 1 } @@ -128,6 +130,10 @@ export class QCodeGenTracker { } } + private countNewLines(str: string) { + return str.split('\n').length - 1 + } + public onTextDocumentChange(e: vscode.TextDocumentChangeEvent) { if ( !runtimeLanguageContext.isLanguageSupported(e.document.languageId) || @@ -143,8 +149,42 @@ export class QCodeGenTracker { return } this._totalNewCodeCharacterCount += contentChange.text.length - this._totalNewCodeLineCount += contentChange.text.split('\n').length - 1 + this._totalNewCodeLineCount += this.countNewLines(contentChange.text) // start 5 min data reporting once valid user input is detected this.tryStartTimer() } + + // add Q inline completion contributed code to total code written + public onInlineCompletionAcceptance(acceptanceEntry: OnRecommendationAcceptanceEntry) { + let typeaheadLength = 0 + if (acceptanceEntry.editor) { + typeaheadLength = acceptanceEntry.editor.document.getText(acceptanceEntry.range).length + } + const documentChangeLength = acceptanceEntry.recommendation.length - typeaheadLength + // if the inline completion is less than 50 characters, it will be auto captured by onTextDocumentChange + // notice that the document change event of such acceptance do not include typeahead + if (documentChangeLength <= QCodeGenTracker.copySnippetThreshold) { + return + } + this._totalNewCodeCharacterCount += acceptanceEntry.recommendation.length + this._totalNewCodeLineCount += this.countNewLines(acceptanceEntry.recommendation) + } + + // add Q chat insert to cursor code to total code written + public onQChatInsertion() {} + + // add Q inline chat acceptance to total code written + public onInlineChat() {} + + // add Q inline chat acceptance to total code written + public onTransformAcceptance() {} + + // add Q feature dev acceptance to total code written + public onFeatureDevAcceptance() {} + + // add Q UTG acceptance to total code written + public onUtgAcceptance() {} + + // add Q UTG acceptance to total code written + public onDocAcceptance() {} } From ad0e0185ba38d13b90cbf26018abf413cb92e84f Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 13 Nov 2024 16:57:38 -0800 Subject: [PATCH 05/19] update --- .../controller/inlineChatController.ts | 2 ++ packages/core/src/codewhisperer/index.ts | 1 + .../codewhisperer/tracker/qCodeGenTracker.ts | 35 +++++++++++-------- .../controllers/chat/telemetryHelper.ts | 6 +++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts index ce0df6a0878..154531eaa25 100644 --- a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -13,6 +13,7 @@ import { computeDecorations } from '../decorations/computeDecorations' import { CodelensProvider } from '../codeLenses/codeLenseProvider' import { PromptMessage, ReferenceLogController } from 'aws-core-vscode/codewhispererChat' import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' +import { QCodeGenTracker } from 'aws-core-vscode/codewhisperer' import { codicon, getIcon, @@ -68,6 +69,7 @@ export class InlineChatController { }, this.task ) + QCodeGenTracker.instance.onInlineChatAcceptance() } const deletions = task.diff.filter((diff) => diff.type === 'deletion') await editor.edit( diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 54a1c508322..76e19a168a7 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -101,3 +101,4 @@ export * as CodeWhispererConstants from '../codewhisperer/models/constants' export { getSelectedCustomization } from './util/customizationUtil' export { Container } from './service/serviceContainer' export * from './util/gitUtil' +export { QCodeGenTracker } from './tracker/qCodeGenTracker' diff --git a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts index c223c5d6c50..37e60a7d5ea 100644 --- a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts +++ b/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts @@ -22,7 +22,7 @@ export class QCodeGenTracker { private _totalNewCodeCharacterCount: number private _totalNewCodeLineCount: number private _timer?: NodeJS.Timer - private _serviceInvocationCount: number + private _qUsageCount: number static #instance: QCodeGenTracker static copySnippetThreshold = 50 @@ -30,17 +30,13 @@ export class QCodeGenTracker { private constructor() { this._totalNewCodeLineCount = 0 this._totalNewCodeCharacterCount = 0 - this._serviceInvocationCount = 0 + this._qUsageCount = 0 } public static get instance() { return (this.#instance ??= new this()) } - public get serviceInvocationCount(): number { - return this._serviceInvocationCount - } - public isActive(): boolean { return TelemetryHelper.instance.isTelemetryEnabled() && AuthUtil.instance.isConnected() } @@ -48,7 +44,7 @@ export class QCodeGenTracker { // this should be invoked whenever there is a successful Q feature invocation // for all Q features public onQFeatureInvoked() { - this._serviceInvocationCount += 1 + this._qUsageCount += 1 } public emitCodeContribution() { @@ -98,7 +94,7 @@ export class QCodeGenTracker { const delay: number = CodeWhispererConstants.defaultCheckPeriodMillis const diffTime: number = startTime + delay if (diffTime <= currentTime) { - if (this._serviceInvocationCount <= 0) { + if (this._qUsageCount <= 0) { getLogger().debug(`Skip emiting code contribution metric. There is no active Amazon Q usage. `) return } @@ -120,7 +116,7 @@ export class QCodeGenTracker { private resetTracker() { this._totalNewCodeLineCount = 0 this._totalNewCodeCharacterCount = 0 - this._serviceInvocationCount = 0 + this._qUsageCount = 0 } private closeTimer() { @@ -171,20 +167,29 @@ export class QCodeGenTracker { } // add Q chat insert to cursor code to total code written - public onQChatInsertion() {} + public onQChatInsertion(acceptedCharacterCount?: number, acceptedLineCount?: number) { + if (acceptedCharacterCount && acceptedLineCount) { + // if the chat inserted code is less than 50 characters, it will be auto captured by onTextDocumentChange + if (acceptedCharacterCount <= QCodeGenTracker.copySnippetThreshold) { + return + } + this._totalNewCodeCharacterCount += acceptedCharacterCount + this._totalNewCodeLineCount += acceptedLineCount + } + } // add Q inline chat acceptance to total code written - public onInlineChat() {} + public onInlineChatAcceptance() {} - // add Q inline chat acceptance to total code written + // TODO: add Q inline chat acceptance to total code written public onTransformAcceptance() {} - // add Q feature dev acceptance to total code written + // TODO: add Q feature dev acceptance to total code written public onFeatureDevAcceptance() {} - // add Q UTG acceptance to total code written + // TODO: add Q UTG acceptance to total code written public onUtgAcceptance() {} - // add Q UTG acceptance to total code written + // TODO: add Q UTG acceptance to total code written public onDocAcceptance() {} } diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index feaeec75969..f460a38a7de 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -38,6 +38,7 @@ import { supportedLanguagesList } from '../chat/chatRequest/converter' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' import { undefinedIfEmpty } from '../../../shared' +import { QCodeGenTracker } from '../../../codewhisperer/tracker/qCodeGenTracker' export function logSendTelemetryEventFailure(error: any) { let requestId: string | undefined @@ -321,7 +322,10 @@ export class CWCTelemetryHelper { return } telemetry.amazonq_interactWithMessage.emit(event) - + QCodeGenTracker.instance.onQChatInsertion( + event.cwsprChatAcceptedCharactersLength, + event.cwsprChatAcceptedNumberOfLines + ) codeWhispererClient .sendTelemetryEvent({ telemetryEvent: { From 288617f6dba8af41df2aefe6450d6e3dd1f643cf Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 11 Dec 2024 13:04:09 -0800 Subject: [PATCH 06/19] update --- .../controller/inlineChatController.ts | 7 +- .../commons/controllers/contentController.ts | 5 + packages/core/src/codewhisperer/activation.ts | 4 +- .../codewhisperer/client/user-service-2.json | 4 +- .../commands/onInlineAcceptance.ts | 4 +- packages/core/src/codewhisperer/index.ts | 2 +- .../service/inlineCompletionItemProvider.ts | 2 + .../service/recommendationHandler.ts | 4 +- ...enTracker.ts => userWrittenCodeTracker.ts} | 118 +++++++--------- .../codewhispererChat/clients/chat/v0/chat.ts | 4 +- .../controllers/chat/telemetryHelper.ts | 6 +- packages/toolkit/package.json | 127 +++++++++++------- 12 files changed, 156 insertions(+), 131 deletions(-) rename packages/core/src/codewhisperer/tracker/{qCodeGenTracker.ts => userWrittenCodeTracker.ts} (59%) diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts index 154531eaa25..f6a8bf94cc2 100644 --- a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -13,7 +13,7 @@ import { computeDecorations } from '../decorations/computeDecorations' import { CodelensProvider } from '../codeLenses/codeLenseProvider' import { PromptMessage, ReferenceLogController } from 'aws-core-vscode/codewhispererChat' import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' -import { QCodeGenTracker } from 'aws-core-vscode/codewhisperer' +import { UserWrittenCodeTracker } from 'aws-core-vscode/codewhisperer' import { codicon, getIcon, @@ -69,7 +69,6 @@ export class InlineChatController { }, this.task ) - QCodeGenTracker.instance.onInlineChatAcceptance() } const deletions = task.diff.filter((diff) => diff.type === 'deletion') await editor.edit( @@ -86,6 +85,7 @@ export class InlineChatController { await this.updateTaskAndLenses(task) this.referenceLogController.addReferenceLog(task.codeReferences, task.replacement ? task.replacement : '') await this.reset() + UserWrittenCodeTracker.instance.onQFinishesEdits() } public async rejectAllChanges(task = this.task, userInvoked: boolean): Promise { @@ -199,7 +199,8 @@ export class InlineChatController { getLogger().info('inlineQuickPick query is empty') return } - + UserWrittenCodeTracker.instance.onQStartsMakingEdits() + UserWrittenCodeTracker.instance.onQFeatureInvoked() this.userQuery = query await textDocumentUtil.addEofNewline(editor) this.task = await this.createTask(query, editor.document, editor.selection) diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index 0af3b317025..a3b0ab8ff01 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -16,6 +16,7 @@ import { getSelectionFromRange, } from '../../../shared/utilities/textDocumentUtilities' import { extractFileAndCodeSelectionFromMessage, fs, getErrorMsg, ToolkitError } from '../../../shared' +import { UserWrittenCodeTracker } from '../../../codewhisperer/tracker/userWrittenCodeTracker' export class ContentProvider implements vscode.TextDocumentContentProvider { constructor(private uri: vscode.Uri) {} @@ -41,6 +42,7 @@ export class EditorContentController { ) { const editor = window.activeTextEditor if (editor) { + UserWrittenCodeTracker.instance.onQStartsMakingEdits() const cursorStart = editor.selection.active const indentRange = new vscode.Range(new vscode.Position(cursorStart.line, 0), cursorStart) // use the user editor intent if the position to the left of cursor is just space or tab @@ -71,6 +73,9 @@ export class EditorContentController { getLogger().error('TextEditor.edit failed: %s', (e as Error).message) } ) + .then(() => { + UserWrittenCodeTracker.instance.onQFinishesEdits() + }) } } diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index b9f980ad0f0..f7e9e216341 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -99,7 +99,7 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr import { setContext } from '../shared/vscode/setContext' import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' import { detectCommentAboveLine } from '../shared/utilities/commentUtils' -import { QCodeGenTracker } from './tracker/qCodeGenTracker' +import { UserWrittenCodeTracker } from './tracker/userWrittenCodeTracker' let localize: nls.LocalizeFunc @@ -556,7 +556,7 @@ export async function activate(context: ExtContext): Promise { } CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e) - QCodeGenTracker.instance.onTextDocumentChange(e) + UserWrittenCodeTracker.instance.onTextDocumentChange(e) /** * Handle this keystroke event only when * 1. It is not a backspace diff --git a/packages/core/src/codewhisperer/client/user-service-2.json b/packages/core/src/codewhisperer/client/user-service-2.json index 123160fb0b3..8a847e603d7 100644 --- a/packages/core/src/codewhisperer/client/user-service-2.json +++ b/packages/core/src/codewhisperer/client/user-service-2.json @@ -626,7 +626,9 @@ "timestamp": { "shape": "Timestamp" }, "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" }, "totalNewCodeCharacterCount": { "shape": "PrimitiveInteger" }, - "totalNewCodeLineCount": { "shape": "PrimitiveInteger" } + "totalNewCodeLineCount": { "shape": "PrimitiveInteger" }, + "userWrittenCodeCharacterCount": { "shape": "PrimitiveInteger" }, + "userWrittenCodeLineCount": { "shape": "PrimitiveInteger" } } }, "CodeFixAcceptanceEvent": { diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts index bb0fe597dca..da581d1aacc 100644 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts @@ -32,7 +32,7 @@ import { RecommendationService } from '../service/recommendationService' import { Container } from '../service/serviceContainer' import { telemetry } from '../../shared/telemetry' import { TelemetryHelper } from '../util/telemetryHelper' -import { QCodeGenTracker } from '../tracker/qCodeGenTracker' +import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' export const acceptSuggestion = Commands.declare( 'aws.amazonq.accept', @@ -127,7 +127,7 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept acceptanceEntry.editor.document.getText(insertedCoderange), acceptanceEntry.editor.document.fileName ) - QCodeGenTracker.instance.onInlineCompletionAcceptance(acceptanceEntry) + UserWrittenCodeTracker.instance.onQFinishesEdits() if (acceptanceEntry.references !== undefined) { const referenceLog = ReferenceLogViewProvider.getReferenceLog( acceptanceEntry.recommendation, diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 76e19a168a7..34450856b7b 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -101,4 +101,4 @@ export * as CodeWhispererConstants from '../codewhisperer/models/constants' export { getSelectedCustomization } from './util/customizationUtil' export { Container } from './service/serviceContainer' export * from './util/gitUtil' -export { QCodeGenTracker } from './tracker/qCodeGenTracker' +export { UserWrittenCodeTracker } from './tracker/userWrittenCodeTracker' diff --git a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts index e5ac2212e06..a6c424c321d 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts @@ -12,6 +12,7 @@ import { ReferenceInlineProvider } from './referenceInlineProvider' import { ImportAdderProvider } from './importAdderProvider' import { application } from '../util/codeWhispererApplication' import path from 'path' +import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' export class CWInlineCompletionItemProvider implements vscode.InlineCompletionItemProvider { private activeItemIndex: number | undefined @@ -170,6 +171,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt this.nextMove = 0 TelemetryHelper.instance.setFirstSuggestionShowTime() session.setPerceivedLatency() + UserWrittenCodeTracker.instance.onQStartsMakingEdits() this._onDidShow.fire() if (matchedCount >= 2 || this.nextToken !== '') { const result = [item] diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index 0cd76c527f9..7945e7b51ce 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -44,7 +44,7 @@ import { openUrl } from '../../shared/utilities/vsCodeUtils' import { indent } from '../../shared/utilities/textUtilities' import path from 'path' import { isIamConnection } from '../../auth/connection' -import { QCodeGenTracker } from '../tracker/qCodeGenTracker' +import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' /** * This class is for getRecommendation/listRecommendation API calls and its states @@ -317,7 +317,7 @@ export class RecommendationHandler { getLogger().debug(msg) if (invocationResult === 'Succeeded') { CodeWhispererCodeCoverageTracker.getTracker(session.language)?.incrementServiceInvocationCount() - QCodeGenTracker.instance.onQFeatureInvoked() + UserWrittenCodeTracker.instance.onQFeatureInvoked() } else { if ( (errorMessage?.includes(invalidCustomizationMessage) && errorCode === 'AccessDeniedException') || diff --git a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts similarity index 59% rename from packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts rename to packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index 37e60a7d5ea..3919c997b0a 100644 --- a/packages/core/src/codewhisperer/tracker/qCodeGenTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -6,7 +6,6 @@ import * as vscode from 'vscode' import { getLogger } from '../../shared/logger/logger' import * as CodeWhispererConstants from '../models/constants' -import { OnRecommendationAcceptanceEntry, vsCodeState } from '../models/model' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { TelemetryHelper } from '../util/telemetryHelper' import { AuthUtil } from '../util/authUtil' @@ -15,22 +14,37 @@ import { codeWhispererClient as client } from '../client/codewhisperer' import { isAwsError } from '../../shared/errors' /** - * This singleton class is mainly used for calculating the total code written by Amazon Q and user - * It is meant to replace `CodeWhispererCodeCoverageTracker` + * This singleton class is mainly used for calculating the user written code + * for active Amazon Q users. + * It reports the user written code per 5 minutes when the user is coding and using Amazon Q features */ -export class QCodeGenTracker { - private _totalNewCodeCharacterCount: number - private _totalNewCodeLineCount: number +export class UserWrittenCodeTracker { + private _userWrittenNewCodeCharacterCount: number + private _userWrittenNewCodeLineCount: number + private _qIsMakingEdits: boolean private _timer?: NodeJS.Timer private _qUsageCount: number + private _lastQInvocationTime: number - static #instance: QCodeGenTracker + static #instance: UserWrittenCodeTracker static copySnippetThreshold = 50 + static resetQIsEditingTimeoutMs = 5 * 60 * 1000 private constructor() { - this._totalNewCodeLineCount = 0 - this._totalNewCodeCharacterCount = 0 + this._userWrittenNewCodeLineCount = 0 + this._userWrittenNewCodeCharacterCount = 0 this._qUsageCount = 0 + this._qIsMakingEdits = false + this._timer = undefined + this._lastQInvocationTime = 0 + } + + private resetTracker() { + this._userWrittenNewCodeLineCount = 0 + this._userWrittenNewCodeCharacterCount = 0 + this._qUsageCount = 0 + this._qIsMakingEdits = false + this._lastQInvocationTime = 0 } public static get instance() { @@ -45,6 +59,15 @@ export class QCodeGenTracker { // for all Q features public onQFeatureInvoked() { this._qUsageCount += 1 + this._lastQInvocationTime = performance.now() + } + + public onQStartsMakingEdits() { + this._qIsMakingEdits = true + } + + public onQFinishesEdits() { + this._qIsMakingEdits = false } public emitCodeContribution() { @@ -60,8 +83,8 @@ export class QCodeGenTracker { acceptedCharacterCount: 0, totalCharacterCount: 0, timestamp: new Date(Date.now()), - totalNewCodeCharacterCount: this._totalNewCodeCharacterCount, - totalNewCodeLineCount: this._totalNewCodeLineCount, + userWrittenCodeCharacterCount: this._userWrittenNewCodeCharacterCount, + userWrittenCodeLineCount: this._userWrittenNewCodeLineCount, }, }, }) @@ -98,7 +121,7 @@ export class QCodeGenTracker { getLogger().debug(`Skip emiting code contribution metric. There is no active Amazon Q usage. `) return } - if (this._totalNewCodeCharacterCount === 0) { + if (this._userWrittenNewCodeCharacterCount === 0) { getLogger().debug(`Skip emiting code contribution metric. There is no new code added. `) return } @@ -113,12 +136,6 @@ export class QCodeGenTracker { }, CodeWhispererConstants.defaultCheckPeriodMillis) } - private resetTracker() { - this._totalNewCodeLineCount = 0 - this._totalNewCodeCharacterCount = 0 - this._qUsageCount = 0 - } - private closeTimer() { if (this._timer !== undefined) { clearTimeout(this._timer) @@ -131,65 +148,32 @@ export class QCodeGenTracker { } public onTextDocumentChange(e: vscode.TextDocumentChangeEvent) { + // do not count code written by Q as user written code if ( !runtimeLanguageContext.isLanguageSupported(e.document.languageId) || - vsCodeState.isCodeWhispererEditing || - e.contentChanges.length === 0 + e.contentChanges.length === 0 || + this._qIsMakingEdits ) { + // if the boolean of qIsMakingEdits was incorrectly set to true + // due to unhandled edge cases or early terminated code paths + // reset it back to false after reasonabe period of time + if (this._qIsMakingEdits) { + if (performance.now() - this._lastQInvocationTime > UserWrittenCodeTracker.resetQIsEditingTimeoutMs) { + this._qIsMakingEdits = false + } + } return } const contentChange = e.contentChanges[0] // if user copies code into the editor for more than 50 characters - // do not count this as total new code, this will skew the data. - if (contentChange.text.length > QCodeGenTracker.copySnippetThreshold) { + // do not count this as total new code, this will skew the data, + // reporting highly inflated user written code + if (contentChange.text.length > UserWrittenCodeTracker.copySnippetThreshold) { return } - this._totalNewCodeCharacterCount += contentChange.text.length - this._totalNewCodeLineCount += this.countNewLines(contentChange.text) + this._userWrittenNewCodeCharacterCount += contentChange.text.length + this._userWrittenNewCodeLineCount += this.countNewLines(contentChange.text) // start 5 min data reporting once valid user input is detected this.tryStartTimer() } - - // add Q inline completion contributed code to total code written - public onInlineCompletionAcceptance(acceptanceEntry: OnRecommendationAcceptanceEntry) { - let typeaheadLength = 0 - if (acceptanceEntry.editor) { - typeaheadLength = acceptanceEntry.editor.document.getText(acceptanceEntry.range).length - } - const documentChangeLength = acceptanceEntry.recommendation.length - typeaheadLength - // if the inline completion is less than 50 characters, it will be auto captured by onTextDocumentChange - // notice that the document change event of such acceptance do not include typeahead - if (documentChangeLength <= QCodeGenTracker.copySnippetThreshold) { - return - } - this._totalNewCodeCharacterCount += acceptanceEntry.recommendation.length - this._totalNewCodeLineCount += this.countNewLines(acceptanceEntry.recommendation) - } - - // add Q chat insert to cursor code to total code written - public onQChatInsertion(acceptedCharacterCount?: number, acceptedLineCount?: number) { - if (acceptedCharacterCount && acceptedLineCount) { - // if the chat inserted code is less than 50 characters, it will be auto captured by onTextDocumentChange - if (acceptedCharacterCount <= QCodeGenTracker.copySnippetThreshold) { - return - } - this._totalNewCodeCharacterCount += acceptedCharacterCount - this._totalNewCodeLineCount += acceptedLineCount - } - } - - // add Q inline chat acceptance to total code written - public onInlineChatAcceptance() {} - - // TODO: add Q inline chat acceptance to total code written - public onTransformAcceptance() {} - - // TODO: add Q feature dev acceptance to total code written - public onFeatureDevAcceptance() {} - - // TODO: add Q UTG acceptance to total code written - public onUtgAcceptance() {} - - // TODO: add Q UTG acceptance to total code written - public onDocAcceptance() {} } diff --git a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts index 22040720bb1..f9a2070473b 100644 --- a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts +++ b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode' import { ToolkitError } from '../../../../shared/errors' import { createCodeWhispererChatStreamingClient } from '../../../../shared/clients/codewhispererChatClient' import { createQDeveloperStreamingClient } from '../../../../shared/clients/qDeveloperChatClient' -import { QCodeGenTracker } from '../../../../codewhisperer/tracker/qCodeGenTracker' +import { UserWrittenCodeTracker } from '../../../../codewhisperer/tracker/userWrittenCodeTracker' export class ChatSession { private sessionId?: string @@ -68,7 +68,7 @@ export class ChatSession { this.sessionId = response.conversationId - QCodeGenTracker.instance.onQFeatureInvoked() + UserWrittenCodeTracker.instance.onQFeatureInvoked() return response } diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index f460a38a7de..7886b6bbe4b 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -38,7 +38,6 @@ import { supportedLanguagesList } from '../chat/chatRequest/converter' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' import { undefinedIfEmpty } from '../../../shared' -import { QCodeGenTracker } from '../../../codewhisperer/tracker/qCodeGenTracker' export function logSendTelemetryEventFailure(error: any) { let requestId: string | undefined @@ -189,6 +188,7 @@ export class CWCTelemetryHelper { ) { const conversationId = this.getConversationId(message.tabID) let event: AmazonqInteractWithMessage | undefined + switch (message.command) { case 'insert_code_at_cursor_position': message = message as InsertCodeAtCursorPosition @@ -322,10 +322,6 @@ export class CWCTelemetryHelper { return } telemetry.amazonq_interactWithMessage.emit(event) - QCodeGenTracker.instance.onQChatInsertion( - event.cwsprChatAcceptedCharactersLength, - event.cwsprChatAcceptedNumberOfLines - ) codeWhispererClient .sendTelemetryEvent({ telemetryEvent: { diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index ab1fb3c1bed..83b9e8f3afe 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -4010,327 +4010,362 @@ "fontCharacter": "\\f1ac" } }, - "aws-amazonq-transform-arrow-dark": { + "aws-amazonq-severity-critical": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ad" } }, - "aws-amazonq-transform-arrow-light": { + "aws-amazonq-severity-high": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ae" } }, - "aws-amazonq-transform-default-dark": { + "aws-amazonq-severity-info": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1af" } }, - "aws-amazonq-transform-default-light": { + "aws-amazonq-severity-low": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b0" } }, - "aws-amazonq-transform-dependencies-dark": { + "aws-amazonq-severity-medium": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b1" } }, - "aws-amazonq-transform-dependencies-light": { + "aws-amazonq-transform-arrow-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b2" } }, - "aws-amazonq-transform-file-dark": { + "aws-amazonq-transform-arrow-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b3" } }, - "aws-amazonq-transform-file-light": { + "aws-amazonq-transform-default-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b4" } }, - "aws-amazonq-transform-logo": { + "aws-amazonq-transform-default-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b5" } }, - "aws-amazonq-transform-step-into-dark": { + "aws-amazonq-transform-dependencies-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b6" } }, - "aws-amazonq-transform-step-into-light": { + "aws-amazonq-transform-dependencies-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b7" } }, - "aws-amazonq-transform-variables-dark": { + "aws-amazonq-transform-file-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b8" } }, - "aws-amazonq-transform-variables-light": { + "aws-amazonq-transform-file-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b9" } }, - "aws-applicationcomposer-icon": { + "aws-amazonq-transform-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ba" } }, - "aws-applicationcomposer-icon-dark": { + "aws-amazonq-transform-step-into-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bb" } }, - "aws-apprunner-service": { + "aws-amazonq-transform-step-into-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bc" } }, - "aws-cdk-logo": { + "aws-amazonq-transform-variables-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bd" } }, - "aws-cloudformation-stack": { + "aws-amazonq-transform-variables-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1be" } }, - "aws-cloudwatch-log-group": { + "aws-applicationcomposer-icon": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bf" } }, - "aws-codecatalyst-logo": { + "aws-applicationcomposer-icon-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c0" } }, - "aws-codewhisperer-icon-black": { + "aws-apprunner-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c1" } }, - "aws-codewhisperer-icon-white": { + "aws-cdk-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c2" } }, - "aws-codewhisperer-learn": { + "aws-cloudformation-stack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c3" } }, - "aws-ecr-registry": { + "aws-cloudwatch-log-group": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c4" } }, - "aws-ecs-cluster": { + "aws-codecatalyst-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c5" } }, - "aws-ecs-container": { + "aws-codewhisperer-icon-black": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c6" } }, - "aws-ecs-service": { + "aws-codewhisperer-icon-white": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c7" } }, - "aws-generic-attach-file": { + "aws-codewhisperer-learn": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c8" } }, - "aws-iot-certificate": { + "aws-ecr-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c9" } }, - "aws-iot-policy": { + "aws-ecs-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ca" } }, - "aws-iot-thing": { + "aws-ecs-container": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cb" } }, - "aws-lambda-function": { + "aws-ecs-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cc" } }, - "aws-mynah-MynahIconBlack": { + "aws-generic-attach-file": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cd" } }, - "aws-mynah-MynahIconWhite": { + "aws-iot-certificate": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ce" } }, - "aws-mynah-logo": { + "aws-iot-policy": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cf" } }, - "aws-redshift-cluster": { + "aws-iot-thing": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d0" } }, - "aws-redshift-cluster-connected": { + "aws-lambda-function": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d1" } }, - "aws-redshift-database": { + "aws-mynah-MynahIconBlack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d2" } }, - "aws-redshift-redshift-cluster-connected": { + "aws-mynah-MynahIconWhite": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d3" } }, - "aws-redshift-schema": { + "aws-mynah-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d4" } }, - "aws-redshift-table": { + "aws-redshift-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d5" } }, - "aws-s3-bucket": { + "aws-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d6" } }, - "aws-s3-create-bucket": { + "aws-redshift-database": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d7" } }, - "aws-schemas-registry": { + "aws-redshift-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d8" } }, - "aws-schemas-schema": { + "aws-redshift-schema": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d9" } }, - "aws-stepfunctions-preview": { + "aws-redshift-table": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1da" } + }, + "aws-s3-bucket": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1db" + } + }, + "aws-s3-create-bucket": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1dc" + } + }, + "aws-schemas-registry": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1dd" + } + }, + "aws-schemas-schema": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1de" + } + }, + "aws-stepfunctions-preview": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1df" + } } }, "notebooks": [ From 26150f44edc82585e258beb9b5d48bf675467610 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 11 Dec 2024 13:06:46 -0800 Subject: [PATCH 07/19] minimize changes --- .../src/codewhispererChat/controllers/chat/telemetryHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index 7886b6bbe4b..feaeec75969 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -188,7 +188,6 @@ export class CWCTelemetryHelper { ) { const conversationId = this.getConversationId(message.tabID) let event: AmazonqInteractWithMessage | undefined - switch (message.command) { case 'insert_code_at_cursor_position': message = message as InsertCodeAtCursorPosition @@ -322,6 +321,7 @@ export class CWCTelemetryHelper { return } telemetry.amazonq_interactWithMessage.emit(event) + codeWhispererClient .sendTelemetryEvent({ telemetryEvent: { From d99e4d1e9b61b110ae7b61e01309319c23aebb3c Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 11 Dec 2024 13:26:23 -0800 Subject: [PATCH 08/19] update --- .../amazonq/src/inlineChat/controller/inlineChatController.ts | 1 - .../core/src/amazonq/commons/controllers/contentController.ts | 3 +++ packages/core/src/amazonqFeatureDev/client/featureDev.ts | 2 ++ packages/core/src/amazonqTest/chat/controller/controller.ts | 4 ++++ packages/core/src/codewhisperer/commands/basicCommands.ts | 3 +++ packages/core/src/codewhisperer/service/testGenHandler.ts | 3 ++- .../codewhisperer/service/transformByQ/transformApiHandler.ts | 2 ++ .../service/transformByQ/transformationResultsViewProvider.ts | 2 ++ packages/core/src/codewhispererChat/clients/chat/v0/chat.ts | 1 + 9 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts index f6a8bf94cc2..f3d5989ffb2 100644 --- a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -200,7 +200,6 @@ export class InlineChatController { return } UserWrittenCodeTracker.instance.onQStartsMakingEdits() - UserWrittenCodeTracker.instance.onQFeatureInvoked() this.userQuery = query await textDocumentUtil.addEofNewline(editor) this.task = await this.createTask(query, editor.document, editor.selection) diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index a3b0ab8ff01..4d7fa35d9a7 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -102,6 +102,7 @@ export class EditorContentController { if (filePath && message?.code?.trim().length > 0 && selection) { try { + UserWrittenCodeTracker.instance.onQStartsMakingEdits() const doc = await vscode.workspace.openTextDocument(filePath) const code = getIndentedCode(message, doc, selection) @@ -135,6 +136,8 @@ export class EditorContentController { const wrappedError = ChatDiffError.chain(error, `Failed to Accept Diff`, { code: chatDiffCode }) getLogger().error('%s: Failed to open diff view %s', chatDiffCode, getErrorMsg(wrappedError, true)) throw wrappedError + } finally { + UserWrittenCodeTracker.instance.onQFinishesEdits() } } } diff --git a/packages/core/src/amazonqFeatureDev/client/featureDev.ts b/packages/core/src/amazonqFeatureDev/client/featureDev.ts index 947949d48a9..cc1c65fd93f 100644 --- a/packages/core/src/amazonqFeatureDev/client/featureDev.ts +++ b/packages/core/src/amazonqFeatureDev/client/featureDev.ts @@ -26,6 +26,7 @@ import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shar import { extensionVersion } from '../../shared/vscode/env' import apiConfig = require('./codewhispererruntime-2022-11-11.json') import { FeatureDevCodeAcceptanceEvent, FeatureDevCodeGenerationEvent, TelemetryEvent } from './featuredevproxyclient' +import { UserWrittenCodeTracker } from '../../codewhisperer' // Re-enable once BE is able to handle retries. const writeAPIRetryOptions = { @@ -255,6 +256,7 @@ export class FeatureDevClient { references?: CodeReference[] } } + UserWrittenCodeTracker.instance.onQFeatureInvoked() const newFileContents: { zipFilePath: string; fileContent: string }[] = [] for (const [filePath, fileContent] of Object.entries(newFiles)) { diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 88490315e45..138af12f6f5 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -21,6 +21,7 @@ import { TestGenerationBuildStep, testGenState, unitTestGenerationCancelMessage, + UserWrittenCodeTracker, } from '../../../codewhisperer' import { fs, @@ -654,12 +655,14 @@ export class TestController { acceptedLines = acceptedLines < 0 ? 0 : acceptedLines acceptedChars -= originalContent.length acceptedChars = acceptedChars < 0 ? 0 : acceptedChars + UserWrittenCodeTracker.instance.onQStartsMakingEdits() const document = await vscode.workspace.openTextDocument(absolutePath) await applyChanges( document, new vscode.Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end), updatedContent ) + UserWrittenCodeTracker.instance.onQFinishesEdits() } else { await fs.writeFile(absolutePath, updatedContent) } @@ -821,6 +824,7 @@ export class TestController { const chatRequest = triggerPayloadToChatRequest(triggerPayload) const client = await createCodeWhispererChatStreamingClient() const response = await client.generateAssistantResponse(chatRequest) + UserWrittenCodeTracker.instance.onQFeatureInvoked() await this.messenger.sendAIResponse( response, session, diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index 07fd358a990..bcdd90073cc 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -66,6 +66,7 @@ import { cancel, confirm } from '../../shared' import { startCodeFixGeneration } from './startCodeFixGeneration' import { DefaultAmazonQAppInitContext } from '../../amazonq/apps/initContext' import path from 'path' +import { UserWrittenCodeTracker } from '../indexNode' const MessageTimeOut = 5_000 @@ -450,6 +451,7 @@ export const applySecurityFix = Commands.declare( } let languageId = undefined try { + UserWrittenCodeTracker.instance.onQStartsMakingEdits() const document = await vscode.workspace.openTextDocument(targetFilePath) languageId = document.languageId const updatedContent = await getPatchedCode(targetFilePath, suggestedFix.code) @@ -552,6 +554,7 @@ export const applySecurityFix = Commands.declare( applyFixTelemetryEntry.result, !!targetIssue.suggestedFixes.length ) + UserWrittenCodeTracker.instance.onQFinishesEdits() } } ) diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts index 36f5b5b1d63..00c1cfbdeb5 100644 --- a/packages/core/src/codewhisperer/service/testGenHandler.ts +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -16,7 +16,7 @@ import CodeWhispererUserClient, { import { CreateUploadUrlError, InvalidSourceZipError, TestGenFailedError, TestGenTimedOutError } from '../models/errors' import { getMd5, uploadArtifactToS3 } from './securityScanHandler' import { fs, randomUUID, sleep, tempDirPath } from '../../shared' -import { ShortAnswer, TestGenerationJobStatus, testGenState } from '..' +import { ShortAnswer, TestGenerationJobStatus, testGenState, UserWrittenCodeTracker } from '..' import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession' import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient' import { downloadExportResultArchive } from '../../shared/utilities/download' @@ -292,5 +292,6 @@ export async function downloadResultArchive( throw e } finally { cwStreamingClient.destroy() + UserWrittenCodeTracker.instance.onQFeatureInvoked() } } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index fb90241ff68..c8ea022435a 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -49,6 +49,7 @@ import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSess import { encodeHTML } from '../../../shared/utilities/textUtilities' import { convertToTimeString } from '../../../shared/datetime' import { getAuthType } from '../../../auth/utils' +import { UserWrittenCodeTracker } from '../../tracker/userWrittenCodeTracker' export function getSha256(buffer: Buffer) { const hasher = crypto.createHash('sha256') @@ -735,6 +736,7 @@ export async function downloadResultArchive( throw e } finally { cwStreamingClient.destroy() + UserWrittenCodeTracker.instance.onQFeatureInvoked() } } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 13951f7508a..19bcd346112 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -27,6 +27,7 @@ import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/ import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' import { setContext } from '../../../shared/vscode/setContext' import * as codeWhisperer from '../../client/codewhisperer' +import { UserWrittenCodeTracker } from '../../tracker/userWrittenCodeTracker' export abstract class ProposedChangeNode { abstract readonly resourcePath: string @@ -426,6 +427,7 @@ export class ProposedTransformationExplorer { throw new Error('Error downloading diff') } finally { cwStreamingClient.destroy() + UserWrittenCodeTracker.instance.onQFeatureInvoked() } let deserializeErrorMessage = undefined diff --git a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts index f9a2070473b..b849b328bac 100644 --- a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts +++ b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts @@ -49,6 +49,7 @@ export class ChatSession { } } + UserWrittenCodeTracker.instance.onQFeatureInvoked() return response } From f75c58a49afedaa99190d9d82a930358b2dd5244 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 11 Dec 2024 13:50:29 -0800 Subject: [PATCH 09/19] fix cyclic dependency --- packages/core/src/codewhisperer/commands/basicCommands.ts | 2 +- .../src/codewhisperer/tracker/userWrittenCodeTracker.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index bcdd90073cc..f67a9a2ec9e 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -66,7 +66,7 @@ import { cancel, confirm } from '../../shared' import { startCodeFixGeneration } from './startCodeFixGeneration' import { DefaultAmazonQAppInitContext } from '../../amazonq/apps/initContext' import path from 'path' -import { UserWrittenCodeTracker } from '../indexNode' +import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' const MessageTimeOut = 5_000 diff --git a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index 3919c997b0a..6ebfd4b2657 100644 --- a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -5,7 +5,6 @@ import * as vscode from 'vscode' import { getLogger } from '../../shared/logger/logger' -import * as CodeWhispererConstants from '../models/constants' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { TelemetryHelper } from '../util/telemetryHelper' import { AuthUtil } from '../util/authUtil' @@ -29,7 +28,7 @@ export class UserWrittenCodeTracker { static #instance: UserWrittenCodeTracker static copySnippetThreshold = 50 static resetQIsEditingTimeoutMs = 5 * 60 * 1000 - + static defaultCheckPeriodMillis = 1000 * 60 * 5 private constructor() { this._userWrittenNewCodeLineCount = 0 this._userWrittenNewCodeCharacterCount = 0 @@ -114,7 +113,7 @@ export class UserWrittenCodeTracker { this._timer = setTimeout(() => { try { const currentTime = performance.now() - const delay: number = CodeWhispererConstants.defaultCheckPeriodMillis + const delay: number = UserWrittenCodeTracker.defaultCheckPeriodMillis const diffTime: number = startTime + delay if (diffTime <= currentTime) { if (this._qUsageCount <= 0) { @@ -133,7 +132,7 @@ export class UserWrittenCodeTracker { this.resetTracker() this.closeTimer() } - }, CodeWhispererConstants.defaultCheckPeriodMillis) + }, UserWrittenCodeTracker.defaultCheckPeriodMillis) } private closeTimer() { From d2c9ff12c6624458c76c3c8e3fdd113241fd8631 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 11 Dec 2024 19:44:36 -0800 Subject: [PATCH 10/19] add unit test --- .../tracker/userWrittenCodeTracker.test.ts | 187 ++++++++++++++++++ .../tracker/userWrittenCodeTracker.ts | 126 ++++++------ 2 files changed, 258 insertions(+), 55 deletions(-) create mode 100644 packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts diff --git a/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts b/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts new file mode 100644 index 00000000000..6adb7380ba0 --- /dev/null +++ b/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts @@ -0,0 +1,187 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import * as sinon from 'sinon' +import * as vscode from 'vscode' +import { UserWrittenCodeTracker, TelemetryHelper, AuthUtil } from 'aws-core-vscode/codewhisperer' +import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' + +describe('userWrittenCodeTracker', function () { + describe('test isActive', function () { + afterEach(async function () { + await resetCodeWhispererGlobalVariables() + UserWrittenCodeTracker.instance.reset() + sinon.restore() + }) + + it('inactive case: telemetryEnable = true, isConnected = false', function () { + sinon.stub(TelemetryHelper.instance, 'isTelemetryEnabled').returns(true) + sinon.stub(AuthUtil.instance, 'isConnected').returns(false) + assert.strictEqual(UserWrittenCodeTracker.instance.isActive(), false) + }) + + it('inactive case: telemetryEnabled = false, isConnected = false', function () { + sinon.stub(TelemetryHelper.instance, 'isTelemetryEnabled').returns(false) + sinon.stub(AuthUtil.instance, 'isConnected').returns(false) + assert.strictEqual(UserWrittenCodeTracker.instance.isActive(), false) + }) + + it('active case: telemetryEnabled = true, isConnected = true', function () { + sinon.stub(TelemetryHelper.instance, 'isTelemetryEnabled').returns(true) + sinon.stub(AuthUtil.instance, 'isConnected').returns(true) + assert.strictEqual(UserWrittenCodeTracker.instance.isActive(), true) + }) + }) + + describe('onDocumentChange', function () { + let tracker: UserWrittenCodeTracker | undefined + + beforeEach(async function () { + await resetCodeWhispererGlobalVariables() + tracker = UserWrittenCodeTracker.instance + if (tracker) { + sinon.stub(tracker, 'isActive').returns(true) + } + }) + + afterEach(function () { + sinon.restore() + UserWrittenCodeTracker.instance.reset() + }) + + it('Should skip when content change size is more than 50', function () { + if (!tracker) { + assert.fail() + } + tracker.onQFeatureInvoked() + tracker.onTextDocumentChange({ + reason: undefined, + document: createMockDocument(), + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 600), + rangeOffset: 0, + rangeLength: 600, + text: 'def twoSum(nums, target):\nfor '.repeat(20), + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + assert.strictEqual(tracker.getUserWrittenLines('python'), 0) + }) + + it('Should not skip when content change size is less than 50', function () { + if (!tracker) { + assert.fail() + } + tracker.onQFeatureInvoked() + tracker.onTextDocumentChange({ + reason: undefined, + document: createMockDocument(), + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 49), + rangeOffset: 0, + rangeLength: 49, + text: 'a = 123'.repeat(7), + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + assert.strictEqual(tracker.getUserWrittenLines('python'), 0) + }) + + it('Should skip when CodeWhisperer is editing', function () { + if (!tracker) { + assert.fail() + } + tracker.onQFeatureInvoked() + tracker.onQStartsMakingEdits() + tracker.onTextDocumentChange({ + reason: undefined, + document: createMockDocument(), + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 30), + rangeOffset: 0, + rangeLength: 30, + text: 'def twoSum(nums, target):\nfor', + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + assert.strictEqual(tracker.getUserWrittenLines('python'), 0) + }) + + it('Should not reduce tokens when delete', function () { + if (!tracker) { + assert.fail() + } + const doc = createMockDocument('import math', 'test.py', 'python') + + tracker.onQFeatureInvoked() + tracker.onTextDocumentChange({ + reason: undefined, + document: doc, + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 1), + rangeOffset: 0, + rangeLength: 0, + text: 'a', + }, + ], + }) + tracker.onTextDocumentChange({ + reason: undefined, + document: doc, + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 1), + rangeOffset: 0, + rangeLength: 0, + text: 'b', + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + tracker.onTextDocumentChange({ + reason: undefined, + document: doc, + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 1), + rangeOffset: 1, + rangeLength: 1, + text: '', + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + }) + }) + + describe('emitCodeWhispererCodeContribution', function () { + let tracker: UserWrittenCodeTracker | undefined + + beforeEach(async function () { + await resetCodeWhispererGlobalVariables() + tracker = UserWrittenCodeTracker.instance + tracker.reset() + if (tracker) { + sinon.stub(tracker, 'isActive').returns(true) + } + }) + + afterEach(function () { + sinon.restore() + }) + + it('should emit correct code coverage telemetry in python file', async function () {}) + + it('Should not emit if user has not use any Q feature', async function () {}) + }) +}) diff --git a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index 6ebfd4b2657..715b9e586e1 100644 --- a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -11,6 +11,7 @@ import { AuthUtil } from '../util/authUtil' import { getSelectedCustomization } from '../util/customizationUtil' import { codeWhispererClient as client } from '../client/codewhisperer' import { isAwsError } from '../../shared/errors' +import { CodewhispererLanguage } from '../../shared' /** * This singleton class is mainly used for calculating the user written code @@ -18,8 +19,8 @@ import { isAwsError } from '../../shared/errors' * It reports the user written code per 5 minutes when the user is coding and using Amazon Q features */ export class UserWrittenCodeTracker { - private _userWrittenNewCodeCharacterCount: number - private _userWrittenNewCodeLineCount: number + private _userWrittenNewCodeCharacterCount: Map + private _userWrittenNewCodeLineCount: Map private _qIsMakingEdits: boolean private _timer?: NodeJS.Timer private _qUsageCount: number @@ -30,22 +31,14 @@ export class UserWrittenCodeTracker { static resetQIsEditingTimeoutMs = 5 * 60 * 1000 static defaultCheckPeriodMillis = 1000 * 60 * 5 private constructor() { - this._userWrittenNewCodeLineCount = 0 - this._userWrittenNewCodeCharacterCount = 0 + this._userWrittenNewCodeLineCount = new Map() + this._userWrittenNewCodeCharacterCount = new Map() this._qUsageCount = 0 this._qIsMakingEdits = false this._timer = undefined this._lastQInvocationTime = 0 } - private resetTracker() { - this._userWrittenNewCodeLineCount = 0 - this._userWrittenNewCodeCharacterCount = 0 - this._qUsageCount = 0 - this._qIsMakingEdits = false - this._lastQInvocationTime = 0 - } - public static get instance() { return (this.#instance ??= new this()) } @@ -69,34 +62,61 @@ export class UserWrittenCodeTracker { this._qIsMakingEdits = false } - public emitCodeContribution() { + public getUserWrittenCharacters(language: CodewhispererLanguage) { + return this._userWrittenNewCodeCharacterCount.get(language) || 0 + } + + public getUserWrittenLines(language: CodewhispererLanguage) { + return this._userWrittenNewCodeLineCount.get(language) || 0 + } + + public reset() { + this._userWrittenNewCodeLineCount = new Map() + this._userWrittenNewCodeCharacterCount = new Map() + this._qUsageCount = 0 + this._qIsMakingEdits = false + this._lastQInvocationTime = 0 + if (this._timer !== undefined) { + clearTimeout(this._timer) + this._timer = undefined + } + } + + public emitCodeContributions() { const selectedCustomization = getSelectedCustomization() - client - .sendTelemetryEvent({ - telemetryEvent: { - codeCoverageEvent: { - customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, - programmingLanguage: { - languageName: 'plaintext', + + for (const [language, charCount] of this._userWrittenNewCodeCharacterCount) { + const lineCount = this._userWrittenNewCodeLineCount.get(language) || 0 + if (charCount > 0) { + client + .sendTelemetryEvent({ + telemetryEvent: { + codeCoverageEvent: { + customizationArn: + selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + programmingLanguage: { + languageName: language, + }, + acceptedCharacterCount: 0, + totalCharacterCount: 0, + timestamp: new Date(Date.now()), + userWrittenCodeCharacterCount: charCount, + userWrittenCodeLineCount: lineCount, + }, }, - acceptedCharacterCount: 0, - totalCharacterCount: 0, - timestamp: new Date(Date.now()), - userWrittenCodeCharacterCount: this._userWrittenNewCodeCharacterCount, - userWrittenCodeLineCount: this._userWrittenNewCodeLineCount, - }, - }, - }) - .then() - .catch((error) => { - let requestId: string | undefined - if (isAwsError(error)) { - requestId = error.requestId - } - getLogger().debug( - `Failed to sendTelemetryEvent, requestId: ${requestId ?? ''}, message: ${error.message}` - ) - }) + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + getLogger().debug( + `Failed to sendTelemetryEvent, requestId: ${requestId ?? ''}, message: ${error.message}` + ) + }) + } + } } private tryStartTimer() { @@ -105,8 +125,7 @@ export class UserWrittenCodeTracker { } if (!this.isActive()) { getLogger().debug(`Skip emiting code contribution metric. Telemetry disabled or not logged in. `) - this.resetTracker() - this.closeTimer() + this.reset() return } const startTime = performance.now() @@ -120,28 +139,20 @@ export class UserWrittenCodeTracker { getLogger().debug(`Skip emiting code contribution metric. There is no active Amazon Q usage. `) return } - if (this._userWrittenNewCodeCharacterCount === 0) { + if (this._userWrittenNewCodeCharacterCount.size === 0) { getLogger().debug(`Skip emiting code contribution metric. There is no new code added. `) return } - this.emitCodeContribution() + this.emitCodeContributions() } } catch (e) { getLogger().verbose(`Exception Thrown from QCodeGenTracker: ${e}`) } finally { - this.resetTracker() - this.closeTimer() + this.reset() } }, UserWrittenCodeTracker.defaultCheckPeriodMillis) } - private closeTimer() { - if (this._timer !== undefined) { - clearTimeout(this._timer) - this._timer = undefined - } - } - private countNewLines(str: string) { return str.split('\n').length - 1 } @@ -170,9 +181,14 @@ export class UserWrittenCodeTracker { if (contentChange.text.length > UserWrittenCodeTracker.copySnippetThreshold) { return } - this._userWrittenNewCodeCharacterCount += contentChange.text.length - this._userWrittenNewCodeLineCount += this.countNewLines(contentChange.text) - // start 5 min data reporting once valid user input is detected - this.tryStartTimer() + const language = runtimeLanguageContext.normalizeLanguage(e.document.languageId) + if (language) { + const charCount = this._userWrittenNewCodeCharacterCount.get(language) || 0 + this._userWrittenNewCodeCharacterCount.set(language, charCount + contentChange.text.length) + const lineCount = this._userWrittenNewCodeLineCount.get(language) || 0 + this._userWrittenNewCodeLineCount.set(language, lineCount + this.countNewLines(contentChange.text)) + // start 5 min data reporting once valid user input is detected + this.tryStartTimer() + } } } From 7b6f9754b622156f120313f0700b3007c7ba0272 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Thu, 12 Dec 2024 14:19:08 -0800 Subject: [PATCH 11/19] fix test case --- .../tracker/userWrittenCodeTracker.test.ts | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts b/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts index 6adb7380ba0..3e6a91e5435 100644 --- a/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts @@ -90,11 +90,26 @@ describe('userWrittenCodeTracker', function () { }, ], }) - assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + tracker.onTextDocumentChange({ + reason: undefined, + document: createMockDocument('', 'test.java', 'java'), + contentChanges: [ + { + range: new vscode.Range(0, 0, 1, 3), + rangeOffset: 0, + rangeLength: 11, + text: 'a = 123\nbcd', + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 49) assert.strictEqual(tracker.getUserWrittenLines('python'), 0) + assert.strictEqual(tracker.getUserWrittenCharacters('java'), 11) + assert.strictEqual(tracker.getUserWrittenLines('java'), 1) + assert.strictEqual(tracker.getUserWrittenLines('cpp'), 0) }) - it('Should skip when CodeWhisperer is editing', function () { + it('Should skip when Q is editing', function () { if (!tracker) { assert.fail() } @@ -112,8 +127,21 @@ describe('userWrittenCodeTracker', function () { }, ], }) - assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) - assert.strictEqual(tracker.getUserWrittenLines('python'), 0) + tracker.onQFinishesEdits() + tracker.onTextDocumentChange({ + reason: undefined, + document: createMockDocument(), + contentChanges: [ + { + range: new vscode.Range(0, 0, 0, 2), + rangeOffset: 0, + rangeLength: 2, + text: '\na', + }, + ], + }) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 2) + assert.strictEqual(tracker.getUserWrittenLines('python'), 1) }) it('Should not reduce tokens when delete', function () { @@ -147,7 +175,7 @@ describe('userWrittenCodeTracker', function () { }, ], }) - assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 2) tracker.onTextDocumentChange({ reason: undefined, document: doc, @@ -160,28 +188,7 @@ describe('userWrittenCodeTracker', function () { }, ], }) - assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0) + assert.strictEqual(tracker.getUserWrittenCharacters('python'), 2) }) }) - - describe('emitCodeWhispererCodeContribution', function () { - let tracker: UserWrittenCodeTracker | undefined - - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - tracker = UserWrittenCodeTracker.instance - tracker.reset() - if (tracker) { - sinon.stub(tracker, 'isActive').returns(true) - } - }) - - afterEach(function () { - sinon.restore() - }) - - it('should emit correct code coverage telemetry in python file', async function () {}) - - it('Should not emit if user has not use any Q feature', async function () {}) - }) }) From 8eaf48dfcc8798421e119b37951c58911fd011de Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Thu, 12 Dec 2024 14:26:53 -0800 Subject: [PATCH 12/19] use runtime language --- .../core/src/codewhisperer/tracker/userWrittenCodeTracker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index 715b9e586e1..e237252dc83 100644 --- a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -95,7 +95,7 @@ export class UserWrittenCodeTracker { customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, programmingLanguage: { - languageName: language, + languageName: runtimeLanguageContext.toRuntimeLanguage(language), }, acceptedCharacterCount: 0, totalCharacterCount: 0, From 69b374cfc97849e32ead53a30900c95cf8dd433b Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Thu, 12 Dec 2024 14:44:44 -0800 Subject: [PATCH 13/19] simplify changes --- .../tracker/userWrittenCodeTracker.ts | 6 +- packages/toolkit/package.json | 208 +++++++++--------- 2 files changed, 104 insertions(+), 110 deletions(-) diff --git a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index e237252dc83..667f4b401fb 100644 --- a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -29,7 +29,8 @@ export class UserWrittenCodeTracker { static #instance: UserWrittenCodeTracker static copySnippetThreshold = 50 static resetQIsEditingTimeoutMs = 5 * 60 * 1000 - static defaultCheckPeriodMillis = 1000 * 60 * 5 + static defaultCheckPeriodMillis = 5 * 60 * 1000 + private constructor() { this._userWrittenNewCodeLineCount = new Map() this._userWrittenNewCodeCharacterCount = new Map() @@ -166,9 +167,10 @@ export class UserWrittenCodeTracker { ) { // if the boolean of qIsMakingEdits was incorrectly set to true // due to unhandled edge cases or early terminated code paths - // reset it back to false after reasonabe period of time + // reset it back to false after a reasonable period of time if (this._qIsMakingEdits) { if (performance.now() - this._lastQInvocationTime > UserWrittenCodeTracker.resetQIsEditingTimeoutMs) { + getLogger().warn(`Reset Q is editing state to false.`) this._qIsMakingEdits = false } } diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 83b9e8f3afe..0344109e11d 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -2,7 +2,7 @@ "name": "aws-toolkit-vscode", "displayName": "AWS Toolkit", "description": "Including CodeCatalyst, Infrastructure Composer, and support for Lambda, S3, CloudWatch Logs, CloudFormation, and many other services.", - "version": "3.39.0-SNAPSHOT", + "version": "3.40.0-SNAPSHOT", "extensionKind": [ "workspace" ], @@ -240,12 +240,17 @@ "type": "object", "markdownDescription": "%AWS.configuration.description.experiments%", "default": { - "jsonResourceModification": false + "jsonResourceModification": false, + "ec2RemoteConnect": false }, "properties": { "jsonResourceModification": { "type": "boolean", "default": false + }, + "ec2RemoteConnect": { + "type": "boolean", + "default": false } }, "additionalProperties": false @@ -1203,27 +1208,27 @@ }, { "command": "aws.ec2.openRemoteConnection", - "when": "aws.isDevMode" + "when": "config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.openTerminal", - "when": "aws.isDevMode" + "when": "config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.linkToLaunch", - "when": "aws.isDevMode" + "when": "config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.startInstance", - "when": "aws.isDevMode" + "when": "config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.stopInstance", - "when": "aws.isDevMode" + "when": "config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.rebootInstance", - "when": "aws.isDevMode" + "when": "config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.dev.openMenu", @@ -1448,62 +1453,67 @@ { "command": "aws.ec2.openTerminal", "group": "0@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" + "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/ && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.openTerminal", "group": "inline@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" + "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/ && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.linkToLaunch", - "group": "0@1", - "when": "viewItem =~ /^(awsEc2ParentNode)$/" + "group": "0@0", + "when": "viewItem =~ /^(awsEc2ParentNode)$/ && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.linkToLaunch", - "group": "inline@1", - "when": "viewItem =~ /^(awsEc2ParentNode)$/" + "group": "inline@0", + "when": "viewItem =~ /^(awsEc2ParentNode)$/ && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.openRemoteConnection", "group": "0@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" + "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/ && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.openRemoteConnection", "group": "inline@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" + "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/ && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.startInstance", "group": "0@1", - "when": "viewItem == awsEc2StoppedNode" + "when": "viewItem == awsEc2StoppedNode && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.startInstance", "group": "inline@1", - "when": "viewItem == awsEc2StoppedNode" + "when": "viewItem == awsEc2StoppedNode && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.stopInstance", "group": "0@1", - "when": "viewItem == awsEc2RunningNode" + "when": "viewItem == awsEc2RunningNode && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.stopInstance", "group": "inline@1", - "when": "viewItem == awsEc2RunningNode" + "when": "viewItem == awsEc2RunningNode && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.rebootInstance", "group": "0@1", - "when": "viewItem == awsEc2RunningNode" + "when": "viewItem == awsEc2RunningNode && config.aws.experiments.ec2RemoteConnect" }, { "command": "aws.ec2.rebootInstance", "group": "inline@1", - "when": "viewItem == awsEc2RunningNode" + "when": "viewItem == awsEc2RunningNode && config.aws.experiments.ec2RemoteConnect" + }, + { + "command": "aws.ec2.copyInstanceId", + "when": "view == aws.explorer && viewItem =~ /^(awsEc2(Running|Stopped|Pending)Node)$/ && config.aws.experiments.ec2RemoteConnect", + "group": "2@0" }, { "command": "aws.ecr.createRepository", @@ -1605,11 +1615,6 @@ "when": "!config.aws.samcli.legacyDeploy && view == aws.explorer && viewItem =~ /^(awsLambdaNode|awsRegionNode|awsCloudFormationRootNode)$/", "group": "1@2" }, - { - "command": "aws.ec2.copyInstanceId", - "when": "view == aws.explorer && viewItem =~ /^(awsEc2(Running|Stopped|Pending)Node)$/", - "group": "2@0" - }, { "command": "aws.ecr.copyTagUri", "when": "view == aws.explorer && viewItem == awsEcrTagNode", @@ -1830,6 +1835,16 @@ "group": "inline@1", "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" }, + { + "command": "aws.cwl.tailLogGroup", + "group": "0@1", + "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" + }, + { + "command": "aws.cwl.tailLogGroup", + "group": "inline@1", + "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" + }, { "command": "aws.apig.copyUrl", "when": "view =~ /^(aws.explorer|aws.appBuilder|aws.appBuilderForFileExplorer)$/ && viewItem =~ /^(awsApiGatewayNode)$/", @@ -3390,6 +3405,18 @@ } } }, + { + "command": "aws.cwl.tailLogGroup", + "title": "%AWS.command.cloudWatchLogs.tailLogGroup%", + "category": "%AWS.title%", + "enablement": "isCloud9 || !aws.isWebExtHost", + "icon": "$(notebook-execute)", + "cloud9": { + "cn": { + "category": "%AWS.title.cn%" + } + } + }, { "command": "aws.saveCurrentLogDataContent", "title": "%AWS.command.saveCurrentLogDataContent%", @@ -4010,361 +4037,326 @@ "fontCharacter": "\\f1ac" } }, - "aws-amazonq-severity-critical": { - "description": "AWS Contributed Icon", - "default": { - "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ad" - } - }, - "aws-amazonq-severity-high": { - "description": "AWS Contributed Icon", - "default": { - "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ae" - } - }, - "aws-amazonq-severity-info": { - "description": "AWS Contributed Icon", - "default": { - "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1af" - } - }, - "aws-amazonq-severity-low": { - "description": "AWS Contributed Icon", - "default": { - "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b0" - } - }, - "aws-amazonq-severity-medium": { - "description": "AWS Contributed Icon", - "default": { - "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b1" - } - }, "aws-amazonq-transform-arrow-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b2" + "fontCharacter": "\\f1ad" } }, "aws-amazonq-transform-arrow-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b3" + "fontCharacter": "\\f1ae" } }, "aws-amazonq-transform-default-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b4" + "fontCharacter": "\\f1af" } }, "aws-amazonq-transform-default-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b5" + "fontCharacter": "\\f1b0" } }, "aws-amazonq-transform-dependencies-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b6" + "fontCharacter": "\\f1b1" } }, "aws-amazonq-transform-dependencies-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b7" + "fontCharacter": "\\f1b2" } }, "aws-amazonq-transform-file-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b8" + "fontCharacter": "\\f1b3" } }, "aws-amazonq-transform-file-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b9" + "fontCharacter": "\\f1b4" } }, "aws-amazonq-transform-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ba" + "fontCharacter": "\\f1b5" } }, "aws-amazonq-transform-step-into-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bb" + "fontCharacter": "\\f1b6" } }, "aws-amazonq-transform-step-into-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bc" + "fontCharacter": "\\f1b7" } }, "aws-amazonq-transform-variables-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bd" + "fontCharacter": "\\f1b8" } }, "aws-amazonq-transform-variables-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1be" + "fontCharacter": "\\f1b9" } }, "aws-applicationcomposer-icon": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bf" + "fontCharacter": "\\f1ba" } }, "aws-applicationcomposer-icon-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c0" + "fontCharacter": "\\f1bb" } }, "aws-apprunner-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c1" + "fontCharacter": "\\f1bc" } }, "aws-cdk-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c2" + "fontCharacter": "\\f1bd" } }, "aws-cloudformation-stack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c3" + "fontCharacter": "\\f1be" } }, "aws-cloudwatch-log-group": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c4" + "fontCharacter": "\\f1bf" } }, "aws-codecatalyst-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c5" + "fontCharacter": "\\f1c0" } }, "aws-codewhisperer-icon-black": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c6" + "fontCharacter": "\\f1c1" } }, "aws-codewhisperer-icon-white": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c7" + "fontCharacter": "\\f1c2" } }, "aws-codewhisperer-learn": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c8" + "fontCharacter": "\\f1c3" } }, "aws-ecr-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c9" + "fontCharacter": "\\f1c4" } }, "aws-ecs-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ca" + "fontCharacter": "\\f1c5" } }, "aws-ecs-container": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cb" + "fontCharacter": "\\f1c6" } }, "aws-ecs-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cc" + "fontCharacter": "\\f1c7" } }, "aws-generic-attach-file": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cd" + "fontCharacter": "\\f1c8" } }, "aws-iot-certificate": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ce" + "fontCharacter": "\\f1c9" } }, "aws-iot-policy": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cf" + "fontCharacter": "\\f1ca" } }, "aws-iot-thing": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d0" + "fontCharacter": "\\f1cb" } }, "aws-lambda-function": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d1" + "fontCharacter": "\\f1cc" } }, "aws-mynah-MynahIconBlack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d2" + "fontCharacter": "\\f1cd" } }, "aws-mynah-MynahIconWhite": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d3" + "fontCharacter": "\\f1ce" } }, "aws-mynah-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d4" + "fontCharacter": "\\f1cf" } }, "aws-redshift-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d5" + "fontCharacter": "\\f1d0" } }, "aws-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d6" + "fontCharacter": "\\f1d1" } }, "aws-redshift-database": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d7" + "fontCharacter": "\\f1d2" } }, "aws-redshift-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d8" + "fontCharacter": "\\f1d3" } }, "aws-redshift-schema": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d9" + "fontCharacter": "\\f1d4" } }, "aws-redshift-table": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1da" + "fontCharacter": "\\f1d5" } }, "aws-s3-bucket": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1db" + "fontCharacter": "\\f1d6" } }, "aws-s3-create-bucket": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1dc" + "fontCharacter": "\\f1d7" } }, "aws-schemas-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1dd" + "fontCharacter": "\\f1d8" } }, "aws-schemas-schema": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1de" + "fontCharacter": "\\f1d9" } }, "aws-stepfunctions-preview": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1df" + "fontCharacter": "\\f1da" } } }, From cf1c0697c0c433bb4dedc0fe7163d24d50e156db Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Thu, 12 Dec 2024 15:23:09 -0800 Subject: [PATCH 14/19] lint --- .../src/amazonq/commons/controllers/contentController.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index 4d7fa35d9a7..c46ea2101e3 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -68,14 +68,13 @@ export class EditorContentController { if (appliedEdits) { trackCodeEdit(editor, cursorStart) } + UserWrittenCodeTracker.instance.onQFinishesEdits() }, (e) => { getLogger().error('TextEditor.edit failed: %s', (e as Error).message) + UserWrittenCodeTracker.instance.onQFinishesEdits() } ) - .then(() => { - UserWrittenCodeTracker.instance.onQFinishesEdits() - }) } } From 3781ef996e7a9bbe54d99007cfcea620e88b8311 Mon Sep 17 00:00:00 2001 From: Lei Gao <97199248+leigaol@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:56:40 -0800 Subject: [PATCH 15/19] Update packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts Co-authored-by: Justin M. Keyes --- .../unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts b/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts index 3e6a91e5435..1d9b878133f 100644 --- a/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/tracker/userWrittenCodeTracker.test.ts @@ -10,7 +10,7 @@ import { UserWrittenCodeTracker, TelemetryHelper, AuthUtil } from 'aws-core-vsco import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' describe('userWrittenCodeTracker', function () { - describe('test isActive', function () { + describe('isActive()', function () { afterEach(async function () { await resetCodeWhispererGlobalVariables() UserWrittenCodeTracker.instance.reset() From 97e8d560b717539e2ca6c1dd72a540e8622f21ec Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Tue, 7 Jan 2025 16:00:05 -0800 Subject: [PATCH 16/19] update Q editing reset timer --- .../core/src/codewhisperer/tracker/userWrittenCodeTracker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index 667f4b401fb..5cab7cf0dce 100644 --- a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -28,7 +28,7 @@ export class UserWrittenCodeTracker { static #instance: UserWrittenCodeTracker static copySnippetThreshold = 50 - static resetQIsEditingTimeoutMs = 5 * 60 * 1000 + static resetQIsEditingTimeoutMs = 2 * 60 * 1000 static defaultCheckPeriodMillis = 5 * 60 * 1000 private constructor() { From f9a9b00a23e51f215bfd9f31ad05ed392dba44a8 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Tue, 14 Jan 2025 15:29:32 -0800 Subject: [PATCH 17/19] add comments --- .../codewhisperer/tracker/codewhispererCodeCoverageTracker.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts b/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts index d0ad76c26da..39416eafe70 100644 --- a/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts +++ b/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts @@ -27,6 +27,8 @@ const autoClosingKeystrokeInputs = ['[]', '{}', '()', '""', "''"] /** * This singleton class is mainly used for calculating the code written by codeWhisperer + * TODO: Remove this tracker, uses user written code tracker instead. + * This is kept in codebase for server side backward compatibility until service fully switch to user written code */ export class CodeWhispererCodeCoverageTracker { private _acceptedTokens: { [key: string]: CodeWhispererToken[] } From f65acde4dbad10fc35f24003a37b02dc3f58fbd7 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 15 Jan 2025 15:37:16 -0800 Subject: [PATCH 18/19] feedback fixes --- .../tracker/userWrittenCodeTracker.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts index 5cab7cf0dce..2497006b0a4 100644 --- a/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts +++ b/packages/core/src/codewhisperer/tracker/userWrittenCodeTracker.ts @@ -6,12 +6,11 @@ import * as vscode from 'vscode' import { getLogger } from '../../shared/logger/logger' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' -import { TelemetryHelper } from '../util/telemetryHelper' import { AuthUtil } from '../util/authUtil' import { getSelectedCustomization } from '../util/customizationUtil' import { codeWhispererClient as client } from '../client/codewhisperer' import { isAwsError } from '../../shared/errors' -import { CodewhispererLanguage } from '../../shared' +import { CodewhispererLanguage, globals, undefinedIfEmpty } from '../../shared' /** * This singleton class is mainly used for calculating the user written code @@ -27,9 +26,9 @@ export class UserWrittenCodeTracker { private _lastQInvocationTime: number static #instance: UserWrittenCodeTracker - static copySnippetThreshold = 50 - static resetQIsEditingTimeoutMs = 2 * 60 * 1000 - static defaultCheckPeriodMillis = 5 * 60 * 1000 + private static copySnippetThreshold = 50 + private static resetQIsEditingTimeoutMs = 2 * 60 * 1000 + private static defaultCheckPeriodMillis = 5 * 60 * 1000 private constructor() { this._userWrittenNewCodeLineCount = new Map() @@ -45,7 +44,7 @@ export class UserWrittenCodeTracker { } public isActive(): boolean { - return TelemetryHelper.instance.isTelemetryEnabled() && AuthUtil.instance.isConnected() + return globals.telemetry.telemetryEnabled && AuthUtil.instance.isConnected() } // this should be invoked whenever there is a successful Q feature invocation @@ -87,14 +86,13 @@ export class UserWrittenCodeTracker { const selectedCustomization = getSelectedCustomization() for (const [language, charCount] of this._userWrittenNewCodeCharacterCount) { - const lineCount = this._userWrittenNewCodeLineCount.get(language) || 0 + const lineCount = this.getUserWrittenLines(language) if (charCount > 0) { client .sendTelemetryEvent({ telemetryEvent: { codeCoverageEvent: { - customizationArn: - selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + customizationArn: undefinedIfEmpty(selectedCustomization.arn), programmingLanguage: { languageName: runtimeLanguageContext.toRuntimeLanguage(language), }, @@ -185,9 +183,9 @@ export class UserWrittenCodeTracker { } const language = runtimeLanguageContext.normalizeLanguage(e.document.languageId) if (language) { - const charCount = this._userWrittenNewCodeCharacterCount.get(language) || 0 + const charCount = this.getUserWrittenCharacters(language) this._userWrittenNewCodeCharacterCount.set(language, charCount + contentChange.text.length) - const lineCount = this._userWrittenNewCodeLineCount.get(language) || 0 + const lineCount = this.getUserWrittenLines(language) this._userWrittenNewCodeLineCount.set(language, lineCount + this.countNewLines(contentChange.text)) // start 5 min data reporting once valid user input is detected this.tryStartTimer() From 7a724ab909fd793837a10638f05ece98c3e2a2e5 Mon Sep 17 00:00:00 2001 From: Lei Gao Date: Wed, 15 Jan 2025 15:51:04 -0800 Subject: [PATCH 19/19] fix ci --- packages/core/src/codewhisperer/service/testGenHandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts index 5a095a86caa..22f9b67b0a6 100644 --- a/packages/core/src/codewhisperer/service/testGenHandler.ts +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -16,7 +16,7 @@ import CodeWhispererUserClient, { import { CreateUploadUrlError, InvalidSourceZipError, TestGenFailedError, TestGenTimedOutError } from '../models/errors' import { getMd5, uploadArtifactToS3 } from './securityScanHandler' import { fs, randomUUID, sleep, tempDirPath } from '../../shared' -import { ShortAnswer, testGenState, UserWrittenCodeTracker } from '..' +import { ShortAnswer, testGenState } from '../models/model' import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession' import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient' import { downloadExportResultArchive } from '../../shared/utilities/download' @@ -24,6 +24,7 @@ import AdmZip from 'adm-zip' import path from 'path' import { ExportIntent } from '@amzn/codewhisperer-streaming' import { glob } from 'glob' +import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' // TODO: Get TestFileName and Framework and to error message export function throwIfCancelled() {