From 645971bb5e77429b195fcaa52d66f4e398e9870b Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:01:15 -0800 Subject: [PATCH 01/18] UTG Error handling --- .../amazonqTest/chat/controller/controller.ts | 77 ++++++++++-------- .../chat/controller/messenger/messenger.ts | 9 ++- packages/core/src/amazonqTest/error.ts | 80 +++++++++++++++++++ .../commands/startTestGeneration.ts | 4 +- .../src/codewhisperer/models/constants.ts | 2 +- .../codewhisperer/service/codeFixHandler.ts | 2 +- .../service/securityScanHandler.ts | 22 +++-- .../codewhisperer/service/testGenHandler.ts | 24 ++++-- .../src/codewhisperer/util/telemetryHelper.ts | 4 +- .../core/src/codewhisperer/util/zipUtil.ts | 43 ++++++---- 10 files changed, 192 insertions(+), 75 deletions(-) create mode 100644 packages/core/src/amazonqTest/error.ts diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 79d4d117057..a6e085f6420 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -241,8 +241,7 @@ export class TestController { // eslint-disable-next-line unicorn/no-null this.messenger.sendUpdatePromptProgress(data.tabID, null) const session = this.sessionStorage.getSession() - const isCancel = data.error.message === unitTestGenerationCancelMessage - + const isCancel = data.error.customerFacingMessage === unitTestGenerationCancelMessage TelemetryHelper.instance.sendTestGenerationToolkitEvent( session, true, @@ -250,42 +249,51 @@ export class TestController { session.startTestGenerationRequestId, performance.now() - session.testGenerationStartTime, getTelemetryReasonDesc(data.error), + data.error.statusCode ?? '0', // If status code is 0, need to investigate where this is originating from. session.isCodeBlockSelected, session.artifactsUploadDuration, session.srcPayloadSize, session.srcZipFileSize ) - if (session.stopIteration) { // Error from Science - this.messenger.sendMessage(data.error.message.replaceAll('```', ''), data.tabID, 'answer') + this.messenger.sendMessage(data.error.customerFacingMessage.replaceAll('```', ''), data.tabID, 'answer') } else { isCancel - ? this.messenger.sendMessage(data.error.message, data.tabID, 'answer') + ? this.messenger.sendMessage(data.error.customerFacingMessage, data.tabID, 'answer') : this.sendErrorMessage(data) } await this.sessionCleanUp() return } // Client side error messages - private sendErrorMessage(data: { tabID: string; error: { code: string; message: string } }) { + private sendErrorMessage(data: { + tabID: string + error: { customerFacingMessage: string; message: string; code: string; statusCode: string } + }) { const { error, tabID } = data + // If user reached montly limit for builderId + if (error.code === 'CreateTestJobError') { + if (error.message.includes(CodeWhispererConstants.utgLimitReached)) { + getLogger().error('Monthly quota reached for QSDA actions.') + return this.messenger.sendMessage( + i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'), + tabID, + 'answer' + ) + } else { + getLogger().error('Too many requests.') + // TODO: move to constants file + return this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID) + } + } if (isAwsError(error)) { if (error.code === 'ThrottlingException') { - // TODO: use the explicitly modeled exception reason for quota vs throttle - if (error.message.includes(CodeWhispererConstants.utgLimitReached)) { - getLogger().error('Monthly quota reached for QSDA actions.') - return this.messenger.sendMessage( - i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'), - tabID, - 'answer' - ) - } else { - getLogger().error('Too many requests.') - // TODO: move to constants file - this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID) - } + // TODO: use the explicitly modeled exception reason for quota vs throttle{ + getLogger().error('Too many requests.') + // TODO: move to constants file + this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID) } else { // other service errors: // AccessDeniedException - should not happen because access is validated before this point in the client @@ -293,18 +301,12 @@ export class TestController { // ConflictException - should not happen because the client will maintain proper state // InternalServerException - shouldn't happen but needs to be caught getLogger().error('Other error message: %s', error.message) - this.messenger.sendErrorMessage( - 'Encountered an unexpected error when generating tests. Please try again', - tabID - ) + this.messenger.sendErrorMessage('', tabID) } } else { // other unexpected errors (TODO enumerate all other failure cases) - getLogger().error('Other error message: %s', error.message) - this.messenger.sendErrorMessage( - 'Encountered an unexpected error when generating tests. Please try again', - tabID - ) + getLogger().error('Other error message: %s', error.customerFacingMessage) + this.messenger.sendErrorMessage('', tabID) } } @@ -721,6 +723,7 @@ export class TestController { session.startTestGenerationRequestId, session.latencyOfTestGeneration, undefined, + '200', session.isCodeBlockSelected, session.artifactsUploadDuration, session.srcPayloadSize, @@ -734,7 +737,6 @@ export class TestController { ) await this.endSession(message, FollowUpTypes.SkipBuildAndFinish) - await this.sessionCleanUp() return if (session.listOfTestGenerationJobId.length === 1) { @@ -842,10 +844,9 @@ export class TestController { session.startTestGenerationRequestId, session.latencyOfTestGeneration, undefined, + '200', session.isCodeBlockSelected, session.artifactsUploadDuration, - session.srcPayloadSize, - session.srcZipFileSize, 0, 0, 0, @@ -853,7 +854,6 @@ export class TestController { session.numberOfTestsGenerated, session.linesOfCodeGenerated ) - telemetry.ui_click.emit({ elementId: 'unitTestGeneration_rejectDiff' }) } @@ -1297,8 +1297,17 @@ export class TestController { 'Deleting output.log and temp result directory. testGenerationLogsDir: %s', testGenerationLogsDir ) - await fs.delete(path.join(testGenerationLogsDir, 'output.log')) - await fs.delete(this.tempResultDirPath, { recursive: true }) + if (await fs.existsFile(path.join(testGenerationLogsDir, 'output.log'))) { + await fs.delete(path.join(testGenerationLogsDir, 'output.log')) + } + if ( + await fs + .stat(this.tempResultDirPath) + .then(() => true) + .catch(() => false) + ) { + await fs.delete(this.tempResultDirPath, { recursive: true }) + } } // TODO: return build command when product approves diff --git a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts index 10b496b69d3..98d52b21c5f 100644 --- a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts @@ -280,9 +280,9 @@ export class Messenger { 'Cancelled', messageId, performance.now() - session.testGenerationStartTime, - getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage) + getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage), + '400' ) - this.dispatcher.sendUpdatePromptProgress( new UpdatePromptProgressMessage(tabID, cancellingProgressField) ) @@ -293,9 +293,10 @@ export class Messenger { false, 'Succeeded', messageId, - performance.now() - session.testGenerationStartTime + performance.now() - session.testGenerationStartTime, + undefined, + '200', ) - this.dispatcher.sendUpdatePromptProgress( new UpdatePromptProgressMessage(tabID, testGenCompletedField) ) diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts new file mode 100644 index 00000000000..59dc10a20a8 --- /dev/null +++ b/packages/core/src/amazonqTest/error.ts @@ -0,0 +1,80 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { CodeWhispererConstants } from '../codewhisperer/indexNode' +import { ToolkitError } from '../shared/errors' + +export const techinalErrorCustomerFacingMessage = + 'I am experiencing technical difficulties at the moment. Please try again in a few minutes.' +const defaultTestGenErrorMessage = 'Amazon Q encountered an error while generating tests. Try again later.' +export class TestGenError extends ToolkitError { + constructor( + error: string, + code: string, + public statusCode: string, + public customerFacingMessage: string + ) { + super(error, { code }) + } +} +export class ProjectZipError extends TestGenError { + constructor(error: string) { + super(error, 'ProjectZipError', '400', defaultTestGenErrorMessage) + } +} +export class InvalidSourceZipError extends TestGenError { + constructor() { + super('Failed to create valid source zip', 'InvalidSourceZipError', '400', defaultTestGenErrorMessage) + } +} +export class CreateUploadUrlError extends TestGenError { + constructor(errorMessage: string, errorCode: string) { + super(errorMessage, 'CreateUploadUrlError', errorCode, techinalErrorCustomerFacingMessage) + } +} +export class UploadTestArtifactToS3Error extends TestGenError { + constructor(error: string) { + super(error, 'UploadTestArtifactToS3Error', '500', techinalErrorCustomerFacingMessage) + } +} +export class CreateTestJobError extends TestGenError { + constructor(error: string, code: string) { + super(error, 'CreateTestJobError', code, techinalErrorCustomerFacingMessage) + } +} +export class TestGenTimedOutError extends TestGenError { + constructor() { + super( + 'Test generation failed. Amazon Q timed out.', + 'TestGenTimedOutError', + '500', + techinalErrorCustomerFacingMessage + ) + } +} +export class TestGenStoppedError extends TestGenError { + constructor() { + super( + CodeWhispererConstants.unitTestGenerationCancelMessage, + 'TestGenCancelled', + '400', + CodeWhispererConstants.unitTestGenerationCancelMessage + ) + } +} +export class TestGenFailedError extends TestGenError { + constructor(code: string, error?: string) { + super( + error ?? 'Test generation failed', + 'TestGenFailedError', + code, + error ?? techinalErrorCustomerFacingMessage + ) + } +} +export class ExportResultsArchiveError extends TestGenError { + constructor(error?: string) { + super(error ?? 'Test generation failed', 'ExportResultsArchiveError', '500', techinalErrorCustomerFacingMessage) + } +} diff --git a/packages/core/src/codewhisperer/commands/startTestGeneration.ts b/packages/core/src/codewhisperer/commands/startTestGeneration.ts index 39750a8b3ff..1041d8f872d 100644 --- a/packages/core/src/codewhisperer/commands/startTestGeneration.ts +++ b/packages/core/src/codewhisperer/commands/startTestGeneration.ts @@ -21,7 +21,7 @@ import { ChildProcess, spawn } from 'child_process' import { BuildStatus } from '../../amazonqTest/chat/session/session' import { fs } from '../../shared/fs/fs' import { TestGenerationJobStatus } from '../models/constants' -import { TestGenFailedError } from '../models/errors' +import { TestGenFailedError } from '../../amazonqTest/error' import { Range } from '../client/codewhispereruserclient' // eslint-disable-next-line unicorn/no-null @@ -121,7 +121,7 @@ export async function startTestGenerationProcess( // TODO: Send status to test summary if (jobStatus === TestGenerationJobStatus.FAILED) { logger.verbose(`Test generation failed.`) - throw new TestGenFailedError() + throw new TestGenFailedError('500') } throwIfCancelled() if (!shouldContinueRunning(tabID)) { diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index bd2245fe3da..1f365692f5e 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -867,7 +867,7 @@ export enum TestGenerationJobStatus { COMPLETED = 'COMPLETED', } -export enum ZipUseCase { +export enum FeatureUseCase { TEST_GENERATION = 'TEST_GENERATION', CODE_SCAN = 'CODE_SCAN', } diff --git a/packages/core/src/codewhisperer/service/codeFixHandler.ts b/packages/core/src/codewhisperer/service/codeFixHandler.ts index 0358d8d3ed9..b707ee01583 100644 --- a/packages/core/src/codewhisperer/service/codeFixHandler.ts +++ b/packages/core/src/codewhisperer/service/codeFixHandler.ts @@ -34,7 +34,7 @@ export async function getPresignedUrlAndUpload( getLogger().verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`) getLogger().verbose(`Complete Getting presigned Url for uploading src context.`) getLogger().verbose(`Uploading src context...`) - await uploadArtifactToS3(zipFilePath, srcResp) + await uploadArtifactToS3(zipFilePath, srcResp, CodeWhispererConstants.FeatureUseCase.CODE_SCAN) getLogger().verbose(`Complete uploading src context.`) const artifactMap: ArtifactMap = { SourceCode: srcResp.uploadId, diff --git a/packages/core/src/codewhisperer/service/securityScanHandler.ts b/packages/core/src/codewhisperer/service/securityScanHandler.ts index d328034d560..dfc9cc017fe 100644 --- a/packages/core/src/codewhisperer/service/securityScanHandler.ts +++ b/packages/core/src/codewhisperer/service/securityScanHandler.ts @@ -43,6 +43,8 @@ import { getTelemetryReasonDesc } from '../../shared/errors' import { CodeWhispererSettings } from '../util/codewhispererSettings' import { detectCommentAboveLine } from '../../shared/utilities/commentUtils' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' +import { FeatureUseCase } from '../models/constants' +import { UploadTestArtifactToS3Error } from '../../amazonqTest/error' export async function listScanResults( client: DefaultCodeWhispererClient, @@ -287,7 +289,7 @@ export async function getPresignedUrlAndUpload( logger.verbose(`CreateUploadUrlRequest request id: ${srcResp.$response.requestId}`) logger.verbose(`Complete Getting presigned Url for uploading src context.`) logger.verbose(`Uploading src context...`) - await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, scope) + await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, FeatureUseCase.CODE_SCAN, scope) logger.verbose(`Complete uploading src context.`) const artifactMap: ArtifactMap = { SourceCode: srcResp.uploadId, @@ -343,6 +345,7 @@ export function throwIfCancelled(scope: CodeWhispererConstants.CodeAnalysisScope export async function uploadArtifactToS3( fileName: string, resp: CreateUploadUrlResponse, + featureUseCase: FeatureUseCase, scope?: CodeWhispererConstants.CodeAnalysisScope ) { const logger = getLoggerForScope(scope) @@ -365,14 +368,19 @@ export async function uploadArtifactToS3( }).response logger.debug(`StatusCode: ${response.status}, Text: ${response.statusText}`) } catch (error) { + let errorMessage = '' + const isCodeScan = featureUseCase === FeatureUseCase.CODE_SCAN + const featureType = isCodeScan ? 'security scans' : 'unit test generation' + const defaultMessage = isCodeScan ? 'Security scan failed.' : 'Test generation failed.' getLogger().error( - `Amazon Q is unable to upload workspace artifacts to Amazon S3 for security scans. For more information, see the Amazon Q documentation or contact your network or organization administrator.` + `Amazon Q is unable to upload workspace artifacts to Amazon S3 for ${featureType}. ` + + 'For more information, see the Amazon Q documentation or contact your network or organization administrator.' ) - const errorMessage = getTelemetryReasonDesc(error)?.includes(`"PUT" request failed with code "403"`) - ? `"PUT" request failed with code "403"` - : (getTelemetryReasonDesc(error) ?? 'Security scan failed.') - - throw new UploadArtifactToS3Error(errorMessage) + const messageWithOutId = getTelemetryReasonDesc(error)?.includes('"PUT" request failed with code "403"') + errorMessage = messageWithOutId + ? '"PUT" request failed with code "403"' + : (getTelemetryReasonDesc(error) ?? defaultMessage) + throw isCodeScan ? new UploadArtifactToS3Error(errorMessage) : new UploadTestArtifactToS3Error(errorMessage) } } diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts index 36f5b5b1d63..64dffcbc4a6 100644 --- a/packages/core/src/codewhisperer/service/testGenHandler.ts +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -13,7 +13,15 @@ import CodeWhispererUserClient, { CreateUploadUrlRequest, TargetCode, } from '../client/codewhispereruserclient' -import { CreateUploadUrlError, InvalidSourceZipError, TestGenFailedError, TestGenTimedOutError } from '../models/errors' +import { + CreateTestJobError, + CreateUploadUrlError, + ExportResultsArchiveError, + InvalidSourceZipError, + TestGenFailedError, + TestGenStoppedError, + TestGenTimedOutError, +} from '../../amazonqTest/error' import { getMd5, uploadArtifactToS3 } from './securityScanHandler' import { fs, randomUUID, sleep, tempDirPath } from '../../shared' import { ShortAnswer, TestGenerationJobStatus, testGenState } from '..' @@ -29,7 +37,7 @@ import { glob } from 'glob' export function throwIfCancelled() { // TODO: fileName will be '' if user gives propt without opening if (testGenState.isCancelling()) { - throw Error(CodeWhispererConstants.unitTestGenerationCancelMessage) + throw new TestGenStoppedError() } } @@ -47,12 +55,12 @@ export async function getPresignedUrlAndUploadTestGen(zipMetadata: ZipMetadata) logger.verbose(`Prepare for uploading src context...`) const srcResp = await codeWhisperer.codeWhispererClient.createUploadUrl(srcReq).catch((err) => { getLogger().error(`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`) - throw new CreateUploadUrlError(err) + throw new CreateUploadUrlError(err.message, err.code) }) logger.verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`) logger.verbose(`Complete Getting presigned Url for uploading src context.`) logger.verbose(`Uploading src context...`) - await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp) + await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, CodeWhispererConstants.FeatureUseCase.TEST_GENERATION) logger.verbose(`Complete uploading src context.`) const artifactMap: ArtifactMap = { SourceCode: srcResp.uploadId, @@ -96,7 +104,7 @@ export async function createTestJob( const resp = await codewhispererClient.codeWhispererClient.startTestGeneration(req).catch((err) => { ChatSessionManager.Instance.getSession().startTestGenerationRequestId = err.requestId logger.error(`Failed creating test job. Request id: ${err.requestId}`) - throw err + throw new CreateTestJobError(err.message, err.statusCode) }) logger.info('Unit test generation request id: %s', resp.$response.requestId) logger.debug('Unit test generation data: %O', resp.$response.data) @@ -151,7 +159,7 @@ export async function pollTestJobStatus( // Stop the Unit test generation workflow if IDE receive stopIteration = true if (shortAnswer.stopIteration === 'true') { session.stopIteration = true - throw new TestGenFailedError(shortAnswer.planSummary) + throw new TestGenFailedError('400', shortAnswer.planSummary) } if (shortAnswer.numberOfTestMethods) { session.numberOfTestsGenerated = Number(shortAnswer.numberOfTestMethods) @@ -250,7 +258,7 @@ export async function exportResultsArchive( } catch (e) { downloadErrorMessage = (e as Error).message getLogger().error(`Unit Test Generation: ExportResultArchive error = ${downloadErrorMessage}`) - throw new Error('Error downloading test generation result artifacts: ' + downloadErrorMessage) + throw new ExportResultsArchiveError(downloadErrorMessage) } } @@ -289,7 +297,7 @@ export async function downloadResultArchive( } catch (e: any) { downloadErrorMessage = (e as Error).message getLogger().error(`Unit Test Generation: ExportResultArchive error = ${downloadErrorMessage}`) - throw e + throw new ExportResultsArchiveError(downloadErrorMessage) } finally { cwStreamingClient.destroy() } diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 9518aa610fc..de65fecdadc 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -65,6 +65,7 @@ export class TelemetryHelper { requestId?: string, perfClientLatency?: number, reasonDesc?: string, + httpStatusCode?: string, isCodeBlockSelected?: boolean, artifactsUploadDuration?: number, buildPayloadBytes?: number, @@ -75,7 +76,7 @@ export class TelemetryHelper { generatedCharactersCount?: number, generatedCount?: number, generatedLinesCount?: number, - reason?: string + reason?: string, ) { telemetry.amazonq_utgGenerateTests.emit({ cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', @@ -99,6 +100,7 @@ export class TelemetryHelper { requestId: requestId, reasonDesc: reasonDesc, reason: reason, + httpStatusCode: httpStatusCode, }) } diff --git a/packages/core/src/codewhisperer/util/zipUtil.ts b/packages/core/src/codewhisperer/util/zipUtil.ts index b75a6798ab2..dca5278e364 100644 --- a/packages/core/src/codewhisperer/util/zipUtil.ts +++ b/packages/core/src/codewhisperer/util/zipUtil.ts @@ -20,9 +20,10 @@ import { NoSourceFilesError, ProjectSizeExceededError, } from '../models/errors' -import { ZipUseCase } from '../models/constants' +import { FeatureUseCase } from '../models/constants' import { ChildProcess, ChildProcessOptions } from '../../shared/utilities/processUtils' import { removeAnsi } from '../../shared' +import { ProjectZipError } from '../../amazonqTest/error' export interface ZipMetadata { rootDir: string @@ -135,13 +136,19 @@ export class ZipUtil { if (this.reachSizeLimit(this._totalSize, scope)) { throw new FileSizeExceededError() } - const zipFilePath = this.getZipDirPath(ZipUseCase.CODE_SCAN) + CodeWhispererConstants.codeScanZipExt + const zipFilePath = this.getZipDirPath(FeatureUseCase.CODE_SCAN) + CodeWhispererConstants.codeScanZipExt zip.writeZip(zipFilePath) return zipFilePath } - protected getZipEntryPath(projectName: string, relativePath: string) { - return path.join(projectName, relativePath) + protected getZipEntryPath(projectName: string, relativePath: string, useCase?: FeatureUseCase) { + // Workspaces with multiple folders have the folder names as the root folder, + // but workspaces with only a single folder don't. So prepend the workspace folder name + // if it is not present. + if (useCase === FeatureUseCase.TEST_GENERATION) { + return path.join(projectName, relativePath) + } + return relativePath.split('/').shift() === projectName ? relativePath : path.join(projectName, relativePath) } /** @@ -203,15 +210,15 @@ export class ZipUtil { await processDirectory(metadataDir) } - protected async zipProject(useCase: ZipUseCase, projectPath?: string, metadataDir?: string) { + protected async zipProject(useCase: FeatureUseCase, projectPath?: string, metadataDir?: string) { const zip = new admZip() let projectPaths = [] - if (useCase === ZipUseCase.TEST_GENERATION && projectPath) { + if (useCase === FeatureUseCase.TEST_GENERATION && projectPath) { projectPaths.push(projectPath) } else { projectPaths = this.getProjectPaths() } - if (useCase === ZipUseCase.CODE_SCAN) { + if (useCase === FeatureUseCase.CODE_SCAN) { await this.processCombinedGitDiff(zip, projectPaths, '', CodeWhispererConstants.CodeAnalysisScope.PROJECT) } const languageCount = new Map() @@ -220,7 +227,7 @@ export class ZipUtil { if (metadataDir) { await this.processMetadataDir(zip, metadataDir) } - if (useCase !== ZipUseCase.TEST_GENERATION) { + if (useCase !== FeatureUseCase.TEST_GENERATION) { this.processOtherFiles(zip, languageCount) } @@ -403,7 +410,7 @@ export class ZipUtil { zip: admZip, languageCount: Map, projectPaths: string[] | undefined, - useCase: ZipUseCase + useCase: FeatureUseCase ) { if (!projectPaths || projectPaths.length === 0) { return @@ -420,7 +427,7 @@ export class ZipUtil { const zipEntryPath = this.getZipEntryPath(projectName, file.relativeFilePath) if (ZipConstants.knownBinaryFileExts.includes(path.extname(file.fileUri.fsPath))) { - if (useCase === ZipUseCase.TEST_GENERATION) { + if (useCase === FeatureUseCase.TEST_GENERATION) { continue } await this.processBinaryFile(zip, file.fileUri, zipEntryPath) @@ -512,10 +519,10 @@ export class ZipUtil { return vscode.workspace.textDocuments.some((document) => document.uri.fsPath === uri.fsPath && document.isDirty) } - public getZipDirPath(useCase: ZipUseCase): string { + public getZipDirPath(useCase: FeatureUseCase): string { if (this._zipDir === '') { const prefix = - useCase === ZipUseCase.TEST_GENERATION + useCase === FeatureUseCase.TEST_GENERATION ? CodeWhispererConstants.TestGenerationTruncDirPrefix : CodeWhispererConstants.codeScanTruncDirPrefix @@ -529,7 +536,7 @@ export class ZipUtil { scope: CodeWhispererConstants.CodeAnalysisScope ): Promise { try { - const zipDirPath = this.getZipDirPath(ZipUseCase.CODE_SCAN) + const zipDirPath = this.getZipDirPath(FeatureUseCase.CODE_SCAN) let zipFilePath: string if ( scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || @@ -537,7 +544,7 @@ export class ZipUtil { ) { zipFilePath = await this.zipFile(uri, scope) } else if (scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT) { - zipFilePath = await this.zipProject(ZipUseCase.CODE_SCAN) + zipFilePath = await this.zipProject(FeatureUseCase.CODE_SCAN) } else { throw new ToolkitError(`Unknown code analysis scope: ${scope}`) } @@ -563,7 +570,7 @@ export class ZipUtil { public async generateZipTestGen(projectPath: string, initialExecution: boolean): Promise { try { // const repoMapFile = await LspClient.instance.getRepoMapJSON() - const zipDirPath = this.getZipDirPath(ZipUseCase.TEST_GENERATION) + const zipDirPath = this.getZipDirPath(FeatureUseCase.TEST_GENERATION) const metadataDir = path.join(zipDirPath, 'utgRequiredArtifactsDir') @@ -591,7 +598,7 @@ export class ZipUtil { } } - const zipFilePath: string = await this.zipProject(ZipUseCase.TEST_GENERATION, projectPath, metadataDir) + const zipFilePath: string = await this.zipProject(FeatureUseCase.TEST_GENERATION, projectPath, metadataDir) const zipFileSize = (await fs.stat(zipFilePath)).size return { rootDir: zipDirPath, @@ -605,7 +612,9 @@ export class ZipUtil { } } catch (error) { getLogger().error('Zip error caused by: %s', error) - throw error + throw new ProjectZipError( + error instanceof Error ? error.message : 'Unknown error occurred during zip operation' + ) } } // TODO: Refactor this From 0afde3608428beca5a170e34898d2ab8244f96c6 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:45:36 -0800 Subject: [PATCH 02/18] Adding statusCode for exportResultArchive API --- packages/core/src/amazonqTest/error.ts | 9 +++++++-- .../core/src/codewhisperer/service/testGenHandler.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index 59dc10a20a8..cb431d4c6de 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -74,7 +74,12 @@ export class TestGenFailedError extends TestGenError { } } export class ExportResultsArchiveError extends TestGenError { - constructor(error?: string) { - super(error ?? 'Test generation failed', 'ExportResultsArchiveError', '500', techinalErrorCustomerFacingMessage) + constructor(error?: string, statusCode?: string) { + super( + error ?? 'Test generation failed', + 'ExportResultsArchiveError', + statusCode ?? '400', + techinalErrorCustomerFacingMessage + ) } } diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts index 64dffcbc4a6..05b6fe3e701 100644 --- a/packages/core/src/codewhisperer/service/testGenHandler.ts +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -297,7 +297,7 @@ export async function downloadResultArchive( } catch (e: any) { downloadErrorMessage = (e as Error).message getLogger().error(`Unit Test Generation: ExportResultArchive error = ${downloadErrorMessage}`) - throw new ExportResultsArchiveError(downloadErrorMessage) + throw new ExportResultsArchiveError(downloadErrorMessage, e.statusCode) } finally { cwStreamingClient.destroy() } From d5994daf8fbb1e3d0da3cf2b212ab9c5aa306e3e Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:50:17 -0800 Subject: [PATCH 03/18] Error handling in UTG --- packages/core/src/amazonqTest/error.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index cb431d4c6de..d7356286253 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -1,8 +1,7 @@ /*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 - */ -import { CodeWhispererConstants } from '../codewhisperer/indexNode' +// */ import { ToolkitError } from '../shared/errors' export const techinalErrorCustomerFacingMessage = @@ -55,12 +54,7 @@ export class TestGenTimedOutError extends TestGenError { } export class TestGenStoppedError extends TestGenError { constructor() { - super( - CodeWhispererConstants.unitTestGenerationCancelMessage, - 'TestGenCancelled', - '400', - CodeWhispererConstants.unitTestGenerationCancelMessage - ) + super('Unit test generation cancelled.', 'TestGenCancelled', '400', 'Unit test generation cancelled.') } } export class TestGenFailedError extends TestGenError { From a3a3894f5986f76f45c6f233147b449fb64a60c7 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:00:40 -0800 Subject: [PATCH 04/18] lint errors --- packages/core/src/amazonqTest/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index d7356286253..b59c51ee852 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -1,7 +1,7 @@ /*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 -// */ + */ import { ToolkitError } from '../shared/errors' export const techinalErrorCustomerFacingMessage = From b9239d4d8bd26752217457ac8a4687ca739f05e5 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:07:06 -0800 Subject: [PATCH 05/18] lint errors --- .../core/src/amazonqTest/chat/controller/controller.ts | 7 +++---- packages/core/src/codewhisperer/models/constants.ts | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index a6e085f6420..e67a0a60e0c 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -20,6 +20,7 @@ import { TelemetryHelper, TestGenerationBuildStep, testGenState, + tooManyRequestErrorMessage, unitTestGenerationCancelMessage, } from '../../../codewhisperer' import { @@ -284,16 +285,14 @@ export class TestController { ) } else { getLogger().error('Too many requests.') - // TODO: move to constants file - return this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID) + return this.messenger.sendErrorMessage(tooManyRequestErrorMessage, tabID) } } if (isAwsError(error)) { if (error.code === 'ThrottlingException') { // TODO: use the explicitly modeled exception reason for quota vs throttle{ getLogger().error('Too many requests.') - // TODO: move to constants file - this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID) + this.messenger.sendErrorMessage(tooManyRequestErrorMessage, tabID) } else { // other service errors: // AccessDeniedException - should not happen because access is validated before this point in the client diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 1f365692f5e..0ffe5ec808d 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -727,6 +727,8 @@ export const noOpenProjectsFoundChatTestGenMessage = `Sorry, I couldn\'t find a export const unitTestGenerationCancelMessage = 'Unit test generation cancelled.' +export const tooManyRequestErrorMessage = 'Too many requests. Please wait before retrying.' + export const noJavaProjectsFoundChatMessage = `I couldn\'t find a project that I can upgrade. Currently, I support Java 8, Java 11, and Java 17 projects built on Maven. Make sure your project is open in the IDE. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` export const linkToDocsHome = 'https://docs.aws.amazon.com/amazonq/latest/aws-builder-use-ug/code-transformation.html' From 8a18b92f0466b00078dc3e3633e476cef4f7bf9a Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:51:38 -0800 Subject: [PATCH 06/18] replacing customerFacingMessage with UiMessage --- .../amazonqTest/chat/controller/controller.ts | 67 ++++++++++++++----- packages/core/src/amazonqTest/error.ts | 2 +- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index e67a0a60e0c..7ad820ca829 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -242,26 +242,31 @@ export class TestController { // eslint-disable-next-line unicorn/no-null this.messenger.sendUpdatePromptProgress(data.tabID, null) const session = this.sessionStorage.getSession() - const isCancel = data.error.customerFacingMessage === unitTestGenerationCancelMessage - TelemetryHelper.instance.sendTestGenerationToolkitEvent( - session, - true, - isCancel ? 'Cancelled' : 'Failed', - session.startTestGenerationRequestId, - performance.now() - session.testGenerationStartTime, - getTelemetryReasonDesc(data.error), - data.error.statusCode ?? '0', // If status code is 0, need to investigate where this is originating from. - session.isCodeBlockSelected, - session.artifactsUploadDuration, - session.srcPayloadSize, - session.srcZipFileSize - ) + const isCancel = data.error.uiMessage === unitTestGenerationCancelMessage + telemetry.amazonq_utgGenerateTests.emit({ + cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', + jobId: session.listOfTestGenerationJobId[0], // For RIV, UTG does only one StartTestGeneration API call + jobGroup: session.testGenerationJobGroupName, + requestId: session.startTestGenerationRequestId, + hasUserPromptSupplied: session.hasUserPromptSupplied, + isCodeBlockSelected: session.isCodeBlockSelected, + buildPayloadBytes: session.srcPayloadSize, + buildZipFileBytes: session.srcZipFileSize, + artifactsUploadDuration: session.artifactsUploadDuration, + perfClientLatency: performance.now() - session.testGenerationStartTime, + result: isCancel ? 'Cancelled' : 'Failed', + reasonDesc: getTelemetryReasonDesc(data.error), + isSupportedLanguage: true, + credentialStartUrl: AuthUtil.instance.startUrl, + httpStatusCode: data.error.statusCode ?? 0, // If status code is 0, need to investigate where this is originating from. + reason: data.error.code, + }) if (session.stopIteration) { // Error from Science - this.messenger.sendMessage(data.error.customerFacingMessage.replaceAll('```', ''), data.tabID, 'answer') + this.messenger.sendMessage(data.error.uiMessage.replaceAll('```', ''), data.tabID, 'answer') } else { isCancel - ? this.messenger.sendMessage(data.error.customerFacingMessage, data.tabID, 'answer') + ? this.messenger.sendMessage(data.error.uiMessage, data.tabID, 'answer') : this.sendErrorMessage(data) } await this.sessionCleanUp() @@ -270,7 +275,7 @@ export class TestController { // Client side error messages private sendErrorMessage(data: { tabID: string - error: { customerFacingMessage: string; message: string; code: string; statusCode: string } + error: { uiMessage: string; message: string; code: string; statusCode: string } }) { const { error, tabID } = data @@ -304,7 +309,7 @@ export class TestController { } } else { // other unexpected errors (TODO enumerate all other failure cases) - getLogger().error('Other error message: %s', error.customerFacingMessage) + getLogger().error('Other error message: %s', error.uiMessage) this.messenger.sendErrorMessage('', tabID) } } @@ -715,6 +720,9 @@ export class TestController { // this.messenger.sendMessage('Accepted', message.tabID, 'prompt') telemetry.ui_click.emit({ elementId: 'unitTestGeneration_acceptDiff' }) + getLogger().info( + `Generated unit tests are accepted for ${session.fileLanguage ?? 'plaintext'} language with jobId: ${session.listOfTestGenerationJobId[0]}, jobGroupName: ${session.testGenerationJobGroupName}, result: Succeeded` + ) TelemetryHelper.instance.sendTestGenerationToolkitEvent( session, true, @@ -735,6 +743,29 @@ export class TestController { session.linesOfCodeGenerated ) + telemetry.amazonq_utgGenerateTests.emit({ + generatedCount: session.numberOfTestsGenerated, + acceptedCount: session.numberOfTestsGenerated, + generatedCharactersCount: session.charsOfCodeGenerated, + acceptedCharactersCount: session.charsOfCodeAccepted, + generatedLinesCount: session.linesOfCodeGenerated, + acceptedLinesCount: session.linesOfCodeAccepted, + cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', + jobId: session.listOfTestGenerationJobId[0], // For RIV, UTG does only one StartTestGeneration API call so jobId = session.listOfTestGenerationJobId[0] + jobGroup: session.testGenerationJobGroupName, + requestId: session.startTestGenerationRequestId, + buildPayloadBytes: session.srcPayloadSize, + buildZipFileBytes: session.srcZipFileSize, + artifactsUploadDuration: session.artifactsUploadDuration, + hasUserPromptSupplied: session.hasUserPromptSupplied, + isCodeBlockSelected: session.isCodeBlockSelected, + perfClientLatency: session.latencyOfTestGeneration, + isSupportedLanguage: true, + credentialStartUrl: AuthUtil.instance.startUrl, + result: 'Succeeded', + httpStatusCode: '200', + }) + await this.endSession(message, FollowUpTypes.SkipBuildAndFinish) return diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index b59c51ee852..2a6930395b1 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -12,7 +12,7 @@ export class TestGenError extends ToolkitError { error: string, code: string, public statusCode: string, - public customerFacingMessage: string + public uiMessage: string ) { super(error, { code }) } From fc60bf056f0c4b37b407bd5a6e7fdc4f04e9c1f6 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:44:42 -0800 Subject: [PATCH 07/18] Minor fixes --- packages/core/src/amazonqTest/chat/controller/controller.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 7ad820ca829..92b154fc3de 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -888,11 +888,8 @@ export class TestController { } await this.sessionCleanUp() - // TODO: revert 'Accepted' to 'Skip build and finish' once supported - const message = step === FollowUpTypes.RejectCode ? 'Rejected' : 'Accepted' - this.messenger.sendMessage(message, data.tabID, 'prompt') - this.messenger.sendMessage(`Unit test generation workflow is completed.`, data.tabID, 'answer') + // this.messenger.sendMessage(`Unit test generation workflow is completed.`, data.tabID, 'answer') this.messenger.sendChatInputEnabled(data.tabID, true) return } From 0cf0945f37aa7b18c9783816a502aa61b846932c Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:01:12 -0800 Subject: [PATCH 08/18] Minor change in science side telemetry errors --- .../core/src/amazonqTest/chat/controller/controller.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 92b154fc3de..cf0c876dda3 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -243,6 +243,10 @@ export class TestController { this.messenger.sendUpdatePromptProgress(data.tabID, null) const session = this.sessionStorage.getSession() const isCancel = data.error.uiMessage === unitTestGenerationCancelMessage + let telemetryErrorMessage = getTelemetryReasonDesc(data.error) + if (session.stopIteration) { + telemetryErrorMessage = getTelemetryReasonDesc(data.error.uiMessage.replaceAll('```', '')) + } telemetry.amazonq_utgGenerateTests.emit({ cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', jobId: session.listOfTestGenerationJobId[0], // For RIV, UTG does only one StartTestGeneration API call @@ -255,7 +259,8 @@ export class TestController { artifactsUploadDuration: session.artifactsUploadDuration, perfClientLatency: performance.now() - session.testGenerationStartTime, result: isCancel ? 'Cancelled' : 'Failed', - reasonDesc: getTelemetryReasonDesc(data.error), + reason: data.error.code, + reasonDesc: telemetryErrorMessage, isSupportedLanguage: true, credentialStartUrl: AuthUtil.instance.startUrl, httpStatusCode: data.error.statusCode ?? 0, // If status code is 0, need to investigate where this is originating from. From cea2049fb7b4acb07461247f090dedbd80a81812 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:36:32 -0800 Subject: [PATCH 09/18] Minor change in UploadTestArtifactToS3Error error message --- .../src/codewhisperer/service/securityScanHandler.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/core/src/codewhisperer/service/securityScanHandler.ts b/packages/core/src/codewhisperer/service/securityScanHandler.ts index dfc9cc017fe..6c0cbae9065 100644 --- a/packages/core/src/codewhisperer/service/securityScanHandler.ts +++ b/packages/core/src/codewhisperer/service/securityScanHandler.ts @@ -376,10 +376,14 @@ export async function uploadArtifactToS3( `Amazon Q is unable to upload workspace artifacts to Amazon S3 for ${featureType}. ` + 'For more information, see the Amazon Q documentation or contact your network or organization administrator.' ) - const messageWithOutId = getTelemetryReasonDesc(error)?.includes('"PUT" request failed with code "403"') - errorMessage = messageWithOutId - ? '"PUT" request failed with code "403"' - : (getTelemetryReasonDesc(error) ?? defaultMessage) + const errorDesc = getTelemetryReasonDesc(error) + if (errorDesc?.includes('"PUT" request failed with code "403"')) { + errorMessage = '"PUT" request failed with code "403"' + } else if (errorDesc?.includes('"PUT" request failed with code "503"')) { + errorMessage = '"PUT" request failed with code "503"' + } else { + errorMessage = errorDesc ?? defaultMessage + } throw isCodeScan ? new UploadArtifactToS3Error(errorMessage) : new UploadTestArtifactToS3Error(errorMessage) } } From 218b43d0a45f891740554d550fa10139f13cc134 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:24:09 -0800 Subject: [PATCH 10/18] Merge conflicts --- .../amazonqTest/chat/controller/controller.ts | 63 +++++++------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index cf0c876dda3..a49c87ae5bb 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -247,25 +247,27 @@ export class TestController { if (session.stopIteration) { telemetryErrorMessage = getTelemetryReasonDesc(data.error.uiMessage.replaceAll('```', '')) } - telemetry.amazonq_utgGenerateTests.emit({ - cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', - jobId: session.listOfTestGenerationJobId[0], // For RIV, UTG does only one StartTestGeneration API call - jobGroup: session.testGenerationJobGroupName, - requestId: session.startTestGenerationRequestId, - hasUserPromptSupplied: session.hasUserPromptSupplied, - isCodeBlockSelected: session.isCodeBlockSelected, - buildPayloadBytes: session.srcPayloadSize, - buildZipFileBytes: session.srcZipFileSize, - artifactsUploadDuration: session.artifactsUploadDuration, - perfClientLatency: performance.now() - session.testGenerationStartTime, - result: isCancel ? 'Cancelled' : 'Failed', - reason: data.error.code, - reasonDesc: telemetryErrorMessage, - isSupportedLanguage: true, - credentialStartUrl: AuthUtil.instance.startUrl, - httpStatusCode: data.error.statusCode ?? 0, // If status code is 0, need to investigate where this is originating from. - reason: data.error.code, - }) + TelemetryHelper.instance.sendTestGenerationToolkitEvent( + session, + true, + isCancel ? 'Cancelled' : 'Failed', + session.startTestGenerationRequestId, + performance.now() - session.testGenerationStartTime, + telemetryErrorMessage, + data.error.statusCode ?? '0', + session.isCodeBlockSelected, + session.artifactsUploadDuration, + session.srcPayloadSize, + session.srcZipFileSize, + session.charsOfCodeAccepted, + session.numberOfTestsGenerated, + session.linesOfCodeAccepted, + session.charsOfCodeGenerated, + session.numberOfTestsGenerated, + session.linesOfCodeGenerated, + data.error.code + ) + if (session.stopIteration) { // Error from Science this.messenger.sendMessage(data.error.uiMessage.replaceAll('```', ''), data.tabID, 'answer') @@ -748,29 +750,6 @@ export class TestController { session.linesOfCodeGenerated ) - telemetry.amazonq_utgGenerateTests.emit({ - generatedCount: session.numberOfTestsGenerated, - acceptedCount: session.numberOfTestsGenerated, - generatedCharactersCount: session.charsOfCodeGenerated, - acceptedCharactersCount: session.charsOfCodeAccepted, - generatedLinesCount: session.linesOfCodeGenerated, - acceptedLinesCount: session.linesOfCodeAccepted, - cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', - jobId: session.listOfTestGenerationJobId[0], // For RIV, UTG does only one StartTestGeneration API call so jobId = session.listOfTestGenerationJobId[0] - jobGroup: session.testGenerationJobGroupName, - requestId: session.startTestGenerationRequestId, - buildPayloadBytes: session.srcPayloadSize, - buildZipFileBytes: session.srcZipFileSize, - artifactsUploadDuration: session.artifactsUploadDuration, - hasUserPromptSupplied: session.hasUserPromptSupplied, - isCodeBlockSelected: session.isCodeBlockSelected, - perfClientLatency: session.latencyOfTestGeneration, - isSupportedLanguage: true, - credentialStartUrl: AuthUtil.instance.startUrl, - result: 'Succeeded', - httpStatusCode: '200', - }) - await this.endSession(message, FollowUpTypes.SkipBuildAndFinish) return From b232e2e18f6960eebf0a464ed4e59aec517c8e3e Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:36:06 -0800 Subject: [PATCH 11/18] Addressing PR --- .../amazonqTest/chat/controller/controller.ts | 29 ++++++++++--------- packages/core/src/amazonqTest/error.ts | 16 +++++----- .../service/securityScanHandler.ts | 4 ++- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index a49c87ae5bb..4dd1e2ea275 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -295,30 +295,31 @@ export class TestController { tabID, 'answer' ) - } else { - getLogger().error('Too many requests.') + } + if (error.message.includes('Too many requests')) { + getLogger().error(error.message) return this.messenger.sendErrorMessage(tooManyRequestErrorMessage, tabID) } } if (isAwsError(error)) { if (error.code === 'ThrottlingException') { // TODO: use the explicitly modeled exception reason for quota vs throttle{ - getLogger().error('Too many requests.') + getLogger().error(error.message) this.messenger.sendErrorMessage(tooManyRequestErrorMessage, tabID) - } else { - // other service errors: - // AccessDeniedException - should not happen because access is validated before this point in the client - // ValidationException - shouldn't happen because client should not send malformed requests - // ConflictException - should not happen because the client will maintain proper state - // InternalServerException - shouldn't happen but needs to be caught - getLogger().error('Other error message: %s', error.message) - this.messenger.sendErrorMessage('', tabID) + return } - } else { - // other unexpected errors (TODO enumerate all other failure cases) - getLogger().error('Other error message: %s', error.uiMessage) + // other service errors: + // AccessDeniedException - should not happen because access is validated before this point in the client + // ValidationException - shouldn't happen because client should not send malformed requests + // ConflictException - should not happen because the client will maintain proper state + // InternalServerException - shouldn't happen but needs to be caught + getLogger().error('Other error message: %s', error.message) this.messenger.sendErrorMessage('', tabID) + return } + // other unexpected errors (TODO enumerate all other failure cases) + getLogger().error('Other error message: %s', error.uiMessage) + this.messenger.sendErrorMessage('', tabID) } // This function handles actions if user clicked on any Button one of these cases will be executed diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index 2a6930395b1..25335f59803 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -4,7 +4,7 @@ */ import { ToolkitError } from '../shared/errors' -export const techinalErrorCustomerFacingMessage = +export const technicalErrorCustomerFacingMessage = 'I am experiencing technical difficulties at the moment. Please try again in a few minutes.' const defaultTestGenErrorMessage = 'Amazon Q encountered an error while generating tests. Try again later.' export class TestGenError extends ToolkitError { @@ -29,17 +29,17 @@ export class InvalidSourceZipError extends TestGenError { } export class CreateUploadUrlError extends TestGenError { constructor(errorMessage: string, errorCode: string) { - super(errorMessage, 'CreateUploadUrlError', errorCode, techinalErrorCustomerFacingMessage) + super(errorMessage, 'CreateUploadUrlError', errorCode, technicalErrorCustomerFacingMessage) } } export class UploadTestArtifactToS3Error extends TestGenError { - constructor(error: string) { - super(error, 'UploadTestArtifactToS3Error', '500', techinalErrorCustomerFacingMessage) + constructor(error: string, statusCode?: string) { + super(error, 'UploadTestArtifactToS3Error', statusCode ?? '400', technicalErrorCustomerFacingMessage) } } export class CreateTestJobError extends TestGenError { constructor(error: string, code: string) { - super(error, 'CreateTestJobError', code, techinalErrorCustomerFacingMessage) + super(error, 'CreateTestJobError', code, technicalErrorCustomerFacingMessage) } } export class TestGenTimedOutError extends TestGenError { @@ -48,7 +48,7 @@ export class TestGenTimedOutError extends TestGenError { 'Test generation failed. Amazon Q timed out.', 'TestGenTimedOutError', '500', - techinalErrorCustomerFacingMessage + technicalErrorCustomerFacingMessage ) } } @@ -63,7 +63,7 @@ export class TestGenFailedError extends TestGenError { error ?? 'Test generation failed', 'TestGenFailedError', code, - error ?? techinalErrorCustomerFacingMessage + error ?? technicalErrorCustomerFacingMessage ) } } @@ -73,7 +73,7 @@ export class ExportResultsArchiveError extends TestGenError { error ?? 'Test generation failed', 'ExportResultsArchiveError', statusCode ?? '400', - techinalErrorCustomerFacingMessage + technicalErrorCustomerFacingMessage ) } } diff --git a/packages/core/src/codewhisperer/service/securityScanHandler.ts b/packages/core/src/codewhisperer/service/securityScanHandler.ts index 6c0cbae9065..c64bd4e41a3 100644 --- a/packages/core/src/codewhisperer/service/securityScanHandler.ts +++ b/packages/core/src/codewhisperer/service/securityScanHandler.ts @@ -384,7 +384,9 @@ export async function uploadArtifactToS3( } else { errorMessage = errorDesc ?? defaultMessage } - throw isCodeScan ? new UploadArtifactToS3Error(errorMessage) : new UploadTestArtifactToS3Error(errorMessage) + throw isCodeScan + ? new UploadArtifactToS3Error(errorMessage) + : new UploadTestArtifactToS3Error(errorMessage, (error as any).statusCode) } } From 39a9a64c2dd9302f5095dcc8146c586771e3d77c Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:39:52 -0800 Subject: [PATCH 12/18] Lint issues --- .../core/src/amazonqTest/chat/controller/controller.ts | 7 ++++--- .../core/src/codewhisperer/commands/startTestGeneration.ts | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 4dd1e2ea275..d10abc6553c 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -286,7 +286,7 @@ export class TestController { }) { const { error, tabID } = data - // If user reached montly limit for builderId + // If user reached monthly limit for builderId if (error.code === 'CreateTestJobError') { if (error.message.includes(CodeWhispererConstants.utgLimitReached)) { getLogger().error('Monthly quota reached for QSDA actions.') @@ -1309,8 +1309,9 @@ export class TestController { 'Deleting output.log and temp result directory. testGenerationLogsDir: %s', testGenerationLogsDir ) - if (await fs.existsFile(path.join(testGenerationLogsDir, 'output.log'))) { - await fs.delete(path.join(testGenerationLogsDir, 'output.log')) + const outputLogPath = path.join(testGenerationLogsDir, 'output.log') + if (await fs.existsFile(outputLogPath)) { + await fs.delete(outputLogPath) } if ( await fs diff --git a/packages/core/src/codewhisperer/commands/startTestGeneration.ts b/packages/core/src/codewhisperer/commands/startTestGeneration.ts index 1041d8f872d..4901a09930f 100644 --- a/packages/core/src/codewhisperer/commands/startTestGeneration.ts +++ b/packages/core/src/codewhisperer/commands/startTestGeneration.ts @@ -75,8 +75,9 @@ export async function startTestGenerationProcess( try { artifactMap = await getPresignedUrlAndUploadTestGen(zipMetadata) } finally { - if (await fs.existsFile(path.join(testGenerationLogsDir, 'output.log'))) { - await fs.delete(path.join(testGenerationLogsDir, 'output.log')) + const outputLogPath = path.join(testGenerationLogsDir, 'output.log') + if (await fs.existsFile(outputLogPath)) { + await fs.delete(outputLogPath) } await zipUtil.removeTmpFiles(zipMetadata) session.artifactsUploadDuration = performance.now() - uploadStartTime From 990b577c966ab636203ec09232e6f71a847169f4 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:24:16 -0800 Subject: [PATCH 13/18] Minor fixes --- .../amazonqTest/chat/controller/controller.ts | 2 ++ .../chat/controller/messenger/messenger.ts | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index d10abc6553c..8f1c1fbaac0 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -862,6 +862,8 @@ export class TestController { '200', session.isCodeBlockSelected, session.artifactsUploadDuration, + session.srcPayloadSize, + session.srcZipFileSize, 0, 0, 0, diff --git a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts index 98d52b21c5f..42778758585 100644 --- a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts @@ -280,8 +280,21 @@ export class Messenger { 'Cancelled', messageId, performance.now() - session.testGenerationStartTime, - getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage), - '400' + getTelemetryReasonDesc( + `TestGenCancelled: ${CodeWhispererConstants.unitTestGenerationCancelMessage}` + ), + '400', + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + 'TestGenCancelled' ) this.dispatcher.sendUpdatePromptProgress( new UpdatePromptProgressMessage(tabID, cancellingProgressField) @@ -295,7 +308,7 @@ export class Messenger { messageId, performance.now() - session.testGenerationStartTime, undefined, - '200', + '200' ) this.dispatcher.sendUpdatePromptProgress( new UpdatePromptProgressMessage(tabID, testGenCompletedField) From 5428d8d79ae19a2c41f035ae4bca1ac86600cbd9 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:29:55 -0800 Subject: [PATCH 14/18] Fixing Lint issues --- packages/core/src/codewhisperer/util/telemetryHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index de65fecdadc..14aed2c49ea 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -76,7 +76,7 @@ export class TelemetryHelper { generatedCharactersCount?: number, generatedCount?: number, generatedLinesCount?: number, - reason?: string, + reason?: string ) { telemetry.amazonq_utgGenerateTests.emit({ cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', From 456fa23882546a5f8d30b013ee0ffa01be85eea2 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:14:25 -0800 Subject: [PATCH 15/18] Fix build failures --- packages/core/src/codewhisperer/util/zipUtil.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/core/src/codewhisperer/util/zipUtil.ts b/packages/core/src/codewhisperer/util/zipUtil.ts index dca5278e364..e73753ed041 100644 --- a/packages/core/src/codewhisperer/util/zipUtil.ts +++ b/packages/core/src/codewhisperer/util/zipUtil.ts @@ -141,14 +141,8 @@ export class ZipUtil { return zipFilePath } - protected getZipEntryPath(projectName: string, relativePath: string, useCase?: FeatureUseCase) { - // Workspaces with multiple folders have the folder names as the root folder, - // but workspaces with only a single folder don't. So prepend the workspace folder name - // if it is not present. - if (useCase === FeatureUseCase.TEST_GENERATION) { - return path.join(projectName, relativePath) - } - return relativePath.split('/').shift() === projectName ? relativePath : path.join(projectName, relativePath) + protected getZipEntryPath(projectName: string, relativePath: string) { + return path.join(projectName, relativePath) } /** From f5e043e1c3a99e841a11a579fe94d06cc24807c2 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:05:06 -0800 Subject: [PATCH 16/18] Updating the statusCode from String to Int --- .../amazonqTest/chat/controller/controller.ts | 7 ++--- packages/core/src/amazonqTest/error.ts | 28 +++++++++---------- .../commands/startTestGeneration.ts | 2 +- .../codewhisperer/service/testGenHandler.ts | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 8f1c1fbaac0..32672011de4 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -254,7 +254,7 @@ export class TestController { session.startTestGenerationRequestId, performance.now() - session.testGenerationStartTime, telemetryErrorMessage, - data.error.statusCode ?? '0', + data.error.statusCode.toString() ?? '0', session.isCodeBlockSelected, session.artifactsUploadDuration, session.srcPayloadSize, @@ -280,10 +280,7 @@ export class TestController { return } // Client side error messages - private sendErrorMessage(data: { - tabID: string - error: { uiMessage: string; message: string; code: string; statusCode: string } - }) { + private sendErrorMessage(data: { tabID: string; error: { uiMessage: string; message: string; code: string } }) { const { error, tabID } = data // If user reached monthly limit for builderId diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index 25335f59803..c525d70a18d 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -11,7 +11,7 @@ export class TestGenError extends ToolkitError { constructor( error: string, code: string, - public statusCode: string, + public statusCode: number, public uiMessage: string ) { super(error, { code }) @@ -19,27 +19,27 @@ export class TestGenError extends ToolkitError { } export class ProjectZipError extends TestGenError { constructor(error: string) { - super(error, 'ProjectZipError', '400', defaultTestGenErrorMessage) + super(error, 'ProjectZipError', 400, defaultTestGenErrorMessage) } } export class InvalidSourceZipError extends TestGenError { constructor() { - super('Failed to create valid source zip', 'InvalidSourceZipError', '400', defaultTestGenErrorMessage) + super('Failed to create valid source zip', 'InvalidSourceZipError', 400, defaultTestGenErrorMessage) } } export class CreateUploadUrlError extends TestGenError { - constructor(errorMessage: string, errorCode: string) { + constructor(errorMessage: string, errorCode: number) { super(errorMessage, 'CreateUploadUrlError', errorCode, technicalErrorCustomerFacingMessage) } } export class UploadTestArtifactToS3Error extends TestGenError { - constructor(error: string, statusCode?: string) { - super(error, 'UploadTestArtifactToS3Error', statusCode ?? '400', technicalErrorCustomerFacingMessage) + constructor(error: string, statusCode?: number) { + super(error, 'UploadTestArtifactToS3Error', statusCode ?? 400, technicalErrorCustomerFacingMessage) } } export class CreateTestJobError extends TestGenError { - constructor(error: string, code: string) { - super(error, 'CreateTestJobError', code, technicalErrorCustomerFacingMessage) + constructor(error: string, statusCode: number) { + super(error, 'CreateTestJobError', statusCode, technicalErrorCustomerFacingMessage) } } export class TestGenTimedOutError extends TestGenError { @@ -47,32 +47,32 @@ export class TestGenTimedOutError extends TestGenError { super( 'Test generation failed. Amazon Q timed out.', 'TestGenTimedOutError', - '500', + 500, technicalErrorCustomerFacingMessage ) } } export class TestGenStoppedError extends TestGenError { constructor() { - super('Unit test generation cancelled.', 'TestGenCancelled', '400', 'Unit test generation cancelled.') + super('Unit test generation cancelled.', 'TestGenCancelled', 400, 'Unit test generation cancelled.') } } export class TestGenFailedError extends TestGenError { - constructor(code: string, error?: string) { + constructor(statusCode: number, error?: string) { super( error ?? 'Test generation failed', 'TestGenFailedError', - code, + statusCode, error ?? technicalErrorCustomerFacingMessage ) } } export class ExportResultsArchiveError extends TestGenError { - constructor(error?: string, statusCode?: string) { + constructor(error?: string, statusCode?: number) { super( error ?? 'Test generation failed', 'ExportResultsArchiveError', - statusCode ?? '400', + statusCode ?? 400, technicalErrorCustomerFacingMessage ) } diff --git a/packages/core/src/codewhisperer/commands/startTestGeneration.ts b/packages/core/src/codewhisperer/commands/startTestGeneration.ts index 4901a09930f..efa795b1f04 100644 --- a/packages/core/src/codewhisperer/commands/startTestGeneration.ts +++ b/packages/core/src/codewhisperer/commands/startTestGeneration.ts @@ -122,7 +122,7 @@ export async function startTestGenerationProcess( // TODO: Send status to test summary if (jobStatus === TestGenerationJobStatus.FAILED) { logger.verbose(`Test generation failed.`) - throw new TestGenFailedError('500') + throw new TestGenFailedError(500) } throwIfCancelled() if (!shouldContinueRunning(tabID)) { diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts index 05b6fe3e701..f928d0da19f 100644 --- a/packages/core/src/codewhisperer/service/testGenHandler.ts +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -159,7 +159,7 @@ export async function pollTestJobStatus( // Stop the Unit test generation workflow if IDE receive stopIteration = true if (shortAnswer.stopIteration === 'true') { session.stopIteration = true - throw new TestGenFailedError('400', shortAnswer.planSummary) + throw new TestGenFailedError(400, shortAnswer.planSummary) } if (shortAnswer.numberOfTestMethods) { session.numberOfTestsGenerated = Number(shortAnswer.numberOfTestMethods) From 9a045271c4de95d4de994b6884ade7d3434d5169 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:50:38 -0800 Subject: [PATCH 17/18] Removing statusCode from the UTG telemetry event --- packages/core/src/amazonqTest/error.ts | 38 ++---- .../commands/startTestGeneration.ts | 2 +- .../service/securityScanHandler.ts | 4 +- .../codewhisperer/service/testGenHandler.ts | 8 +- packages/toolkit/package.json | 127 +++++++++++------- 5 files changed, 100 insertions(+), 79 deletions(-) diff --git a/packages/core/src/amazonqTest/error.ts b/packages/core/src/amazonqTest/error.ts index c525d70a18d..a6694b35863 100644 --- a/packages/core/src/amazonqTest/error.ts +++ b/packages/core/src/amazonqTest/error.ts @@ -11,7 +11,6 @@ export class TestGenError extends ToolkitError { constructor( error: string, code: string, - public statusCode: number, public uiMessage: string ) { super(error, { code }) @@ -19,27 +18,27 @@ export class TestGenError extends ToolkitError { } export class ProjectZipError extends TestGenError { constructor(error: string) { - super(error, 'ProjectZipError', 400, defaultTestGenErrorMessage) + super(error, 'ProjectZipError', defaultTestGenErrorMessage) } } export class InvalidSourceZipError extends TestGenError { constructor() { - super('Failed to create valid source zip', 'InvalidSourceZipError', 400, defaultTestGenErrorMessage) + super('Failed to create valid source zip', 'InvalidSourceZipError', defaultTestGenErrorMessage) } } export class CreateUploadUrlError extends TestGenError { - constructor(errorMessage: string, errorCode: number) { - super(errorMessage, 'CreateUploadUrlError', errorCode, technicalErrorCustomerFacingMessage) + constructor(errorMessage: string) { + super(errorMessage, 'CreateUploadUrlError', technicalErrorCustomerFacingMessage) } } export class UploadTestArtifactToS3Error extends TestGenError { - constructor(error: string, statusCode?: number) { - super(error, 'UploadTestArtifactToS3Error', statusCode ?? 400, technicalErrorCustomerFacingMessage) + constructor(error: string) { + super(error, 'UploadTestArtifactToS3Error', technicalErrorCustomerFacingMessage) } } export class CreateTestJobError extends TestGenError { - constructor(error: string, statusCode: number) { - super(error, 'CreateTestJobError', statusCode, technicalErrorCustomerFacingMessage) + constructor(error: string) { + super(error, 'CreateTestJobError', technicalErrorCustomerFacingMessage) } } export class TestGenTimedOutError extends TestGenError { @@ -47,33 +46,22 @@ export class TestGenTimedOutError extends TestGenError { super( 'Test generation failed. Amazon Q timed out.', 'TestGenTimedOutError', - 500, technicalErrorCustomerFacingMessage ) } } export class TestGenStoppedError extends TestGenError { constructor() { - super('Unit test generation cancelled.', 'TestGenCancelled', 400, 'Unit test generation cancelled.') + super('Unit test generation cancelled.', 'TestGenCancelled', 'Unit test generation cancelled.') } } export class TestGenFailedError extends TestGenError { - constructor(statusCode: number, error?: string) { - super( - error ?? 'Test generation failed', - 'TestGenFailedError', - statusCode, - error ?? technicalErrorCustomerFacingMessage - ) + constructor(error?: string) { + super(error ?? 'Test generation failed', 'TestGenFailedError', error ?? technicalErrorCustomerFacingMessage) } } export class ExportResultsArchiveError extends TestGenError { - constructor(error?: string, statusCode?: number) { - super( - error ?? 'Test generation failed', - 'ExportResultsArchiveError', - statusCode ?? 400, - technicalErrorCustomerFacingMessage - ) + constructor(error?: string) { + super(error ?? 'Test generation failed', 'ExportResultsArchiveError', technicalErrorCustomerFacingMessage) } } diff --git a/packages/core/src/codewhisperer/commands/startTestGeneration.ts b/packages/core/src/codewhisperer/commands/startTestGeneration.ts index efa795b1f04..37c4d7eeccb 100644 --- a/packages/core/src/codewhisperer/commands/startTestGeneration.ts +++ b/packages/core/src/codewhisperer/commands/startTestGeneration.ts @@ -122,7 +122,7 @@ export async function startTestGenerationProcess( // TODO: Send status to test summary if (jobStatus === TestGenerationJobStatus.FAILED) { logger.verbose(`Test generation failed.`) - throw new TestGenFailedError(500) + throw new TestGenFailedError() } throwIfCancelled() if (!shouldContinueRunning(tabID)) { diff --git a/packages/core/src/codewhisperer/service/securityScanHandler.ts b/packages/core/src/codewhisperer/service/securityScanHandler.ts index c64bd4e41a3..6c0cbae9065 100644 --- a/packages/core/src/codewhisperer/service/securityScanHandler.ts +++ b/packages/core/src/codewhisperer/service/securityScanHandler.ts @@ -384,9 +384,7 @@ export async function uploadArtifactToS3( } else { errorMessage = errorDesc ?? defaultMessage } - throw isCodeScan - ? new UploadArtifactToS3Error(errorMessage) - : new UploadTestArtifactToS3Error(errorMessage, (error as any).statusCode) + throw isCodeScan ? new UploadArtifactToS3Error(errorMessage) : new UploadTestArtifactToS3Error(errorMessage) } } diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts index f928d0da19f..c7ed29ae2fd 100644 --- a/packages/core/src/codewhisperer/service/testGenHandler.ts +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -55,7 +55,7 @@ export async function getPresignedUrlAndUploadTestGen(zipMetadata: ZipMetadata) logger.verbose(`Prepare for uploading src context...`) const srcResp = await codeWhisperer.codeWhispererClient.createUploadUrl(srcReq).catch((err) => { getLogger().error(`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`) - throw new CreateUploadUrlError(err.message, err.code) + throw new CreateUploadUrlError(err.message) }) logger.verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`) logger.verbose(`Complete Getting presigned Url for uploading src context.`) @@ -104,7 +104,7 @@ export async function createTestJob( const resp = await codewhispererClient.codeWhispererClient.startTestGeneration(req).catch((err) => { ChatSessionManager.Instance.getSession().startTestGenerationRequestId = err.requestId logger.error(`Failed creating test job. Request id: ${err.requestId}`) - throw new CreateTestJobError(err.message, err.statusCode) + throw new CreateTestJobError(err.message) }) logger.info('Unit test generation request id: %s', resp.$response.requestId) logger.debug('Unit test generation data: %O', resp.$response.data) @@ -159,7 +159,7 @@ export async function pollTestJobStatus( // Stop the Unit test generation workflow if IDE receive stopIteration = true if (shortAnswer.stopIteration === 'true') { session.stopIteration = true - throw new TestGenFailedError(400, shortAnswer.planSummary) + throw new TestGenFailedError(shortAnswer.planSummary) } if (shortAnswer.numberOfTestMethods) { session.numberOfTestsGenerated = Number(shortAnswer.numberOfTestMethods) @@ -297,7 +297,7 @@ export async function downloadResultArchive( } catch (e: any) { downloadErrorMessage = (e as Error).message getLogger().error(`Unit Test Generation: ExportResultArchive error = ${downloadErrorMessage}`) - throw new ExportResultsArchiveError(downloadErrorMessage, e.statusCode) + throw new ExportResultsArchiveError(downloadErrorMessage) } finally { cwStreamingClient.destroy() } diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 311f3d02d36..412dd585c9f 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -4032,327 +4032,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 9dbc32d8aa3b5fe21455913b5bbb4a5e57df1702 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:01:41 -0800 Subject: [PATCH 18/18] Minor edits --- packages/core/src/amazonqTest/chat/controller/controller.ts | 3 --- .../src/amazonqTest/chat/controller/messenger/messenger.ts | 4 +--- packages/core/src/codewhisperer/util/telemetryHelper.ts | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 32672011de4..f24da62e4ae 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -254,7 +254,6 @@ export class TestController { session.startTestGenerationRequestId, performance.now() - session.testGenerationStartTime, telemetryErrorMessage, - data.error.statusCode.toString() ?? '0', session.isCodeBlockSelected, session.artifactsUploadDuration, session.srcPayloadSize, @@ -735,7 +734,6 @@ export class TestController { session.startTestGenerationRequestId, session.latencyOfTestGeneration, undefined, - '200', session.isCodeBlockSelected, session.artifactsUploadDuration, session.srcPayloadSize, @@ -856,7 +854,6 @@ export class TestController { session.startTestGenerationRequestId, session.latencyOfTestGeneration, undefined, - '200', session.isCodeBlockSelected, session.artifactsUploadDuration, session.srcPayloadSize, diff --git a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts index 42778758585..fc159eeabd2 100644 --- a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts @@ -283,7 +283,6 @@ export class Messenger { getTelemetryReasonDesc( `TestGenCancelled: ${CodeWhispererConstants.unitTestGenerationCancelMessage}` ), - '400', undefined, undefined, undefined, @@ -307,8 +306,7 @@ export class Messenger { 'Succeeded', messageId, performance.now() - session.testGenerationStartTime, - undefined, - '200' + undefined ) this.dispatcher.sendUpdatePromptProgress( new UpdatePromptProgressMessage(tabID, testGenCompletedField) diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 14aed2c49ea..9518aa610fc 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -65,7 +65,6 @@ export class TelemetryHelper { requestId?: string, perfClientLatency?: number, reasonDesc?: string, - httpStatusCode?: string, isCodeBlockSelected?: boolean, artifactsUploadDuration?: number, buildPayloadBytes?: number, @@ -100,7 +99,6 @@ export class TelemetryHelper { requestId: requestId, reasonDesc: reasonDesc, reason: reason, - httpStatusCode: httpStatusCode, }) }