Skip to content

Commit a8d819b

Browse files
Merge main into feature/remote-chat-lsp
2 parents 61270ed + 2d5ca0b commit a8d819b

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
@@ -569,6 +582,32 @@ class BrowserConnector(
569582
}
570583
}
571584

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

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