-
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 2 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.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 | ||
|
@@ -72,6 +76,8 @@ | |
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 | ||
|
@@ -108,13 +114,23 @@ | |
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.CodeLine | ||
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.CodeWhispererCodeScanSession | ||
|
||
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(), | ||
|
@@ -124,6 +140,7 @@ | |
val uiReady = CompletableDeferred<Boolean>() | ||
private val chatCommunicationManager = ChatCommunicationManager.getInstance(project) | ||
private val chatAsyncResultManager = ChatAsyncResultManager.getInstance(project) | ||
private val codeScanManager = CodeWhispererCodeScanManager.getInstance(project) | ||
|
||
|
||
suspend fun connect( | ||
browser: Browser, | ||
|
@@ -572,6 +589,33 @@ | |
} | ||
} | ||
|
||
data class flareCodeScanIssue( | ||
Check notice on line 592 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
|
||
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, | ||
|
@@ -585,10 +629,74 @@ | |
throw error | ||
} | ||
chatCommunicationManager.removePartialChatMessage(partialResultToken) | ||
val params = value?.let { encryptionManager?.decrypt(it) }.orEmpty() | ||
|
||
val jsonObject = Gson().fromJson(params, Map::class.java) | ||
val additionalMessages = jsonObject["additionalMessages"] as? MutableList<Map<String, Any>> | ||
val findingsMessage = additionalMessages?.find {message -> | ||
(message["messageId"] as String).endsWith(CODE_REVIEW_FINDINGS_SUFFIX) | ||
|| (message["messageId"] as String).endsWith(DISPLAY_FINDINGS_SUFFIX)} | ||
val scannedFiles = mutableListOf<VirtualFile>(); | ||
|
||
if (findingsMessage != null) { | ||
additionalMessages.remove(findingsMessage) | ||
val gson = Gson() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: would recommend using the same instance of Gson throughout the class There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will add in follow up PR |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are receiving a List of AggregatedCodeScanIssues as a string from FLARE message, this is formatting them and putting them into a List object. |
||
val file = try { | ||
LocalFileSystem.getInstance().findFileByIoFile( | ||
Path.of(aggregatedIssue.filePath).toFile() | ||
) | ||
} catch (e: Exception) { | ||
null | ||
} | ||
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 = codeScanManager.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 | ||
)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
codeScanManager.addOnDemandIssues(mappedFindings, scannedFiles, CodeWhispererConstants.CodeAnalysisScope.AGENTIC) | ||
codeScanManager.showCodeScanUI() | ||
} | ||
|
||
val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( | ||
SEND_CHAT_COMMAND_PROMPT, | ||
tabId, | ||
value?.let { encryptionManager?.decrypt(it) }.orEmpty(), | ||
Gson().toJson(jsonObject), | ||
isPartialResult = false | ||
) | ||
browser.postChat(messageToChat) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,33 +18,42 @@ | |
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendToPromptParams | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType | ||
|
||
class ExplainCodeIssueAction : AnAction(), DumbAware { | ||
class HandleIssueCommandAction : AnAction(), DumbAware { | ||
Check warning on line 21 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 startLineString = issueContext["startLine"] | ||
val endLineString = issueContext["endLine"] | ||
val startLineInteger = Integer.parseInt(startLineString) | ||
|
||
val endLineInteger = Integer.parseInt(endLineString) | ||
return if (startLineInteger == endLineInteger) { | ||
|
||
"[${startLineInteger}]" | ||
} else { | ||
"[${startLineInteger}, ${endLineInteger}]" | ||
} | ||
} | ||
|
||
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 | ||
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["issue"]}\" \n" | ||
|
||
val params = SendToPromptParams( | ||
selection = codeSelection, | ||
|
Uh oh!
There was an error while loading. Please reload this page.