Skip to content

Commit 16bc1a9

Browse files
authored
Amazon Q: Added telemetry metrics and modified customer facing messages for Q Codescans. (#4442)
* Adding specific exception metrics and client messages for QCA * Removing the LOG.error messages from codescans * Added Change log * Adding UploadArtifactToS3 error message to CX
1 parent f2df380 commit 16bc1a9

File tree

9 files changed

+116
-76
lines changed

9 files changed

+116
-76
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Security Scan: Improved error notifications"
4+
}

plugins/toolkit/jetbrains-core/it/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeScanIntegrationTest.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import org.assertj.core.api.Assertions.assertThat
88
import org.junit.Test
99
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppFileName
1010
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.cppTestLeftContext
11-
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig
12-
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
1311
import software.aws.toolkits.jetbrains.utils.rules.RunWithRealCredentials.RequiresRealCredentials
1412
import software.aws.toolkits.resources.message
1513

@@ -58,14 +56,7 @@ class CodeWhispererCodeScanIntegrationTest : CodeWhispererIntegrationTestBase()
5856
projectRule.fixture.openFileInEditor(file.virtualFile)
5957
}
6058
testCodeScanWithErrorMessage(
61-
message(
62-
"codewhisperer.codescan.file_too_large",
63-
CodeScanSessionConfig.create(
64-
file.virtualFile,
65-
projectRule.project,
66-
CodeWhispererConstants.CodeAnalysisScope.PROJECT
67-
).getPresentablePayloadLimit()
68-
)
59+
message("codewhisperer.codescan.file_too_large")
6960
)
7061
}
7162

plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanException.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import software.aws.toolkits.resources.message
77

88
open class CodeWhispererCodeScanException(override val message: String?) : RuntimeException()
99

10+
open class UploadCodeScanException(override val message: String?) : Exception()
11+
1012
internal fun noFileOpenError(): Nothing =
1113
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.no_file_open"))
1214

13-
internal fun codeScanFailed(): Nothing =
14-
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.service_error"))
15+
internal fun codeScanFailed(errorMessage: String): Nothing =
16+
throw Exception(errorMessage)
1517

1618
internal fun cannotFindFile(file: String?): Nothing =
1719
error(message("codewhisperer.codescan.file_not_found", file ?: ""))
@@ -22,11 +24,14 @@ internal fun cannotFindBuildArtifacts(): Nothing =
2224
internal fun fileFormatNotSupported(format: String): Nothing =
2325
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.file_ext_not_supported", format))
2426

25-
internal fun fileTooLarge(presentableSize: String): Nothing =
26-
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.file_too_large", presentableSize))
27+
internal fun fileTooLarge(): Nothing =
28+
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.file_too_large"))
29+
30+
internal fun uploadArtifactFailedError(errorMessage: String): Nothing =
31+
throw UploadCodeScanException(errorMessage)
2732

28-
internal fun uploadArtifactFailedError(): Nothing =
29-
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.upload_to_s3_failed"))
33+
internal fun invalidSourceZipError(): Nothing =
34+
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.invalid_source_zip_telemetry"))
3035

3136
internal fun noSupportedFilesError(): Nothing =
3237
throw CodeWhispererCodeScanException(message("codewhisperer.codescan.unsupported_language_error"))

plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ class CodeWhispererCodeScanManager(val project: Project) {
305305
if (e.cause?.message?.contains("com.intellij.openapi.compiler.CompilerPaths") == true) {
306306
message("codewhisperer.codescan.java_module_not_found")
307307
} else {
308-
message("codewhisperer.codescan.service_error")
308+
null
309309
}
310310
}
311311
else -> null
@@ -324,10 +324,14 @@ class CodeWhispererCodeScanManager(val project: Project) {
324324

325325
fun handleException(coroutineContext: CoroutineContext, e: Exception, scope: CodeWhispererConstants.CodeAnalysisScope): String {
326326
val errorMessage = when (e) {
327-
is CodeWhispererException -> e.awsErrorDetails().errorMessage() ?: message("codewhisperer.codescan.service_error")
328-
is CodeWhispererCodeScanException -> e.message
327+
is CodeWhispererException -> e.awsErrorDetails().errorMessage() ?: message("codewhisperer.codescan.run_scan_error")
328+
is CodeWhispererCodeScanException -> when (e.message) {
329+
message("codewhisperer.codescan.invalid_source_zip_telemetry") -> message("codewhisperer.codescan.run_scan_error")
330+
else -> e.message
331+
}
332+
is UploadCodeScanException -> message("codewhisperer.codescan.upload_to_s3_failed")
329333
is WaiterTimeoutException, is TimeoutCancellationException -> message("codewhisperer.codescan.scan_timed_out")
330-
is CancellationException -> "Code scan job cancelled by user."
334+
is CancellationException -> message("codewhisperer.codescan.cancelled_by_user_exception")
331335
else -> null
332336
} ?: message("codewhisperer.codescan.run_scan_error")
333337

@@ -358,7 +362,22 @@ class CodeWhispererCodeScanManager(val project: Project) {
358362
"stacktrace: ${e.stackTrace.contentDeepToString()}"
359363
}
360364
}
361-
return errorMessage
365+
366+
val telemetryErrorMessage = when (e) {
367+
is CodeWhispererException -> e.awsErrorDetails().errorMessage() ?: message("codewhisperer.codescan.run_scan_error_telemetry")
368+
is CodeWhispererCodeScanException -> when (e.message) {
369+
message("codewhisperer.codescan.no_file_open") -> message("codewhisperer.codescan.no_file_open_telemetry")
370+
message("codewhisperer.codescan.unsupported_language_error") -> message("codewhisperer.codescan.unsupported_language_error_telemetry")
371+
message("codewhisperer.codescan.file_too_large") -> message("codewhisperer.codescan.file_too_large_telemetry")
372+
else -> e.message
373+
}
374+
is UploadCodeScanException -> e.message
375+
is WaiterTimeoutException, is TimeoutCancellationException -> message("codewhisperer.codescan.scan_timed_out")
376+
is CancellationException -> message("codewhisperer.codescan.cancelled_by_user_exception")
377+
else -> e.message
378+
} ?: message("codewhisperer.codescan.run_scan_error_telemetry")
379+
380+
return telemetryErrorMessage
362381
}
363382

364383
/**

plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer
5757
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_MILLIS_IN_SECOND
5858
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit
5959
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
60+
import software.aws.toolkits.resources.message
6061
import software.aws.toolkits.telemetry.CodewhispererLanguage
6162
import java.io.File
6263
import java.io.FileInputStream
@@ -152,7 +153,8 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
152153
"Status: ${createCodeScanResponse.status()} for request id: ${createCodeScanResponse.responseMetadata().requestId()}"
153154
}
154155
}
155-
codeScanFailed()
156+
val errorMessage = createCodeScanResponse.errorMessage()?.let { it } ?: message("codewhisperer.codescan.run_scan_error_telemetry")
157+
codeScanFailed(errorMessage)
156158
}
157159
val jobId = createCodeScanResponse.jobId()
158160
codeScanResponseContext = codeScanResponseContext.copy(codeScanJobId = jobId)
@@ -188,7 +190,8 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
188190
"Status: ${getCodeScanResponse.status()} for request id: ${getCodeScanResponse.responseMetadata().requestId()}"
189191
}
190192
}
191-
codeScanFailed()
193+
val errorMessage = getCodeScanResponse.errorMessage()?.let { it } ?: message("codewhisperer.codescan.run_scan_error_telemetry")
194+
codeScanFailed(errorMessage)
192195
}
193196
}
194197

@@ -250,7 +253,11 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
250253
/**
251254
* Creates an upload URL and uplaods the zip file to the presigned URL
252255
*/
253-
fun createUploadUrlAndUpload(zipFile: File, artifactType: String, codeScanName: String): CreateUploadUrlResponse = try {
256+
fun createUploadUrlAndUpload(zipFile: File, artifactType: String, codeScanName: String): CreateUploadUrlResponse {
257+
// Throw error if zipFile is invalid.
258+
if (!zipFile.exists()) {
259+
invalidSourceZipError()
260+
}
254261
val fileMd5: String = Base64.getEncoder().encodeToString(DigestUtils.md5(FileInputStream(zipFile)))
255262
val createUploadUrlResponse = createUploadUrl(fileMd5, artifactType, codeScanName)
256263
val url = createUploadUrlResponse.uploadUrl()
@@ -265,20 +272,21 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
265272
createUploadUrlResponse.kmsKeyArn(),
266273
createUploadUrlResponse.requestHeaders()
267274
)
268-
createUploadUrlResponse
269-
} catch (e: Exception) {
270-
LOG.error { "Security scan failed. Something went wrong uploading artifacts: ${e.message}" }
271-
uploadArtifactFailedError()
275+
return createUploadUrlResponse
272276
}
273277

274-
fun createUploadUrl(md5Content: String, artifactType: String, codeScanName: String): CreateUploadUrlResponse = clientAdaptor.createUploadUrl(
275-
CreateUploadUrlRequest.builder()
276-
.contentMd5(md5Content)
277-
.artifactType(artifactType)
278-
.uploadIntent(getUploadIntent(sessionContext.codeAnalysisScope))
279-
.uploadContext(UploadContext.fromCodeAnalysisUploadContext(CodeAnalysisUploadContext.builder().codeScanName(codeScanName).build()))
280-
.build()
281-
)
278+
fun createUploadUrl(md5Content: String, artifactType: String, codeScanName: String): CreateUploadUrlResponse = try {
279+
clientAdaptor.createUploadUrl(
280+
CreateUploadUrlRequest.builder()
281+
.contentMd5(md5Content)
282+
.artifactType(artifactType)
283+
.uploadIntent(getUploadIntent(sessionContext.codeAnalysisScope))
284+
.uploadContext(UploadContext.fromCodeAnalysisUploadContext(CodeAnalysisUploadContext.builder().codeScanName(codeScanName).build()))
285+
.build()
286+
)
287+
} catch (e: Exception) {
288+
throw e
289+
}
282290

283291
private fun getUploadIntent(scope: CodeWhispererConstants.CodeAnalysisScope): UploadIntent = when (scope) {
284292
CodeWhispererConstants.CodeAnalysisScope.FILE -> UploadIntent.AUTOMATIC_FILE_SECURITY_SCAN
@@ -287,25 +295,30 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
287295

288296
@Throws(IOException::class)
289297
fun uploadArtifactToS3(url: String, uploadId: String, fileToUpload: File, md5: String, kmsArn: String?, requestHeaders: Map<String, String>?) {
290-
val uploadIdJson = """{"uploadId":"$uploadId"}"""
291-
HttpRequests.put(url, "application/zip").userAgent(AwsClientManager.getUserAgent()).tuner {
292-
if (requestHeaders.isNullOrEmpty()) {
293-
it.setRequestProperty(CONTENT_MD5, md5)
294-
it.setRequestProperty(CONTENT_TYPE, APPLICATION_ZIP)
295-
it.setRequestProperty(SERVER_SIDE_ENCRYPTION, AWS_KMS)
296-
if (kmsArn?.isNotEmpty() == true) {
297-
it.setRequestProperty(SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID, kmsArn)
298-
}
299-
it.setRequestProperty(SERVER_SIDE_ENCRYPTION_CONTEXT, Base64.getEncoder().encodeToString(uploadIdJson.toByteArray()))
300-
} else {
301-
requestHeaders.forEach { entry ->
302-
it.setRequestProperty(entry.key, entry.value)
298+
try {
299+
val uploadIdJson = """{"uploadId":"$uploadId"}"""
300+
HttpRequests.put(url, "application/zip").userAgent(AwsClientManager.getUserAgent()).tuner {
301+
if (requestHeaders.isNullOrEmpty()) {
302+
it.setRequestProperty(CONTENT_MD5, md5)
303+
it.setRequestProperty(CONTENT_TYPE, APPLICATION_ZIP)
304+
it.setRequestProperty(SERVER_SIDE_ENCRYPTION, AWS_KMS)
305+
if (kmsArn?.isNotEmpty() == true) {
306+
it.setRequestProperty(SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID, kmsArn)
307+
}
308+
it.setRequestProperty(SERVER_SIDE_ENCRYPTION_CONTEXT, Base64.getEncoder().encodeToString(uploadIdJson.toByteArray()))
309+
} else {
310+
requestHeaders.forEach { entry ->
311+
it.setRequestProperty(entry.key, entry.value)
312+
}
303313
}
314+
}.connect {
315+
val connection = it.connection as HttpURLConnection
316+
connection.setFixedLengthStreamingMode(fileToUpload.length())
317+
IoUtils.copy(fileToUpload.inputStream(), connection.outputStream)
304318
}
305-
}.connect {
306-
val connection = it.connection as HttpURLConnection
307-
connection.setFixedLengthStreamingMode(fileToUpload.length())
308-
IoUtils.copy(fileToUpload.inputStream(), connection.outputStream)
319+
} catch (e: Exception) {
320+
val errorMessage = e.message?.let { it } ?: message("codewhisperer.codescan.run_scan_error_telemetry")
321+
throw uploadArtifactFailedError(errorMessage)
309322
}
310323
}
311324

plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer
3131
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_PAYLOAD_LIMIT_IN_BYTES
3232
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FILE_SCAN_PAYLOAD_SIZE_LIMIT_IN_BYTES
3333
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FILE_SCAN_TIMEOUT_IN_SECONDS
34-
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_BYTES_IN_KB
35-
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_BYTES_IN_MB
3634
import software.aws.toolkits.telemetry.CodewhispererLanguage
3735
import java.io.File
3836
import java.nio.file.Files
@@ -87,7 +85,7 @@ class CodeScanSessionConfig(
8785

8886
// Fail fast if the selected file size is greater than the payload limit.
8987
if (selectedFile != null && selectedFile.length > getPayloadLimitInBytes()) {
90-
fileTooLarge(getPresentablePayloadLimit())
88+
fileTooLarge()
9189
}
9290

9391
val start = Instant.now().toEpochMilli()
@@ -131,11 +129,6 @@ class CodeScanSessionConfig(
131129
*/
132130
fun createPayloadTimeoutInSeconds(): Long = CODE_SCAN_CREATE_PAYLOAD_TIMEOUT_IN_SECONDS
133131

134-
fun getPresentablePayloadLimit(): String = when (getPayloadLimitInBytes() >= TOTAL_BYTES_IN_MB) {
135-
true -> "${getPayloadLimitInBytes() / TOTAL_BYTES_IN_MB}MB"
136-
false -> "${getPayloadLimitInBytes() / TOTAL_BYTES_IN_KB}KB"
137-
}
138-
139132
private fun countLinesInVirtualFile(virtualFile: VirtualFile): Int {
140133
val bufferedReader = virtualFile.inputStream.bufferedReader()
141134
return bufferedReader.useLines { lines -> lines.count() }

plugins/toolkit/jetbrains-core/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeFileScanTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,21 +274,21 @@ class CodeWhispererCodeFileScanTest : CodeWhispererCodeScanTestBase(PythonCodeIn
274274
val codeScanResponse = codeScanSessionSpy.run()
275275
assertThat(codeScanResponse).isInstanceOf<CodeScanResponse.Failure>()
276276
assertThat(codeScanResponse.responseContext.payloadContext).isEqualTo(payloadContext)
277-
assertThat((codeScanResponse as CodeScanResponse.Failure).failureReason).isInstanceOf<CodeWhispererCodeScanException>()
277+
assertThat((codeScanResponse as CodeScanResponse.Failure).failureReason).isInstanceOf<Exception>()
278278
}
279279
}
280280

281281
@Test
282282
fun `test run() - createCodeScan error`() {
283283
mockClient.stub {
284-
onGeneric { createCodeScan(any(), any()) }.thenThrow(CodeWhispererException::class.java)
284+
onGeneric { createCodeScan(any(), any()) }.thenThrow(CodeWhispererCodeScanException::class.java)
285285
}
286286

287287
runBlocking {
288288
val codeScanResponse = codeScanSessionSpy.run()
289289
assertThat(codeScanResponse).isInstanceOf<CodeScanResponse.Failure>()
290290
assertThat(codeScanResponse.responseContext.payloadContext).isEqualTo(payloadContext)
291-
assertThat((codeScanResponse as CodeScanResponse.Failure).failureReason).isInstanceOf<CodeWhispererException>()
291+
assertThat((codeScanResponse as CodeScanResponse.Failure).failureReason).isInstanceOf<CodeWhispererCodeScanException>()
292292
}
293293
}
294294

@@ -302,7 +302,7 @@ class CodeWhispererCodeFileScanTest : CodeWhispererCodeScanTestBase(PythonCodeIn
302302
val codeScanResponse = codeScanSessionSpy.run()
303303
assertThat(codeScanResponse).isInstanceOf<CodeScanResponse.Failure>()
304304
assertThat(codeScanResponse.responseContext.payloadContext).isEqualTo(payloadContext)
305-
assertThat((codeScanResponse as CodeScanResponse.Failure).failureReason).isInstanceOf<CodeWhispererCodeScanException>()
305+
assertThat((codeScanResponse as CodeScanResponse.Failure).failureReason).isInstanceOf<Exception>()
306306
}
307307
}
308308

0 commit comments

Comments
 (0)