Skip to content

Commit 2d5ca0b

Browse files
BlakeLazarineblakelaz-amazonLee-WonJunLiGaCurli
authored
feat(amazonq): add support for CodeReview and DisplayFindings tools, alter behavior of explain and fix buttons (#5970)
* feat(amazonq): add support for CodeReview and DisplayFindings tools, alter behavior of explain and fix buttons * fix(amazonq): remove commented lines * feat(amazonq): add support for CodeReview and DisplayFindings tools, alter behavior of explain and fix buttons * fix(amazonq): remove commented lines * fix(amazonq): general code quality fixes for CodeReview and DisplayFindings tools * fix(amazonq): Prevents Q UriError : Validate file URI and fallback to Path.toUri() for WSL (#5960) Fix repeated UriError when Amazon Q starts with a WSL-backed workspace. We now validate the constructed file: URI and fallback to JDK’s Path.toUri() if the URI violates Rule (no authority while the path begins with //, maybe RFC 3986). This keeps legacy behavior where it’s safe, and prevents Q from rejecting the workspace folder URI * feat(amazonq): show the opt-in checkbox for server-side context (#5894) We need to support client-side opt-out for internal users who have workspace context server running by default. * fix(amazonq): delete AwsServerCapabilitiesProvider (#5833) It is not actually needed; we can read the values from AmazonQLspService * feat(amazonq): add support for CodeReview and DisplayFindings tools, alter behavior of explain and fix buttons * Revert "feat(amazonq): add support for CodeReview and DisplayFindings tools, alter behavior of explain and fix buttons" This reverts commit f21c028. * fix(amazonq): fix formatting issues * fix(amazonq): fix more formatting issues * fix(amazonq): fix build failures * fix(amazonq): undo unrelated change * fix(amazonq): address codeReview comments * fix(amazonq): fixing detekt errors * feat(amazonq): emit telemetry events for explainIssue and applyFix commands * feat(amazonq): add unit tests for parseFindingsMessage * fix(amazonq): fix detekt errors * fix(amazonq): fix re-direct of /review action * fix(amazonq): improve handling of case where messageId is null * fix(amazonq): Add changelog, code quality improvements * fix(amazonq): address comments * fix(amazonq): remove unused import --------- Co-authored-by: Blake Lazarine <[email protected]> Co-authored-by: Lee Won Jun <[email protected]> Co-authored-by: Jiatong Li <[email protected]> Co-authored-by: Richard Li <[email protected]> Co-authored-by: Laxman Reddy <[email protected]>
1 parent f1e89a9 commit 2d5ca0b

File tree

22 files changed

+494
-169
lines changed

22 files changed

+494
-169
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Enable agentic code review"
4+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mockitoKotlin = "5.4.1-SNAPSHOT"
2828
mockk = "1.13.17"
2929
nimbus-jose-jwt = "9.40"
3030
node-gradle = "7.0.2"
31-
telemetryGenerator = "1.0.322"
31+
telemetryGenerator = "1.0.329"
3232
testLogger = "4.0.0"
3333
testRetry = "1.5.10"
3434
# test-only; platform provides slf4j transitively at runtime

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import com.google.gson.Gson
1010
import com.intellij.ide.BrowserUtil
1111
import com.intellij.ide.util.RunOnceUtil
1212
import com.intellij.openapi.application.runInEdt
13+
import com.intellij.openapi.application.runReadAction
14+
import com.intellij.openapi.fileEditor.FileDocumentManager
1315
import com.intellij.openapi.fileEditor.FileEditorManager
1416
import com.intellij.openapi.options.ShowSettingsUtil
1517
import com.intellij.openapi.project.Project
18+
import com.intellij.openapi.vfs.LocalFileSystem
19+
import com.intellij.openapi.vfs.VirtualFile
1620
import com.intellij.ui.jcef.JBCefJSQuery.Response
1721
import kotlinx.coroutines.CancellationException
1822
import kotlinx.coroutines.CompletableDeferred
@@ -71,6 +75,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_
7175
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS
7276
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE
7377
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE
78+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CODE_REVIEW_FINDINGS_SUFFIX
79+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.DISPLAY_FINDINGS_SUFFIX
7480
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams
7581
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams
7682
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
@@ -107,10 +113,17 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestA
107113
import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
108114
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
109115
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
116+
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue
117+
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager
118+
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Description
119+
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation
120+
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix
110121
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable
122+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
111123
import software.aws.toolkits.jetbrains.settings.MeetQSettings
112124
import software.aws.toolkits.telemetry.MetricResult
113125
import software.aws.toolkits.telemetry.Telemetry
126+
import java.nio.file.Path
114127
import java.util.concurrent.CompletableFuture
115128
import java.util.concurrent.CompletionException
116129
import java.util.function.Function
@@ -568,6 +581,32 @@ class BrowserConnector(
568581
}
569582
}
570583

584+
data class FlareCodeScanIssue(
585+
val startLine: Int,
586+
val endLine: Int,
587+
val comment: String?,
588+
val title: String,
589+
val description: Description,
590+
val detectorId: String,
591+
val detectorName: String,
592+
val findingId: String,
593+
val ruleId: String?,
594+
val relatedVulnerabilities: List<String>,
595+
val severity: String,
596+
val recommendation: Recommendation,
597+
val suggestedFixes: List<SuggestedFix>,
598+
val scanJobId: String,
599+
val language: String,
600+
val autoDetected: Boolean,
601+
val filePath: String,
602+
val findingContext: String,
603+
)
604+
605+
data class AggregatedCodeScanIssue(
606+
val filePath: String,
607+
val issues: List<FlareCodeScanIssue>,
608+
)
609+
571610
private fun showResult(
572611
result: CompletableFuture<String>,
573612
partialResultToken: String,
@@ -581,10 +620,14 @@ class BrowserConnector(
581620
throw error
582621
}
583622
chatCommunicationManager.removePartialChatMessage(partialResultToken)
623+
val decryptedMessage = Gson().fromJson(value?.let { encryptionManager?.decrypt(it) }.orEmpty(), Map::class.java)
624+
as Map<String, *>
625+
parseFindingsMessages(decryptedMessage)
626+
584627
val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat(
585628
SEND_CHAT_COMMAND_PROMPT,
586629
tabId,
587-
value?.let { encryptionManager?.decrypt(it) }.orEmpty(),
630+
Gson().toJson(decryptedMessage),
588631
isPartialResult = false
589632
)
590633
browser.postChat(messageToChat)
@@ -598,6 +641,85 @@ class BrowserConnector(
598641
}
599642
}
600643

644+
fun parseFindingsMessages(messagesMap: Map<String, *>) {
645+
try {
646+
val additionalMessages = messagesMap["additionalMessages"] as? MutableList<Map<String, Any>>
647+
val findingsMessages = additionalMessages?.filter { message ->
648+
if (message.contains("messageId")) {
649+
(message["messageId"] as String).endsWith(CODE_REVIEW_FINDINGS_SUFFIX) ||
650+
(message["messageId"] as String).endsWith(DISPLAY_FINDINGS_SUFFIX)
651+
} else {
652+
false
653+
}
654+
}
655+
val scannedFiles = mutableListOf<VirtualFile>()
656+
if (findingsMessages != null) {
657+
for (findingsMessage in findingsMessages) {
658+
additionalMessages.remove(findingsMessage)
659+
val gson = Gson()
660+
val jsonFindings = gson.fromJson(findingsMessage["body"] as String, List::class.java)
661+
val mappedFindings = mutableListOf<CodeWhispererCodeScanIssue>()
662+
for (aggregatedIssueUnformatted in jsonFindings) {
663+
val aggregatedIssue = gson.fromJson(gson.toJson(aggregatedIssueUnformatted), AggregatedCodeScanIssue::class.java)
664+
val file = LocalFileSystem.getInstance().findFileByIoFile(
665+
Path.of(aggregatedIssue.filePath).toFile()
666+
)
667+
if (file?.isDirectory == false) {
668+
scannedFiles.add(file)
669+
runReadAction {
670+
FileDocumentManager.getInstance().getDocument(file)
671+
}?.let { document ->
672+
for (issue in aggregatedIssue.issues) {
673+
val endLineInDocument = minOf(maxOf(0, issue.endLine - 1), document.lineCount - 1)
674+
val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1
675+
val isIssueIgnored = CodeWhispererCodeScanManager.getInstance(project)
676+
.isIgnoredIssue(issue.title, document, file, issue.startLine - 1)
677+
if (isIssueIgnored) {
678+
continue
679+
}
680+
mappedFindings.add(
681+
CodeWhispererCodeScanIssue(
682+
startLine = issue.startLine,
683+
startCol = 1,
684+
endLine = issue.endLine,
685+
endCol = endCol,
686+
file = file,
687+
project = project,
688+
title = issue.title,
689+
description = issue.description,
690+
detectorId = issue.detectorId,
691+
detectorName = issue.detectorName,
692+
findingId = issue.findingId,
693+
ruleId = issue.ruleId,
694+
relatedVulnerabilities = issue.relatedVulnerabilities,
695+
severity = issue.severity,
696+
recommendation = issue.recommendation,
697+
suggestedFixes = issue.suggestedFixes,
698+
codeSnippet = emptyList(),
699+
isVisible = true,
700+
autoDetected = issue.autoDetected,
701+
scanJobId = issue.scanJobId,
702+
),
703+
)
704+
}
705+
}
706+
}
707+
}
708+
709+
CodeWhispererCodeScanManager.getInstance(project)
710+
.addOnDemandIssues(
711+
mappedFindings,
712+
scannedFiles,
713+
CodeWhispererConstants.CodeAnalysisScope.AGENTIC
714+
)
715+
CodeWhispererCodeScanManager.getInstance(project).showCodeScanUI()
716+
}
717+
}
718+
} catch (e: Exception) {
719+
LOG.error { "Failed to parse findings message $e" }
720+
}
721+
}
722+
601723
private suspend fun updateQuickActionsInBrowser(browser: Browser) {
602724
val isFeatureDevAvailable = isFeatureDevAvailable(project)
603725
val isCodeTransformAvailable = isCodeTransformAvailable(project)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/EditorContextCommand.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ enum class EditorContextCommand(
3434
verb = "sendToPrompt",
3535
actionId = "aws.amazonq.sendToPrompt",
3636
),
37-
ExplainCodeScanIssue(
38-
verb = "ExplainIssue",
39-
actionId = "aws.amazonq.explainCodeScanIssue",
37+
HandleCodeScanIssue(
38+
verb = "HandleCodeScanIssue",
39+
actionId = "aws.amazonq.handleCodeScanIssueCommand",
4040
),
4141
}
Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,56 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatP
1717
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_TO_PROMPT
1818
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendToPromptParams
1919
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType
20+
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper
21+
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
22+
23+
class HandleIssueCommandAction : AnAction(), DumbAware {
24+
val contextDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.handleIssueCommandContext")
25+
val actionDataKey = DataKey.create<String>("amazonq.codescan.handleIssueCommandAction")
2026

21-
class ExplainCodeIssueAction : AnAction(), DumbAware {
2227
override fun getActionUpdateThread() = ActionUpdateThread.BGT
2328

2429
override fun update(e: AnActionEvent) {
2530
e.presentation.isEnabledAndVisible = e.project != null
2631
}
32+
fun createLineRangeText(issueContext: MutableMap<String, String>): String {
33+
val startLine = issueContext["startLine"]
34+
val endLine = issueContext["endLine"]
35+
return if (startLine.equals(endLine)) {
36+
"[$startLine]"
37+
} else {
38+
"[$startLine, $endLine]"
39+
}
40+
}
2741

2842
override fun actionPerformed(e: AnActionEvent) {
2943
val project = e.project ?: return
30-
val issueDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.explainissue")
31-
val issueContext = e.getData(issueDataKey) ?: return
44+
val context = e.getData(contextDataKey) ?: return
45+
val action = e.getData(actionDataKey) ?: return
46+
47+
// Emit telemetry event
48+
TelemetryHelper.recordTelemetryIssueCommandAction(
49+
context["findingId"].orEmpty(),
50+
context["detectorId"].orEmpty(),
51+
context["ruleId"].orEmpty(),
52+
context["autoDetected"].orEmpty(),
53+
getStartUrl(project).orEmpty(),
54+
action, // The action name (explainIssue or applyFix)
55+
"Succeeded"
56+
)
3257

3358
ActionManager.getInstance().getAction("q.openchat").actionPerformed(e)
3459

3560
ApplicationManager.getApplication().executeOnPooledThread {
3661
runBlocking {
3762
// https://github.com/aws/aws-toolkit-vscode/blob/master/packages/amazonq/src/lsp/chat/commands.ts#L30
38-
val codeSelection = "\n```\n${issueContext["code"]?.trimIndent()?.trim()}\n```\n"
63+
val codeSelection = "\n```\n${context["code"]?.trimIndent()?.trim()}\n```\n"
64+
val actionString = if (action == "explainIssue") "Explain" else "Fix"
3965

40-
val prompt = "Explain the issue \n\n " +
41-
"Issue: \"${issueContext["title"]}\" \n" +
42-
"Code: $codeSelection"
66+
val prompt = "$actionString ${context["title"]} issue in ${context["fileName"]} at ${createLineRangeText(context)}"
4367

44-
val modelPrompt = "Explain the issue ${issueContext["title"]} \n\n " +
45-
"Issue: \"${issueContext["title"]}\" \n" +
46-
"Description: ${issueContext["description"]} \n" +
47-
"Code: $codeSelection and generate the code demonstrating the fix"
68+
val modelPrompt = "$actionString ${context["title"]} issue in ${context["fileName"]} at ${createLineRangeText(context)}" +
69+
"Issue: \"${context}\" \n"
4870

4971
val params = SendToPromptParams(
5072
selection = codeSelection,

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,26 @@ class TelemetryHelper(private val project: Project, private val sessionStorage:
441441
.credentialStartUrl(startUrl)
442442
}
443443
}
444+
445+
fun recordTelemetryIssueCommandAction(
446+
findingId: String,
447+
detectorId: String,
448+
ruleId: String,
449+
autoDetected: String,
450+
startUrl: String,
451+
metricName: String,
452+
result: String,
453+
) {
454+
Telemetry.amazonq.codeReviewTool.use {
455+
it.reason(metricName)
456+
.setAttribute("findingId", findingId)
457+
.setAttribute("detectorId", detectorId)
458+
.setAttribute("ruleId", ruleId)
459+
.setAttribute("credentialStartUrl", startUrl)
460+
.setAttribute("autoDetected", autoDetected)
461+
.setAttribute("result", result)
462+
}
463+
}
444464
}
445465
}
446466

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/userIntent/UserIntentRecognizer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class UserIntentRecognizer {
1515
EditorContextCommand.Refactor -> UserIntent.SUGGEST_ALTERNATE_IMPLEMENTATION
1616
EditorContextCommand.Fix -> UserIntent.APPLY_COMMON_BEST_PRACTICES
1717
EditorContextCommand.Optimize -> UserIntent.IMPROVE_CODE
18-
EditorContextCommand.ExplainCodeScanIssue -> UserIntent.EXPLAIN_CODE_SELECTION
18+
EditorContextCommand.HandleCodeScanIssue -> UserIntent.EXPLAIN_CODE_SELECTION
1919
EditorContextCommand.GenerateUnitTests -> UserIntent.GENERATE_UNIT_TESTS
2020
EditorContextCommand.SendToPrompt -> null
2121
}

0 commit comments

Comments
 (0)