-
Notifications
You must be signed in to change notification settings - Fork 273
feat(amazonq): add support for CodeReview and DisplayFindings tools, alter behavior of explain and fix buttons #5970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
f76d69d
b05c4f1
99d8433
ec2d4fd
19f51a9
d28f486
ea41226
d37106e
fe668ee
f21c028
683189f
45b7fe2
2588777
7d4404d
5e878f9
b26778c
5d2c6f5
8ed39dd
b0ebef0
3725505
e913055
f8e45e8
9819471
685db1a
7baa5f2
683944b
98e8b3c
9debeac
2a007ad
718153b
f675828
f9c0976
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,13 @@ import com.google.gson.Gson | |
import com.intellij.ide.BrowserUtil | ||
import com.intellij.ide.util.RunOnceUtil | ||
import com.intellij.openapi.application.runInEdt | ||
import com.intellij.openapi.application.runReadAction | ||
import com.intellij.openapi.fileEditor.FileDocumentManager | ||
import com.intellij.openapi.fileEditor.FileEditorManager | ||
import com.intellij.openapi.options.ShowSettingsUtil | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.vfs.LocalFileSystem | ||
import com.intellij.openapi.vfs.VirtualFile | ||
import com.intellij.ui.jcef.JBCefJSQuery.Response | ||
import kotlinx.coroutines.CancellationException | ||
import kotlinx.coroutines.CompletableDeferred | ||
|
@@ -71,6 +75,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_ | |
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CODE_REVIEW_FINDINGS_SUFFIX | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.DISPLAY_FINDINGS_SUFFIX | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD | ||
|
@@ -107,13 +113,21 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestA | |
import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable | ||
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable | ||
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Description | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable | ||
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants | ||
import software.aws.toolkits.jetbrains.settings.MeetQSettings | ||
import software.aws.toolkits.telemetry.MetricResult | ||
import software.aws.toolkits.telemetry.Telemetry | ||
import java.nio.file.Path | ||
import java.util.concurrent.CompletableFuture | ||
import java.util.concurrent.CompletionException | ||
import java.util.function.Function | ||
import kotlin.collections.get | ||
|
||
class BrowserConnector( | ||
private val serializer: MessageSerializer = MessageSerializer.getInstance(), | ||
|
@@ -568,6 +582,32 @@ class BrowserConnector( | |
} | ||
} | ||
|
||
data class FlareCodeScanIssue( | ||
val startLine: Int, | ||
val endLine: Int, | ||
val comment: String?, | ||
val title: String, | ||
val description: Description, | ||
val detectorId: String, | ||
val detectorName: String, | ||
val findingId: String, | ||
val ruleId: String?, | ||
val relatedVulnerabilities: List<String>, | ||
val severity: String, | ||
val recommendation: Recommendation, | ||
val suggestedFixes: List<SuggestedFix>, | ||
val scanJobId: String, | ||
val language: String, | ||
val autoDetected: Boolean, | ||
val filePath: String, | ||
val findingContext: String, | ||
) | ||
|
||
data class AggregatedCodeScanIssue( | ||
val filePath: String, | ||
val issues: List<FlareCodeScanIssue>, | ||
) | ||
|
||
private fun showResult( | ||
result: CompletableFuture<String>, | ||
partialResultToken: String, | ||
|
@@ -581,10 +621,13 @@ class BrowserConnector( | |
throw error | ||
} | ||
chatCommunicationManager.removePartialChatMessage(partialResultToken) | ||
val decryptedMessage = Gson().fromJson(value?.let { encryptionManager?.decrypt(it) }.orEmpty(), Map::class.java) | ||
parseFindingsMessages(decryptedMessage) | ||
|
||
val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( | ||
SEND_CHAT_COMMAND_PROMPT, | ||
tabId, | ||
value?.let { encryptionManager?.decrypt(it) }.orEmpty(), | ||
Gson().toJson(decryptedMessage), | ||
isPartialResult = false | ||
) | ||
browser.postChat(messageToChat) | ||
|
@@ -598,6 +641,77 @@ class BrowserConnector( | |
} | ||
} | ||
|
||
private fun parseFindingsMessages(messagesMap: Map<*, *>) { | ||
|
||
val additionalMessages = messagesMap["additionalMessages"] as? MutableList<Map<String, Any>> | ||
|
||
val findingsMessages = additionalMessages?.filter { message -> | ||
message["messageId"] != null && (message["messageId"] as String).endsWith(CODE_REVIEW_FINDINGS_SUFFIX) || | ||
(message["messageId"] as String).endsWith(DISPLAY_FINDINGS_SUFFIX) | ||
} | ||
val scannedFiles = mutableListOf<VirtualFile>() | ||
if (findingsMessages != null) { | ||
for (findingsMessage in findingsMessages) { | ||
additionalMessages.remove(findingsMessage) | ||
val gson = Gson() | ||
val jsonFindings = gson.fromJson(findingsMessage["body"] as String, List::class.java) | ||
val mappedFindings = mutableListOf<CodeWhispererCodeScanIssue>() | ||
for (aggregatedIssueUnformatted in jsonFindings) { | ||
val aggregatedIssue = gson.fromJson(gson.toJson(aggregatedIssueUnformatted), AggregatedCodeScanIssue::class.java) | ||
val file = LocalFileSystem.getInstance().findFileByIoFile( | ||
Path.of(aggregatedIssue.filePath).toFile() | ||
) | ||
if (file?.isDirectory == false) { | ||
scannedFiles.add(file) | ||
runReadAction { | ||
FileDocumentManager.getInstance().getDocument(file) | ||
}?.let { document -> | ||
for (issue in aggregatedIssue.issues) { | ||
val endLineInDocument = minOf(maxOf(0, issue.endLine - 1), document.lineCount - 1) | ||
val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1 | ||
val isIssueIgnored = CodeWhispererCodeScanManager.getInstance(project) | ||
.isIgnoredIssue(issue.title, document, file, issue.startLine - 1) | ||
if (isIssueIgnored) { | ||
continue | ||
} | ||
mappedFindings.add( | ||
CodeWhispererCodeScanIssue( | ||
startLine = issue.startLine, | ||
startCol = 1, | ||
endLine = issue.endLine, | ||
endCol = endCol, | ||
file = file, | ||
project = project, | ||
title = issue.title, | ||
description = issue.description, | ||
detectorId = issue.detectorId, | ||
detectorName = issue.detectorName, | ||
findingId = issue.findingId, | ||
ruleId = issue.ruleId, | ||
relatedVulnerabilities = issue.relatedVulnerabilities, | ||
severity = issue.severity, | ||
recommendation = issue.recommendation, | ||
suggestedFixes = issue.suggestedFixes, | ||
codeSnippet = emptyList(), | ||
isVisible = true, | ||
autoDetected = issue.autoDetected, | ||
scanJobId = issue.scanJobId, | ||
), | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
||
CodeWhispererCodeScanManager.getInstance(project) | ||
.addOnDemandIssues( | ||
mappedFindings, | ||
scannedFiles, | ||
CodeWhispererConstants.CodeAnalysisScope.AGENTIC | ||
) | ||
CodeWhispererCodeScanManager.getInstance(project).showCodeScanUI() | ||
} | ||
} | ||
} | ||
|
||
private suspend fun updateQuickActionsInBrowser(browser: Browser) { | ||
val isFeatureDevAvailable = isFeatureDevAvailable(project) | ||
val isCodeTransformAvailable = isCodeTransformAvailable(project) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,34 +17,55 @@ | |
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_TO_PROMPT | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendToPromptParams | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType | ||
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper | ||
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl | ||
|
||
class ExplainCodeIssueAction : AnAction(), DumbAware { | ||
class HandleIssueCommandAction : AnAction(), DumbAware { | ||
Check warning on line 23 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/HandleIssueCommandAction.kt
|
||
|
||
override fun getActionUpdateThread() = ActionUpdateThread.BGT | ||
|
||
override fun update(e: AnActionEvent) { | ||
e.presentation.isEnabledAndVisible = e.project != null | ||
} | ||
fun createLineRangeText(issueContext: MutableMap<String, String>): String { | ||
val startLine = issueContext["startLine"] | ||
val endLine = issueContext["endLine"] | ||
return if (startLine.equals(endLine)) { | ||
"[$startLine]" | ||
} else { | ||
"[$startLine, $endLine]" | ||
} | ||
} | ||
|
||
override fun actionPerformed(e: AnActionEvent) { | ||
val project = e.project ?: return | ||
val issueDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.explainissue") | ||
val issueContext = e.getData(issueDataKey) ?: return | ||
val contextDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.handleIssueCommandContext") | ||
val actionDataKey = DataKey.create<String>("amazonq.codescan.handleIssueCommandAction") | ||
|
||
val context = e.getData(contextDataKey) ?: return | ||
val action = e.getData(actionDataKey) ?: return | ||
|
||
// Emit telemetry event | ||
TelemetryHelper.recordTelemetryIssueCommandAction( | ||
context["findingId"].orEmpty(), | ||
context["detectorId"].orEmpty(), | ||
context["ruleId"].orEmpty(), | ||
context["autoDetected"].orEmpty(), | ||
getStartUrl(project).orEmpty(), | ||
action, // The action name (explainIssue or applyFix) | ||
"Succeeded" | ||
) | ||
|
||
ActionManager.getInstance().getAction("q.openchat").actionPerformed(e) | ||
|
||
ApplicationManager.getApplication().executeOnPooledThread { | ||
runBlocking { | ||
// https://github.com/aws/aws-toolkit-vscode/blob/master/packages/amazonq/src/lsp/chat/commands.ts#L30 | ||
val codeSelection = "\n```\n${issueContext["code"]?.trimIndent()?.trim()}\n```\n" | ||
val codeSelection = "\n```\n${context["code"]?.trimIndent()?.trim()}\n```\n" | ||
val actionString = if (action == "explainIssue") "Explain" else "Fix" | ||
|
||
val prompt = "Explain the issue \n\n " + | ||
"Issue: \"${issueContext["title"]}\" \n" + | ||
"Code: $codeSelection" | ||
val prompt = "$actionString ${context["title"]} issue in ${context["fileName"]} at ${createLineRangeText(context)}" | ||
|
||
val modelPrompt = "Explain the issue ${issueContext["title"]} \n\n " + | ||
"Issue: \"${issueContext["title"]}\" \n" + | ||
"Description: ${issueContext["description"]} \n" + | ||
"Code: $codeSelection and generate the code demonstrating the fix" | ||
val modelPrompt = "$actionString ${context["title"]} issue in ${context["fileName"]} at ${createLineRangeText(context)}" + | ||
"Issue: \"${context}\" \n" | ||
|
||
val params = SendToPromptParams( | ||
selection = codeSelection, | ||
|
Uh oh!
There was an error while loading. Please reload this page.