Skip to content

Commit 5f3953f

Browse files
committed
UTG Error handling
1 parent 01acfb0 commit 5f3953f

File tree

9 files changed

+188
-65
lines changed

9 files changed

+188
-65
lines changed

packages/core/src/amazonqTest/chat/controller/controller.ts

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export class TestController {
241241
// eslint-disable-next-line unicorn/no-null
242242
this.messenger.sendUpdatePromptProgress(data.tabID, null)
243243
const session = this.sessionStorage.getSession()
244-
const isCancel = data.error.message === unitTestGenerationCancelMessage
244+
const isCancel = data.error.customerFacingMessage === unitTestGenerationCancelMessage
245245
telemetry.amazonq_utgGenerateTests.emit({
246246
cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext',
247247
jobId: session.listOfTestGenerationJobId[0], // For RIV, UTG does only one StartTestGeneration API call
@@ -257,56 +257,60 @@ export class TestController {
257257
reasonDesc: getTelemetryReasonDesc(data.error),
258258
isSupportedLanguage: true,
259259
credentialStartUrl: AuthUtil.instance.startUrl,
260+
httpStatusCode: data.error.statusCode ?? 0, // If status code is 0, need to investigate where this is originating from.
260261
})
261262
if (session.stopIteration) {
262263
// Error from Science
263-
this.messenger.sendMessage(data.error.message.replaceAll('```', ''), data.tabID, 'answer')
264+
this.messenger.sendMessage(data.error.customerFacingMessage.replaceAll('```', ''), data.tabID, 'answer')
264265
} else {
265266
isCancel
266-
? this.messenger.sendMessage(data.error.message, data.tabID, 'answer')
267+
? this.messenger.sendMessage(data.error.customerFacingMessage, data.tabID, 'answer')
267268
: this.sendErrorMessage(data)
268269
}
269270
await this.sessionCleanUp()
270271
return
271272
}
272273
// Client side error messages
273-
private sendErrorMessage(data: { tabID: string; error: { code: string; message: string } }) {
274+
private sendErrorMessage(data: {
275+
tabID: string
276+
error: { customerFacingMessage: string; message: string; code: string; statusCode: string }
277+
}) {
274278
const { error, tabID } = data
275279

280+
// If user reached montly limit for builderId
281+
if (error.code === 'CreateTestJobError') {
282+
if (error.message.includes(CodeWhispererConstants.utgLimitReached)) {
283+
getLogger().error('Monthly quota reached for QSDA actions.')
284+
return this.messenger.sendMessage(
285+
i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'),
286+
tabID,
287+
'answer'
288+
)
289+
} else {
290+
getLogger().error('Too many requests.')
291+
// TODO: move to constants file
292+
return this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID)
293+
}
294+
}
276295
if (isAwsError(error)) {
277296
if (error.code === 'ThrottlingException') {
278-
// TODO: use the explicitly modeled exception reason for quota vs throttle
279-
if (error.message.includes(CodeWhispererConstants.utgLimitReached)) {
280-
getLogger().error('Monthly quota reached for QSDA actions.')
281-
return this.messenger.sendMessage(
282-
i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'),
283-
tabID,
284-
'answer'
285-
)
286-
} else {
287-
getLogger().error('Too many requests.')
288-
// TODO: move to constants file
289-
this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID)
290-
}
297+
// TODO: use the explicitly modeled exception reason for quota vs throttle{
298+
getLogger().error('Too many requests.')
299+
// TODO: move to constants file
300+
this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID)
291301
} else {
292302
// other service errors:
293303
// AccessDeniedException - should not happen because access is validated before this point in the client
294304
// ValidationException - shouldn't happen because client should not send malformed requests
295305
// ConflictException - should not happen because the client will maintain proper state
296306
// InternalServerException - shouldn't happen but needs to be caught
297307
getLogger().error('Other error message: %s', error.message)
298-
this.messenger.sendErrorMessage(
299-
'Encountered an unexpected error when generating tests. Please try again',
300-
tabID
301-
)
308+
this.messenger.sendErrorMessage('', tabID)
302309
}
303310
} else {
304311
// other unexpected errors (TODO enumerate all other failure cases)
305-
getLogger().error('Other error message: %s', error.message)
306-
this.messenger.sendErrorMessage(
307-
'Encountered an unexpected error when generating tests. Please try again',
308-
tabID
309-
)
312+
getLogger().error('Other error message: %s', error.customerFacingMessage)
313+
this.messenger.sendErrorMessage('', tabID)
310314
}
311315
}
312316

@@ -716,6 +720,14 @@ export class TestController {
716720
// TODO: send the message once again once build is enabled
717721
//this.messenger.sendMessage('Accepted', message.tabID, 'prompt')
718722
telemetry.ui_click.emit({ elementId: 'unitTestGeneration_acceptDiff' })
723+
getLogger().info(
724+
session.fileLanguage ?? 'plaintext',
725+
session.listOfTestGenerationJobId[0],
726+
session.testGenerationJobGroupName,
727+
'Succeeded',
728+
AuthUtil.instance.startUrl,
729+
'200'
730+
)
719731
telemetry.amazonq_utgGenerateTests.emit({
720732
generatedCount: session.numberOfTestsGenerated,
721733
acceptedCount: session.numberOfTestsGenerated,
@@ -736,10 +748,10 @@ export class TestController {
736748
isSupportedLanguage: true,
737749
credentialStartUrl: AuthUtil.instance.startUrl,
738750
result: 'Succeeded',
751+
httpStatusCode: '200',
739752
})
740753

741754
await this.endSession(message, FollowUpTypes.SkipBuildAndFinish)
742-
await this.sessionCleanUp()
743755
return
744756

745757
if (session.listOfTestGenerationJobId.length === 1) {
@@ -860,6 +872,7 @@ export class TestController {
860872
isSupportedLanguage: true,
861873
credentialStartUrl: AuthUtil.instance.startUrl,
862874
result: 'Succeeded',
875+
httpStatusCode: '200',
863876
})
864877
telemetry.ui_click.emit({ elementId: 'unitTestGeneration_rejectDiff' })
865878
}
@@ -1304,8 +1317,17 @@ export class TestController {
13041317
'Deleting output.log and temp result directory. testGenerationLogsDir: %s',
13051318
testGenerationLogsDir
13061319
)
1307-
await fs.delete(path.join(testGenerationLogsDir, 'output.log'))
1308-
await fs.delete(this.tempResultDirPath, { recursive: true })
1320+
if (await fs.existsFile(path.join(testGenerationLogsDir, 'output.log'))) {
1321+
await fs.delete(path.join(testGenerationLogsDir, 'output.log'))
1322+
}
1323+
if (
1324+
await fs
1325+
.stat(this.tempResultDirPath)
1326+
.then(() => true)
1327+
.catch(() => false)
1328+
) {
1329+
await fs.delete(this.tempResultDirPath, { recursive: true })
1330+
}
13091331
}
13101332

13111333
// TODO: return build command when product approves

packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ export class Messenger {
283283
reasonDesc: getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage),
284284
isSupportedLanguage: false,
285285
credentialStartUrl: AuthUtil.instance.startUrl,
286+
httpStatusCode: '400',
286287
requestId: messageId,
287288
})
288289

@@ -298,6 +299,7 @@ export class Messenger {
298299
result: 'Succeeded',
299300
isSupportedLanguage: false,
300301
credentialStartUrl: AuthUtil.instance.startUrl,
302+
httpStatusCode: '200',
301303
requestId: messageId,
302304
})
303305
this.dispatcher.sendUpdatePromptProgress(
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import { CodeWhispererConstants } from '../codewhisperer/indexNode'
6+
import { ToolkitError } from '../shared/errors'
7+
8+
export const techinalErrorCustomerFacingMessage =
9+
'I am experiencing technical difficulties at the moment. Please try again in a few minutes.'
10+
const defaultTestGenErrorMessage = 'Amazon Q encountered an error while generating tests. Try again later.'
11+
export class TestGenError extends ToolkitError {
12+
constructor(
13+
error: string,
14+
code: string,
15+
public statusCode: string,
16+
public customerFacingMessage: string
17+
) {
18+
super(error, { code })
19+
}
20+
}
21+
export class ProjectZipError extends TestGenError {
22+
constructor(error: string) {
23+
super(error, 'ProjectZipError', '400', defaultTestGenErrorMessage)
24+
}
25+
}
26+
export class InvalidSourceZipError extends TestGenError {
27+
constructor() {
28+
super('Failed to create valid source zip', 'InvalidSourceZipError', '400', defaultTestGenErrorMessage)
29+
}
30+
}
31+
export class CreateUploadUrlError extends TestGenError {
32+
constructor(errorMessage: string, errorCode: string) {
33+
super(errorMessage, 'CreateUploadUrlError', errorCode, techinalErrorCustomerFacingMessage)
34+
}
35+
}
36+
export class UploadTestArtifactToS3Error extends TestGenError {
37+
constructor(error: string) {
38+
super(error, 'UploadTestArtifactToS3Error', '500', techinalErrorCustomerFacingMessage)
39+
}
40+
}
41+
export class CreateTestJobError extends TestGenError {
42+
constructor(error: string, code: string) {
43+
super(error, 'CreateTestJobError', code, techinalErrorCustomerFacingMessage)
44+
}
45+
}
46+
export class TestGenTimedOutError extends TestGenError {
47+
constructor() {
48+
super(
49+
'Test generation failed. Amazon Q timed out.',
50+
'TestGenTimedOutError',
51+
'500',
52+
techinalErrorCustomerFacingMessage
53+
)
54+
}
55+
}
56+
export class TestGenStoppedError extends TestGenError {
57+
constructor() {
58+
super(
59+
CodeWhispererConstants.unitTestGenerationCancelMessage,
60+
'TestGenCancelled',
61+
'400',
62+
CodeWhispererConstants.unitTestGenerationCancelMessage
63+
)
64+
}
65+
}
66+
export class TestGenFailedError extends TestGenError {
67+
constructor(code: string, error?: string) {
68+
super(
69+
error ?? 'Test generation failed',
70+
'TestGenFailedError',
71+
code,
72+
error ?? techinalErrorCustomerFacingMessage
73+
)
74+
}
75+
}
76+
export class ExportResultsArchiveError extends TestGenError {
77+
constructor(error?: string) {
78+
super(error ?? 'Test generation failed', 'ExportResultsArchiveError', '500', techinalErrorCustomerFacingMessage)
79+
}
80+
}

packages/core/src/codewhisperer/commands/startTestGeneration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { ChildProcess, spawn } from 'child_process'
2121
import { BuildStatus } from '../../amazonqTest/chat/session/session'
2222
import { fs } from '../../shared/fs/fs'
2323
import { TestGenerationJobStatus } from '../models/constants'
24-
import { TestGenFailedError } from '../models/errors'
24+
import { TestGenFailedError } from '../../amazonqTest/error'
2525
import { Range } from '../client/codewhispereruserclient'
2626

2727
// eslint-disable-next-line unicorn/no-null
@@ -121,7 +121,7 @@ export async function startTestGenerationProcess(
121121
//TODO: Send status to test summary
122122
if (jobStatus === TestGenerationJobStatus.FAILED) {
123123
logger.verbose(`Test generation failed.`)
124-
throw new TestGenFailedError()
124+
throw new TestGenFailedError('500')
125125
}
126126
throwIfCancelled()
127127
if (!shouldContinueRunning(tabID)) {

packages/core/src/codewhisperer/models/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ export enum TestGenerationJobStatus {
863863
COMPLETED = 'COMPLETED',
864864
}
865865

866-
export enum ZipUseCase {
866+
export enum FeatureUseCase {
867867
TEST_GENERATION = 'TEST_GENERATION',
868868
CODE_SCAN = 'CODE_SCAN',
869869
}

packages/core/src/codewhisperer/service/codeFixHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export async function getPresignedUrlAndUpload(
3434
getLogger().verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`)
3535
getLogger().verbose(`Complete Getting presigned Url for uploading src context.`)
3636
getLogger().verbose(`Uploading src context...`)
37-
await uploadArtifactToS3(zipFilePath, srcResp)
37+
await uploadArtifactToS3(zipFilePath, srcResp, CodeWhispererConstants.FeatureUseCase.CODE_SCAN)
3838
getLogger().verbose(`Complete uploading src context.`)
3939
const artifactMap: ArtifactMap = {
4040
SourceCode: srcResp.uploadId,

packages/core/src/codewhisperer/service/securityScanHandler.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import { getTelemetryReasonDesc } from '../../shared/errors'
4343
import { CodeWhispererSettings } from '../util/codewhispererSettings'
4444
import { detectCommentAboveLine } from '../../shared/utilities/commentUtils'
4545
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
46+
import { FeatureUseCase } from '../models/constants'
47+
import { UploadTestArtifactToS3Error } from '../../amazonqTest/error'
4648

4749
export async function listScanResults(
4850
client: DefaultCodeWhispererClient,
@@ -287,7 +289,7 @@ export async function getPresignedUrlAndUpload(
287289
logger.verbose(`CreateUploadUrlRequest request id: ${srcResp.$response.requestId}`)
288290
logger.verbose(`Complete Getting presigned Url for uploading src context.`)
289291
logger.verbose(`Uploading src context...`)
290-
await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, scope)
292+
await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, FeatureUseCase.CODE_SCAN, scope)
291293
logger.verbose(`Complete uploading src context.`)
292294
const artifactMap: ArtifactMap = {
293295
SourceCode: srcResp.uploadId,
@@ -343,6 +345,7 @@ export function throwIfCancelled(scope: CodeWhispererConstants.CodeAnalysisScope
343345
export async function uploadArtifactToS3(
344346
fileName: string,
345347
resp: CreateUploadUrlResponse,
348+
featureUseCase: FeatureUseCase,
346349
scope?: CodeWhispererConstants.CodeAnalysisScope
347350
) {
348351
const logger = getLoggerForScope(scope)
@@ -365,14 +368,19 @@ export async function uploadArtifactToS3(
365368
}).response
366369
logger.debug(`StatusCode: ${response.status}, Text: ${response.statusText}`)
367370
} catch (error) {
371+
let errorMessage = ''
372+
const isCodeScan = featureUseCase === FeatureUseCase.CODE_SCAN
373+
const featureType = isCodeScan ? 'security scans' : 'unit test generation'
374+
const defaultMessage = isCodeScan ? 'Security scan failed.' : 'Test generation failed.'
368375
getLogger().error(
369-
`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.`
376+
`Amazon Q is unable to upload workspace artifacts to Amazon S3 for ${featureType}. ` +
377+
'For more information, see the Amazon Q documentation or contact your network or organization administrator.'
370378
)
371-
const errorMessage = getTelemetryReasonDesc(error)?.includes(`"PUT" request failed with code "403"`)
372-
? `"PUT" request failed with code "403"`
373-
: (getTelemetryReasonDesc(error) ?? 'Security scan failed.')
374-
375-
throw new UploadArtifactToS3Error(errorMessage)
379+
const messageWithOutId = getTelemetryReasonDesc(error)?.includes('"PUT" request failed with code "403"')
380+
errorMessage = messageWithOutId
381+
? '"PUT" request failed with code "403"'
382+
: (getTelemetryReasonDesc(error) ?? defaultMessage)
383+
throw isCodeScan ? new UploadArtifactToS3Error(errorMessage) : new UploadTestArtifactToS3Error(errorMessage)
376384
}
377385
}
378386

0 commit comments

Comments
 (0)