diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2bc5d0ace..a5c9bfcdde 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ mockitoKotlin = "5.4.0" mockk = "1.13.10" nimbus-jose-jwt = "9.40" node-gradle = "7.0.2" -telemetryGenerator = "1.0.262" +telemetryGenerator = "1.0.272" testLogger = "4.0.0" testRetry = "1.5.10" # test-only; platform provides slf4j transitively at runtime diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt index ccb6434fd2..e1804ef0f0 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.onStart import software.amazon.awssdk.awscore.exception.AwsServiceException import software.amazon.awssdk.services.codewhispererstreaming.model.CodeWhispererStreamingException import software.aws.toolkits.core.utils.convertMarkdownToHTML +import software.aws.toolkits.core.utils.extractCodeBlockLanguage import software.aws.toolkits.jetbrains.services.cwc.clients.chat.exceptions.ChatApiException import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatRequestData import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatResponseEvent @@ -33,6 +34,8 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) { private val codeReferences = mutableListOf() private var requestId: String = "" private var statusCode: Int = 0 + private val defaultTestGenResponseLanguage: String = "plaintext" + private var codeBlockLanguage: String = defaultTestGenResponseLanguage companion object { private val CODE_BLOCK_PATTERN = Regex("
\\s*? = null,
     val codeReference: List? = null,
     val userIntent: UserIntent? = null,
+    val codeBlockLanguage: String? = "plaintext",
 ) : UiMessage(
     tabId = tabId,
     type = "chatMessage",
diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
index 0cba45227e..95658f7530 100644
--- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
+++ b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
@@ -420,7 +420,8 @@ class TelemetryHelperTest {
                 "insertionTargetType",
                 "eventId",
                 codeBlockIndex,
-                totalCodeBlocks
+                totalCodeBlocks,
+                lang
             )
         )
 
@@ -484,7 +485,8 @@ class TelemetryHelperTest {
                 emptyList(),
                 eventId,
                 codeBlockIndex,
-                totalCodeBlocks
+                totalCodeBlocks,
+                lang
             )
         )
 
diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts
index 9752c44e99..ffbca2fb06 100644
--- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts
+++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts
@@ -101,6 +101,7 @@ export class Connector {
         codeBlockIndex?: number,
         totalCodeBlocks?: number,
         userIntent?: string,
+        codeBlockLanguage?: string,
     ): void => {
         this.sendMessageToExtension({
             tabID: tabID,
@@ -113,7 +114,8 @@ export class Connector {
             eventId,
             codeBlockIndex,
             totalCodeBlocks,
-            userIntent
+            userIntent,
+            codeBlockLanguage
         })
     }
 
@@ -127,6 +129,7 @@ export class Connector {
         codeBlockIndex?: number,
         totalCodeBlocks?: number,
         userIntent?: string,
+        codeBlockLanguage?: string,
     ): void => {
         this.sendMessageToExtension({
             tabID: tabID,
@@ -139,7 +142,8 @@ export class Connector {
             eventId,
             codeBlockIndex,
             totalCodeBlocks,
-            userIntent
+            userIntent,
+            codeBlockLanguage
         })
     }
 
@@ -271,6 +275,7 @@ export class Connector {
                 canBeVoted: true,
                 codeReference: messageData.codeReference,
                 userIntent: messageData.userIntent,
+                codeBlockLanguage: messageData.codeBlockLanguage,
             }
 
             // If it is not there we will not set it
@@ -304,6 +309,7 @@ export class Connector {
                 messageId: messageData.messageId,
                 codeReference: messageData.codeReference,
                 userIntent: messageData.userIntent,
+                codeBlockLanguage: messageData.codeBlockLanguage,
                 followUp:
                     messageData.followUps !== undefined && messageData.followUps.length > 0
                         ? {
diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts
index efe6e6fb6a..cb5ecd1e08 100644
--- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts
+++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts
@@ -31,7 +31,8 @@ export interface ChatPayload {
 }
 
 export interface CWCChatItem extends ChatItem {
-    userIntent?: string
+    userIntent?: string,
+    codeBlockLanguage?: string,
 }
 
 export interface ConnectorProps {
@@ -235,7 +236,8 @@ export class Connector {
         eventId?: string,
         codeBlockIndex?: number,
         totalCodeBlocks?: number,
-        userIntent?: string
+        userIntent?: string,
+        codeBlockLanguage?: string
     ): void => {
         switch (this.tabsStorage.getTab(tabID)?.type) {
             case 'cwc':
@@ -248,7 +250,8 @@ export class Connector {
                     eventId,
                     codeBlockIndex,
                     totalCodeBlocks,
-                    userIntent
+                    userIntent,
+                    codeBlockLanguage
                 )
                 break
             case 'featuredev':
@@ -266,7 +269,8 @@ export class Connector {
         eventId?: string,
         codeBlockIndex?: number,
         totalCodeBlocks?: number,
-        userIntent?: string
+        userIntent?: string,
+        codeBlockLanguage?: string
     ): void => {
         switch (this.tabsStorage.getTab(tabID)?.type) {
             case 'cwc':
@@ -279,7 +283,8 @@ export class Connector {
                     eventId,
                     codeBlockIndex,
                     totalCodeBlocks,
-                    userIntent
+                    userIntent,
+                    codeBlockLanguage
                 )
                 break
             case 'featuredev':
diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts
index 72f87ef5a7..7f073b4f44 100644
--- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts
+++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts
@@ -24,7 +24,7 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT
     let mynahUI: MynahUI
     // eslint-disable-next-line prefer-const
     let connector: Connector
-    const messageUserIntentMap = new Map()
+    const responseMetadata = new Map()
 
     const tabsStorage = new TabsStorage({
         onTabTimeout: tabID => {
@@ -254,8 +254,8 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT
                         ? { type: ChatItemType.CODE_RESULT, fileList: item.fileList }
                         : {}),
                 })
-                if (item.messageId !== undefined && item.userIntent !== undefined) {
-                    messageUserIntentMap.set(item.messageId, item.userIntent)
+                if (item.messageId !== undefined && item.userIntent !== undefined && item.codeBlockLanguage !== undefined) {
+                    responseMetadata.set(item.messageId, [item.userIntent, item.codeBlockLanguage])
                 }
                 return
             }
@@ -466,7 +466,8 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT
                         eventId,
                         codeBlockIndex,
                         totalCodeBlocks,
-                        messageUserIntentMap.get(messageId) ?? undefined
+                        responseMetadata.get(messageId)?.[0] ?? undefined,
+                        responseMetadata.get(messageId)?.[1] ?? undefined
                     )
                     break
                 case 'copy':
@@ -479,7 +480,8 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT
                         eventId,
                         codeBlockIndex,
                         totalCodeBlocks,
-                        messageUserIntentMap.get(messageId) ?? undefined
+                        responseMetadata.get(messageId)?.[0] ?? undefined,
+                        responseMetadata.get(messageId)?.[1] ?? undefined
                     )
                     mynahUI.notify({
                         type: NotificationType.SUCCESS,
diff --git a/plugins/core/core/src/software/aws/toolkits/core/utils/TextUtils.kt b/plugins/core/core/src/software/aws/toolkits/core/utils/TextUtils.kt
index 2c277aa1d9..a7d7132d71 100644
--- a/plugins/core/core/src/software/aws/toolkits/core/utils/TextUtils.kt
+++ b/plugins/core/core/src/software/aws/toolkits/core/utils/TextUtils.kt
@@ -17,6 +17,25 @@ fun convertMarkdownToHTML(markdown: String): String {
     return htmlRenderer.render(document)
 }
 
+fun extractCodeBlockLanguage(message: String): String {
+    // This fulfills both the cases of unit test generation(java, python) and general use case(Non java and Non python) languages.
+    val defaultTestGenResponseLanguage = "plaintext"
+    val indexStart = 3
+    val codeBlockStart = message.indexOf("```")
+    if (codeBlockStart == -1) {
+        return defaultTestGenResponseLanguage
+    }
+
+    val languageStart = codeBlockStart + indexStart
+    val languageEnd = message.indexOf('\n', languageStart)
+
+    if (languageEnd == -1) {
+        return defaultTestGenResponseLanguage
+    }
+
+    return message.substring(languageStart, languageEnd).trim().ifEmpty { defaultTestGenResponseLanguage }
+}
+
 class CodeBlockRenderer(private val html: HtmlWriter) : NodeRenderer {
     override fun getNodeTypes(): Set> = setOf(FencedCodeBlock::class.java)
     override fun render(node: Node?) {
diff --git a/plugins/core/jetbrains-community/resources/telemetryOverride.json b/plugins/core/jetbrains-community/resources/telemetryOverride.json
index f9f1c5ee8e..e456dad31e 100644
--- a/plugins/core/jetbrains-community/resources/telemetryOverride.json
+++ b/plugins/core/jetbrains-community/resources/telemetryOverride.json
@@ -99,11 +99,6 @@
             "type": "int",
             "description": "Cpu usage of LSP server"
         },
-        {
-            "name": "cwsprChatProgrammingLanguage",
-            "type": "string",
-            "description": "Programming language associated with the message"
-        },
         {
             "name": "cwsprChatConversationType",
             "type": "string",
@@ -644,57 +639,6 @@
                 }
             ]
         },
-        {
-            "name": "amazonq_interactWithMessage",
-            "description": "When a user interacts with a message in the conversation",
-            "metadata": [
-                {
-                    "type": "cwsprChatConversationId"
-                },
-                {
-                    "type": "cwsprChatMessageId"
-                },
-                {
-                    "type": "cwsprChatUserIntent",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatInteractionType"
-                },
-                {
-                    "type": "cwsprChatInteractionTarget",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatCodeBlockIndex",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatTotalCodeBlocks",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatAcceptedCharactersLength",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatAcceptedNumberOfLines",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatHasReference",
-                    "required": false
-                },
-                {
-                    "type": "credentialStartUrl",
-                    "required": false
-                },
-                {
-                    "type": "cwsprChatHasProjectContext",
-                    "required": false
-                }
-            ]
-        },
         {
             "name": "amazonq_modifyCode",
             "description": "Percentage of code modified by the user after copying/inserting code from a message",
diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/utils/TextUtilsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/utils/TextUtilsTest.kt
index be5b9ece77..e93918fbc7 100644
--- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/utils/TextUtilsTest.kt
+++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/utils/TextUtilsTest.kt
@@ -11,12 +11,15 @@ import org.intellij.lang.annotations.Language
 import org.junit.Rule
 import org.junit.Test
 import software.aws.toolkits.core.utils.convertMarkdownToHTML
+import software.aws.toolkits.core.utils.extractCodeBlockLanguage
 
 class TextUtilsTest {
     @Rule
     @JvmField
     val projectRule = ProjectRule()
 
+    private val defaultTestGenResponseLanguage = "plaintext"
+
     @Test
     fun textGetsFormatted() {
         @Language("JSON")
@@ -155,4 +158,46 @@ class TextUtilsTest {
         val actual = applyPatch(inputPatch, fileContent, inputFilePath)
         assertThat(actual).isEqualTo("dummy\ndummy\ndummy\ndummy\nfirst line\nthird line\nforth line")
     }
+
+    @Test
+    fun `extractCodeBlockLanguage returns default when no code block is present`() {
+        val message = "This is a message without a code block"
+        assertThat(defaultTestGenResponseLanguage).isEqualTo(extractCodeBlockLanguage(message))
+    }
+
+    @Test
+    fun `extractCodeBlockLanguage returns language when code block with language is present`() {
+        val message = "Here's a code block:\n```kotlin\nval x = 5\n```"
+        assertThat("kotlin").isEqualTo(extractCodeBlockLanguage(message))
+    }
+
+    @Test
+    fun `extractCodeBlockLanguage returns default when code block has no language specified`() {
+        val message = "Here's a code block:\n```\nval x = 5\n```"
+        assertThat(defaultTestGenResponseLanguage).isEqualTo(extractCodeBlockLanguage(message))
+    }
+
+    @Test
+    fun `extractCodeBlockLanguage returns language when multiple code blocks are present`() {
+        val message = "First block:\n```java\nint x = 5;\n```\nSecond block:\n```python\nx = 5\n```"
+        assertThat("java").isEqualTo(extractCodeBlockLanguage(message))
+    }
+
+    @Test
+    fun `extractCodeBlockLanguage returns default when code block is not closed`() {
+        val message = "Incomplete code block:\n```kotlin\nval x = 5"
+        assertThat("kotlin").isEqualTo(extractCodeBlockLanguage(message))
+    }
+
+    @Test
+    fun `extractCodeBlockLanguage trims whitespace from language`() {
+        val message = "Code block with spaces:\n```  kotlin  \nval x = 5\n```"
+        assertThat("kotlin").isEqualTo(extractCodeBlockLanguage(message))
+    }
+
+    @Test
+    fun `extractCodeBlockLanguage handles empty message`() {
+        val message = ""
+        assertThat(defaultTestGenResponseLanguage).isEqualTo(extractCodeBlockLanguage(message))
+    }
 }