Skip to content

Commit 76e1af3

Browse files
authored
Merge branch 'main' into e2e
2 parents dc17a87 + 8c292c5 commit 76e1af3

File tree

17 files changed

+166
-172
lines changed

17 files changed

+166
-172
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Amazon Q: Fix data isolation between tabs to prevent interference when using /doc in multiple tabs"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "/review: Code reviews are now created with additional workspace context to enable grouping of related scans in the backend"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "removal",
3+
"description" : "The Amazon Q inline suggestion popup goes back to being under the suggestions and is always showing."
4+
}

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ reported the issue. Please try to include as much information as you can. Detail
3131
brew install dotnet@6
3232
```
3333
* If Gradle cannot find `dotnet`, run `./gradlew --stop` and `./gradlew projects` to reload the daemon. Note that this should be done in your terminal as invoking Gradle through the IDE will use the IDE's cached PATH.
34+
* It is recommended to [launch your IDE from the terminal](https://www.jetbrains.com/help/idea/working-with-the-ide-features-from-command-line.html) due to a known issue with Gradle/Java 21 where the Gradle daemon does not respect your PATH variable when the IDE is started from the desktop/Toolbox.
3435
3536
### Instructions
3637

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mockitoKotlin = "5.4.0"
2727
mockk = "1.13.10"
2828
nimbus-jose-jwt = "9.40"
2929
node-gradle = "7.0.2"
30-
telemetryGenerator = "1.0.297"
30+
telemetryGenerator = "1.0.301"
3131
testLogger = "4.0.0"
3232
testRetry = "1.5.10"
3333
# test-only; platform provides slf4j transitively at runtime

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,8 @@ class DocController(
132132
private val authController: AuthController = AuthController(),
133133
) : InboundAppMessagesHandler {
134134
val messenger = context.messagesFromAppToUi
135-
var mode: Mode = Mode.CREATE
136135
val toolWindow = ToolWindowManager.getInstance(context.project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID)
137-
var docGenerationTask = DocGenerationTask()
136+
private val docGenerationTasks = DocGenerationTasks()
138137

139138
override suspend fun processPromptChatMessage(message: IncomingDocMessage.ChatPrompt) {
140139
handleChat(
@@ -148,7 +147,7 @@ class DocController(
148147
}
149148

150149
override suspend fun processTabRemovedMessage(message: IncomingDocMessage.TabRemoved) {
151-
docGenerationTask.reset()
150+
docGenerationTasks.deleteTask(message.tabId)
152151
chatSessionStorage.deleteSession(message.tabId)
153152
}
154153

@@ -160,6 +159,7 @@ class DocController(
160159

161160
override suspend fun processFollowupClickedMessage(message: IncomingDocMessage.FollowupClicked) {
162161
val session = getSessionInfo(message.tabId)
162+
val docGenerationTask = docGenerationTasks.getTask(message.tabId)
163163

164164
session.preloader(message.followUp.pillText, messenger) // also stores message in session history
165165

@@ -173,7 +173,7 @@ class DocController(
173173
FollowUpTypes.CLOSE_SESSION -> closeSession(message.tabId)
174174
FollowUpTypes.CREATE_DOCUMENTATION -> {
175175
docGenerationTask.interactionType = DocInteractionType.GENERATE_README
176-
mode = Mode.CREATE
176+
docGenerationTask.mode = Mode.CREATE
177177
promptForDocTarget(message.tabId)
178178
}
179179

@@ -183,19 +183,19 @@ class DocController(
183183
}
184184

185185
FollowUpTypes.CANCEL_FOLDER_SELECTION -> {
186-
docGenerationTask.folderLevel = DocFolderLevel.ENTIRE_WORKSPACE
186+
docGenerationTask.reset()
187187
newTask(message.tabId)
188188
}
189189

190-
FollowUpTypes.PROCEED_FOLDER_SELECTION -> if (mode == Mode.EDIT) makeChanges(message.tabId) else onDocsGeneration(message)
190+
FollowUpTypes.PROCEED_FOLDER_SELECTION -> if (docGenerationTask.mode == Mode.EDIT) makeChanges(message.tabId) else onDocsGeneration(message)
191191
FollowUpTypes.ACCEPT_CHANGES -> {
192192
docGenerationTask.userDecision = DocUserDecision.ACCEPT
193193
sendDocAcceptanceTelemetry(message.tabId)
194194
acceptChanges(message)
195195
}
196196

197197
FollowUpTypes.MAKE_CHANGES -> {
198-
mode = Mode.EDIT
198+
docGenerationTask.mode = Mode.EDIT
199199
makeChanges(message.tabId)
200200
}
201201

@@ -206,12 +206,12 @@ class DocController(
206206
}
207207

208208
FollowUpTypes.SYNCHRONIZE_DOCUMENTATION -> {
209-
mode = Mode.SYNC
209+
docGenerationTask.mode = Mode.SYNC
210210
promptForDocTarget(message.tabId)
211211
}
212212

213213
FollowUpTypes.EDIT_DOCUMENTATION -> {
214-
mode = Mode.EDIT
214+
docGenerationTask.mode = Mode.EDIT
215215
docGenerationTask.interactionType = DocInteractionType.EDIT_README
216216
promptForDocTarget(message.tabId)
217217
}
@@ -241,7 +241,6 @@ class DocController(
241241
session.sessionState.token?.cancel()
242242
}
243243

244-
docGenerationTask.reset()
245244
newTask(message.tabId)
246245
}
247246

@@ -307,13 +306,14 @@ class DocController(
307306

308307
private suspend fun promptForDocTarget(tabId: String) {
309308
val session = getSessionInfo(tabId)
309+
val docGenerationTask = docGenerationTasks.getTask(tabId)
310310

311311
val currentSourceFolder = session.context.selectedSourceFolder
312312

313313
try {
314314
messenger.sendFolderConfirmationMessage(
315315
tabId = tabId,
316-
message = if (mode == Mode.CREATE) message("amazonqDoc.prompt.create.confirmation") else message("amazonqDoc.prompt.update"),
316+
message = if (docGenerationTask.mode == Mode.CREATE) message("amazonqDoc.prompt.create.confirmation") else message("amazonqDoc.prompt.update"),
317317
folderPath = currentSourceFolder.name,
318318
followUps = listOf(
319319
FollowUp(
@@ -452,6 +452,9 @@ class DocController(
452452
var session: DocSession? = null
453453
try {
454454
session = getSessionInfo(tabId)
455+
val docGenerationTask = docGenerationTasks.getTask(tabId)
456+
docGenerationTask.mode = Mode.NONE
457+
455458
logger.debug { "$FEATURE_NAME: Session created with id: ${session.tabID}" }
456459

457460
val credentialState = authController.getAuthNeededStates(context.project).amazonQ
@@ -528,7 +531,7 @@ class DocController(
528531
}
529532

530533
private suspend fun newTask(tabId: String) {
531-
docGenerationTask = DocGenerationTask()
534+
docGenerationTasks.deleteTask(tabId)
532535
chatSessionStorage.deleteSession(tabId)
533536

534537
messenger.sendAnswer(
@@ -577,7 +580,7 @@ class DocController(
577580
)
578581

579582
messenger.sendChatInputEnabledMessage(tabId = tabId, enabled = false)
580-
docGenerationTask.reset()
583+
docGenerationTasks.deleteTask(tabId)
581584
}
582585

583586
private suspend fun provideFeedbackAndRegenerateCode(tabId: String) {
@@ -728,6 +731,7 @@ class DocController(
728731
message: String,
729732
) {
730733
var session: DocSession? = null
734+
val docGenerationTask = docGenerationTasks.getTask(tabId)
731735
try {
732736
logger.debug { "$FEATURE_NAME: Processing message: $message" }
733737
session = getSessionInfo(tabId)
@@ -746,7 +750,7 @@ class DocController(
746750

747751
when (session.sessionState.phase) {
748752
SessionStatePhase.CODEGEN -> {
749-
onCodeGeneration(session, message, tabId, mode)
753+
onCodeGeneration(session, message, tabId, docGenerationTask.mode)
750754
}
751755

752756
else -> null
@@ -756,7 +760,7 @@ class DocController(
756760
is PrepareDocGenerationState -> state.filePaths
757761
else -> emptyList()
758762
}
759-
sendDocGenerationTelemetry(filePaths, session)
763+
sendDocGenerationTelemetry(filePaths, session, docGenerationTask)
760764
broadcastQEvent(QFeatureEvent.INVOCATION)
761765

762766
if (filePaths.isNotEmpty()) {
@@ -767,7 +771,7 @@ class DocController(
767771
} catch (err: Exception) {
768772
// For non edit mode lock the chat input until they explicitly click one of the follow-ups
769773
var isEnableChatInput = false
770-
if (err is DocException && Mode.EDIT == mode) {
774+
if (err is DocException && docGenerationTask.mode == Mode.EDIT) {
771775
isEnableChatInput = err.remainingIterations != null && err.remainingIterations > 0
772776
}
773777

@@ -779,15 +783,16 @@ class DocController(
779783
messenger.sendUpdatePromptProgress(tabId = followUpMessage.tabId, inProgress(progress = 10, message("amazonqDoc.progress_message.scanning")))
780784

781785
val session = getSessionInfo(followUpMessage.tabId)
786+
val docGenerationTask = docGenerationTasks.getTask(followUpMessage.tabId)
782787

783788
messenger.sendAnswer(
784-
message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, this.mode),
789+
message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, docGenerationTask.mode),
785790
messageType = DocMessageType.AnswerPart,
786791
tabId = followUpMessage.tabId,
787792
)
788793

789794
try {
790-
val sessionMessage: String = when (mode) {
795+
val sessionMessage: String = when (docGenerationTask.mode) {
791796
Mode.CREATE -> message("amazonqDoc.session.create")
792797
else -> message("amazonqDoc.session.sync")
793798
}
@@ -821,10 +826,10 @@ class DocController(
821826
return
822827
}
823828

824-
sendDocGenerationTelemetry(filePaths, session)
829+
sendDocGenerationTelemetry(filePaths, session, docGenerationTask)
825830

826831
messenger.sendAnswer(
827-
message = docGenerationProgressMessage(DocGenerationStep.COMPLETE, mode),
832+
message = docGenerationProgressMessage(DocGenerationStep.COMPLETE, docGenerationTask.mode),
828833
messageType = DocMessageType.AnswerPart,
829834
tabId = followUpMessage.tabId,
830835
)
@@ -907,7 +912,6 @@ class DocController(
907912

908913
private suspend fun retryRequests(tabId: String) {
909914
var session: DocSession? = null
910-
docGenerationTask = DocGenerationTask()
911915
try {
912916
messenger.sendAsyncEventProgress(
913917
tabId = tabId,
@@ -954,6 +958,7 @@ class DocController(
954958
val session = getSessionInfo(tabId)
955959
val currentSourceFolder = session.context.selectedSourceFolder
956960
val projectRoot = session.context.projectRoot
961+
val docGenerationTask = docGenerationTasks.getTask(tabId)
957962

958963
withContext(EDT) {
959964
messenger.sendAnswer(
@@ -1017,7 +1022,7 @@ class DocController(
10171022
}
10181023
}
10191024

1020-
private fun sendDocGenerationTelemetry(filePaths: List<NewFileZipInfo>, session: DocSession) {
1025+
private fun sendDocGenerationTelemetry(filePaths: List<NewFileZipInfo>, session: DocSession, docGenerationTask: DocGenerationTask) {
10211026
docGenerationTask.conversationId = session.conversationId
10221027
val (totalGeneratedChars, totalGeneratedLines, totalGeneratedFiles) = session.countedGeneratedContent(filePaths, docGenerationTask.interactionType)
10231028
docGenerationTask.numberOfGeneratedChars = totalGeneratedChars
@@ -1030,6 +1035,7 @@ class DocController(
10301035

10311036
private fun sendDocAcceptanceTelemetry(tabId: String) {
10321037
val session = getSessionInfo(tabId)
1038+
val docGenerationTask = docGenerationTasks.getTask(tabId)
10331039
var filePaths: List<NewFileZipInfo> = emptyList()
10341040

10351041
when (val state = session.sessionState) {

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocControllerExtensions.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ suspend fun DocController.onCodeGeneration(session: DocSession, message: String,
3131
messenger.sendAsyncEventProgress(tabId, inProgress = true)
3232
messenger.sendUpdatePromptProgress(tabId, inProgress(progress = 10, message("amazonqDoc.progress_message.scanning")))
3333
messenger.sendAnswer(
34-
message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, this.mode),
34+
message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, mode),
3535
messageType = DocMessageType.AnswerPart,
3636
tabId = tabId,
3737
)
@@ -108,7 +108,7 @@ suspend fun DocController.onCodeGeneration(session: DocSession, message: String,
108108
messenger.sendAnswer(
109109
tabId = tabId,
110110
messageType = DocMessageType.Answer,
111-
message = if (this.mode === Mode.CREATE) {
111+
message = if (mode === Mode.CREATE) {
112112
message("amazonqDoc.answer.readmeCreated")
113113
} else {
114114
"${message("amazonqDoc.answer.readmeUpdated")} ${message("amazonqDoc.answer.codeResult")}"

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocGenerationTask.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,19 @@ import software.amazon.awssdk.services.codewhispererruntime.model.DocV2Generatio
1111
import software.aws.toolkits.core.utils.debug
1212
import software.aws.toolkits.core.utils.getLogger
1313

14+
class DocGenerationTasks {
15+
private val tasks: MutableMap<String, DocGenerationTask> = mutableMapOf()
16+
17+
fun getTask(tabId: String): DocGenerationTask = tasks.getOrPut(tabId) { DocGenerationTask() }
18+
19+
fun deleteTask(tabId: String) {
20+
tasks.remove(tabId)
21+
}
22+
}
23+
1424
class DocGenerationTask {
25+
var mode: Mode = Mode.NONE
26+
1527
// Telemetry fields
1628
var conversationId: String? = null
1729
var numberOfAddedChars: Int? = null
@@ -22,8 +34,8 @@ class DocGenerationTask {
2234
var numberOfGeneratedFiles: Int? = null
2335
var userDecision: DocUserDecision? = null
2436
var interactionType: DocInteractionType? = null
25-
var numberOfNavigations = 0
26-
var folderLevel: DocFolderLevel? = DocFolderLevel.ENTIRE_WORKSPACE
37+
var numberOfNavigations: Int = 0
38+
var folderLevel: DocFolderLevel = DocFolderLevel.ENTIRE_WORKSPACE
2739
fun docGenerationEventBase(): DocV2GenerationEvent {
2840
val undefinedProps = this::class.java.declaredFields
2941
.filter { it.get(this) == null }

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import com.fasterxml.jackson.databind.DeserializationFeature
77
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
88
import com.fasterxml.jackson.module.kotlin.readValue
99
import com.intellij.openapi.application.ApplicationInfo
10+
import com.intellij.openapi.application.ApplicationManager
1011
import com.intellij.openapi.application.runInEdt
1112
import com.intellij.openapi.application.runReadAction
1213
import com.intellij.openapi.fileEditor.FileDocumentManager
1314
import com.intellij.openapi.project.Project
15+
import com.intellij.openapi.project.modules
16+
import com.intellij.openapi.roots.ModuleRootManager
1417
import com.intellij.openapi.vfs.LocalFileSystem
1518
import kotlinx.coroutines.delay
1619
import kotlinx.coroutines.ensureActive
1720
import kotlinx.coroutines.isActive
1821
import kotlinx.coroutines.time.withTimeout
1922
import kotlinx.coroutines.withContext
23+
import migration.software.aws.toolkits.jetbrains.settings.AwsSettings
24+
import org.apache.commons.codec.digest.DigestUtils
2025
import software.amazon.awssdk.services.codewhisperer.model.ArtifactType
2126
import software.amazon.awssdk.services.codewhisperer.model.CodeScanFindingsSchema
2227
import software.amazon.awssdk.services.codewhisperer.model.CodeScanStatus
@@ -33,12 +38,14 @@ import software.aws.toolkits.core.utils.Waiters.waitUntil
3338
import software.aws.toolkits.core.utils.debug
3439
import software.aws.toolkits.core.utils.getLogger
3540
import software.aws.toolkits.core.utils.info
41+
import software.aws.toolkits.core.utils.toHexString
3642
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig
3743
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
3844
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
3945
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
4046
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CodeScanResponseContext
4147
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CreateUploadUrlServiceInvocationContext
48+
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
4249
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
4350
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_POLLING_INTERVAL_IN_SECONDS
4451
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FILE_SCANS_THROTTLING_MESSAGE
@@ -52,12 +59,14 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer
5259
import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage
5360
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
5461
import software.aws.toolkits.resources.message
62+
import software.aws.toolkits.telemetry.CodewhispererCodeScanScope
5563
import software.aws.toolkits.telemetry.CodewhispererLanguage
5664
import java.nio.file.Path
5765
import java.time.Duration
5866
import java.time.Instant
5967
import java.util.UUID
6068
import kotlin.coroutines.coroutineContext
69+
import kotlin.io.path.pathString
6170

6271
class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
6372
private val clientToken: UUID = UUID.randomUUID()
@@ -103,7 +112,7 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
103112
// 2 & 3. CreateUploadURL and upload the context.
104113
currentCoroutineContext.ensureActive()
105114
val artifactsUploadStartTime = now()
106-
val codeScanName = UUID.randomUUID().toString()
115+
val codeScanName = generateScanName()
107116

108117
val taskType = if (sessionContext.codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.PROJECT) {
109118
CodeWhispererConstants.UploadTaskType.SCAN_PROJECT
@@ -366,6 +375,26 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
366375
private fun isAutoScan(): Boolean =
367376
sessionContext.codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.FILE && !sessionContext.sessionConfig.isInitiatedByChat()
368377

378+
private fun generateScanName(): String {
379+
val clientId = AwsSettings.getInstance().clientId
380+
val filePath = sessionContext.sessionConfig.getSelectedFile()?.toNioPath()?.pathString
381+
val scope = CodeWhispererTelemetryService.getInstance().mapToTelemetryScope(
382+
sessionContext.codeAnalysisScope,
383+
sessionContext.sessionConfig.isInitiatedByChat()
384+
)
385+
val projectId = if (scope != CodewhispererCodeScanScope.PROJECT && filePath != null) {
386+
filePath
387+
} else {
388+
ApplicationManager.getApplication().runReadAction<String> {
389+
sessionContext.project.modules.map { module ->
390+
ModuleRootManager.getInstance(module).contentRoots.firstOrNull()?.path
391+
}.joinToString(",")
392+
}
393+
}
394+
395+
return DigestUtils.sha256("$clientId::$projectId::$scope").toHexString()
396+
}
397+
369398
companion object {
370399
private val LOG = getLogger<CodeWhispererCodeScanSession>()
371400
private val MAPPER = jacksonObjectMapper()

0 commit comments

Comments
 (0)