Skip to content

Commit f1bec1c

Browse files
authored
Merge branch 'main' into rli/local-context-executor
2 parents 9806d63 + beabdf1 commit f1bec1c

File tree

28 files changed

+403
-145
lines changed

28 files changed

+403
-145
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" : "Amazon Q /doc: support making changes to architecture diagrams"
4+
}

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.295"
30+
telemetryGenerator = "1.0.297"
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/amazonqCodeTest/CodeTestChatApp.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class CodeTestChatApp(private val scope: CoroutineScope) : AmazonQApp {
3939
"start-test-gen" to IncomingCodeTestMessage.StartTestGen::class,
4040
"response-body-link-click" to IncomingCodeTestMessage.ClickedLink::class,
4141
"button-click" to IncomingCodeTestMessage.ButtonClicked::class,
42+
"chat-item-voted" to IncomingCodeTestMessage.ChatItemVoted::class,
43+
"chat-item-feedback" to IncomingCodeTestMessage.ChatItemFeedback::class,
44+
"button-click" to IncomingCodeTestMessage.ButtonClicked::class,
4245
"auth-follow-up-was-clicked" to IncomingCodeTestMessage.AuthFollowUpWasClicked::class
4346
)
4447

@@ -80,6 +83,8 @@ class CodeTestChatApp(private val scope: CoroutineScope) : AmazonQApp {
8083
is IncomingCodeTestMessage.StartTestGen -> inboundAppMessagesHandler.processStartTestGen(message)
8184
is IncomingCodeTestMessage.ClickedLink -> inboundAppMessagesHandler.processLinkClick(message)
8285
is IncomingCodeTestMessage.ButtonClicked -> inboundAppMessagesHandler.processButtonClickedMessage(message)
86+
is IncomingCodeTestMessage.ChatItemVoted -> inboundAppMessagesHandler.processChatItemVoted(message)
87+
is IncomingCodeTestMessage.ChatItemFeedback -> inboundAppMessagesHandler.processChatItemFeedBack(message)
8388
is IncomingCodeTestMessage.AuthFollowUpWasClicked -> inboundAppMessagesHandler.processAuthFollowUpClick(message)
8489
}
8590
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
297297
// TODO: Modify text according to FnF
298298
codeTestChatHelper.addAnswer(
299299
CodeTestChatMessageContent(
300-
message = message("testgen.message.failed"),
300+
message = message("testgen.error.generic_technical_error_message"),
301301
type = ChatMessageType.Answer,
302302
canBeVoted = true
303303
)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/InboundAppMessagesHandler.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,9 @@ interface InboundAppMessagesHandler {
2222

2323
suspend fun processButtonClickedMessage(message: IncomingCodeTestMessage.ButtonClicked)
2424

25+
suspend fun processChatItemVoted(message: IncomingCodeTestMessage.ChatItemVoted)
26+
27+
suspend fun processChatItemFeedBack(message: IncomingCodeTestMessage.ChatItemFeedback)
28+
2529
suspend fun processAuthFollowUpClick(message: IncomingCodeTestMessage.AuthFollowUpWasClicked)
2630
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
package software.aws.toolkits.jetbrains.services.amazonqCodeTest.controller
5-
5+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
66
import com.intellij.diff.DiffContentFactory
77
import com.intellij.diff.DiffManager
88
import com.intellij.diff.DiffManagerEx
@@ -44,8 +44,11 @@ import software.amazon.awssdk.services.codewhispererstreaming.model.TextDocument
4444
import software.amazon.awssdk.services.codewhispererstreaming.model.UserInputMessage
4545
import software.amazon.awssdk.services.codewhispererstreaming.model.UserInputMessageContext
4646
import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
47+
import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment
4748
import software.aws.toolkits.core.utils.debug
4849
import software.aws.toolkits.core.utils.getLogger
50+
import software.aws.toolkits.core.utils.info
51+
import software.aws.toolkits.core.utils.warn
4952
import software.aws.toolkits.jetbrains.core.AwsClientManager
5053
import software.aws.toolkits.jetbrains.core.coroutines.EDT
5154
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
@@ -81,14 +84,19 @@ import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.TriggerTy
8184
import software.aws.toolkits.jetbrains.services.cwc.clients.chat.v1.ChatSessionV1.Companion.validLanguages
8285
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.StaticPrompt
8386
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.StaticTextResponse
87+
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.FeedbackComment
8488
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
8589
import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContext
8690
import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContextExtractor
8791
import software.aws.toolkits.jetbrains.services.cwc.editor.context.ExtractionTriggerType
8892
import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.FileContext
8993
import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType
94+
import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
95+
import software.aws.toolkits.jetbrains.utils.notifyError
9096
import software.aws.toolkits.resources.message
9197
import software.aws.toolkits.telemetry.AmazonqTelemetry
98+
import software.aws.toolkits.telemetry.FeatureId
99+
import software.aws.toolkits.telemetry.InteractionType
92100
import software.aws.toolkits.telemetry.MetricResult
93101
import software.aws.toolkits.telemetry.UiTelemetry
94102
import java.io.File
@@ -403,6 +411,57 @@ class CodeTestChatController(
403411
.build()
404412
}
405413

414+
override suspend fun processChatItemFeedBack(message: IncomingCodeTestMessage.ChatItemFeedback) {
415+
LOG.debug { "$FEATURE_NAME: Processing ChatItemFeedBackMessage: ${message.comment}" }
416+
417+
val session = codeTestChatHelper.getActiveSession()
418+
419+
val comment = FeedbackComment(
420+
conversationId = session.startTestGenerationRequestId,
421+
userComment = message.comment.orEmpty(),
422+
reason = message.selectedOption,
423+
type = "testgen-chat-answer-feedback",
424+
messageId = "",
425+
)
426+
427+
try {
428+
TelemetryService.getInstance().sendFeedback(
429+
sentiment = Sentiment.NEGATIVE,
430+
comment = objectMapper.writeValueAsString(comment),
431+
)
432+
LOG.info { "$FEATURE_NAME answer feedback sent: \"Negative\"" }
433+
} catch (e: Throwable) {
434+
e.notifyError(message("feedback.submit_failed", e))
435+
LOG.warn(e) { "Failed to submit feedback" }
436+
return
437+
}
438+
}
439+
440+
override suspend fun processChatItemVoted(message: IncomingCodeTestMessage.ChatItemVoted) {
441+
LOG.debug { "$FEATURE_NAME: Processing ChatItemVotedMessage: $message" }
442+
443+
val session = codeTestChatHelper.getActiveSession()
444+
when (message.vote) {
445+
"upvote" -> {
446+
AmazonqTelemetry.feedback(
447+
featureId = FeatureId.AmazonQTest,
448+
interactionType = InteractionType.Upvote,
449+
credentialStartUrl = getStartUrl(project = context.project),
450+
amazonqConversationId = session.startTestGenerationRequestId
451+
452+
)
453+
}
454+
"downvote" -> {
455+
AmazonqTelemetry.feedback(
456+
featureId = FeatureId.AmazonQTest,
457+
interactionType = InteractionType.Downvote,
458+
credentialStartUrl = getStartUrl(project = context.project),
459+
amazonqConversationId = session.startTestGenerationRequestId
460+
)
461+
}
462+
}
463+
}
464+
406465
override suspend fun processNewTabCreatedMessage(message: IncomingCodeTestMessage.NewTabCreated) {
407466
newTabOpened(message.tabId)
408467
LOG.debug { "$FEATURE_NAME: New tab created: $message" }
@@ -1312,5 +1371,7 @@ class CodeTestChatController(
13121371

13131372
companion object {
13141373
private val LOG = getLogger<CodeTestChatController>()
1374+
1375+
private val objectMapper = jacksonObjectMapper()
13151376
}
13161377
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/messages/CodeTestMessage.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ sealed interface IncomingCodeTestMessage : CodeTestBaseMessage {
9595
@JsonProperty("actionID") val actionID: String,
9696
) : IncomingCodeTestMessage
9797

98+
data class ChatItemVoted(
99+
@JsonProperty("tabID") val tabId: String,
100+
val vote: String,
101+
) : IncomingCodeTestMessage
102+
103+
data class ChatItemFeedback(
104+
@JsonProperty("tabID") val tabId: String,
105+
val selectedOption: String,
106+
val comment: String?,
107+
) : IncomingCodeTestMessage
108+
98109
data class AuthFollowUpWasClicked(
99110
@JsonProperty("tabID") val tabId: String,
100111
val authType: AuthFollowUpType,

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

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonqDoc
55

6-
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUp
7-
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUpStatusType
8-
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUpTypes
9-
import software.aws.toolkits.resources.message
10-
116
const val FEATURE_EVALUATION_PRODUCT_NAME = "DocGeneration"
127

138
const val FEATURE_NAME = "Amazon Q Documentation Generation"
@@ -21,25 +16,8 @@ const val DEFAULT_RETRY_LIMIT = 0
2116
// Max allowed size for a repository in bytes
2217
const val MAX_PROJECT_SIZE_BYTES: Long = 200 * 1024 * 1024
2318

24-
enum class ModifySourceFolderErrorReason(
25-
private val reasonText: String,
26-
) {
27-
ClosedBeforeSelection("ClosedBeforeSelection"),
28-
NotInWorkspaceFolder("NotInWorkspaceFolder"),
29-
;
30-
31-
override fun toString(): String = reasonText
32-
}
33-
34-
val NEW_SESSION_FOLLOWUPS: List<FollowUp> = listOf(
35-
FollowUp(
36-
pillText = message("amazonqDoc.prompt.reject.new_task"),
37-
type = FollowUpTypes.NEW_TASK,
38-
status = FollowUpStatusType.Info
39-
),
40-
FollowUp(
41-
pillText = message("amazonqDoc.prompt.reject.close_session"),
42-
type = FollowUpTypes.CLOSE_SESSION,
43-
status = FollowUpStatusType.Info
44-
)
45-
)
19+
const val INFRA_DIAGRAM_PREFIX = "infra."
20+
const val DIAGRAM_SVG_EXT = "svg"
21+
const val DIAGRAM_DOT_EXT = "dot"
22+
val SUPPORTED_DIAGRAM_EXT_SET: Set<String> = setOf(DIAGRAM_SVG_EXT, DIAGRAM_DOT_EXT)
23+
val SUPPORTED_DIAGRAM_FILE_NAME_SET: Set<String> = SUPPORTED_DIAGRAM_EXT_SET.map { INFRA_DIAGRAM_PREFIX + it }.toSet()

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

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import com.intellij.diff.requests.SimpleDiffRequest
1212
import com.intellij.diff.util.DiffUserDataKeys
1313
import com.intellij.ide.BrowserUtil
1414
import com.intellij.openapi.application.runInEdt
15+
import com.intellij.openapi.fileEditor.FileEditorManager
1516
import com.intellij.openapi.project.Project
1617
import com.intellij.openapi.roots.ProjectRootManager
1718
import com.intellij.openapi.vfs.LocalFileSystem
1819
import com.intellij.openapi.vfs.VfsUtil
1920
import com.intellij.openapi.wm.ToolWindowManager
21+
import com.intellij.testFramework.LightVirtualFile
2022
import kotlinx.coroutines.withContext
23+
import org.intellij.images.fileTypes.impl.SvgFileType
2124
import software.amazon.awssdk.services.codewhispererruntime.model.DocFolderLevel
2225
import software.amazon.awssdk.services.codewhispererruntime.model.DocInteractionType
2326
import software.amazon.awssdk.services.codewhispererruntime.model.DocUserDecision
@@ -33,6 +36,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitConte
3336
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
3437
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
3538
import software.aws.toolkits.jetbrains.services.amazonqDoc.DEFAULT_RETRY_LIMIT
39+
import software.aws.toolkits.jetbrains.services.amazonqDoc.DIAGRAM_SVG_EXT
3640
import software.aws.toolkits.jetbrains.services.amazonqDoc.DocException
3741
import software.aws.toolkits.jetbrains.services.amazonqDoc.FEATURE_NAME
3842
import software.aws.toolkits.jetbrains.services.amazonqDoc.InboundAppMessagesHandler
@@ -374,45 +378,51 @@ class DocController(
374378

375379
override suspend fun processOpenDiff(message: IncomingDocMessage.OpenDiff) {
376380
val session = getSessionInfo(message.tabId)
377-
378-
val project = context.project
379381
val sessionState = session.sessionState
380382

381-
when (sessionState) {
382-
is PrepareDocGenerationState -> {
383-
runInEdt {
384-
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
385-
386-
val leftDiffContent = if (existingFile == null) {
387-
EmptyContent()
388-
} else {
389-
DiffContentFactory.getInstance().create(project, existingFile)
390-
}
391-
392-
val newFileContent = sessionState.filePaths.find { it.zipFilePath == message.filePath }?.fileContent
383+
if (sessionState !is PrepareDocGenerationState) {
384+
logger.error { "$FEATURE_NAME: OpenDiff event is received for a conversation that has ${session.sessionState.phase} phase" }
385+
messenger.sendError(
386+
tabId = message.tabId,
387+
errMessage = message("amazonqFeatureDev.exception.open_diff_failed"),
388+
retries = 0,
389+
conversationId = session.conversationIdUnsafe
390+
)
391+
return
392+
}
393393

394-
val rightDiffContent = if (message.deleted || newFileContent == null) {
395-
EmptyContent()
396-
} else {
397-
DiffContentFactory.getInstance().create(newFileContent)
398-
}
394+
runInEdt {
395+
val newFileContent = sessionState.filePaths.find { it.zipFilePath == message.filePath }?.fileContent
399396

400-
val request = SimpleDiffRequest(message.filePath, leftDiffContent, rightDiffContent, null, null)
401-
request.putUserData(DiffUserDataKeys.FORCE_READ_ONLY, true)
397+
val isSvgFile = message.filePath.lowercase().endsWith(".".plus(DIAGRAM_SVG_EXT))
398+
if (isSvgFile && newFileContent != null) {
399+
// instead of diff display generated svg in edit/preview window
400+
val inMemoryFile = LightVirtualFile(
401+
message.filePath,
402+
SvgFileType.INSTANCE,
403+
newFileContent
404+
)
405+
inMemoryFile.isWritable = false
406+
FileEditorManager.getInstance(context.project).openFile(inMemoryFile, true)
407+
} else {
408+
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
409+
val leftDiffContent = if (existingFile == null) {
410+
EmptyContent()
411+
} else {
412+
DiffContentFactory.getInstance().create(context.project, existingFile)
413+
}
402414

403-
val newDiff = ChainDiffVirtualFile(SimpleDiffRequestChain(request), message.filePath)
404-
DiffEditorTabFilesManager.getInstance(context.project).showDiffFile(newDiff, true)
415+
val rightDiffContent = if (message.deleted || newFileContent == null) {
416+
EmptyContent()
417+
} else {
418+
DiffContentFactory.getInstance().create(newFileContent)
405419
}
406-
}
407420

408-
else -> {
409-
logger.error { "$FEATURE_NAME: OpenDiff event is received for a conversation that has ${session.sessionState.phase} phase" }
410-
messenger.sendError(
411-
tabId = message.tabId,
412-
errMessage = message("amazonqFeatureDev.exception.open_diff_failed"),
413-
retries = 0,
414-
conversationId = session.conversationIdUnsafe
415-
)
421+
val request = SimpleDiffRequest(message.filePath, leftDiffContent, rightDiffContent, null, null)
422+
request.putUserData(DiffUserDataKeys.FORCE_READ_ONLY, true)
423+
424+
val newDiff = ChainDiffVirtualFile(SimpleDiffRequestChain(request), message.filePath)
425+
DiffEditorTabFilesManager.getInstance(context.project).showDiffFile(newDiff, true)
416426
}
417427
}
418428
}
@@ -738,6 +748,7 @@ class DocController(
738748
SessionStatePhase.CODEGEN -> {
739749
onCodeGeneration(session, message, tabId, mode)
740750
}
751+
741752
else -> null
742753
}
743754

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/messages/DocMessagePublisherExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthNeededState
77
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
88
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.ProgressField
99
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.PromptProgressMessage
10-
import software.aws.toolkits.jetbrains.services.amazonqDoc.NEW_SESSION_FOLLOWUPS
10+
import software.aws.toolkits.jetbrains.services.amazonqDoc.ui.NEW_SESSION_FOLLOWUPS
1111
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.CodeReferenceGenerated
1212
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.DeletedFileInfo
1313
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.NewFileZipInfo

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/session/DocSession.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import software.aws.toolkits.jetbrains.common.session.SessionStateConfigData
2020
import software.aws.toolkits.jetbrains.common.util.AmazonQCodeGenService
2121
import software.aws.toolkits.jetbrains.common.util.resolveAndCreateOrUpdateFile
2222
import software.aws.toolkits.jetbrains.common.util.resolveAndDeleteFile
23-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
2423
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
2524
import software.aws.toolkits.jetbrains.services.amazonqDoc.CODE_GENERATION_RETRY_LIMIT
2625
import software.aws.toolkits.jetbrains.services.amazonqDoc.FEATURE_NAME
@@ -40,7 +39,7 @@ import java.security.MessageDigest
4039
private val logger = getLogger<AmazonQCodeGenerateClient>()
4140

4241
class DocSession(val tabID: String, val project: Project) {
43-
var context: FeatureDevSessionContext
42+
var context: DocSessionContext = DocSessionContext(project, MAX_PROJECT_SIZE_BYTES)
4443
val sessionStartTime = System.currentTimeMillis()
4544

4645
var state: SessionState?
@@ -59,7 +58,6 @@ class DocSession(val tabID: String, val project: Project) {
5958
var isAuthenticating: Boolean
6059

6160
init {
62-
context = FeatureDevSessionContext(project, MAX_PROJECT_SIZE_BYTES)
6361
proxyClient = AmazonQCodeGenerateClient.getInstance(project)
6462
amazonQCodeGenService = AmazonQCodeGenService(proxyClient, project)
6563
state = ConversationNotStartedState("", tabID, token = null)

0 commit comments

Comments
 (0)