Skip to content

Commit 645971b

Browse files
committed
UTG Error handling
1 parent 5bfb867 commit 645971b

File tree

10 files changed

+192
-75
lines changed

10 files changed

+192
-75
lines changed

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

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -241,70 +241,72 @@ 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
245-
244+
const isCancel = data.error.customerFacingMessage === unitTestGenerationCancelMessage
246245
TelemetryHelper.instance.sendTestGenerationToolkitEvent(
247246
session,
248247
true,
249248
isCancel ? 'Cancelled' : 'Failed',
250249
session.startTestGenerationRequestId,
251250
performance.now() - session.testGenerationStartTime,
252251
getTelemetryReasonDesc(data.error),
252+
data.error.statusCode ?? '0', // If status code is 0, need to investigate where this is originating from.
253253
session.isCodeBlockSelected,
254254
session.artifactsUploadDuration,
255255
session.srcPayloadSize,
256256
session.srcZipFileSize
257257
)
258-
259258
if (session.stopIteration) {
260259
// Error from Science
261-
this.messenger.sendMessage(data.error.message.replaceAll('```', ''), data.tabID, 'answer')
260+
this.messenger.sendMessage(data.error.customerFacingMessage.replaceAll('```', ''), data.tabID, 'answer')
262261
} else {
263262
isCancel
264-
? this.messenger.sendMessage(data.error.message, data.tabID, 'answer')
263+
? this.messenger.sendMessage(data.error.customerFacingMessage, data.tabID, 'answer')
265264
: this.sendErrorMessage(data)
266265
}
267266
await this.sessionCleanUp()
268267
return
269268
}
270269
// Client side error messages
271-
private sendErrorMessage(data: { tabID: string; error: { code: string; message: string } }) {
270+
private sendErrorMessage(data: {
271+
tabID: string
272+
error: { customerFacingMessage: string; message: string; code: string; statusCode: string }
273+
}) {
272274
const { error, tabID } = data
273275

276+
// If user reached montly limit for builderId
277+
if (error.code === 'CreateTestJobError') {
278+
if (error.message.includes(CodeWhispererConstants.utgLimitReached)) {
279+
getLogger().error('Monthly quota reached for QSDA actions.')
280+
return this.messenger.sendMessage(
281+
i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'),
282+
tabID,
283+
'answer'
284+
)
285+
} else {
286+
getLogger().error('Too many requests.')
287+
// TODO: move to constants file
288+
return this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID)
289+
}
290+
}
274291
if (isAwsError(error)) {
275292
if (error.code === 'ThrottlingException') {
276-
// TODO: use the explicitly modeled exception reason for quota vs throttle
277-
if (error.message.includes(CodeWhispererConstants.utgLimitReached)) {
278-
getLogger().error('Monthly quota reached for QSDA actions.')
279-
return this.messenger.sendMessage(
280-
i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'),
281-
tabID,
282-
'answer'
283-
)
284-
} else {
285-
getLogger().error('Too many requests.')
286-
// TODO: move to constants file
287-
this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID)
288-
}
293+
// TODO: use the explicitly modeled exception reason for quota vs throttle{
294+
getLogger().error('Too many requests.')
295+
// TODO: move to constants file
296+
this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID)
289297
} else {
290298
// other service errors:
291299
// AccessDeniedException - should not happen because access is validated before this point in the client
292300
// ValidationException - shouldn't happen because client should not send malformed requests
293301
// ConflictException - should not happen because the client will maintain proper state
294302
// InternalServerException - shouldn't happen but needs to be caught
295303
getLogger().error('Other error message: %s', error.message)
296-
this.messenger.sendErrorMessage(
297-
'Encountered an unexpected error when generating tests. Please try again',
298-
tabID
299-
)
304+
this.messenger.sendErrorMessage('', tabID)
300305
}
301306
} else {
302307
// other unexpected errors (TODO enumerate all other failure cases)
303-
getLogger().error('Other error message: %s', error.message)
304-
this.messenger.sendErrorMessage(
305-
'Encountered an unexpected error when generating tests. Please try again',
306-
tabID
307-
)
308+
getLogger().error('Other error message: %s', error.customerFacingMessage)
309+
this.messenger.sendErrorMessage('', tabID)
308310
}
309311
}
310312

@@ -721,6 +723,7 @@ export class TestController {
721723
session.startTestGenerationRequestId,
722724
session.latencyOfTestGeneration,
723725
undefined,
726+
'200',
724727
session.isCodeBlockSelected,
725728
session.artifactsUploadDuration,
726729
session.srcPayloadSize,
@@ -734,7 +737,6 @@ export class TestController {
734737
)
735738

736739
await this.endSession(message, FollowUpTypes.SkipBuildAndFinish)
737-
await this.sessionCleanUp()
738740
return
739741

740742
if (session.listOfTestGenerationJobId.length === 1) {
@@ -842,18 +844,16 @@ export class TestController {
842844
session.startTestGenerationRequestId,
843845
session.latencyOfTestGeneration,
844846
undefined,
847+
'200',
845848
session.isCodeBlockSelected,
846849
session.artifactsUploadDuration,
847-
session.srcPayloadSize,
848-
session.srcZipFileSize,
849850
0,
850851
0,
851852
0,
852853
session.charsOfCodeGenerated,
853854
session.numberOfTestsGenerated,
854855
session.linesOfCodeGenerated
855856
)
856-
857857
telemetry.ui_click.emit({ elementId: 'unitTestGeneration_rejectDiff' })
858858
}
859859

@@ -1297,8 +1297,17 @@ export class TestController {
12971297
'Deleting output.log and temp result directory. testGenerationLogsDir: %s',
12981298
testGenerationLogsDir
12991299
)
1300-
await fs.delete(path.join(testGenerationLogsDir, 'output.log'))
1301-
await fs.delete(this.tempResultDirPath, { recursive: true })
1300+
if (await fs.existsFile(path.join(testGenerationLogsDir, 'output.log'))) {
1301+
await fs.delete(path.join(testGenerationLogsDir, 'output.log'))
1302+
}
1303+
if (
1304+
await fs
1305+
.stat(this.tempResultDirPath)
1306+
.then(() => true)
1307+
.catch(() => false)
1308+
) {
1309+
await fs.delete(this.tempResultDirPath, { recursive: true })
1310+
}
13021311
}
13031312

13041313
// TODO: return build command when product approves

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,9 @@ export class Messenger {
280280
'Cancelled',
281281
messageId,
282282
performance.now() - session.testGenerationStartTime,
283-
getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage)
283+
getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage),
284+
'400'
284285
)
285-
286286
this.dispatcher.sendUpdatePromptProgress(
287287
new UpdatePromptProgressMessage(tabID, cancellingProgressField)
288288
)
@@ -293,9 +293,10 @@ export class Messenger {
293293
false,
294294
'Succeeded',
295295
messageId,
296-
performance.now() - session.testGenerationStartTime
296+
performance.now() - session.testGenerationStartTime,
297+
undefined,
298+
'200',
297299
)
298-
299300
this.dispatcher.sendUpdatePromptProgress(
300301
new UpdatePromptProgressMessage(tabID, testGenCompletedField)
301302
)
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
@@ -867,7 +867,7 @@ export enum TestGenerationJobStatus {
867867
COMPLETED = 'COMPLETED',
868868
}
869869

870-
export enum ZipUseCase {
870+
export enum FeatureUseCase {
871871
TEST_GENERATION = 'TEST_GENERATION',
872872
CODE_SCAN = 'CODE_SCAN',
873873
}

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)