in plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt [81:250]
suspend fun run(): CodeScanResponse {
var codeScanResponseContext = defaultCodeScanResponseContext()
val currentCoroutineContext = coroutineContext
try {
assertIsNonDispatchThread()
currentCoroutineContext.ensureActive()
val startTime = now()
val (payloadContext, sourceZip) = withTimeout(Duration.ofSeconds(sessionContext.sessionConfig.createPayloadTimeoutInSeconds())) {
sessionContext.sessionConfig.createPayload()
}
if (isProjectScope()) {
LOG.debug {
"Total size of source payload in KB: ${payloadContext.srcPayloadSize * 1.0 / TOTAL_BYTES_IN_KB} \n" +
"Total size of source zip file in KB: ${payloadContext.srcZipFileSize * 1.0 / TOTAL_BYTES_IN_KB} \n" +
"Total number of lines reviewed: ${payloadContext.totalLines} \n" +
"Total number of files included in payload: ${payloadContext.totalFiles} \n" +
"Total time taken for creating payload: ${payloadContext.totalTimeInMilliseconds * 1.0 / TOTAL_MILLIS_IN_SECOND} seconds\n" +
"Payload context language: ${payloadContext.language}"
}
}
codeScanResponseContext = codeScanResponseContext.copy(payloadContext = payloadContext)
// 2 & 3. CreateUploadURL and upload the context.
currentCoroutineContext.ensureActive()
val artifactsUploadStartTime = now()
val codeScanName = UUID.randomUUID().toString()
val taskType = if (isAutoScan()) {
CodeWhispererConstants.UploadTaskType.SCAN_FILE
} else {
CodeWhispererConstants.UploadTaskType.SCAN_PROJECT
}
val sourceZipUploadResponse =
CodeWhispererZipUploadManager.getInstance(sessionContext.project).createUploadUrlAndUpload(
sourceZip,
"SourceCode",
taskType,
codeScanName,
CodeWhispererConstants.FeatureName.CODE_REVIEW
)
if (isProjectScope()) {
LOG.debug {
"Successfully uploaded source zip to s3: " +
"Upload id: ${sourceZipUploadResponse.uploadId()} " +
"Request id: ${sourceZipUploadResponse.responseMetadata().requestId()}"
}
}
urlResponse[ArtifactType.SOURCE_CODE] = sourceZipUploadResponse
currentCoroutineContext.ensureActive()
val artifactsUploadDuration = now() - artifactsUploadStartTime
codeScanResponseContext = codeScanResponseContext.copy(
serviceInvocationContext = codeScanResponseContext.serviceInvocationContext.copy(artifactsUploadDuration = artifactsUploadDuration)
)
// 4. Call createCodeScan to start a code scan
currentCoroutineContext.ensureActive()
val serviceInvocationStartTime = now()
val createCodeScanResponse = createCodeScan(payloadContext.language.toString(), codeScanName)
if (isProjectScope()) {
LOG.debug {
"Successfully created code review with " +
"status: ${createCodeScanResponse.status()} " +
"for request id: ${createCodeScanResponse.responseMetadata().requestId()}"
}
}
var codeScanStatus = createCodeScanResponse.status()
if (codeScanStatus == CodeAnalysisStatus.FAILED) {
if (isProjectScope()) {
LOG.debug {
"CodeWhisperer service error occurred. Something went wrong when creating a code review: $createCodeScanResponse " +
"Status: ${createCodeScanResponse.status()} for request id: ${createCodeScanResponse.responseMetadata().requestId()}"
}
}
val errorMessage = createCodeScanResponse.errorMessage()?.let { it } ?: message("codewhisperer.codescan.run_scan_error_telemetry")
codeScanFailed(errorMessage)
}
val jobId = createCodeScanResponse.jobId()
codeScanResponseContext = codeScanResponseContext.copy(codeScanJobId = jobId)
if (isProjectScope()) {
delay(PROJECT_SCAN_INITIAL_POLLING_INTERVAL_IN_SECONDS * TOTAL_MILLIS_IN_SECOND)
} else {
delay(FILE_SCAN_INITIAL_POLLING_INTERVAL_IN_SECONDS * TOTAL_MILLIS_IN_SECOND)
}
// 5. Keep polling the API GetCodeScan to wait for results for a given timeout period.
waitUntil(
succeedOn = { codeScanStatus == CodeAnalysisStatus.COMPLETED },
maxDuration = Duration.ofSeconds(sessionContext.sessionConfig.overallJobTimeoutInSeconds())
) {
currentCoroutineContext.ensureActive()
val elapsedTime = (now() - startTime) * 1.0 / TOTAL_MILLIS_IN_SECOND
if (isProjectScope()) {
LOG.debug { "Waiting for code review to complete. Elapsed time: $elapsedTime sec." }
}
val getCodeScanResponse = getCodeScan(jobId)
codeScanStatus = getCodeScanResponse.status()
if (isProjectScope()) {
LOG.debug {
"Get code review status: ${getCodeScanResponse.status()}, " +
"request id: ${getCodeScanResponse.responseMetadata().requestId()}"
}
}
delay(CODE_SCAN_POLLING_INTERVAL_IN_SECONDS * TOTAL_MILLIS_IN_SECOND)
if (codeScanStatus == CodeAnalysisStatus.FAILED) {
if (isProjectScope()) {
LOG.debug {
"CodeWhisperer service error occurred. Something went wrong fetching results for code review: $getCodeScanResponse " +
"Status: ${getCodeScanResponse.status()} for request id: ${getCodeScanResponse.responseMetadata().requestId()}"
}
}
val errorMessage = getCodeScanResponse.errorMessage()?.let { it } ?: message("codewhisperer.codescan.run_scan_error_telemetry")
codeScanFailed(errorMessage)
}
}
LOG.debug { "Code review completed successfully by Amazon Q." }
// 6. Return the results from the ListCodeScan API.
currentCoroutineContext.ensureActive()
var listCodeScanFindingsResponse = listCodeScanFindings(jobId, null)
val serviceInvocationDuration = now() - serviceInvocationStartTime
codeScanResponseContext = codeScanResponseContext.copy(
serviceInvocationContext = codeScanResponseContext.serviceInvocationContext.copy(serviceInvocationDuration = serviceInvocationDuration)
)
val documents = mutableListOf<String>()
documents.add(listCodeScanFindingsResponse.codeAnalysisFindings())
// coroutineContext helps to actively cancel the bigger projects quickly
withContext(currentCoroutineContext) {
while (listCodeScanFindingsResponse.nextToken() != null && currentCoroutineContext.isActive) {
listCodeScanFindingsResponse = listCodeScanFindings(jobId, listCodeScanFindingsResponse.nextToken())
documents.add(listCodeScanFindingsResponse.codeAnalysisFindings())
}
}
if (isProjectScope()) {
LOG.debug { "Rendering response to display code review results." }
}
currentCoroutineContext.ensureActive()
val issues = mapToCodeScanIssues(documents, sessionContext.project, jobId).filter { it.isVisible }
codeScanResponseContext = codeScanResponseContext.copy(codeScanTotalIssues = issues.count())
codeScanResponseContext = codeScanResponseContext.copy(codeScanIssuesWithFixes = issues.count { it.suggestedFixes.isNotEmpty() })
codeScanResponseContext = codeScanResponseContext.copy(reason = "Succeeded")
return CodeScanResponse.Success(issues, codeScanResponseContext)
} catch (e: Exception) {
val exception = e as? CodeWhispererRuntimeException
val awsError = exception?.awsErrorDetails()
if (awsError != null) {
if (awsError.errorCode() == "ThrottlingException" && awsError.errorMessage() != null) {
if (awsError.errorMessage()!!.contains(PROJECT_SCANS_THROTTLING_MESSAGE)) {
LOG.info { "Project reviews limit reached" }
notifyErrorCodeWhispererUsageLimit(sessionContext.project, true)
} else if (awsError.errorMessage()!!.contains(FILE_SCANS_THROTTLING_MESSAGE)) {
LOG.info { "File reviews limit reached" }
CodeWhispererExplorerActionManager.getInstance().setMonthlyQuotaForCodeScansExceeded(true)
}
}
}
LOG.debug(e) {
"Failed to run code review and display results. Caused by: ${e.message}, status code: ${awsError?.errorCode()}, " +
"exception: ${e::class.simpleName}, request ID: ${exception?.requestId()}" +
"Jetbrains IDE: ${ApplicationInfo.getInstance().fullApplicationName}, " +
"IDE version: ${ApplicationInfo.getInstance().apiVersion}, "
}
return CodeScanResponse.Failure(codeScanResponseContext, e)
}
}