()
- 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*
- // Don't emit any other responses if we cancelled the collection
- if (error is CancellationException) {
- return@onCompletion
- } // for any other exception, let the `catch` operator handle it.
- else if (error != null) {
- throw error
- }
-
- // Send the gathered suggestions in a final answer-part message
- if (relatedSuggestions.isNotEmpty()) {
- val suggestionMessage = ChatMessage(
- tabId = tabId,
- triggerId = triggerId,
- messageId = requestId,
- messageType = ChatMessageType.AnswerPart,
- message = responseText.toString(),
- relatedSuggestions = relatedSuggestions,
- userIntent = data.userIntent,
- )
- emit(suggestionMessage)
- }
-
- // Send the Answer message to indicate the end of the response stream
- val response = ChatMessage(
- tabId = tabId,
- triggerId = triggerId,
- messageId = requestId,
- messageType = ChatMessageType.Answer,
- followUps = followUps,
- userIntent = data.userIntent,
- )
-
- telemetryHelper.setResponseStreamTotalTime(tabId)
- telemetryHelper.setResponseHasProjectContext(
- requestId,
- telemetryHelper.getIsProjectContextEnabled() && data.useRelevantDocuments && data.relevantTextDocuments.isNotEmpty()
- )
- telemetryHelper.recordAddMessage(data, response, responseText.length, statusCode, countTotalNumberOfCodeBlocks(responseText))
- emit(response)
- }
- .catch { exception ->
- val statusCode = if (exception is AwsServiceException) exception.statusCode() else 0
- telemetryHelper.recordMessageResponseError(data, tabId, statusCode)
- if (exception is CodeWhispererStreamingException) {
- throw ChatApiException(
- message = exception.message ?: "Encountered exception calling the API",
- sessionId = session.conversationId,
- requestId = exception.requestId(),
- statusCode = exception.statusCode(),
- cause = exception,
- )
- } else {
- throw ChatApiException(
- message = exception.message ?: "Encountered exception calling the API",
- sessionId = session.conversationId,
- requestId = null,
- statusCode = null,
- cause = exception,
- )
- }
- }
- .onEach { responseEvent ->
- if (isInlineChat) processChatEvent(tabId, triggerId, data, responseEvent, shouldAddIndexInProgressMessage)?.let { emit(it) }
- }
- .collect { responseEvent ->
- if (!isInlineChat) {
- processChatEvent(
- tabId,
- triggerId,
- data,
- responseEvent,
- shouldAddIndexInProgressMessage
- )?.let { emit(it) }
- }
- }
- }
-
- private fun processChatEvent(
- tabId: String,
- triggerId: String,
- data: ChatRequestData,
- event: ChatResponseEvent,
- shouldAddIndexInProgressMessage: Boolean,
- ): ChatMessage? {
- requestId = event.requestId
- statusCode = event.statusCode
-
- if (event.codeReferences != null) {
- codeReferences += event.codeReferences.map { reference ->
- CodeReference(
- licenseName = reference.licenseName,
- repository = reference.repository,
- url = reference.url,
- recommendationContentSpan = RecommendationContentSpan(
- reference.recommendationContentSpan?.start ?: 0,
- reference.recommendationContentSpan?.end ?: 0,
- ),
- information = "Reference code under **${reference.licenseName}** license from repository `${reference.repository}`",
- )
- }
- }
-
- if (event.suggestions != null) {
- var index = 0
- relatedSuggestions += event.suggestions.map { apiSuggestion ->
- Suggestion(
- title = apiSuggestion.title,
- url = apiSuggestion.url,
- body = apiSuggestion.body,
- id = index++,
- type = apiSuggestion.type,
- context = apiSuggestion.context,
- )
- }
- }
-
- if (event.followUps != null) {
- event.followUps.forEach { item ->
- item.pillText?.let {
- followUps += FollowUp(
- type = item.type,
- pillText = item.pillText,
- prompt = item.prompt ?: item.pillText,
- )
- }
- item.message?.let {
- followUps += FollowUp(
- type = item.type,
- pillText = item.message,
- prompt = item.message,
- )
- }
- }
- }
-
- return if (event.token != null) {
- responseText.append(event.token)
- telemetryHelper.setResponseStreamTimeForChunks(tabId)
- val message = if (shouldAddIndexInProgressMessage) {
- "$responseText \n\n${message("amazonqChat.project_context.index_in_progress")}"
- } else {
- responseText.toString()
- }
- if (codeBlockLanguage == defaultTestGenResponseLanguage) {
- // To get the language of generated code in Q chat.
- codeBlockLanguage = extractCodeBlockLanguage(message)
- }
- ChatMessage(
- tabId = tabId,
- triggerId = triggerId,
- messageId = event.requestId,
- messageType = ChatMessageType.AnswerPart,
- message = message,
- codeReference = codeReferences,
- userIntent = data.userIntent,
- codeBlockLanguage = codeBlockLanguage,
- )
- } else {
- null
- }
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/prompts/PromptsGenerator.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/prompts/PromptsGenerator.kt
deleted file mode 100644
index bd3e85833c6..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/prompts/PromptsGenerator.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.controller.chat.prompts
-
-class PromptsGenerator {
- public fun getPromptForCommandVerb(commandVerb: String, selectedCode: String): String {
- val trimSelectedCode = selectedCode.trimStart().trimEnd()
- return "$commandVerb the following part of my code to me: $trimSelectedCode"
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt
deleted file mode 100644
index abe0aa3a020..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/telemetry/TelemetryHelper.kt
+++ /dev/null
@@ -1,473 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry
-
-import com.intellij.openapi.project.Project
-import org.jetbrains.annotations.VisibleForTesting
-import software.amazon.awssdk.services.codewhispererruntime.model.ChatInteractWithMessageEvent
-import software.amazon.awssdk.services.codewhispererruntime.model.ChatMessageInteractionType
-import software.amazon.awssdk.services.codewhispererruntime.model.InlineChatUserDecision
-import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
-import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment
-import software.amazon.q.core.utils.debug
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.info
-import software.amazon.q.core.utils.warn
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager
-import software.amazon.q.jetbrains.core.credentials.pinning.QConnection
-import software.amazon.q.jetbrains.services.telemetry.TelemetryService
-import software.amazon.q.jetbrains.utils.notifyError
-import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatRequestData
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.TriggerType
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.v1.ChatSessionV1
-import software.aws.toolkits.jetbrains.services.cwc.controller.ChatController
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.IncomingCwcMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.LinkType
-import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionStorage
-import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
-import software.aws.toolkits.resources.message
-import software.aws.toolkits.telemetry.AuthTelemetry
-import software.aws.toolkits.telemetry.CwsprChatCommandType
-import software.aws.toolkits.telemetry.CwsprChatConversationType
-import software.aws.toolkits.telemetry.CwsprChatInteractionType
-import software.aws.toolkits.telemetry.CwsprChatTriggerInteraction
-import software.aws.toolkits.telemetry.CwsprChatUserIntent
-import software.aws.toolkits.telemetry.Telemetry
-import java.time.Duration
-import java.time.Instant
-import software.amazon.awssdk.services.codewhispererruntime.model.UserIntent as CWClientUserIntent
-
-class TelemetryHelper(private val project: Project, private val sessionStorage: ChatSessionStorage) {
- private val responseStreamStartTime: MutableMap = mutableMapOf()
- private val responseStreamTotalTime: MutableMap = mutableMapOf()
- private val responseStreamTimeForChunks: MutableMap> = mutableMapOf()
- private val responseHasProjectContext: MutableMap = mutableMapOf()
-
- private val customization: CodeWhispererCustomization?
- get() = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)
-
- fun getConversationId(tabId: String): String? = sessionStorage.getSession(tabId)?.session?.conversationId
-
- private fun getTelemetryUserIntent(userIntent: UserIntent): CwsprChatUserIntent = when (userIntent) {
- UserIntent.SUGGEST_ALTERNATE_IMPLEMENTATION -> CwsprChatUserIntent.SuggestAlternateImplementation
- UserIntent.APPLY_COMMON_BEST_PRACTICES -> CwsprChatUserIntent.ApplyCommonBestPractices
- UserIntent.IMPROVE_CODE -> CwsprChatUserIntent.ImproveCode
- UserIntent.SHOW_EXAMPLES -> CwsprChatUserIntent.ShowExample
- UserIntent.CITE_SOURCES -> CwsprChatUserIntent.CiteSources
- UserIntent.EXPLAIN_LINE_BY_LINE -> CwsprChatUserIntent.ExplainLineByLine
- UserIntent.EXPLAIN_CODE_SELECTION -> CwsprChatUserIntent.ExplainCodeSelection
- UserIntent.GENERATE_UNIT_TESTS -> CwsprChatUserIntent.GenerateUnitTests
- UserIntent.UNKNOWN_TO_SDK_VERSION -> CwsprChatUserIntent.Unknown
- UserIntent.GENERATE_CLOUDFORMATION_TEMPLATE -> CwsprChatUserIntent.Unknown
- UserIntent.CODE_GENERATION -> CwsprChatUserIntent.Unknown
- }
-
- private fun getTelemetryTriggerType(triggerType: TriggerType): CwsprChatTriggerInteraction = when (triggerType) {
- TriggerType.Click, TriggerType.CodeScanButton, TriggerType.Inline -> CwsprChatTriggerInteraction.Click
- TriggerType.ContextMenu, TriggerType.Hotkeys -> CwsprChatTriggerInteraction.ContextMenu
- }
-
- fun getIsProjectContextEnabled() = CodeWhispererSettings.getInstance().isProjectContextEnabled()
-
- // When chat panel is focused
- fun recordEnterFocusChat() {
- Telemetry.amazonq.enterFocusChat.use { it.passive(true) }
- }
-
- // When chat panel is unfocused
- fun recordExitFocusChat() {
- Telemetry.amazonq.exitFocusChat.use { it.passive(true) }
- }
-
- fun recordStartConversation(tabId: String, data: ChatRequestData) {
- val sessionHistory = sessionStorage.getSession(tabId)?.history ?: return
- if (sessionHistory.size > 1) return
-
- Telemetry.amazonq.startConversation.use { span ->
- span.cwsprChatConversationId(getConversationId(tabId).orEmpty())
- .cwsprChatTriggerInteraction(getTelemetryTriggerType(data.triggerType))
- .cwsprChatConversationType(CwsprChatConversationType.Chat)
- .cwsprChatUserIntent(data.userIntent?.let { getTelemetryUserIntent(it) })
- .cwsprChatHasCodeSnippet(data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty() == true)
- .cwsprChatProgrammingLanguage(data.activeFileContext.fileContext?.fileLanguage)
- .credentialStartUrl(getStartUrl(project))
- .cwsprChatHasProjectContext(getIsProjectContextEnabled() && data.useRelevantDocuments && data.relevantTextDocuments.isNotEmpty())
- }
- }
-
- // When Chat API responds to a user message (full response streamed)
- fun recordAddMessage(data: ChatRequestData, response: ChatMessage, responseLength: Int, statusCode: Int, numberOfCodeBlocks: Int) {
- Telemetry.amazonq.addMessage.use { span ->
- span.cwsprChatConversationId(getConversationId(response.tabId).orEmpty())
- .cwsprChatMessageId(response.messageId)
- .cwsprChatTriggerInteraction(getTelemetryTriggerType(data.triggerType))
- .cwsprChatUserIntent(data.userIntent?.let { getTelemetryUserIntent(it) })
- .cwsprChatHasCodeSnippet(data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty() ?: false)
- .cwsprChatProgrammingLanguage(data.activeFileContext.fileContext?.fileLanguage)
- .cwsprChatActiveEditorTotalCharacters(data.activeFileContext.focusAreaContext?.codeSelection?.length?.toLong())
- .cwsprChatActiveEditorImportCount(data.activeFileContext.focusAreaContext?.codeNames?.fullyQualifiedNames?.used?.size?.toLong())
- .cwsprChatResponseCodeSnippetCount(numberOfCodeBlocks.toLong())
- .cwsprChatResponseCode(statusCode.toLong())
- .cwsprChatSourceLinkCount(response.relatedSuggestions?.size?.toLong())
- .cwsprChatReferencesCount(0L) // TODO
- .cwsprChatFollowUpCount(response.followUps?.size?.toLong())
- .cwsprChatTimeToFirstChunk(getResponseStreamTimeToFirstChunk(response.tabId).toLong())
- .cwsprChatTimeBetweenChunks("[${getResponseStreamTimeBetweenChunks(response.tabId).joinToString(")")}]")
- .cwsprChatFullResponseLatency(responseStreamTotalTime[response.tabId]?.toLong() ?: 0)
- .cwsprChatRequestLength(data.message.length.toLong())
- .cwsprChatResponseLength(responseLength.toLong())
- .cwsprChatConversationType(CwsprChatConversationType.Chat)
- .credentialStartUrl(getStartUrl(project))
- .codewhispererCustomizationArn(data.customization?.arn)
- .cwsprChatHasProjectContext(getMessageHasProjectContext(response.messageId))
- }
-
- val programmingLanguage = data.activeFileContext.fileContext?.fileLanguage
- val validProgrammingLanguage = if (ChatSessionV1.validLanguages.contains(programmingLanguage)) programmingLanguage else null
-
- CodeWhispererClientAdaptor.getInstance(project).sendChatAddMessageTelemetry(
- getConversationId(response.tabId).orEmpty(),
- response.messageId,
- CWClientUserIntent.fromValue(data.userIntent?.name),
- (data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty() ?: false),
- validProgrammingLanguage,
- data.activeFileContext.focusAreaContext?.codeSelection?.length,
- getResponseStreamTimeToFirstChunk(response.tabId),
- getResponseStreamTimeBetweenChunks(response.tabId),
- (responseStreamTotalTime[response.tabId] ?: 0).toDouble(),
- data.message.length,
- responseLength,
- numberOfCodeBlocks,
- getMessageHasProjectContext(response.messageId),
- data.customization
- )
- }
-
- fun recordInlineChatTelemetry(
- requestId: String,
- inputLength: Int?,
- numSelectedLines: Int?,
- codeIntent: Boolean?,
- userDecision: InlineChatUserDecision?,
- responseStartLatency: Double?,
- responseEndLatency: Double?,
- numSuggestionAddChars: Int?,
- numSuggestionAddLines: Int?,
- numSuggestionDelChars: Int?,
- numSuggestionDelLines: Int?,
- programmingLanguage: String?,
- ) {
- CodeWhispererClientAdaptor.getInstance(project).sendInlineChatTelemetry(
- requestId, inputLength, numSelectedLines, codeIntent, userDecision,
- responseStartLatency, responseEndLatency, numSuggestionAddChars, numSuggestionAddLines, numSuggestionDelChars, numSuggestionDelLines,
- programmingLanguage
- )
- }
-
- fun recordMessageResponseError(data: ChatRequestData, tabId: String, responseCode: Int) {
- Telemetry.amazonq.messageResponseError.use { span ->
- span.cwsprChatConversationId(getConversationId(tabId).orEmpty())
- .cwsprChatTriggerInteraction(getTelemetryTriggerType(data.triggerType))
- .cwsprChatUserIntent(data.userIntent?.let { getTelemetryUserIntent(it) })
- .cwsprChatHasCodeSnippet(data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty() ?: false)
- .cwsprChatProgrammingLanguage(data.activeFileContext.fileContext?.fileLanguage)
- .cwsprChatActiveEditorTotalCharacters(data.activeFileContext.focusAreaContext?.codeSelection?.length?.toLong())
- .cwsprChatActiveEditorImportCount(data.activeFileContext.focusAreaContext?.codeNames?.fullyQualifiedNames?.used?.size?.toLong())
- .cwsprChatResponseCode(responseCode.toLong())
- .cwsprChatRequestLength(data.message.length.toLong())
- .cwsprChatConversationType(CwsprChatConversationType.Chat)
- .credentialStartUrl(getStartUrl(project))
- }
- }
-
- // When user interacts with a message (e.g. copy code, insert code, vote)
- suspend fun recordInteractWithMessage(message: IncomingCwcMessage) = Telemetry.amazonq.interactWithMessage.use { span ->
- if (message is IncomingCwcMessage.TabId) {
- span.cwsprChatConversationId(message.tabId?.let { getConversationId(it) }.orEmpty())
- }
-
- if (message is IncomingCwcMessage.MessageId) {
- span.cwsprChatMessageId(message.messageId.orEmpty())
- .cwsprChatHasProjectContext(message.messageId?.let { getMessageHasProjectContext(it) })
- }
-
- span.credentialStartUrl(getStartUrl(project))
-
- val event: ChatInteractWithMessageEvent? = when (message) {
- is IncomingCwcMessage.ChatItemVoted -> {
- span.cwsprChatInteractionType(
- when (message.vote) {
- "upvote" -> CwsprChatInteractionType.Upvote
- "downvote" -> CwsprChatInteractionType.Downvote
- else -> CwsprChatInteractionType.Unknown
- }
- )
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(getConversationId(message.tabId).orEmpty())
- messageId(message.messageId)
- interactionType(
- when (message.vote) {
- "upvote" -> ChatMessageInteractionType.UPVOTE
- "downvote" -> ChatMessageInteractionType.DOWNVOTE
- else -> ChatMessageInteractionType.UNKNOWN_TO_SDK_VERSION
- }
- )
- hasProjectLevelContext(getMessageHasProjectContext(message.messageId))
- }.build()
- }
-
- is IncomingCwcMessage.FollowupClicked -> {
- span.cwsprChatInteractionType(CwsprChatInteractionType.ClickFollowUp)
-
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(getConversationId(message.tabId).orEmpty())
- messageId(message.messageId.orEmpty())
- interactionType(ChatMessageInteractionType.CLICK_FOLLOW_UP)
- hasProjectLevelContext(getMessageHasProjectContext(message.messageId.orEmpty()))
- }.build()
- }
-
- is IncomingCwcMessage.CopyCodeToClipboard -> {
- span.cwsprChatUserIntent(message.userIntent?.let { getTelemetryUserIntent(it) })
- .cwsprChatInteractionType(CwsprChatInteractionType.CopySnippet)
- .cwsprChatAcceptedCharactersLength(message.code.length)
- .cwsprChatInteractionTarget(message.insertionTargetType)
- .cwsprChatCodeBlockIndex(message.codeBlockIndex)
- .cwsprChatTotalCodeBlocks(message.totalCodeBlocks)
- .cwsprChatProgrammingLanguage(message.codeBlockLanguage)
-
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(getConversationId(message.tabId).orEmpty())
- messageId(message.messageId)
- interactionType(ChatMessageInteractionType.COPY_SNIPPET)
- interactionTarget(message.insertionTargetType)
- acceptedCharacterCount(message.code.length)
- hasProjectLevelContext(getMessageHasProjectContext(message.messageId))
- }.build()
- }
-
- is IncomingCwcMessage.InsertCodeAtCursorPosition -> {
- span.cwsprChatUserIntent(message.userIntent?.let { getTelemetryUserIntent(it) })
- .cwsprChatInteractionType(CwsprChatInteractionType.InsertAtCursor)
- .cwsprChatAcceptedCharactersLength(message.code.length)
- .cwsprChatAcceptedNumberOfLines(message.code.lines().size)
- .cwsprChatInteractionTarget(message.insertionTargetType)
- .cwsprChatCodeBlockIndex(message.codeBlockIndex)
- .cwsprChatTotalCodeBlocks(message.totalCodeBlocks)
- .cwsprChatProgrammingLanguage(message.codeBlockLanguage)
-
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(getConversationId(message.tabId).orEmpty())
- messageId(message.messageId)
- interactionType(ChatMessageInteractionType.INSERT_AT_CURSOR)
- interactionTarget(message.insertionTargetType)
- acceptedCharacterCount(message.code.length)
- acceptedLineCount(message.code.lines().size)
- hasProjectLevelContext(getMessageHasProjectContext(message.messageId))
- }.build()
- }
-
- is IncomingCwcMessage.ClickedLink -> {
- // Null when internal Amazon link is clicked
- if (message.messageId == null) return null
-
- val linkInteractionType = when (message.type) {
- LinkType.SourceLink -> CwsprChatInteractionType.ClickLink
- LinkType.BodyLink -> CwsprChatInteractionType.ClickBodyLink
- else -> CwsprChatInteractionType.Unknown
- }
- span.cwsprChatInteractionType(linkInteractionType)
- span.cwsprChatInteractionTarget(message.link)
-
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(getConversationId(message.tabId).orEmpty())
- messageId(message.messageId)
- interactionType(
- when (message.type) {
- LinkType.SourceLink -> ChatMessageInteractionType.CLICK_LINK
- LinkType.BodyLink -> ChatMessageInteractionType.CLICK_BODY_LINK
- else -> ChatMessageInteractionType.UNKNOWN_TO_SDK_VERSION
- }
- )
- interactionTarget(message.link)
- hasProjectLevelContext(getMessageHasProjectContext(message.messageId))
- }.build()
- }
-
- is IncomingCwcMessage.ChatItemFeedback -> {
- span.cwsprChatInteractionType(CwsprChatInteractionType.Unknown)
- recordFeedback(message)
- null
- }
-
- else -> {
- span.cwsprChatInteractionType(CwsprChatInteractionType.Unknown)
-
- null
- }
- }?.let {
- // override request and add customizationArn if it's not null, else return itself
- customization?.let { myCustomization ->
- it.toBuilder()
- .customizationArn(myCustomization.arn)
- .build()
- } ?: it
- }
-
- event?.let {
- val steResponse = CodeWhispererClientAdaptor.getInstance(project).sendChatInteractWithMessageTelemetry(it)
- logger.debug {
- "Successfully sendTelemetryEvent for ChatInteractWithMessage with requestId=${steResponse.responseMetadata().requestId()}"
- }
- }
- }
-
- private suspend fun recordFeedback(message: IncomingCwcMessage.ChatItemFeedback) {
- val comment = FeedbackComment(
- conversationId = getConversationId(message.tabId).orEmpty(),
- messageId = message.messageId,
- reason = message.selectedOption,
- userComment = message.comment.orEmpty(),
- )
-
- try {
- TelemetryService.getInstance().sendFeedback(
- sentiment = Sentiment.NEGATIVE,
- comment = ChatController.objectMapper.writeValueAsString(comment),
- )
- logger.info { "CodeWhispererChat answer feedback sent: \"Negative\"" }
- recordFeedbackResult(true)
- } catch (e: Throwable) {
- e.notifyError(message("feedback.submit_failed", e))
- logger.warn(e) { "Failed to submit feedback" }
- recordFeedbackResult(false)
- return
- }
- }
-
- private fun recordFeedbackResult(success: Boolean) {
- Telemetry.feedback.result.use { it.success(success) }
- }
-
- // When a conversation(tab) is focused
- fun recordEnterFocusConversation(tabId: String) {
- getConversationId(tabId)?.let {
- Telemetry.amazonq.enterFocusConversation.use { span ->
- span.cwsprChatConversationId(it)
- }
- }
- }
-
- // When a conversation(tab) is unfocused
- fun recordExitFocusConversation(tabId: String) {
- getConversationId(tabId)?.let {
- Telemetry.amazonq.exitFocusConversation.use { span ->
- span.cwsprChatConversationId(it)
- }
- }
- }
-
- fun setResponseStreamStartTime(tabId: String) {
- responseStreamStartTime[tabId] = Instant.now()
- responseStreamTimeForChunks[tabId] = mutableListOf(Instant.now())
- }
-
- fun setResponseStreamTimeForChunks(tabId: String) {
- val chunkTimes = responseStreamTimeForChunks.getOrPut(tabId) { mutableListOf() }
- chunkTimes += Instant.now()
- }
-
- fun setResponseStreamTotalTime(tabId: String) {
- val totalTime = Duration.between(responseStreamStartTime[tabId], Instant.now()).toMillis().toInt()
- responseStreamTotalTime[tabId] = totalTime
- }
-
- fun setResponseHasProjectContext(messageId: String, hasProjectContext: Boolean) {
- responseHasProjectContext[messageId] = hasProjectContext
- }
-
- private fun getMessageHasProjectContext(messageId: String) = responseHasProjectContext.getOrDefault(messageId, false)
-
- @VisibleForTesting
- fun getResponseStreamTimeToFirstChunk(tabId: String): Double {
- val chunkTimes = responseStreamTimeForChunks[tabId] ?: return 0.0
- if (chunkTimes.size == 1) return Duration.between(chunkTimes[0], Instant.now()).toMillis().toDouble()
- return Duration.between(chunkTimes[0], chunkTimes[1]).toMillis().toDouble()
- }
-
- @VisibleForTesting
- fun getResponseStreamTimeBetweenChunks(tabId: String): List = try {
- val chunkDeltaTimes = mutableListOf()
- val chunkTimes = responseStreamTimeForChunks[tabId] ?: listOf(Instant.now())
- for (idx in 0 until (chunkTimes.size - 1)) {
- chunkDeltaTimes += Duration.between(chunkTimes[idx], chunkTimes[idx + 1]).toMillis().toDouble()
- }
- chunkDeltaTimes.take(100)
- } catch (e: Exception) {
- listOf(-1.0)
- }
-
- companion object {
- private val logger = getLogger()
-
- private fun getQConnection(project: Project): ToolkitConnection? = ToolkitConnectionManager.getInstance(
- project
- ).activeConnectionForFeature(QConnection.getInstance())
-
- fun recordOpenChat(project: Project) {
- Telemetry.amazonq.openChat.use { it.passive(true) }
- if (getQConnection(project) == null) {
- AuthTelemetry.signInPageOpened()
- }
- }
-
- fun recordCloseChat(project: Project) {
- Telemetry.amazonq.closeChat.use { it.passive(true) }
- if (getQConnection(project) == null) {
- AuthTelemetry.signInPageClosed()
- }
- }
-
- fun recordTelemetryChatRunCommand(type: CwsprChatCommandType, name: String? = null, startUrl: String? = null) {
- Telemetry.amazonq.runCommand.use {
- it.cwsprChatCommandType(type)
- .cwsprChatCommandName(name)
- .credentialStartUrl(startUrl)
- }
- }
-
- fun recordTelemetryIssueCommandAction(
- findingId: String,
- detectorId: String,
- ruleId: String,
- autoDetected: String,
- startUrl: String,
- metricName: String,
- result: String,
- ) {
- Telemetry.amazonq.codeReviewTool.use {
- it.reason(metricName)
- .setAttribute("findingId", findingId)
- .setAttribute("detectorId", detectorId)
- .setAttribute("ruleId", ruleId)
- .setAttribute("credentialStartUrl", startUrl)
- .setAttribute("autoDetected", autoDetected)
- .setAttribute("result", result)
- }
- }
- }
-}
-
-data class FeedbackComment(
- val conversationId: String,
- val messageId: String?,
- val reason: String,
- val userComment: String,
- val type: String = "codewhisperer-chat-answer-feedback",
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/userIntent/UserIntentRecognizer.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/userIntent/UserIntentRecognizer.kt
deleted file mode 100644
index 640c594795b..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/userIntent/UserIntentRecognizer.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.controller.chat.userIntent
-
-import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
-import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteraction
-import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteractionType
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FollowUpType
-import software.aws.toolkits.jetbrains.services.cwc.commands.EditorContextCommand
-
-class UserIntentRecognizer {
- fun getUserIntentFromContextMenuCommand(command: EditorContextCommand) = when (command) {
- EditorContextCommand.Explain -> UserIntent.EXPLAIN_CODE_SELECTION
- EditorContextCommand.Refactor -> UserIntent.SUGGEST_ALTERNATE_IMPLEMENTATION
- EditorContextCommand.Fix -> UserIntent.APPLY_COMMON_BEST_PRACTICES
- EditorContextCommand.Optimize -> UserIntent.IMPROVE_CODE
- EditorContextCommand.HandleCodeScanIssue -> UserIntent.EXPLAIN_CODE_SELECTION
- EditorContextCommand.GenerateUnitTests -> UserIntent.GENERATE_UNIT_TESTS
- EditorContextCommand.SendToPrompt -> null
- }
-
- fun getUserIntentFromPromptChatMessage(prompt: String) = when {
- prompt.startsWith("Explain") -> UserIntent.EXPLAIN_CODE_SELECTION
- prompt.startsWith("Refactor") -> UserIntent.SUGGEST_ALTERNATE_IMPLEMENTATION
- prompt.startsWith("Fix") -> UserIntent.APPLY_COMMON_BEST_PRACTICES
- prompt.startsWith("Optimize") -> UserIntent.IMPROVE_CODE
- prompt.startsWith("Generate unit tests") -> UserIntent.GENERATE_UNIT_TESTS
- else -> null
- }
-
- fun getUserIntentFromFollowupType(type: FollowUpType) = when (type) {
- FollowUpType.Alternatives -> UserIntent.SUGGEST_ALTERNATE_IMPLEMENTATION
- FollowUpType.CommonPractices -> UserIntent.APPLY_COMMON_BEST_PRACTICES
- FollowUpType.Improvements -> UserIntent.IMPROVE_CODE
- FollowUpType.MoreExamples -> UserIntent.SHOW_EXAMPLES
- FollowUpType.CiteSources -> UserIntent.CITE_SOURCES
- FollowUpType.LineByLine -> UserIntent.EXPLAIN_LINE_BY_LINE
- FollowUpType.ExplainInDetail -> UserIntent.EXPLAIN_CODE_SELECTION
- FollowUpType.Generated -> null
- FollowUpType.StopCodeTransform -> null
- FollowUpType.NewCodeTransform -> null
- FollowUpType.CreateDocumentation -> null
- FollowUpType.NewCodeScan -> null
- FollowUpType.ViewDiff -> UserIntent.GENERATE_UNIT_TESTS
- }
-
- fun getUserIntentFromOnboardingPageInteraction(interaction: OnboardingPageInteraction) = when (interaction.type) {
- OnboardingPageInteractionType.CwcButtonClick -> null
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContext.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContext.kt
deleted file mode 100644
index f2e79037455..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContext.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context
-
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.FileContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.FocusAreaContext
-
-data class ActiveFileContext(
- val fileContext: FileContext?,
- val focusAreaContext: FocusAreaContext?,
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContextExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContextExtractor.kt
deleted file mode 100644
index 7d1231b9551..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ActiveFileContextExtractor.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context
-
-import com.intellij.openapi.project.Project
-import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.FileContextExtractor
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.FocusAreaContextExtractor
-
-class ActiveFileContextExtractor(
- private val fileContextExtractor: FileContextExtractor,
- private val focusAreaContextExtractor: FocusAreaContextExtractor,
-) {
-
- suspend fun extractContextForTrigger(triggerType: ExtractionTriggerType) =
- ActiveFileContext(
- extractActiveFileContext(triggerType),
- extractFocusAreaContext(triggerType),
- )
-
- private suspend fun extractFocusAreaContext(triggerType: ExtractionTriggerType) = when (triggerType) {
- ExtractionTriggerType.ChatMessage -> focusAreaContextExtractor.extract()
- ExtractionTriggerType.ContextMenu -> focusAreaContextExtractor.extract()
- ExtractionTriggerType.CodeScanButton -> focusAreaContextExtractor.extract()
- ExtractionTriggerType.OnboardingPageInteraction -> null
- }
-
- private suspend fun extractActiveFileContext(triggerType: ExtractionTriggerType) = when (triggerType) {
- ExtractionTriggerType.ChatMessage -> fileContextExtractor.extract()
- ExtractionTriggerType.ContextMenu -> fileContextExtractor.extract()
- ExtractionTriggerType.CodeScanButton -> fileContextExtractor.extract()
- ExtractionTriggerType.OnboardingPageInteraction -> null
- }
-
- companion object {
- fun create(fqnWebviewAdapter: FqnWebviewAdapter?, project: Project) = ActiveFileContextExtractor(
- fileContextExtractor = FileContextExtractor(fqnWebviewAdapter, project),
- focusAreaContextExtractor = FocusAreaContextExtractor(fqnWebviewAdapter, project),
- )
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ExtractionTriggerType.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ExtractionTriggerType.kt
deleted file mode 100644
index e57b8f717cc..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/ExtractionTriggerType.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context
-
-enum class ExtractionTriggerType {
- ChatMessage,
- ContextMenu,
- OnboardingPageInteraction,
- CodeScanButton,
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContext.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContext.kt
deleted file mode 100644
index 0c9f0f22300..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContext.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.file
-
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.MatchPolicy
-
-data class FileContext(
- val fileLanguage: String?,
- val filePath: String?,
- val matchPolicy: MatchPolicy?,
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt
deleted file mode 100644
index c8fec78f9dc..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/FileContextExtractor.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.file
-
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.editor.Document
-import com.intellij.openapi.fileEditor.FileEditorManager
-import com.intellij.openapi.project.Project
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiFile
-import software.amazon.q.jetbrains.utils.computeOnEdt
-import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend
-import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util.LanguageExtractor
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util.MatchPolicyExtractor
-
-class FileContextExtractor(private val fqnWebviewAdapter: FqnWebviewAdapter?, private val project: Project) {
- private val languageExtractor: LanguageExtractor = LanguageExtractor()
- suspend fun extract(): FileContext? {
- val editorManager = FileEditorManager.getInstance(project)
-
- val editor = computeOnEdt {
- when {
- isRunningOnRemoteBackend() -> editorManager.selectedTextEditorWithRemotes.firstOrNull()
- else -> editorManager.selectedTextEditor
- }
- } ?: return null
-
- val fileLanguage = computeOnEdt {
- languageExtractor.extractLanguageNameFromCurrentFile(editor)
- }
- val fileText = computeOnEdt {
- editor.document.text
- }
-
- val filePath = runReadAction {
- val doc: Document = editor.document
- val psiFile: PsiFile? = PsiDocumentManager.getInstance(project).getPsiFile(doc)
- psiFile?.virtualFile?.path
- }
-
- val matchPolicy = MatchPolicyExtractor.extractMatchPolicyFromCurrentFile(
- isCodeSelected = false,
- fileLanguage = fileLanguage,
- fileText = fileText,
- fqnWebviewAdapter,
- )
-
- return FileContext(
- fileLanguage = fileLanguage,
- filePath = filePath,
- matchPolicy = matchPolicy,
- )
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/LanguageExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/LanguageExtractor.kt
deleted file mode 100644
index 27416587264..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/LanguageExtractor.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util
-
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.editor.Editor
-import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage
-
-class LanguageExtractor {
- fun extractLanguageNameFromCurrentFile(editor: Editor): String =
- runReadAction {
- editor.virtualFile?.programmingLanguage()?.languageId ?: "plaintext"
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/MatchPolicyExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/MatchPolicyExtractor.kt
deleted file mode 100644
index 0d342be4bff..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/MatchPolicyExtractor.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util
-
-import com.fasterxml.jackson.module.kotlin.readValue
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.warn
-import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.MatchPolicy
-import software.aws.toolkits.jetbrains.services.cwc.controller.ChatController
-
-object MatchPolicyExtractor {
- suspend fun extractMatchPolicyFromCurrentFile(
- isCodeSelected: Boolean = false,
- fileLanguage: String?,
- fileText: String?,
- fqnWebviewAdapter: FqnWebviewAdapter?,
- ): MatchPolicy? {
- val should = extractAdditionalLanguageMatchPolicies(fileLanguage)
-
- val must = mutableSetOf()
-
- if (fileLanguage == null || fileText == null) return MatchPolicy()
-
- if (isCodeSelected) must.add(fileLanguage) else should.add(fileLanguage)
-
- val readImportsRequest = ReadImportsRequest(fileText, fileLanguage)
- val requestString = ChatController.objectMapper.writeValueAsString(readImportsRequest)
-
- return try {
- val importsString = fqnWebviewAdapter?.readImports(requestString) ?: "[]"
- val imports = ChatController.objectMapper.readValue>(importsString)
-
- imports
- .filterIndexed { index, elem -> index == imports.indexOf(elem) && elem != fileLanguage }
- .forEach { importKey -> should.add(importKey) }
- MatchPolicy(must, should)
- } catch (e: Exception) {
- getLogger().warn(e) { "Failed to extract imports from file" }
- null
- }
- }
-
- private fun extractAdditionalLanguageMatchPolicies(languageId: String?): MutableSet {
- if (languageId == null) {
- return mutableSetOf()
- }
-
- if (
- languages.contains(languageId)
- ) {
- return mutableSetOf()
- }
-
- return when (languageId) {
- "bat" -> mutableSetOf("windows")
- "cpp", "csharp", "fsharp", "git-commit", "git-rebase", "objective-c", "objective-cpp",
- "plaintext", "jade", "shellscript", "vb",
- -> mutableSetOf()
-
- "cuda-cpp" -> mutableSetOf("cuda")
- "dockerfile" -> mutableSetOf("docker")
- "javascriptreact", "typescriptreact" -> mutableSetOf("react")
- "jsonc" -> mutableSetOf("comments")
- "razor" -> mutableSetOf("html")
- "scss" -> mutableSetOf("scss", "css")
- "vue-html" -> mutableSetOf("html")
- else -> {
- if (listOf("javascript", "node").any { identifier -> languageId.contains(identifier) } ||
- languageId.contains("typescript") ||
- languageId.contains("python")
- ) {
- mutableSetOf()
- } else {
- mutableSetOf()
- }
- }
- }
- }
-
- val languages = listOf(
- "yaml",
- "xsl",
- "xml",
- "vue",
- "tex",
- "typescript",
- "swift",
- "stylus",
- "sql",
- "slim",
- "shaderlab",
- "sass",
- "rust",
- "ruby",
- "r",
- "python",
- "pug",
- "powershell",
- "php",
- "perl",
- "markdown",
- "makefile",
- "lua",
- "less",
- "latex",
- "json",
- "javascript",
- "java",
- "ini",
- "html",
- "haml",
- "handlebars",
- "groovy",
- "go",
- "diff",
- "css",
- "c",
- "coffeescript",
- "clojure",
- "bibtex",
- "abap",
- )
-}
-
-data class ReadImportsRequest(
- val fileContent: String,
- val language: String,
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContext.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContext.kt
deleted file mode 100644
index f2b64a04c68..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContext.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea
-
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNames
-
-data class FocusAreaContext(
- val codeSelection: String?,
- val codeSelectionRange: UICodeSelectionRange?,
- val trimmedSurroundingFileText: String?,
- val codeNames: CodeNames?,
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt
deleted file mode 100644
index 3b61d76cd65..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea
-
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.LogicalPosition
-import com.intellij.openapi.editor.SelectionModel
-import com.intellij.openapi.fileEditor.FileEditorManager
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.TextRange
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.warn
-import software.amazon.q.jetbrains.utils.computeOnEdt
-import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend
-import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNames
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNamesImpl
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FullyQualifiedNames
-import software.aws.toolkits.jetbrains.services.cwc.controller.ChatController
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.util.LanguageExtractor
-import java.awt.Point
-import kotlin.math.min
-
-class FocusAreaContextExtractor(private val fqnWebviewAdapter: FqnWebviewAdapter?, private val project: Project) {
-
- private val languageExtractor: LanguageExtractor = LanguageExtractor()
- suspend fun extract(): FocusAreaContext? {
- val editorManager = FileEditorManager.getInstance(project)
- val editor = computeOnEdt {
- when {
- isRunningOnRemoteBackend() -> editorManager.selectedTextEditorWithRemotes.firstOrNull()
- else -> editorManager.selectedTextEditor
- }
- } ?: return null
-
- if (editor.document.text.isBlank()) return null
-
- // Get 10k characters around the cursor
- val (trimmedFileText, trimmedFileTextSelection) = computeOnEdt {
- val (start, end) = getOffsetRangeAtCursor(MAX_LENGTH, editor)
- val fileTextStartPos = editor.offsetToLogicalPosition(start)
- val fileTextEndPos = editor.offsetToLogicalPosition(end)
- val trimmedFileText = getTextAtOffsets(fileTextStartPos, fileTextEndPos, editor)
- val trimmedFileTextSelection = UICodeSelectionRange(
- start = UICodeSelectionLineRange(
- row = fileTextStartPos.line,
- column = fileTextStartPos.column,
- ),
- end = UICodeSelectionLineRange(
- row = fileTextEndPos.line,
- column = fileTextEndPos.column,
- ),
- )
- Pair(trimmedFileText, trimmedFileTextSelection)
- }
-
- // Get user selected code or visible text area
- val (codeSelection, codeSelectionRange) = computeOnEdt {
- val selectionModel: SelectionModel = editor.selectionModel
- val selectedText = selectionModel.selectedText
- if (selectedText == null) {
- // Get visible area text
- val visibleArea = editor.scrollingModel.visibleArea
- val startOffset = editor.xyToLogicalPosition(Point(visibleArea.x, visibleArea.y))
- val endOffset = editor.xyToLogicalPosition(Point(visibleArea.x + visibleArea.width, visibleArea.y + visibleArea.height))
-
- // Get text of visible area
- val visibleAreaText = getTextAtOffsets(startOffset, endOffset, editor)
-
- // If visible area text too big use trimmedSurroundingText
- if (visibleAreaText.length > MAX_LENGTH) {
- Pair(trimmedFileText, trimmedFileTextSelection)
- } else {
- // Ensure end line isn't beyond the end of the document
- val endLine = min(endOffset.line, editor.document.lineCount - 1)
- val endColumn = min(endOffset.column, visibleAreaText.lengthOfLastLine() - 1)
-
- val codeSelectionRange = UICodeSelectionRange(
- start = UICodeSelectionLineRange(
- row = startOffset.line,
- column = startOffset.column,
- ),
- end = UICodeSelectionLineRange(
- row = endLine,
- column = endColumn,
- ),
- )
- Pair(visibleAreaText, codeSelectionRange)
- }
- } else if (selectedText.length > MAX_LENGTH) {
- Pair(trimmedFileText, trimmedFileTextSelection)
- } else {
- // Use selected text ranges
- val selectedStartPos = editor.offsetToLogicalPosition(selectionModel.selectionStart)
- val selectedEndPos = editor.offsetToLogicalPosition(selectionModel.selectionEnd)
-
- val codeSelectionRange = UICodeSelectionRange(
- start = UICodeSelectionLineRange(
- row = selectedStartPos.line,
- column = selectedStartPos.column,
- ),
- end = UICodeSelectionLineRange(
- row = selectedEndPos.line,
- column = selectedEndPos.column,
- ),
- )
- Pair(selectedText, codeSelectionRange)
- }
- }
-
- // Retrieve from trimmedFileText
- val fileLanguage = computeOnEdt {
- languageExtractor.extractLanguageNameFromCurrentFile(editor)
- }
- val fileText = editor.document.text
- val fileName = editor.virtualFile?.name ?: "unknown"
-
- // Offset the selection range to the start of the trimmedFileText
- val selectionInsideTrimmedFileTextRange = codeSelectionRange.let {
- UICodeSelectionRange(
- start = UICodeSelectionLineRange(
- row = it.start.row - trimmedFileTextSelection.start.row,
- column = it.start.column
- ),
- end = UICodeSelectionLineRange(
- row = it.end.row - trimmedFileTextSelection.start.row,
- column = it.end.column
- ),
- )
- }
-
- var codeNames: CodeNames? = null
- if (fileLanguage != null) {
- val extractNamesRequest = ExtractNamesRequest(
- language = fileLanguage,
- fileContent = fileText,
- codeSelection = UICodeSelection(
- selectedCode = trimmedFileText,
- file = UICodeSelectionFile(
- name = fileName,
- range = selectionInsideTrimmedFileTextRange,
- ),
- ),
- )
- val requestString = ChatController.objectMapper.writeValueAsString(extractNamesRequest)
-
- codeNames = try {
- fqnWebviewAdapter?.let {
- ChatController.objectMapper.readValue(it.extractNames(requestString), CodeNamesImpl::class.java)
- } ?: CodeNamesImpl(simpleNames = emptyList(), fullyQualifiedNames = FullyQualifiedNames(used = emptyList()))
- } catch (e: Exception) {
- getLogger().warn(e) { "Failed to extract names from file" }
- null
- }
- }
-
- return FocusAreaContext(
- codeSelection = codeSelection,
- codeSelectionRange = selectionInsideTrimmedFileTextRange,
- trimmedSurroundingFileText = trimmedFileText,
- codeNames = codeNames,
- )
- }
-
- private fun getTextAtOffsets(startOffset: LogicalPosition, endOffset: LogicalPosition, editor: Editor): String {
- val startInt = editor.logicalPositionToOffset(startOffset)
- val endInt = editor.logicalPositionToOffset(endOffset)
-
- return editor.document.getText(TextRange(startInt, endInt))
- }
-
- // Get 10k characters range around the cursor
- private fun getOffsetRangeAtCursor(maxCharacters: Int, editor: Editor): Pair {
- // Get cursor position
- val caretModel = editor.caretModel
- val offset = caretModel.offset
-
- // Get entire file text
- val document = editor.document
- val fileText = document.text
-
- // Calculate the start and end offsets
- val halfMaxCharacters = maxCharacters / 2
- val startOffset = 0.coerceAtLeast(offset - halfMaxCharacters)
- val endOffset = fileText.length.coerceAtMost(offset + halfMaxCharacters)
-
- // Adjust the start and end offsets if necessary to ensure a total of 40k characters
- val excessCharacters = maxCharacters - (endOffset - startOffset)
- val adjustedStartOffset = 0.coerceAtLeast(startOffset - excessCharacters)
- val adjustedEndOffset = fileText.length.coerceAtMost(endOffset + excessCharacters)
-
- return Pair(adjustedStartOffset, adjustedEndOffset)
- }
-
- private fun String.lengthOfLastLine(): Int {
- for (i in length - 1 downTo 0) {
- if (this[i] == '\n') {
- return length - i
- }
- }
- return length
- }
-
- companion object {
- const val MAX_LENGTH = 40000
- }
-}
-
-data class ExtractNamesRequest(
- val fileContent: String,
- val language: String,
- val codeSelection: UICodeSelection,
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/UICodeSelection.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/UICodeSelection.kt
deleted file mode 100644
index b80ed00cd4e..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/UICodeSelection.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea
-
-data class UICodeSelection(
- val selectedCode: String = "",
- val file: UICodeSelectionFile,
-)
-
-data class UICodeSelectionRange(val start: UICodeSelectionLineRange, val end: UICodeSelectionLineRange)
-data class UICodeSelectionLineRange(val row: Int, val column: Int)
-
-data class UICodeSelectionFile(val name: String, val range: UICodeSelectionRange)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/exceptions/ChatException.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/exceptions/ChatException.kt
deleted file mode 100644
index 6df9460784b..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/exceptions/ChatException.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.exceptions
-
-/**
- * Base class for exceptions thrown by this app
- */
-open class ChatException(
- message: String,
- cause: Throwable? = null,
-) : Exception(message, cause)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatActionPromoter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatActionPromoter.kt
deleted file mode 100644
index aa5212267f6..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatActionPromoter.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline
-
-import com.intellij.openapi.actionSystem.ActionPromoter
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.keymap.KeymapUtil
-import com.intellij.openapi.util.SystemInfo
-
-class InlineChatActionPromoter : ActionPromoter {
- // temporary until we find a better key binding
- override fun promote(actions: MutableList, context: DataContext): MutableList {
- val results = actions.toMutableList()
- if (context.getData(CommonDataKeys.EDITOR) == null ||
- context.getData(CommonDataKeys.PROJECT) == null
- ) {
- return results
- }
- val shortCut = KeymapUtil.getShortcutText("aws.toolkit.jetbrains.core.services.cwc.inline.openChat")
- // only promote for the default key bindings
- if (SystemInfo.isMac && shortCut != "⌘I") return results
- if (!SystemInfo.isMac && shortCut != "Ctrl+I") return results
-
- results.sortWith { a, b ->
- when {
- isOpenChatInputAction(a) -> -1
- isOpenChatInputAction(b) -> 1
- else -> 0
- }
- }
- return results
- }
-
- private fun isOpenChatInputAction(action: AnAction): Boolean =
- action is OpenChatInputAction
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatController.kt
deleted file mode 100644
index 3e2b72f9357..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatController.kt
+++ /dev/null
@@ -1,734 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline
-
-import com.github.difflib.text.DiffRow
-import com.github.difflib.text.DiffRowGenerator
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.application.ReadAction
-import com.intellij.openapi.application.invokeLater
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.command.CommandProcessor
-import com.intellij.openapi.command.WriteCommandAction
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.components.service
-import com.intellij.openapi.editor.Document
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.RangeMarker
-import com.intellij.openapi.editor.event.CaretEvent
-import com.intellij.openapi.editor.event.CaretListener
-import com.intellij.openapi.editor.markup.HighlighterLayer
-import com.intellij.openapi.editor.markup.HighlighterTargetArea
-import com.intellij.openapi.editor.markup.RangeHighlighter
-import com.intellij.openapi.editor.markup.TextAttributes
-import com.intellij.openapi.fileEditor.FileEditorManagerListener
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.popup.JBPopup
-import com.intellij.openapi.ui.popup.JBPopupListener
-import com.intellij.openapi.ui.popup.LightweightWindowEvent
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.wm.ToolWindowManager
-import com.intellij.ui.JBColor
-import com.intellij.ui.jcef.JBCefApp
-import com.jetbrains.rd.util.AtomicInteger
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.toList
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
-import org.apache.commons.text.StringEscapeUtils
-import software.amazon.awssdk.services.codewhispererruntime.model.InlineChatUserDecision
-import software.amazon.q.core.utils.debug
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.info
-import software.amazon.q.core.utils.warn
-import software.amazon.q.jetbrains.core.coroutines.EDT
-import software.amazon.q.jetbrains.core.gettingstarted.requestCredentialsForQ
-import software.amazon.q.jetbrains.core.webview.BrowserState
-import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel
-import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener
-import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AMAZON_Q_WINDOW_ID
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatRequestData
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.TriggerType
-import software.aws.toolkits.jetbrains.services.cwc.controller.ReferenceLogController
-import software.aws.toolkits.jetbrains.services.cwc.controller.chat.messenger.ChatPromptHandler
-import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper
-import software.aws.toolkits.jetbrains.services.cwc.controller.chat.userIntent.UserIntentRecognizer
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContextExtractor
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.ExtractionTriggerType
-import software.aws.toolkits.jetbrains.services.cwc.inline.listeners.InlineChatFileListener
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType
-import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionStorage
-import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
-import software.aws.toolkits.telemetry.FeatureId
-import java.util.UUID
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.math.max
-
-@Service(Service.Level.PROJECT)
-class InlineChatController(
- private val project: Project,
- private val scope: CoroutineScope,
-) : Disposable {
- private var currentPopup: JBPopup? = null
- private var rangeHighlighter: RangeHighlighter? = null
- private var rejectAction: (() -> Unit)? = null
- private var acceptAction: (() -> Unit)? = null
- private var insertionLine = AtomicInteger(-1)
- private val sessionStorage = ChatSessionStorage()
- private val telemetryHelper = TelemetryHelper(project, sessionStorage)
- private val shouldShowActions = AtomicBoolean(false)
- private val isInProgress = AtomicBoolean(false)
- private var metrics: InlineChatMetrics? = null
- private var canPopupAbort = AtomicBoolean(true)
- private var currentSelectionRange: RangeMarker? = null
- private val listener = InlineChatFileListener(project, this)
- private var isAbandoned = AtomicBoolean(false)
-
- init {
- Disposer.register(this, listener)
- project.messageBus.connect(this).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener)
- project.messageBus.connect(this).subscribe(
- QRegionProfileSelectedListener.TOPIC,
- object : QRegionProfileSelectedListener {
- override fun onProfileSelected(project: Project, profile: QRegionProfile?) {
- sessionStorage.deleteAllSessions()
- }
- }
- )
- }
-
- data class InlineChatMetrics(
- val requestId: String,
- val inputLength: Int? = null,
- val numSelectedLines: Int? = null,
- val codeIntent: Boolean? = null,
- var userDecision: InlineChatUserDecision? = null,
- val responseStartLatency: Double? = null,
- val responseEndLatency: Double? = null,
- var numSuggestionAddChars: Int? = null,
- var numSuggestionAddLines: Int? = null,
- var numSuggestionDelChars: Int? = null,
- var numSuggestionDelLines: Int? = null,
- var programmingLanguage: String? = null,
- )
-
- private val popupSubmitHandler: suspend (String, String, Int, Editor) -> String = {
- prompt: String, selectedCode: String, selectedLineStart: Int, editor: Editor ->
- runBlocking {
- isInProgress.set(true)
- val message = handleChat(prompt, selectedCode, editor, selectedLineStart)
- message
- }
- }
-
- val popupCancelHandler: (editor: Editor) -> Unit = { editor ->
- isAbandoned.set(true)
- if (canPopupAbort.get() && currentPopup != null) {
- undoChanges()
- restoreSelection(editor)
- ApplicationManager.getApplication().executeOnPooledThread {
- recordInlineChatTelemetry(InlineChatUserDecision.DISMISS)
- }
- currentPopup?.dispose()
- }
- }
-
- private fun recordInlineChatTelemetry(decision: InlineChatUserDecision) {
- val metrics = metrics ?: return
- metrics.userDecision = decision
-
- if (metrics.requestId.isNotEmpty()) {
- telemetryHelper.recordInlineChatTelemetry(
- metrics.requestId,
- metrics.inputLength,
- metrics.numSelectedLines,
- metrics.codeIntent,
- metrics.userDecision,
- metrics.responseStartLatency,
- metrics.responseEndLatency,
- metrics.numSuggestionAddChars,
- metrics.numSuggestionAddLines,
- metrics.numSuggestionDelChars,
- metrics.numSuggestionDelLines,
- metrics.programmingLanguage
- )
- }
- this.metrics = null
- }
-
- private fun undoChanges() {
- scope.launch(EDT) {
- rejectAction?.invoke()
- rejectAction = null
- acceptAction = null
- }
- }
-
- private val diffAcceptHandler: () -> Unit = {
- scope.launch(EDT) {
- rejectAction = null
- acceptAction?.invoke()
- acceptAction = null
- invokeLater { hidePopup() }
- }
- ApplicationManager.getApplication().executeOnPooledThread {
- recordInlineChatTelemetry(InlineChatUserDecision.ACCEPT)
- }
- }
-
- private val diffRejectHandler: (editor: Editor) -> Unit = { editor ->
- undoChanges()
- invokeLater { hidePopup() }
- restoreSelection(editor)
- ApplicationManager.getApplication().executeOnPooledThread {
- recordInlineChatTelemetry(InlineChatUserDecision.REJECT)
- }
- }
-
- private fun addPopupListeners(popup: JBPopup, editor: Editor) {
- val popupListener = object : JBPopupListener {
- override fun onClosed(event: LightweightWindowEvent) {
- if (canPopupAbort.get() && event.asPopup().isDisposed) {
- popupCancelHandler.invoke(editor)
- }
- }
- }
- popup.addListener(popupListener)
- }
-
- fun initPopup(editor: Editor) {
- currentPopup?.let { Disposer.dispose(it) }
- val popup = InlineChatPopupFactory(
- acceptHandler = diffAcceptHandler,
- rejectHandler = { diffRejectHandler(editor) },
- submitHandler = popupSubmitHandler,
- cancelHandler = { popupCancelHandler(editor) }
- ).createPopup(editor, scope).also {
- currentPopup = it
- }
-
- addPopupListeners(popup, editor)
- Disposer.register(this, popup)
- canPopupAbort.set(true)
- val caretListener = createCaretListener(editor)
- editor.caretModel.addCaretListener(caretListener)
- }
-
- private fun createCaretListener(editor: Editor): CaretListener = object : CaretListener {
- override fun caretPositionChanged(event: CaretEvent) {
- disposePopup(false)
-
- editor.caretModel.removeCaretListener(this)
- }
- }
-
- private fun removeSelection(editor: Editor) {
- scope.launch(EDT) {
- val selectionModel = editor.selectionModel
- selectionModel.removeSelection()
- }
- }
-
- private fun restoreSelection(editor: Editor) {
- currentSelectionRange?.let { range ->
- scope.launch(EDT) {
- val selectionModel = editor.selectionModel
- selectionModel.setSelection(range.startOffset, range.endOffset)
- }
- }
- currentSelectionRange = null
- }
-
- private fun highlightCodeWithBackgroundColor(editor: Editor, startOffset: Int, endOffset: Int, isGreen: Boolean) {
- val greenBackgroundAttributes = TextAttributes().apply {
- backgroundColor = JBColor(0xAADEAA, 0x294436)
- effectColor = JBColor(0xAADEAA, 0x294436)
- }
-
- val redBackgroundAttributes = TextAttributes().apply {
- backgroundColor = JBColor(0xFFC8BD, 0x45302B)
- effectColor = JBColor(0xFFC8BD, 0x45302B)
- }
- val attributes = if (isGreen) greenBackgroundAttributes else redBackgroundAttributes
- rangeHighlighter = editor.markupModel.addRangeHighlighter(
- startOffset, endOffset, HighlighterLayer.SELECTION + 1,
- attributes, HighlighterTargetArea.EXACT_RANGE
- )
- }
-
- private fun hidePopup() {
- canPopupAbort.set(false)
- currentPopup?.closeOk(null)
- isInProgress.set(false)
- shouldShowActions.set(false)
- currentSelectionRange = null
- }
-
- fun disposePopup(isFromFileChange: Boolean) {
- if (currentPopup != null && !shouldShowActions.get() || isFromFileChange) {
- currentPopup?.let { Disposer.dispose(it) }
- hidePopup()
- currentPopup = null
- }
- }
-
- private fun compareDiffs(original: List, recommendation: List): List {
- val generator = DiffRowGenerator.create().showInlineDiffs(false).build()
- val rows: List = generator.generateDiffRows(original, recommendation)
- return rows
- }
-
- private fun unescape(s: String): String = StringEscapeUtils.unescapeHtml3(s)
- .replace(""", "\"")
- .replace("'", "'")
- .replace("=>", "=>")
-
- private fun processNewCode(editor: Editor, line: Int, event: ChatMessage, prevMessage: String) {
- if (isAbandoned.get()) return
- runBlocking {
- val code = event.message?.let { unescape(it) } ?: return@runBlocking
- logger.debug { "received inline chat recommendation with code: \n $code" }
- var insertLine = line
- var linesToAdd = emptyList()
- val prevLines = prevMessage.split("\n")
- if (prevLines.size > 1 && code.startsWith(prevMessage)) {
- if (insertionLine.get() != -1) insertLine = insertionLine.get()
- linesToAdd = code.split("\n").drop(prevLines.size - 1)
- } else {
- linesToAdd = code.split("\n")
- }
- if (linesToAdd.last() == "") linesToAdd = linesToAdd.dropLast(1)
- val stringToAdd = if (linesToAdd.size > 1) linesToAdd.joinToString(separator = "\n") else linesToAdd.first()
- if (currentPopup?.isVisible != true) {
- logger.debug { "inline chat popup cancelled before diff is shown" }
- isInProgress.set(false)
- isAbandoned.set(true)
- recordInlineChatTelemetry(InlineChatUserDecision.DISMISS)
- return@runBlocking
- }
- withContext(EDT) {
- insertNewLineIfNeeded(insertLine, editor)
- insertString(editor, getLineStartOffset(editor.document, insertLine), stringToAdd + "\n")
- }
- insertLine += linesToAdd.size
- insertionLine.set(insertLine)
- acceptAction = {
- removeHighlighter(editor)
- try {
- val caretPosition = CaretPosition(offset = getLineStartOffset(editor.document, line), line = line)
- ReferenceLogController.addReferenceLog(code, event.codeReference, editor, project, inlineChatStartPosition = caretPosition)
- } catch (e: Exception) {
- logger.warn { "error logging reference for inline chat: ${e.stackTraceToString()}" }
- }
- }
- rejectAction = {
- val startOffset = getLineStartOffset(editor.document, line)
- val endOffset = getLineEndOffset(editor.document, line + code.split("\n").size - 1)
- replaceString(editor.document, startOffset, endOffset, "")
- removeHighlighter(editor)
- }
- isInProgress.set(false)
- shouldShowActions.set(true)
- }
- }
-
- private fun processHighlights(diffRows: List, startLine: Int, editor: Editor) {
- removeHighlighter(editor)
- var currentDocumentLine = startLine
- diffRows.forEach { row ->
- when (row.tag) {
- DiffRow.Tag.EQUAL -> {
- currentDocumentLine++
- }
- DiffRow.Tag.DELETE -> {
- val startOffset = getLineStartOffset(editor.document, currentDocumentLine)
- val endOffset = getLineEndOffset(editor.document, currentDocumentLine, true)
- highlightString(editor, startOffset, endOffset, false)
- currentDocumentLine++
- }
-
- DiffRow.Tag.CHANGE -> {
- val startOffset = getLineStartOffset(editor.document, currentDocumentLine)
- val endOffset = getLineEndOffset(editor.document, currentDocumentLine, true)
- highlightString(editor, startOffset, endOffset, false)
- val insetStartOffset = getLineStartOffset(editor.document, currentDocumentLine + 1)
- val insertEndOffset = getLineEndOffset(editor.document, currentDocumentLine + 1, true)
- highlightString(editor, insetStartOffset, insertEndOffset, true)
- currentDocumentLine += 2
- }
-
- DiffRow.Tag.INSERT -> {
- val insetStartOffset = getLineStartOffset(editor.document, currentDocumentLine)
- val insertEndOffset = getLineEndOffset(editor.document, currentDocumentLine, true)
- highlightString(editor, insetStartOffset, insertEndOffset, true)
- currentDocumentLine++
- }
- }
- }
- }
-
- private fun applyChunk(recommendation: String, editor: Editor, startLine: Int, endLine: Int) {
- val startOffset = getLineStartOffset(editor.document, startLine)
- val endOffset = getLineEndOffset(editor.document, endLine)
- replaceString(editor.document, startOffset, endOffset, recommendation)
- }
-
- private fun constructPatch(diff: List): String {
- var patchString = ""
- diff.forEach { row ->
- when (row.tag) {
- DiffRow.Tag.EQUAL -> {
- patchString += row.oldLine + "\n"
- }
-
- DiffRow.Tag.DELETE -> {
- patchString += row.oldLine + "\n"
- }
-
- DiffRow.Tag.CHANGE -> {
- patchString += row.oldLine + "\n"
- patchString += row.newLine + "\n"
- }
-
- DiffRow.Tag.INSERT -> {
- patchString += row.newLine + "\n"
- }
- }
- }
- return unescape(patchString)
- }
-
- private fun finalComputation(selectedCode: String, finalMessage: String?) {
- if (finalMessage == null) {
- canPopupAbort.set(true)
- throw Exception("No suggestions from Q; please try a different instruction.")
- }
- var numSuggestionAddChars = 0
- var numSuggestionAddLines = 0
- var numSuggestionDelChars = 0
- var numSuggestionDelLines = 0
-
- val selection = selectedCode.split("\n")
- val recommendationList = unescape(finalMessage).split("\n")
- val diff = compareDiffs(selection, recommendationList)
- var isAllEqual = true
- diff.forEach { row ->
- when (row.tag) {
- DiffRow.Tag.EQUAL -> {
- // no-op
- }
- DiffRow.Tag.DELETE -> {
- isAllEqual = false
- numSuggestionDelLines += 1
- numSuggestionDelChars += row.oldLine.length
- }
-
- DiffRow.Tag.CHANGE -> {
- isAllEqual = false
- numSuggestionDelLines += 1
- numSuggestionDelChars += row.oldLine.length
- numSuggestionAddLines += 1
- numSuggestionAddChars = row.newLine.length
- }
-
- DiffRow.Tag.INSERT -> {
- isAllEqual = false
- numSuggestionAddLines += 1
- numSuggestionAddChars += row.newLine.length
- }
- }
- }
- metrics?.numSuggestionAddChars = numSuggestionAddChars
- metrics?.numSuggestionAddLines = numSuggestionAddLines
- metrics?.numSuggestionDelChars = numSuggestionDelChars
- metrics?.numSuggestionDelLines = numSuggestionDelLines
- if (isAllEqual) {
- canPopupAbort.set(true)
- throw Exception("No suggestions from Q; please try a different instruction.")
- }
- }
-
- private fun processChatDiff(selectedCode: String, event: ChatMessage, editor: Editor, selectionRange: RangeMarker) {
- if (isAbandoned.get()) return
- if (event.message?.isNotEmpty() == true) {
- logger.info { "inline chat recommendation: \n ${event.message}" }
- runBlocking {
- val recommendation = unescape(event.message)
- val selection = selectedCode.split("\n")
- val recommendationList = recommendation.split("\n")
- val diff = compareDiffs(selection, recommendationList)
- val startLine = getLineNumber(editor.document, selectionRange.startOffset)
- val endLine = getLineNumber(editor.document, selectionRange.endOffset)
- val patchString = constructPatch(diff)
- if (currentPopup?.isVisible != true) {
- logger.debug { "inline chat popup cancelled before diff is shown" }
- isInProgress.set(false)
- isAbandoned.set(true)
- recordInlineChatTelemetry(InlineChatUserDecision.DISMISS)
- return@runBlocking
- }
- withContext(EDT) {
- removeSelection(editor)
- applyChunk(patchString, editor, startLine, endLine)
- processHighlights(diff, startLine, editor)
- }
- acceptAction = {
- val startOffset = getLineStartOffset(editor.document, startLine)
- val endOffset = getLineEndOffset(editor.document, max((startLine + patchString.split("\n").size - 1), endLine))
- replaceString(editor.document, startOffset, endOffset, recommendation)
- removeHighlighter(editor)
- try {
- val caretPosition =
- CaretPosition(offset = selectionRange.startOffset, line = getLineNumber(editor.document, selectionRange.startOffset))
- ReferenceLogController.addReferenceLog(recommendation, event.codeReference, editor, project, inlineChatStartPosition = caretPosition)
- } catch (e: Exception) {
- logger.warn { "error logging reference for inline chat: ${e.stackTraceToString()}" }
- }
- }
- rejectAction = {
- val startOffset = getLineStartOffset(editor.document, startLine)
- val endOffset = getLineEndOffset(editor.document, max((startLine + patchString.split("\n").size - 1), endLine))
- replaceString(editor.document, startOffset, endOffset, selectedCode)
- removeHighlighter(editor)
- }
-
- isInProgress.set(false)
- shouldShowActions.set(true)
- }
- }
- }
-
- private fun insertNewLineIfNeeded(row: Int, editor: Editor): Int {
- var newLineInserted = 0
- while (row > editor.document.lineCount - 1) {
- insertString(editor, editor.document.textLength, "\n")
- newLineInserted++
- }
- return newLineInserted
- }
-
- private fun getLineStartOffset(document: Document, row: Int): Int = ReadAction.compute {
- document.getLineStartOffset(row)
- }
-
- private fun getLineEndOffset(document: Document, row: Int, includeLastNewLine: Boolean = false): Int = ReadAction.compute {
- if (row == document.lineCount - 1) {
- document.getLineEndOffset(row)
- } else if (row < document.lineCount - 1) {
- val lineEnd = document.getLineEndOffset(row)
- if (includeLastNewLine) lineEnd + 1 else lineEnd
- } else {
- document.getLineEndOffset((document.lineCount - 1).coerceAtLeast(0))
- }
- }
-
- private fun getLineNumber(document: Document, offset: Int): Int = ReadAction.compute {
- document.getLineNumber(offset)
- }
-
- private fun insertString(editor: Editor, offset: Int, text: String): RangeMarker {
- lateinit var rangeMarker: RangeMarker
-
- ApplicationManager.getApplication().invokeAndWait {
- CommandProcessor.getInstance().runUndoTransparentAction {
- WriteCommandAction.runWriteCommandAction(project) {
- editor.document.insertString(offset, text)
- rangeMarker = editor.document.createRangeMarker(offset, offset + text.length)
- }
- highlightCodeWithBackgroundColor(editor, rangeMarker.startOffset, rangeMarker.endOffset, true)
- }
- }
- return rangeMarker
- }
-
- private fun replaceString(document: Document, start: Int, end: Int, text: String) {
- ApplicationManager.getApplication().invokeAndWait {
- CommandProcessor.getInstance().runUndoTransparentAction {
- WriteCommandAction.runWriteCommandAction(project) {
- document.replaceString(start, end, text)
- }
- }
- }
- }
-
- private fun highlightString(editor: Editor, start: Int, end: Int, isInsert: Boolean) {
- ApplicationManager.getApplication().invokeAndWait {
- CommandProcessor.getInstance().runUndoTransparentAction {
- WriteCommandAction.runWriteCommandAction(project) {
- highlightCodeWithBackgroundColor(editor, start, end, isInsert)
- }
- }
- }
- }
-
- private fun removeHighlighter(editor: Editor) {
- ApplicationManager.getApplication().invokeAndWait {
- CommandProcessor.getInstance().runUndoTransparentAction {
- WriteCommandAction.runWriteCommandAction(project) {
- editor.markupModel.removeAllHighlighters()
- }
- }
- }
- }
-
- private fun checkCredentials(): String? {
- val authController = AuthController()
- val credentialState = authController.getAuthNeededStates(project).chat
- if (credentialState != null) {
- if (!JBCefApp.isSupported()) {
- requestCredentialsForQ(project, isReauth = false)
- } else {
- runInEdt {
- QWebviewPanel.getInstance(project).browser?.prepareBrowser(BrowserState(FeatureId.AmazonQ))
- ToolWindowManager.getInstance(project).getToolWindow(AMAZON_Q_WINDOW_ID)?.activate(null, false)
- ToolWindowManager.getInstance(project).getToolWindow(AMAZON_Q_WINDOW_ID)?.show()
- }
- }
- scope.launch {
- delay(3000)
- withContext(EDT) {
- disposePopup(true)
- }
- }
- return "Please sign in to Amazon Q"
- }
- return null
- }
-
- private suspend fun handleChat(message: String, selectedCode: String = "", editor: Editor, selectedLineStart: Int): String {
- insertionLine.set(-1)
- isAbandoned.set(false)
- val authError = checkCredentials()
- if (authError != null) {
- return authError
- }
- val selectionStart = getLineStartOffset(editor.document, selectedLineStart)
- var selectionRange: RangeMarker? = null
- if (selectedCode.isNotEmpty()) {
- WriteCommandAction.runWriteCommandAction(project) {
- selectionRange = editor.document.createRangeMarker(selectionStart, selectionStart + selectedCode.length)
- currentSelectionRange = selectionRange
- }
- }
- val startTime = System.currentTimeMillis()
- var firstResponseLatency = 0.0
- val messages = mutableListOf()
- val intentRecognizer = UserIntentRecognizer()
-
- val contextExtractor = ActiveFileContextExtractor.create(fqnWebviewAdapter = null, project = project)
- val fileContext = contextExtractor.extractContextForTrigger(ExtractionTriggerType.ChatMessage)
-
- val tabId = UUID.randomUUID().toString()
-
- val requestData = ChatRequestData(
- tabId = tabId,
- message = message,
- activeFileContext = fileContext,
- userIntent = intentRecognizer.getUserIntentFromPromptChatMessage(message),
- triggerType = TriggerType.Inline,
- customization = CodeWhispererModelConfigurator.getInstance().activeCustomization(project),
- relevantTextDocuments = emptyList(),
- useRelevantDocuments = false
- )
-
- val sessionInfo = sessionStorage.getSession(tabId, project)
- val mutex = Mutex()
- val isReferenceAllowed = CodeWhispererSettings.getInstance().isIncludeCodeWithReference()
-
- var errorMessage = ""
- var prevMessage = ""
- val chat = sessionInfo.scope.async {
- ChatPromptHandler(telemetryHelper).handle(
- tabId,
- UUID.randomUUID().toString(),
- requestData,
- sessionInfo,
- shouldAddIndexInProgressMessage = false,
- isInlineChat = true
- )
- .catch { e ->
- canPopupAbort.set(true)
- undoChanges()
- logger.warn { "Error in inline chat request: ${e.message}" }
- errorMessage = "Error processing request; please try again"
- }
- .onEach { event: ChatMessage ->
- if (event.message?.isNotEmpty() == true && prevMessage.isEmpty()) {
- firstResponseLatency = (System.currentTimeMillis() - startTime).toDouble()
- }
- if (event.message?.isNotEmpty() == true && prevMessage != event.message) {
- mutex.withLock {
- if (event.codeReference?.isNotEmpty() == true && !isReferenceAllowed) {
- canPopupAbort.set(true)
- undoChanges()
- errorMessage = "Suggestion had code reference; removed per setting."
- return@withLock
- }
- try {
- selectionRange?.let {
- processChatDiff(selectedCode, event, editor, it)
- } ?: run {
- processNewCode(editor, selectedLineStart, event, prevMessage)
- }
- } catch (e: Exception) {
- canPopupAbort.set(true)
- undoChanges()
- logger.warn { "error streaming chat message to editor: ${e.stackTraceToString()}" }
- errorMessage = "Error processing request; please try again."
- }
- prevMessage = unescape(event.message)
- }
- }
- messages.add(event)
- }
- .toList()
- }
- chat.await()
- val finalMessage = messages.lastOrNull { m -> m.messageType == ChatMessageType.AnswerPart }
- val lastResponseLatency = (System.currentTimeMillis() - startTime).toDouble()
- val requestId = messages.lastOrNull()?.messageId
- requestId?.let {
- metrics = InlineChatMetrics(
- requestId = it, inputLength = message.length, numSelectedLines = selectedCode.split("\n").size,
- codeIntent = true, responseStartLatency = firstResponseLatency, responseEndLatency = lastResponseLatency,
- programmingLanguage = fileContext.fileContext?.fileLanguage
- )
- }
- if (finalMessage != null) {
- try {
- finalComputation(selectedCode, finalMessage.message)
- } catch (e: Exception) {
- errorMessage = e.message ?: "Error processing request; please try again."
- }
- }
- if (errorMessage.isNotEmpty()) {
- canPopupAbort.set(true)
- undoChanges()
- }
- return errorMessage
- }
-
- companion object {
- fun getInstance(project: Project) = project.service()
- private val logger = getLogger()
- }
-
- override fun dispose() {
- currentPopup?.let { Disposer.dispose(it) }
- hidePopup()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatEditorHint.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatEditorHint.kt
deleted file mode 100644
index 4ad0e108913..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatEditorHint.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline
-import com.intellij.codeInsight.hint.HintManager
-import com.intellij.codeInsight.hint.HintManagerImpl
-import com.intellij.codeInsight.hint.HintUtil
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.VisualPosition
-import com.intellij.openapi.keymap.KeymapUtil
-import com.intellij.ui.LightweightHint
-import com.intellij.ui.SimpleColoredText
-import com.intellij.ui.SimpleTextAttributes
-import icons.AwsIcons
-import software.aws.toolkits.resources.AmazonQBundle.message
-import java.awt.BorderLayout
-import java.awt.Point
-import javax.swing.JPanel
-
-class InlineChatEditorHint {
- private val hint = createHint()
- private val hintXOffset = 20
-
- private fun getHintLocation(editor: Editor): Point {
- val range = editor.calculateVisibleRange()
- val document = editor.document
- val selectionEnd = editor.selectionModel.selectionEnd
- val isOneLineSelection = isOneLineSelection(editor)
- val offset = document.getLineEndOffset(document.getLineNumber(selectionEnd))
- val offsetXy = editor.offsetToXY(offset)
- val potentialXy = Point(offsetXy.x + hintXOffset, offsetXy.y)
- val isBelow = potentialXy !in editor.scrollingModel.visibleArea
- val areEdgesOutsideOfVisibleArea = editor.selectionModel.selectionStart !in range && editor.selectionModel.selectionEnd !in range
- val offsetForHint = when {
- areEdgesOutsideOfVisibleArea -> document.getLineEndOffset(getLineByVisualStart(editor, editor.caretModel.offset, true))
- isBelow -> document.getLineEndOffset(getLineByVisualStart(editor, selectionEnd, true))
- else -> document.getLineEndOffset(getLineByVisualStart(editor, selectionEnd, false))
- }
- val visualPosition = editor.offsetToVisualPosition(offsetForHint)
- val hintPoint = HintManagerImpl.getHintPosition(hint, editor, visualPosition, HintManager.RIGHT_UNDER)
- hintPoint.translate(if (!isBelow) hintXOffset else 0, if (!isOneLineSelection || isBelow) editor.lineHeight else 0)
- return hintPoint
- }
-
- private fun isOneLineSelection(editor: Editor): Boolean {
- val document = editor.document
- val selectionModel = editor.selectionModel
- val startLine = document.getLineNumber(selectionModel.selectionStart)
- val endLine = document.getLineNumber(selectionModel.selectionEnd)
- return startLine == endLine
- }
-
- private fun getLineByVisualStart(editor: Editor, offset: Int, skipLineStartOffset: Boolean): Int {
- val visualPosition = editor.offsetToVisualPosition(offset)
- val skipCurrentLine = skipLineStartOffset && visualPosition.column == 0
- val line = if (skipCurrentLine) maxOf(visualPosition.line - 1, 0) else visualPosition.line
- val lineStartPosition = VisualPosition(line, 0)
- return editor.visualToLogicalPosition(lineStartPosition).line
- }
-
- private fun createHint(): LightweightHint {
- val icon = AwsIcons.Logos.AWS_Q_GREY
-
- val component = HintUtil.createInformationComponent()
- component.isIconOnTheRight = false
- component.icon = icon
- val coloredText =
- SimpleColoredText(message("amazonqInlineChat.hint.edit"), SimpleTextAttributes.REGULAR_ATTRIBUTES)
-
- coloredText.appendToComponent(component)
- val shortcutComponent = HintUtil.createInformationComponent()
- val shortCut = KeymapUtil.getShortcutText("aws.toolkit.jetbrains.core.services.cwc.inline.openChat")
- val shortcutText =
- SimpleColoredText(shortCut, SimpleTextAttributes.REGULAR_ATTRIBUTES)
- shortcutText.appendToComponent(shortcutComponent)
-
- val panel = JPanel(BorderLayout()).apply {
- add(component, BorderLayout.WEST)
- add(shortcutComponent, BorderLayout.EAST)
- isOpaque = true
- background = component.background
- revalidate()
- repaint()
- }
-
- return LightweightHint(panel)
- }
-
- fun show(editor: Editor) {
- val location = getHintLocation(editor)
- HintManagerImpl.getInstanceImpl().showEditorHint(
- hint,
- editor,
- location,
- HintManager.HIDE_BY_TEXT_CHANGE or HintManager.HIDE_BY_SCROLLING,
- 0,
- false,
- HintManagerImpl.createHintHint(editor, location, hint, HintManager.RIGHT).setContentActive(false)
- )
- }
-
- fun hide() {
- hint.hide()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupFactory.kt
deleted file mode 100644
index 6d0eb1322d2..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupFactory.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.ReadAction
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.VisualPosition
-import com.intellij.openapi.ui.popup.IconButton
-import com.intellij.openapi.ui.popup.JBPopup
-import com.intellij.openapi.ui.popup.JBPopupFactory
-import com.intellij.openapi.util.TextRange
-import com.intellij.ui.IdeBorderFactory
-import com.intellij.ui.awt.RelativePoint
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-import software.amazon.q.jetbrains.core.coroutines.EDT
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_BUTTON_BORDER
-import software.aws.toolkits.resources.AmazonQBundle.message
-import java.awt.Point
-
-class InlineChatPopupFactory(
- private val submitHandler: suspend (String, String, Int, Editor) -> String,
- private val acceptHandler: () -> Unit,
- private val rejectHandler: () -> Unit,
- private val cancelHandler: () -> Unit,
- private val popupBufferHeight: Int = 150,
-) : Disposable {
-
- private fun getSelectedText(editor: Editor): String = ReadAction.compute {
- val selectionStartOffset = editor.selectionModel.selectionStart
- val selectionEndOffset = editor.selectionModel.selectionEnd
- if (selectionEndOffset > selectionStartOffset) {
- val selectionLineStart = editor.document.getLineStartOffset(editor.document.getLineNumber(selectionStartOffset))
- val selectionLineEnd = editor.document.getLineEndOffset(editor.document.getLineNumber(selectionEndOffset))
- editor.document.getText(TextRange(selectionLineStart, selectionLineEnd))
- } else {
- ""
- }
- }
-
- private fun getSelectionStartLine(editor: Editor): Int = ReadAction.compute {
- editor.document.getLineNumber(editor.selectionModel.selectionStart)
- }
-
- fun createPopup(editor: Editor, scope: CoroutineScope): JBPopup {
- val popupPanel = InlineChatPopupPanel(this).apply {
- border = IdeBorderFactory.createRoundedBorder(10).apply {
- setColor(POPUP_BUTTON_BORDER)
- }
-
- val submitListener: () -> Unit = {
- val prompt = textField.text
- if (prompt.isNotBlank()) {
- submitButton.isEnabled = false
- cancelButton.isEnabled = false
- textField.isEnabled = false
- setLabel(message("amazonqInlineChat.popup.generating"))
- revalidate()
-
- scope.launch {
- val selectedCode = getSelectedText(editor)
- val selectedLineStart = getSelectionStartLine(editor)
- var errorMessage = ""
- runBlocking {
- errorMessage = submitHandler(prompt, selectedCode, selectedLineStart, editor)
- }
- if (errorMessage.isNotEmpty()) {
- withContext(EDT) {
- setErrorMessage(errorMessage)
- revalidate()
- }
- } else {
- val acceptAction = {
- acceptHandler.invoke()
- }
- val rejectAction = {
- rejectHandler.invoke()
- }
- withContext(EDT) {
- addCodeActionsPanel(acceptAction, rejectAction)
- revalidate()
- }
- }
- }
- }
- }
- setSubmitClickListener(submitListener)
- }
- val popup = initPopup(popupPanel)
- showPopupInEditor(popup, popupPanel, editor)
-
- return popup
- }
-
- private fun showPopupInEditor(popup: JBPopup, popupPanel: InlineChatPopupPanel, editor: Editor) {
- val selectionEnd = editor.selectionModel.selectionEnd
- val selectionStart = editor.selectionModel.selectionStart
- val preferredXY = editor.offsetToXY(selectionStart)
- val visibleArea = editor.scrollingModel.visibleArea
- val isBelow = preferredXY.y - visibleArea.y < popupBufferHeight
- val xOffset = getLineByVisualStart(editor, selectionStart)
- val preferredX = editor.contentComponent.locationOnScreen.x + xOffset
-
- if (isBelow) {
- val offsetXY = editor.offsetToXY(selectionEnd)
- val point = Point(preferredX, offsetXY.y - visibleArea.y + popupBufferHeight)
- popup.show(RelativePoint(point))
- } else {
- val popupXY = Point(preferredX, preferredXY.y - visibleArea.y - editor.lineHeight)
- popup.show(RelativePoint(popupXY))
- }
-
- popupPanel.textField.requestFocusInWindow()
- popupPanel.textField.addActionListener { e ->
- val inputText = popupPanel.textField.text.trim()
- if (inputText.isNotEmpty()) {
- popupPanel.submitButton.doClick()
- }
- }
- }
-
- private fun getIndentationForLine(editor: Editor, lineNumber: Int): Int {
- val document = editor.document
- val lineStartOffset = document.getLineStartOffset(lineNumber)
- val lineEndOffset = document.getLineEndOffset(lineNumber)
- val lineText = document.getText(TextRange(lineStartOffset, lineEndOffset))
-
- // Find the index of the first non-whitespace character
- val firstNonWhitespace = lineText.indexOfFirst { !it.isWhitespace() }
-
- return if (firstNonWhitespace == -1) {
- 0
- } else {
- firstNonWhitespace + 1
- }
- }
-
- private fun getLineByVisualStart(editor: Editor, offset: Int): Int {
- val visualPosition = editor.offsetToVisualPosition(offset)
- val line = visualPosition.line
- val column = getIndentationForLine(editor, line)
- val lineStartPosition = VisualPosition(line, column)
- return editor.visualToLogicalPosition(lineStartPosition).line
- }
-
- private fun initPopup(panel: InlineChatPopupPanel): JBPopup {
- val cancelButton = IconButton(message("amazonqInlineChat.popup.cancel"), AllIcons.Actions.Cancel)
- val popup = JBPopupFactory.getInstance()
- .createComponentPopupBuilder(panel, panel.textField)
- .setMovable(true)
- .setResizable(true)
- .setTitle(message("amazonqInlineChat.popup.title"))
- .setCancelButton(cancelButton)
- .setCancelCallback {
- cancelHandler.invoke()
- true
- }
- .setShowBorder(true)
- .setCancelOnWindowDeactivation(false)
- .setCancelOnClickOutside(false)
- .setCancelOnOtherWindowOpen(false)
- .setFocusable(true)
- .setRequestFocus(true)
- .setLocateWithinScreenBounds(true)
- .createPopup()
- return popup
- }
-
- override fun dispose() {
- cancelHandler.invoke()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupPanel.kt
deleted file mode 100644
index a257565f8d4..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/InlineChatPopupPanel.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.actionSystem.IdeActions
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.actionSystem.EditorActionHandler
-import com.intellij.openapi.editor.actionSystem.EditorActionManager
-import com.intellij.openapi.util.Disposer
-import com.intellij.ui.IdeBorderFactory
-import icons.AwsIcons
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_BUTTON_BORDER
-import software.aws.toolkits.resources.AmazonQBundle.message
-import java.awt.BorderLayout
-import java.awt.Dimension
-import javax.swing.BorderFactory
-import javax.swing.JButton
-import javax.swing.JLabel
-import javax.swing.JPanel
-import javax.swing.JTextField
-import javax.swing.SwingConstants
-import javax.swing.event.DocumentEvent
-import javax.swing.event.DocumentListener
-
-class InlineChatPopupPanel(private val parentDisposable: Disposable) : JPanel() {
- private var submitClickListener: (() -> Unit)? = null
- private val popupButtonFontSize = 14f
- private val popupWidth = 600
- private val popupHeight = 90
- private val popupButtonHeight = 30
- private val popupButtonWidth = 80
- private val popupInputHeight = 40
- private val popupInputWidth = 500
-
- val textField = createTextField().apply {
- document.addDocumentListener(object : DocumentListener {
- fun updateButtonState() {
- submitButton.isEnabled = text.isNotEmpty()
- }
-
- override fun insertUpdate(e: DocumentEvent) = updateButtonState()
- override fun removeUpdate(e: DocumentEvent) = updateButtonState()
- override fun changedUpdate(e: DocumentEvent) = updateButtonState()
- })
- }
-
- val submitButton = createButton(message("amazonqInlineChat.popup.confirm")).apply {
- isEnabled = false
- }
-
- val cancelButton = createButton(message("amazonqInlineChat.popup.cancel")).apply {
- addActionListener {
- if (!Disposer.isDisposed(parentDisposable)) {
- Disposer.dispose(parentDisposable)
- }
- }
- }
-
- private val buttonsPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
- add(submitButton, BorderLayout.WEST)
- add(cancelButton, BorderLayout.EAST)
- }
-
- private val acceptButton = createButton(message("amazonqInlineChat.popup.accept"))
- private val rejectButton = createButton(message("amazonqInlineChat.popup.reject"))
- private val textLabel = JLabel(message("amazonqInlineChat.popup.editCode"), AwsIcons.Logos.AWS_Q_GREY, SwingConstants.RIGHT).apply {
- font = font.deriveFont(popupButtonFontSize)
- }
-
- private val inputPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createEmptyBorder(10, 10, 5, 10)
- }
-
- private val logoPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
- add(textLabel, BorderLayout.CENTER)
- }
-
- private val bottomPanel = JPanel(BorderLayout()).apply {
- add(logoPanel, BorderLayout.WEST)
- add(buttonsPanel, BorderLayout.EAST)
- }
-
- private val emptyTextField = createTextField().apply {
- isEnabled = false
- }
-
- override fun getPreferredSize(): Dimension = Dimension(popupWidth, popupHeight)
-
- private fun createTextField(): JTextField = JTextField().apply {
- preferredSize = Dimension(popupInputWidth, popupInputHeight)
- border = IdeBorderFactory.createRoundedBorder().apply {
- setColor(POPUP_BUTTON_BORDER)
- }
- }
-
- private fun createButton(text: String): JButton = JButton(text).apply {
- preferredSize = Dimension(popupButtonWidth, popupButtonHeight)
- isOpaque = false
- isContentAreaFilled = false
- isBorderPainted = false
- font = font.deriveFont(popupButtonFontSize)
- }
-
- init {
- layout = BorderLayout()
- inputPanel.add(textField, BorderLayout.CENTER)
- add(inputPanel, BorderLayout.CENTER)
- add(bottomPanel, BorderLayout.SOUTH)
-
- submitButton.addActionListener {
- submitClickListener?.invoke()
- }
- }
-
- fun setSubmitClickListener(listener: () -> Unit) {
- submitClickListener = listener
- }
-
- private fun addActionListener(id: String, action: EditorActionHandler) {
- val actionManager = EditorActionManager.getInstance()
- val originalHandler = actionManager.getActionHandler(id)
-
- actionManager.setActionHandler(id, action)
- val restorer = Disposable { actionManager.setActionHandler(id, originalHandler) }
- Disposer.register(parentDisposable, restorer)
- }
-
- private fun getEditorActionHandler(action: () -> Unit): EditorActionHandler = object : EditorActionHandler() {
- override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
- action.invoke()
- Disposer.dispose(parentDisposable)
- }
- }
-
- fun addCodeActionsPanel(acceptAction: () -> Unit, rejectAction: () -> Unit) {
- textLabel.text = message("amazonqInlineChat.popup.editCode")
- // this is a workaround somehow the textField will interfere with the enter handler
- emptyTextField.text = textField.text
- inputPanel.remove(textField)
- inputPanel.add(emptyTextField, BorderLayout.CENTER)
-
- buttonsPanel.remove(submitButton)
- buttonsPanel.remove(cancelButton)
- buttonsPanel.add(acceptButton, BorderLayout.WEST)
- buttonsPanel.add(rejectButton, BorderLayout.EAST)
- acceptButton.addActionListener { acceptAction.invoke() }
- rejectButton.addActionListener { rejectAction.invoke() }
-
- val enterHandler = getEditorActionHandler(acceptAction)
- addActionListener(IdeActions.ACTION_EDITOR_ENTER, enterHandler)
-
- val escapeHandler = getEditorActionHandler(rejectAction)
- addActionListener(IdeActions.ACTION_EDITOR_ESCAPE, escapeHandler)
- revalidate()
- }
-
- fun setErrorMessage(message: String) {
- setLabel(message)
- inputPanel.remove(emptyTextField)
- textField.text = ""
- textField.isEnabled = true
- inputPanel.add(textField, BorderLayout.CENTER)
-
- buttonsPanel.remove(acceptButton)
- buttonsPanel.remove(rejectButton)
- buttonsPanel.add(submitButton, BorderLayout.WEST)
- buttonsPanel.add(cancelButton, BorderLayout.EAST)
- submitButton.isEnabled = false
- cancelButton.isEnabled = true
- revalidate()
- }
-
- fun setLabel(text: String) {
- textLabel.text = text
- textField.isEnabled = false
- revalidate()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/OpenChatInputAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/OpenChatInputAction.kt
deleted file mode 100644
index f581a217226..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/OpenChatInputAction.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline
-
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.intellij.openapi.util.Key
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
-
-class OpenChatInputAction : AnAction() {
- override fun actionPerformed(e: AnActionEvent) {
- val editor = e.getData(CommonDataKeys.EDITOR) ?: return
- // FIX_WHEN_MIN_IS_241: change below to use ConsoleViewImpl.IS_CONSOLE_DOCUMENT
- var isConsole: Any? = null
- val key: Key<*>? = Key.findKeyByName("IS_CONSOLE_DOCUMENT")
- if (key != null) {
- isConsole = editor.document.getUserData(key)
- }
- if (isConsole == true) return
- if (!editor.document.isWritable) return
- val project = editor.project ?: return
-
- val inlineChatController = InlineChatController.getInstance(project)
- inlineChatController.initPopup(editor)
- }
- override fun update(e: AnActionEvent) {
- val project = e.project ?: return
- e.presentation.isEnabledAndVisible = !QRegionProfileManager.getInstance().hasValidConnectionButNoActiveProfile(project)
- }
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatFileListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatFileListener.kt
deleted file mode 100644
index 653967c8213..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatFileListener.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline.listeners
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.fileEditor.FileEditorManager
-import com.intellij.openapi.fileEditor.FileEditorManagerEvent
-import com.intellij.openapi.fileEditor.FileEditorManagerListener
-import com.intellij.openapi.fileEditor.TextEditor
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.Disposer
-import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatController
-
-class InlineChatFileListener(project: Project, private val controller: InlineChatController) : FileEditorManagerListener, Disposable {
- private var currentEditor: Editor? = null
- private var selectionListener: InlineChatSelectionListener? = null
-
- init {
- val editor = project.let { FileEditorManager.getInstance(it).selectedTextEditorWithRemotes.firstOrNull() }
- if (editor != null) {
- setupListenersForEditor(editor)
- currentEditor = editor
- }
- }
-
- override fun selectionChanged(event: FileEditorManagerEvent) {
- val newEditor = (event.newEditor as? TextEditor)?.editor ?: return
- if (newEditor != currentEditor) {
- currentEditor?.let { removeListenersFromCurrentEditor(it) }
- setupListenersForEditor(newEditor)
- currentEditor = newEditor
- controller.disposePopup(true)
- }
- }
-
- private fun setupListenersForEditor(editor: Editor) {
- selectionListener = InlineChatSelectionListener().also { listener ->
- editor.selectionModel.addSelectionListener(listener)
- Disposer.register(this, listener)
- }
- }
-
- private fun removeListenersFromCurrentEditor(editor: Editor) {
- selectionListener?.let { listener ->
- editor.selectionModel.removeSelectionListener(listener)
- Disposer.dispose(listener)
- }
- selectionListener = null
- }
-
- override fun dispose() {
- currentEditor?.let { removeListenersFromCurrentEditor(it) }
- currentEditor = null
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt
deleted file mode 100644
index c4e1e01d5cc..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/inline/listeners/InlineChatSelectionListener.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.inline.listeners
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.editor.event.SelectionEvent
-import com.intellij.openapi.editor.event.SelectionListener
-import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend
-import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatEditorHint
-
-class InlineChatSelectionListener : SelectionListener, Disposable {
- private val inlineChatEditorHint = InlineChatEditorHint()
- override fun selectionChanged(e: SelectionEvent) {
- if (isRunningOnRemoteBackend()) return
- val editor = e.editor
- val selectionModel = editor.selectionModel
-
- if (selectionModel.hasSelection()) {
- inlineChatEditorHint.show(editor)
- } else {
- inlineChatEditorHint.hide()
- }
- }
-
- override fun dispose() {
- inlineChatEditorHint.hide()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt
deleted file mode 100644
index f695ba49930..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.messages
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.fasterxml.jackson.annotation.JsonValue
-import com.fasterxml.jackson.core.JsonGenerator
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonDeserializer
-import com.fasterxml.jackson.databind.JsonSerializer
-import com.fasterxml.jackson.databind.SerializerProvider
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.annotation.JsonSerialize
-import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
-import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthFollowUpType
-import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
-import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteractionType
-import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FollowUpType
-import java.time.Instant
-
-sealed interface CwcMessage : AmazonQMessage
-
-// === UI -> App Messages ===
-sealed interface IncomingCwcMessage : CwcMessage {
- interface TabId {
- val tabId: String?
- }
-
- interface MessageId {
- val messageId: String?
- }
-
- data class ClearChat(
- @JsonProperty("tabID") override val tabId: String,
- ) : IncomingCwcMessage, TabId
-
- data class Help(
- @JsonProperty("tabID") override val tabId: String,
- ) : IncomingCwcMessage, TabId
-
- data class ChatPrompt(
- val chatMessage: String,
- val command: String,
- @JsonProperty("tabID") override val tabId: String,
- val userIntent: String?,
- ) : IncomingCwcMessage, TabId
-
- data class TabAdded(
- @JsonProperty("tabID") override val tabId: String,
- val tabType: String,
- ) : IncomingCwcMessage, TabId
-
- data class TabRemoved(
- @JsonProperty("tabID") override val tabId: String,
- val tabType: String,
- ) : IncomingCwcMessage, TabId
-
- data class TabChanged(
- @JsonProperty("tabID") override val tabId: String,
- @JsonProperty("prevTabID") val prevTabId: String?,
- ) : IncomingCwcMessage, TabId
-
- data class FollowupClicked(
- val followUp: FollowUp,
- @JsonProperty("tabID") override val tabId: String,
- override val messageId: String?,
- val command: String,
- val tabType: String,
- ) : IncomingCwcMessage, TabId, MessageId
-
- data class CopyCodeToClipboard(
- val command: String?,
- @JsonProperty("tabID") override val tabId: String,
- override val messageId: String,
- val userIntent: UserIntent?,
- val code: String,
- val insertionTargetType: String?,
- val eventId: String?,
- val codeBlockIndex: Int?,
- val totalCodeBlocks: Int?,
- val codeBlockLanguage: String?,
- ) : IncomingCwcMessage, TabId, MessageId
-
- data class InsertCodeAtCursorPosition(
- @JsonProperty("tabID") override val tabId: String,
- override val messageId: String,
- val userIntent: UserIntent?,
- val code: String,
- val insertionTargetType: String?,
- val codeReference: List?,
- val eventId: String?,
- val codeBlockIndex: Int?,
- val totalCodeBlocks: Int?,
- val codeBlockLanguage: String?,
- ) : IncomingCwcMessage, TabId, MessageId
-
- data class TriggerTabIdReceived(
- @JsonProperty("triggerID") val triggerId: String,
- @JsonProperty("tabID") override val tabId: String,
- ) : IncomingCwcMessage, TabId
-
- data class StopResponse(
- @JsonProperty("tabID") override val tabId: String,
- ) : IncomingCwcMessage, TabId
-
- data class ChatItemVoted(
- @JsonProperty("tabID") override val tabId: String,
- override val messageId: String,
- val vote: String, // upvote / downvote
- ) : IncomingCwcMessage, TabId, MessageId
-
- data class ChatItemFeedback(
- @JsonProperty("tabID") override val tabId: String,
- val selectedOption: String,
- val comment: String?,
- override val messageId: String,
- ) : IncomingCwcMessage, TabId, MessageId
-
- data class UIFocus(
- val command: String,
- @JsonDeserialize(using = FocusTypeDeserializer::class)
- @JsonSerialize(using = FocusTypeSerializer::class)
- val type: FocusType,
- ) : IncomingCwcMessage
-
- data class OpenUserGuide(
- val userGuideLink: String,
- ) : IncomingCwcMessage
-
- data class ClickedLink(
- @JsonProperty("command") val type: LinkType,
- @JsonProperty("tabID") override val tabId: String,
- override val messageId: String?,
- val link: String,
- ) : IncomingCwcMessage, TabId, MessageId
-
- data class AuthFollowUpWasClicked(
- @JsonProperty("tabID") override val tabId: String,
- val authType: AuthFollowUpType,
- ) : IncomingCwcMessage, TabId
-
- data class OpenSettings(
- @JsonProperty("tabID") override val tabId: String? = null,
- ) : IncomingCwcMessage, TabId
-}
-
-enum class FocusType {
- FOCUS,
- BLUR,
-}
-
-enum class LinkType(
- @field:JsonValue val command: String,
-) {
- SourceLink("source-link-click"),
- BodyLink("response-body-link-click"),
- FooterInfoLink("footer-info-link-click"),
-}
-
-class FocusTypeDeserializer : JsonDeserializer() {
- override fun deserialize(p: JsonParser, ctxt: DeserializationContext): FocusType = FocusType.valueOf(p.valueAsString.uppercase())
-}
-
-class FocusTypeSerializer : JsonSerializer() {
- override fun serialize(value: FocusType, gen: JsonGenerator, serializers: SerializerProvider) {
- gen.writeString(value.name.lowercase())
- }
-}
-
-sealed class UiMessage(
- open val tabId: String?,
- open val type: String,
-) : CwcMessage {
- val time = Instant.now().epochSecond
- val sender = "CWChat"
-}
-
-enum class ChatMessageType(
- @field:JsonValue val json: String,
-) {
- AnswerStream("answer-stream"),
- AnswerPart("answer-part"),
- Answer("answer"),
- AIPrompt("ai-prompt"),
- Prompt("prompt"),
-}
-
-data class CodeReference(
- val licenseName: String? = null,
- val repository: String? = null,
- val url: String? = null,
- val recommendationContentSpan: RecommendationContentSpan? = null,
- val information: String,
-)
-
-data class RecommendationContentSpan(
- val start: Int,
- val end: Int,
-)
-
-data class FollowUp(
- val type: FollowUpType,
- val pillText: String,
- val prompt: String,
- val status: String? = null,
-)
-
-data class Suggestion(
- val title: String,
- val url: String,
- val body: String,
- val id: Int,
- val type: String?,
- val context: List,
-)
-
-// === App -> UI messages ===
-
-data class ChatMessage(
- @JsonProperty("tabID") override val tabId: String,
- @JsonProperty("triggerID") val triggerId: String,
- val messageType: ChatMessageType,
- val messageId: String,
- val message: String? = null,
- val followUps: List? = null,
- val followUpsHeader: String? = null,
- val relatedSuggestions: List? = null,
- val codeReference: List? = null,
- val userIntent: UserIntent? = null,
- val codeBlockLanguage: String? = "plaintext",
-) : UiMessage(
- tabId = tabId,
- type = "chatMessage",
-)
-
-data class EditorContextCommandMessage(
- val message: String?,
- @JsonProperty("triggerID") val triggerId: String?,
- val command: String?,
-) : UiMessage(
- tabId = null,
- type = "editorContextCommandMessage",
-)
-
-data class AuthNeededException(
- @JsonProperty("tabID") override val tabId: String,
- @JsonProperty("triggerID") val triggerId: String,
- val authType: AuthFollowUpType,
- val message: String,
-) : UiMessage(
- tabId = tabId,
- type = "authNeededException",
-)
-
-data class ErrorMessage(
- @JsonProperty("tabID") override val tabId: String,
- val title: String,
- val message: String,
- val messageId: String?,
-) : UiMessage(
- tabId = tabId,
- type = "errorMessage",
-)
-
-data class FeatureConfigsAvailableMessage(
- val highlightCommand: HighlightCommand?,
-
-) : UiMessage(
- null,
- type = "featureConfigsAvailableMessage",
-)
-
-data class QuickActionMessage(
- val message: String,
- @JsonProperty("triggerID") val triggerId: String,
-) : UiMessage(
- tabId = null,
- type = "editorContextCommandMessage",
-)
-
-data class OnboardingPageInteractionMessage(
- val message: String,
- val interactionType: OnboardingPageInteractionType,
- @JsonProperty("triggerID") val triggerId: String,
-) : UiMessage(
- tabId = null,
- type = "editorContextCommandMessage",
-)
-
-data class OpenSettingsMessage(
- @JsonProperty("tabID") override val tabId: String,
-) : UiMessage(
- tabId = tabId,
- type = "openSettingsMessage",
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionInfo.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionInfo.kt
deleted file mode 100644
index 37181333dd2..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionInfo.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.storage
-
-import kotlinx.coroutines.CoroutineScope
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.ChatSession
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatRequestData
-
-data class ChatSessionInfo(
- val session: ChatSession,
- val scope: CoroutineScope,
- val history: MutableList,
-)
diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionStorage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionStorage.kt
deleted file mode 100644
index e11d0693b7c..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/storage/ChatSessionStorage.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.cwc.storage
-
-import com.intellij.openapi.project.Project
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.ChatSessionFactory
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.v1.ChatSessionFactoryV1
-
-class ChatSessionStorage(
- private val chatSessionFactory: ChatSessionFactory = ChatSessionFactoryV1(),
-) {
- private val sessions = mutableMapOf()
-
- fun getSession(tabId: String) = sessions[tabId]
-
- fun getSession(tabId: String, project: Project) = sessions.getOrPut(tabId) {
- val session = chatSessionFactory.create(project)
- val scope = CoroutineScope(SupervisorJob())
- ChatSessionInfo(session = session, scope = scope, history = mutableListOf())
- }
-
- fun deleteSession(tabId: String) {
- sessions.remove(tabId)?.scope?.cancel()
- }
-
- fun deleteAllSessions() {
- sessions.clear()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt b/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
deleted file mode 100644
index 1c22bcdfe89..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/tst-242-252/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
+++ /dev/null
@@ -1,618 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.amazonq
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.project.Project
-import com.intellij.testFramework.ProjectExtension
-import com.intellij.testFramework.junit5.TestDisposable
-import com.intellij.testFramework.registerServiceInstance
-import com.intellij.testFramework.replaceService
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runTest
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.extension.RegisterExtension
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.stub
-import org.mockito.kotlin.times
-import org.mockito.kotlin.verify
-import software.amazon.awssdk.awscore.DefaultAwsResponseMetadata
-import software.amazon.awssdk.awscore.util.AwsHeader.AWS_REQUEST_ID
-import software.amazon.awssdk.http.SdkHttpResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.ChatInteractWithMessageEvent
-import software.amazon.awssdk.services.codewhispererruntime.model.ChatMessageInteractionType
-import software.amazon.awssdk.services.codewhispererruntime.model.SendTelemetryEventResponse
-import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
-import software.amazon.awssdk.services.ssooidc.SsoOidcClient
-import software.amazon.q.core.telemetry.MetricEvent
-import software.amazon.q.core.telemetry.TelemetryBatcher
-import software.amazon.q.jetbrains.core.MockClientManagerExtension
-import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager
-import software.amazon.q.jetbrains.core.credentials.pinning.QConnection
-import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES
-import software.amazon.q.jetbrains.services.telemetry.MockTelemetryServiceExtension
-import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.ChatSession
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatRequestData
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNamesImpl
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FullyQualifiedName
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FullyQualifiedNames
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.TriggerType
-import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.FileContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.FocusAreaContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.UICodeSelectionLineRange
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.UICodeSelectionRange
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType
-import software.aws.toolkits.jetbrains.services.cwc.messages.IncomingCwcMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.LinkType
-import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionInfo
-import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionStorage
-import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
-import software.aws.toolkits.telemetry.CwsprChatConversationType
-import software.aws.toolkits.telemetry.CwsprChatInteractionType
-import software.aws.toolkits.telemetry.CwsprChatTriggerInteraction
-import software.aws.toolkits.telemetry.CwsprChatUserIntent
-import kotlin.test.assertNotNull
-
-class TelemetryHelperTest {
- // sut
- private lateinit var sut: TelemetryHelper
-
- private lateinit var appInitContext: AmazonQAppInitContext
- private lateinit var sessionStorage: ChatSessionStorage
-
- // dependencies
- private lateinit var mockBatcher: TelemetryBatcher
- private lateinit var mockClient: CodeWhispererClientAdaptor
- private lateinit var mockConnectionManager: ToolkitConnectionManager
- private lateinit var mockModelConfigurator: CodeWhispererModelConfigurator
-
- private lateinit var mockConnection: ToolkitConnection
- private val project: Project
- get() = projectExtension.project
-
- @JvmField
- @RegisterExtension
- val mockClientManager = MockClientManagerExtension()
-
- @JvmField
- @RegisterExtension
- val mockTelemetryService = MockTelemetryServiceExtension()
-
- @TestDisposable
- private lateinit var disposable: Disposable
-
- companion object {
- @JvmField
- @RegisterExtension
- val projectExtension = ProjectExtension()
-
- private const val mockUrl = "mockUrl"
- private const val mockRegion = "us-east-1"
- private const val tabId = "tabId"
- private const val messageId = "messageId"
- private val userIntent = UserIntent.SHOW_EXAMPLES
- private const val conversationId = "conversationId"
- private const val triggerId = "triggerId"
- private const val customizationArn = "customizationArn"
- private const val steRequestId = "sendTelemetryEventRequestId"
- private const val lang = "java"
- private val mockCustomization = CodeWhispererCustomization(customizationArn, "name", "description")
- private val data = ChatRequestData(
- tabId = tabId,
- message = "foo",
- activeFileContext = ActiveFileContext(
- FileContext(lang, "~/foo/bar/baz", null),
- FocusAreaContext(
- codeSelection = "",
- codeSelectionRange = UICodeSelectionRange(
- UICodeSelectionLineRange(1, 2),
- UICodeSelectionLineRange(3, 4)
- ),
- trimmedSurroundingFileText = "",
- codeNames = CodeNamesImpl(
- listOf("simpleName_1"),
- FullyQualifiedNames(
- listOf(
- FullyQualifiedName(
- listOf("source_1"),
- listOf("symbol_1")
- )
- )
- )
- )
- )
- ),
- userIntent = UserIntent.IMPROVE_CODE,
- triggerType = TriggerType.Hotkeys,
- customization = mockCustomization,
- relevantTextDocuments = emptyList(),
- useRelevantDocuments = true,
- )
- private val response = ChatMessage(
- tabId = tabId,
- triggerId = triggerId,
- messageType = ChatMessageType.Prompt,
- messageId = messageId,
- followUps = listOf(mock(), mock())
- )
- private val mockSteResponse = SendTelemetryEventResponse.builder()
- .apply {
- this.sdkHttpResponse(
- SdkHttpResponse.builder().build()
- )
- this.responseMetadata(
- DefaultAwsResponseMetadata.create(
- mapOf(AWS_REQUEST_ID to steRequestId)
- )
- )
- }.build()
- }
-
- @BeforeEach
- fun setup() {
- // set up sut
- appInitContext = AmazonQAppInitContext(
- project = project,
- messagesFromAppToUi = mock(),
- messagesFromUiToApp = mock(),
- messageTypeRegistry = mock(),
- fqnWebviewAdapter = mock()
- )
- val mockSession = mock {
- on { this.conversationId } doReturn conversationId
- }
- sessionStorage = mock {
- on { this.getSession(eq(tabId)) } doReturn ChatSessionInfo(session = mockSession, scope = mock(), history = mutableListOf())
- }
- sut = TelemetryHelper(appInitContext.project, sessionStorage)
-
- // set up client
- mockClientManager.create()
-
- // set up connection
- mockConnection = LegacyManagedBearerSsoConnection(
- mockUrl,
- mockRegion,
- Q_SCOPES,
- mock()
- )
- mockConnectionManager = mock {
- on { activeConnectionForFeature(eq(QConnection.getInstance())) } doReturn mockConnection
- }
- project.replaceService(ToolkitConnectionManager::class.java, mockConnectionManager, disposable)
-
- // set up telemetry service
- mockBatcher = mockTelemetryService.batcher()
-
- // set up client
- mockClient = mock()
- // TODO: use registerService instead of replace service because it's codewhisperer package, and replaceService will fail in 232
- project.registerServiceInstance(CodeWhispererClientAdaptor::class.java, mockClient)
-
- // set up customization
- mockModelConfigurator = mock {
- on { activeCustomization(project) } doReturn mockCustomization
- }
- // TODO: use registerService instead of replace service because it's codewhisperer package, and replaceService will fail in 232
- ApplicationManager.getApplication().registerServiceInstance(CodeWhispererModelConfigurator::class.java, mockModelConfigurator)
- }
-
- @Test
- fun recordAddMessageTest() {
- mockClient.stub {
- on {
- sendChatAddMessageTelemetry(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any())
- } doReturn mockSteResponse
- }
-
- // set up request data
- val responseLength = 10
- val statusCode = 400
- val numberOfCodeBlocks = 1
-
- sut.recordAddMessage(
- data = data,
- response = response,
- responseLength = responseLength,
- statusCode = statusCode,
- numberOfCodeBlocks = numberOfCodeBlocks
- )
-
- // Q STE
- verify(mockClient).sendChatAddMessageTelemetry(
- sessionId = eq(conversationId),
- requestId = eq(messageId),
- userIntent = eq(software.amazon.awssdk.services.codewhispererruntime.model.UserIntent.fromValue(data.userIntent?.name)),
- hasCodeSnippet = any(),
- programmingLanguage = eq(lang),
- activeEditorTotalCharacters = eq(data.activeFileContext.focusAreaContext?.codeSelection?.length),
- timeToFirstChunkMilliseconds = eq(sut.getResponseStreamTimeToFirstChunk(tabId)),
- timeBetweenChunks = eq(sut.getResponseStreamTimeBetweenChunks(tabId)),
- fullResponselatency = any(), // TODO
- requestLength = eq(data.message.length),
- responseLength = eq(responseLength),
- numberOfCodeBlocks = eq(numberOfCodeBlocks),
- hasProjectLevelContext = eq(CodeWhispererSettings.getInstance().isProjectContextEnabled()),
- customization = eq(mockCustomization)
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_addMessage" }
- assertNotNull(event)
- assertThat(event)
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversation id doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == "messageId" }, "message id doesn't match")
- .matches(
- { it.metadata["cwsprChatTriggerInteraction"] == CwsprChatTriggerInteraction.ContextMenu.toString() },
- "trigger type doesn't match"
- )
- .matches({ it.metadata["cwsprChatUserIntent"] == CwsprChatUserIntent.ImproveCode.toString() }, "user intent doesn't match")
- .matches({
- it.metadata["cwsprChatHasCodeSnippet"] == (
- data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty()
- ?: false
- ).toString()
- }, "has code snippet doesn't match")
- .matches({ it.metadata["cwsprChatProgrammingLanguage"] == "java" }, "language doesn't match")
- .matches(
- { it.metadata["cwsprChatActiveEditorTotalCharacters"] == data.activeFileContext.focusAreaContext?.codeSelection?.length?.toString() },
- "total characters doesn't match"
- )
- .matches(
- {
- it.metadata["cwsprChatActiveEditorImportCount"] ==
- data.activeFileContext.focusAreaContext?.codeNames?.fullyQualifiedNames?.used?.size?.toString()
- },
- "import count doesn't match"
- )
- .matches(
- { it.metadata["cwsprChatResponseCodeSnippetCount"] == numberOfCodeBlocks.toString() },
- "number of code blocks doesn't match"
- )
- .matches({ it.metadata["cwsprChatResponseCode"] == statusCode.toString() }, "response code doesn't match")
- .matches(
- { it.metadata["cwsprChatSourceLinkCount"] == response.relatedSuggestions?.size?.toString() },
- "source link count doesn't match"
- )
- .matches({ it.metadata["cwsprChatFollowUpCount"] == response.followUps?.size?.toString() }, "follow up count doesn't match")
- .matches(
- { it.metadata["cwsprChatTimeToFirstChunk"] == sut.getResponseStreamTimeToFirstChunk(response.tabId).toInt().toString() },
- "time to first chunk doesn't match"
- )
- .matches({
- it.metadata["cwsprChatTimeBetweenChunks"] == "[${
- sut.getResponseStreamTimeBetweenChunks(response.tabId).joinToString(", ")
- }]"
- }, "time between chunks doesn't match")
- .matches({ it.metadata["cwsprChatRequestLength"] == data.message.length.toString() }, "request length doesn't match")
- .matches({ it.metadata["cwsprChatResponseLength"] == responseLength.toString() }, "response length doesn't match")
- .matches(
- { it.metadata["cwsprChatConversationType"] == CwsprChatConversationType.Chat.toString() },
- "conversation type doesn't match"
- )
- .matches({ it.metadata["codewhispererCustomizationArn"] == "customizationArn" }, "user intent doesn't match")
- .matches({
- it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString()
- }, "customization description doesn't match")
-// .matches({ it.metadata["cwsprChatFullResponseLatency"] == "" }, "latency") TODO
- }
- }
-
- @Test
- fun `recordInteractWithMessage - ChatItemVoted`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- sut.recordInteractWithMessage(IncomingCwcMessage.ChatItemVoted(tabId, messageId, "upvote"))
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.UPVOTE)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(event)
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.Upvote.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "startUrl doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `recordInteractWithMessage - FollowupClicked`() {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- runBlocking {
- sut.setResponseHasProjectContext(messageId, true)
- sut.recordInteractWithMessage(IncomingCwcMessage.FollowupClicked(mock(), tabId, messageId, "command", "tabType"))
- }
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.CLICK_FOLLOW_UP)
- customizationArn(customizationArn)
- hasProjectLevelContext(true)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(event)
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.ClickFollowUp.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "startUrl doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == "true" },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `recordInteractWithMessage - CopyCodeToClipboard`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- val codeBlockIndex = 1
- val totalCodeBlocks = 10
-
- sut.recordInteractWithMessage(
- IncomingCwcMessage.CopyCodeToClipboard(
- "command",
- tabId,
- messageId,
- userIntent,
- "println()",
- "insertionTargetType",
- "eventId",
- codeBlockIndex,
- totalCodeBlocks,
- lang
- )
- )
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.COPY_SNIPPET)
- interactionTarget("insertionTargetType")
- acceptedCharacterCount("println()".length)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(event)
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.CopySnippet.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["cwsprChatAcceptedCharactersLength"] == "println()".length.toString() }, "acceptedCharLength doesn't match")
- .matches({ it.metadata["cwsprChatInteractionTarget"] == "insertionTargetType" }, "insertionTargetType doesn't match")
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "startUrl doesn't match")
- .matches({ it.metadata["cwsprChatCodeBlockIndex"] == codeBlockIndex.toString() }, "cwsprChatCodeBlockIndex doesn't match")
- .matches({ it.metadata["cwsprChatTotalCodeBlocks"] == totalCodeBlocks.toString() }, "cwsprChatTotalCodeBlocks doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `recordInteractWithMessage - InsertCodeAtCursorPosition`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
- val codeBlockIndex = 1
- val totalCodeBlocks = 10
- val inserTionTargetType = "insertionTargetType"
- val eventId = "eventId"
- val code = "println()"
-
- sut.recordInteractWithMessage(
- IncomingCwcMessage.InsertCodeAtCursorPosition(
- tabId,
- messageId,
- userIntent,
- code,
- inserTionTargetType,
- emptyList(),
- eventId,
- codeBlockIndex,
- totalCodeBlocks,
- lang
- )
- )
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.INSERT_AT_CURSOR)
- interactionTarget(inserTionTargetType)
- acceptedCharacterCount(code.length)
- acceptedLineCount(code.lines().size)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(event).matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.InsertAtCursor.toString() },
- "interaction type doesn't match"
- )
- .matches(
- { it.metadata["cwsprChatAcceptedCharactersLength"] == code.length.toString() },
- "cwsprChatAcceptedCharactersLength doesn't match"
- )
- .matches(
- { it.metadata["cwsprChatAcceptedNumberOfLines"] == code.lines().size.toString() },
- "cwsprChatAcceptedNumberOfLines doesn't match"
- )
- .matches({ it.metadata["cwsprChatInteractionTarget"] == inserTionTargetType }, "cwsprChatInteractionTarget doesn't match")
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "credentialStartUrl doesn't match")
- .matches({ it.metadata["cwsprChatCodeBlockIndex"] == codeBlockIndex.toString() }, "cwsprChatCodeBlockIndex doesn't match")
- .matches({ it.metadata["cwsprChatTotalCodeBlocks"] == totalCodeBlocks.toString() }, "cwsprChatTotalCodeBlocks doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `recordInteractWithMessage - ClickedLink`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- val link = "https://foo.bar.com"
- sut.recordInteractWithMessage(
- IncomingCwcMessage.ClickedLink(
- LinkType.SourceLink,
- tabId,
- messageId,
- link
- )
- )
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.CLICK_LINK)
- interactionTarget(link)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(event).matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.ClickLink.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["cwsprChatInteractionTarget"] == link }, "cwsprChatInteractionTarget doesn't match")
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "credentialStartUrl doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `recordInteractWithMessage - ChatItemFeedback`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- val selectedOption = "foo"
- val comment = "bar"
-
- sut.recordInteractWithMessage(
- IncomingCwcMessage.ChatItemFeedback(
- tabId,
- selectedOption,
- comment,
- messageId,
- )
- )
-
- // TODO: STE, not implemented yet
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher, times(2)).enqueue(capture())
- val event = firstValue.data.find { it.name == "feedback_result" }
- assertNotNull(event)
- assertThat(event).matches { it.metadata["result"] == "Succeeded" }
- }
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt b/plugins/amazonq/chat/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
deleted file mode 100644
index 83e3d6ebe8a..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/tst-253+/software/aws/toolkits/jetbrains/services/amazonq/TelemetryHelperTest.kt
+++ /dev/null
@@ -1,639 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.amazonq
-
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.SystemInfo
-import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
-import com.intellij.testFramework.fixtures.CodeInsightTestFixture
-import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
-import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl
-import com.intellij.testFramework.registerServiceInstance
-import com.intellij.testFramework.replaceService
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runTest
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeAll
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.stub
-import org.mockito.kotlin.times
-import org.mockito.kotlin.verify
-import software.amazon.awssdk.awscore.DefaultAwsResponseMetadata
-import software.amazon.awssdk.awscore.util.AwsHeader.AWS_REQUEST_ID
-import software.amazon.awssdk.http.SdkHttpResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.ChatInteractWithMessageEvent
-import software.amazon.awssdk.services.codewhispererruntime.model.ChatMessageInteractionType
-import software.amazon.awssdk.services.codewhispererruntime.model.SendTelemetryEventResponse
-import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
-import software.amazon.awssdk.services.ssooidc.SsoOidcClient
-import software.amazon.q.core.telemetry.MetricEvent
-import software.amazon.q.core.telemetry.TelemetryBatcher
-import software.amazon.q.jetbrains.core.MockClientManagerExtension
-import software.amazon.q.jetbrains.core.credentials.LegacyManagedBearerSsoConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager
-import software.amazon.q.jetbrains.core.credentials.pinning.QConnection
-import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES
-import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
-import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.ChatSession
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.ChatRequestData
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.CodeNamesImpl
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FullyQualifiedName
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.FullyQualifiedNames
-import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.TriggerType
-import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.FileContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.FocusAreaContext
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.UICodeSelectionLineRange
-import software.aws.toolkits.jetbrains.services.cwc.editor.context.focusArea.UICodeSelectionRange
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType
-import software.aws.toolkits.jetbrains.services.cwc.messages.IncomingCwcMessage
-import software.aws.toolkits.jetbrains.services.cwc.messages.LinkType
-import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionInfo
-import software.aws.toolkits.jetbrains.services.cwc.storage.ChatSessionStorage
-import software.aws.toolkits.jetbrains.services.telemetry.MockTelemetryServiceExtension
-import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
-import software.aws.toolkits.telemetry.CwsprChatConversationType
-import software.aws.toolkits.telemetry.CwsprChatInteractionType
-import software.aws.toolkits.telemetry.CwsprChatTriggerInteraction
-import software.aws.toolkits.telemetry.CwsprChatUserIntent
-import kotlin.test.assertNotNull
-
-class TelemetryHelperTest {
- private lateinit var myFixture: CodeInsightTestFixture
-
- // sut
- private lateinit var sut: TelemetryHelper
-
- private lateinit var appInitContext: AmazonQAppInitContext
- private lateinit var sessionStorage: ChatSessionStorage
-
- // dependencies
- private lateinit var mockBatcher: TelemetryBatcher
- private lateinit var mockClient: CodeWhispererClientAdaptor
- private lateinit var mockConnectionManager: ToolkitConnectionManager
- private lateinit var mockModelConfigurator: CodeWhispererModelConfigurator
-
- private lateinit var mockConnection: ToolkitConnection
-
- // Manual initialization instead of extensions to control timing
- private val mockClientManager = MockClientManagerExtension()
- private val mockTelemetryService = MockTelemetryServiceExtension()
-
- companion object {
- private const val mockUrl = "mockUrl"
- private const val mockRegion = "us-east-1"
- private const val tabId = "tabId"
- private const val messageId = "messageId"
- private val userIntent = UserIntent.SHOW_EXAMPLES
- private const val conversationId = "conversationId"
- private const val triggerId = "triggerId"
- private const val customizationArn = "customizationArn"
- private const val steRequestId = "sendTelemetryEventRequestId"
- private const val lang = "java"
- private val mockCustomization = CodeWhispererCustomization(customizationArn, "name", "description")
-
- @JvmStatic
- @BeforeAll
- fun allowWindowsPythonPaths() {
- if (SystemInfo.isWindows) {
- VfsRootAccess.allowRootAccess(Disposer.newDisposable(), "C:/Program Files")
- }
- }
- private val data = ChatRequestData(
- tabId = tabId,
- message = "foo",
- activeFileContext = ActiveFileContext(
- FileContext(lang, "~/foo/bar/baz", null),
- FocusAreaContext(
- codeSelection = "",
- codeSelectionRange = UICodeSelectionRange(
- UICodeSelectionLineRange(1, 2),
- UICodeSelectionLineRange(3, 4)
- ),
- trimmedSurroundingFileText = "",
- codeNames = CodeNamesImpl(
- listOf("simpleName_1"),
- FullyQualifiedNames(
- listOf(
- FullyQualifiedName(
- listOf("source_1"),
- listOf("symbol_1")
- )
- )
- )
- )
- )
- ),
- userIntent = UserIntent.IMPROVE_CODE,
- triggerType = TriggerType.Hotkeys,
- customization = mockCustomization,
- relevantTextDocuments = emptyList(),
- useRelevantDocuments = true,
- )
- private val response = ChatMessage(
- tabId = tabId,
- triggerId = triggerId,
- messageType = ChatMessageType.Prompt,
- messageId = messageId,
- followUps = listOf(mock(), mock())
- )
- private val mockSteResponse = SendTelemetryEventResponse.builder()
- .apply {
- this.sdkHttpResponse(
- SdkHttpResponse.builder().build()
- )
- this.responseMetadata(
- DefaultAwsResponseMetadata.create(
- mapOf(AWS_REQUEST_ID to steRequestId)
- )
- )
- }.build()
- }
-
- @BeforeEach
- fun setUp() {
- // Create lightweight test fixture FIRST - this initializes Application
- val factory = IdeaTestFixtureFactory.getFixtureFactory()
- val fixtureBuilder = factory.createLightFixtureBuilder("TelemetryHelperTest")
- myFixture = factory.createCodeInsightFixture(fixtureBuilder.fixture, LightTempDirTestFixtureImpl(true))
- myFixture.setUp()
-
- // NOW manually initialize mocks - Application exists now
- mockClientManager.beforeEach(null)
- mockTelemetryService.beforeEach(null)
-
- // Enable telemetry for tests
- software.aws.toolkits.jetbrains.settings.AwsSettings.getInstance().isTelemetryEnabled = true
-
- // set up sut
- appInitContext = AmazonQAppInitContext(
- project = myFixture.project,
- messagesFromAppToUi = mock(),
- messagesFromUiToApp = mock(),
- messageTypeRegistry = mock(),
- fqnWebviewAdapter = mock()
- )
- val mockSession = mock {
- on { this.conversationId } doReturn conversationId
- }
- sessionStorage = mock {
- on { this.getSession(eq(tabId)) } doReturn ChatSessionInfo(session = mockSession, scope = mock(), history = mutableListOf())
- }
- sut = TelemetryHelper(appInitContext.project, sessionStorage)
-
- // set up client
- mockClientManager.create()
-
- // set up connection
- mockConnection = LegacyManagedBearerSsoConnection(
- mockUrl,
- mockRegion,
- Q_SCOPES,
- mock()
- )
- mockConnectionManager = mock {
- on { activeConnectionForFeature(eq(QConnection.getInstance())) } doReturn mockConnection
- }
- myFixture.project.replaceService(ToolkitConnectionManager::class.java, mockConnectionManager, myFixture.testRootDisposable)
-
- // set up telemetry service
- mockBatcher = mockTelemetryService.batcher()
-
- // set up client
- mockClient = mock()
- myFixture.project.registerServiceInstance(CodeWhispererClientAdaptor::class.java, mockClient)
-
- // set up customization
- mockModelConfigurator = mock {
- on { activeCustomization(myFixture.project) } doReturn mockCustomization
- }
- ApplicationManager.getApplication().registerServiceInstance(CodeWhispererModelConfigurator::class.java, mockModelConfigurator)
- }
-
- @AfterEach
- fun tearDown() {
- // Clean up mocks first
- mockTelemetryService.afterEach(null)
- mockClientManager.afterEach(null)
-
- // Then tear down fixture
- myFixture.tearDown()
- }
-
- @Test
- fun testRecordAddMessage() {
- mockClient.stub {
- on {
- sendChatAddMessageTelemetry(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any())
- } doReturn mockSteResponse
- }
-
- // set up request data
- val responseLength = 10
- val statusCode = 400
- val numberOfCodeBlocks = 1
-
- sut.recordAddMessage(
- data = data,
- response = response,
- responseLength = responseLength,
- statusCode = statusCode,
- numberOfCodeBlocks = numberOfCodeBlocks
- )
-
- // Q STE
- verify(mockClient).sendChatAddMessageTelemetry(
- sessionId = eq(conversationId),
- requestId = eq(messageId),
- userIntent = eq(software.amazon.awssdk.services.codewhispererruntime.model.UserIntent.fromValue(data.userIntent?.name)),
- hasCodeSnippet = any(),
- programmingLanguage = eq(lang),
- activeEditorTotalCharacters = eq(data.activeFileContext.focusAreaContext?.codeSelection?.length),
- timeToFirstChunkMilliseconds = eq(sut.getResponseStreamTimeToFirstChunk(tabId)),
- timeBetweenChunks = eq(sut.getResponseStreamTimeBetweenChunks(tabId)),
- fullResponselatency = any(), // TODO
- requestLength = eq(data.message.length),
- responseLength = eq(responseLength),
- numberOfCodeBlocks = eq(numberOfCodeBlocks),
- hasProjectLevelContext = eq(CodeWhispererSettings.getInstance().isProjectContextEnabled()),
- customization = eq(mockCustomization)
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_addMessage" }
- assertNotNull(event)
- assertThat(requireNotNull(event))
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversation id doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == "messageId" }, "message id doesn't match")
- .matches(
- { it.metadata["cwsprChatTriggerInteraction"] == CwsprChatTriggerInteraction.ContextMenu.toString() },
- "trigger type doesn't match"
- )
- .matches({ it.metadata["cwsprChatUserIntent"] == CwsprChatUserIntent.ImproveCode.toString() }, "user intent doesn't match")
- .matches({
- it.metadata["cwsprChatHasCodeSnippet"] == (
- data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty()
- ?: false
- ).toString()
- }, "has code snippet doesn't match")
- .matches({ it.metadata["cwsprChatProgrammingLanguage"] == "java" }, "language doesn't match")
- .matches(
- { it.metadata["cwsprChatActiveEditorTotalCharacters"] == data.activeFileContext.focusAreaContext?.codeSelection?.length?.toString() },
- "total characters doesn't match"
- )
- .matches(
- {
- it.metadata["cwsprChatActiveEditorImportCount"] ==
- data.activeFileContext.focusAreaContext?.codeNames?.fullyQualifiedNames?.used?.size?.toString()
- },
- "import count doesn't match"
- )
- .matches(
- { it.metadata["cwsprChatResponseCodeSnippetCount"] == numberOfCodeBlocks.toString() },
- "number of code blocks doesn't match"
- )
- .matches({ it.metadata["cwsprChatResponseCode"] == statusCode.toString() }, "response code doesn't match")
- .matches(
- { it.metadata["cwsprChatSourceLinkCount"] == response.relatedSuggestions?.size?.toString() },
- "source link count doesn't match"
- )
- .matches({ it.metadata["cwsprChatFollowUpCount"] == response.followUps?.size?.toString() }, "follow up count doesn't match")
- .matches(
- { it.metadata["cwsprChatTimeToFirstChunk"] == sut.getResponseStreamTimeToFirstChunk(response.tabId).toInt().toString() },
- "time to first chunk doesn't match"
- )
- .matches({
- it.metadata["cwsprChatTimeBetweenChunks"] == "[${
- sut.getResponseStreamTimeBetweenChunks(response.tabId).joinToString(", ")
- }]"
- }, "time between chunks doesn't match")
- .matches({ it.metadata["cwsprChatRequestLength"] == data.message.length.toString() }, "request length doesn't match")
- .matches({ it.metadata["cwsprChatResponseLength"] == responseLength.toString() }, "response length doesn't match")
- .matches(
- { it.metadata["cwsprChatConversationType"] == CwsprChatConversationType.Chat.toString() },
- "conversation type doesn't match"
- )
- .matches({ it.metadata["codewhispererCustomizationArn"] == "customizationArn" }, "user intent doesn't match")
- .matches({
- it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString()
- }, "customization description doesn't match")
-// .matches({ it.metadata["cwsprChatFullResponseLatency"] == "" }, "latency") TODO
- }
- }
-
- @Test
- fun `test recordInteractWithMessage - ChatItemVoted`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- sut.recordInteractWithMessage(IncomingCwcMessage.ChatItemVoted(tabId, messageId, "upvote"))
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.UPVOTE)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(requireNotNull(event))
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.Upvote.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "startUrl doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `test recordInteractWithMessage - FollowupClicked`() {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- runBlocking {
- sut.setResponseHasProjectContext(messageId, true)
- sut.recordInteractWithMessage(IncomingCwcMessage.FollowupClicked(mock(), tabId, messageId, "command", "tabType"))
- }
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.CLICK_FOLLOW_UP)
- customizationArn(customizationArn)
- hasProjectLevelContext(true)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(requireNotNull(event))
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.ClickFollowUp.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "startUrl doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == "true" },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `test recordInteractWithMessage - CopyCodeToClipboard`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- val codeBlockIndex = 1
- val totalCodeBlocks = 10
-
- sut.recordInteractWithMessage(
- IncomingCwcMessage.CopyCodeToClipboard(
- "command",
- tabId,
- messageId,
- userIntent,
- "println()",
- "insertionTargetType",
- "eventId",
- codeBlockIndex,
- totalCodeBlocks,
- lang
- )
- )
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.COPY_SNIPPET)
- interactionTarget("insertionTargetType")
- acceptedCharacterCount("println()".length)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(requireNotNull(event))
- .matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.CopySnippet.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["cwsprChatAcceptedCharactersLength"] == "println()".length.toString() }, "acceptedCharLength doesn't match")
- .matches({ it.metadata["cwsprChatInteractionTarget"] == "insertionTargetType" }, "insertionTargetType doesn't match")
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "startUrl doesn't match")
- .matches({ it.metadata["cwsprChatCodeBlockIndex"] == codeBlockIndex.toString() }, "cwsprChatCodeBlockIndex doesn't match")
- .matches({ it.metadata["cwsprChatTotalCodeBlocks"] == totalCodeBlocks.toString() }, "cwsprChatTotalCodeBlocks doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `test recordInteractWithMessage - InsertCodeAtCursorPosition`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
- val codeBlockIndex = 1
- val totalCodeBlocks = 10
- val inserTionTargetType = "insertionTargetType"
- val eventId = "eventId"
- val code = "println()"
-
- sut.recordInteractWithMessage(
- IncomingCwcMessage.InsertCodeAtCursorPosition(
- tabId,
- messageId,
- userIntent,
- code,
- inserTionTargetType,
- emptyList(),
- eventId,
- codeBlockIndex,
- totalCodeBlocks,
- lang
- )
- )
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.INSERT_AT_CURSOR)
- interactionTarget(inserTionTargetType)
- acceptedCharacterCount(code.length)
- acceptedLineCount(code.lines().size)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(requireNotNull(event)).matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.InsertAtCursor.toString() },
- "interaction type doesn't match"
- )
- .matches(
- { it.metadata["cwsprChatAcceptedCharactersLength"] == code.length.toString() },
- "cwsprChatAcceptedCharactersLength doesn't match"
- )
- .matches(
- { it.metadata["cwsprChatAcceptedNumberOfLines"] == code.lines().size.toString() },
- "cwsprChatAcceptedNumberOfLines doesn't match"
- )
- .matches({ it.metadata["cwsprChatInteractionTarget"] == inserTionTargetType }, "cwsprChatInteractionTarget doesn't match")
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "credentialStartUrl doesn't match")
- .matches({ it.metadata["cwsprChatCodeBlockIndex"] == codeBlockIndex.toString() }, "cwsprChatCodeBlockIndex doesn't match")
- .matches({ it.metadata["cwsprChatTotalCodeBlocks"] == totalCodeBlocks.toString() }, "cwsprChatTotalCodeBlocks doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `test recordInteractWithMessage - ClickedLink`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- val link = "https://foo.bar.com"
- sut.recordInteractWithMessage(
- IncomingCwcMessage.ClickedLink(
- LinkType.SourceLink,
- tabId,
- messageId,
- link
- )
- )
-
- // STE
- verify(mockClient).sendChatInteractWithMessageTelemetry(
- eq(
- ChatInteractWithMessageEvent.builder().apply {
- conversationId(conversationId)
- messageId(messageId)
- interactionType(ChatMessageInteractionType.CLICK_LINK)
- interactionTarget(link)
- customizationArn(customizationArn)
- hasProjectLevelContext(false)
- }.build()
- )
- )
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher).enqueue(capture())
- val event = firstValue.data.find { it.name == "amazonq_interactWithMessage" }
- assertNotNull(event)
- assertThat(requireNotNull(event)).matches({ it.metadata["cwsprChatConversationId"] == conversationId }, "conversationId doesn't match")
- .matches({ it.metadata["cwsprChatMessageId"] == messageId }, "messageId doesn't match")
- .matches(
- { it.metadata["cwsprChatInteractionType"] == CwsprChatInteractionType.ClickLink.toString() },
- "interaction type doesn't match"
- )
- .matches({ it.metadata["cwsprChatInteractionTarget"] == link }, "cwsprChatInteractionTarget doesn't match")
- .matches({ it.metadata["credentialStartUrl"] == mockUrl }, "credentialStartUrl doesn't match")
- .matches(
- { it.metadata["cwsprChatHasProjectContext"] == CodeWhispererSettings.getInstance().isProjectContextEnabled().toString() },
- "hasProjectContext doesn't match"
- )
- }
- }
-
- @Test
- fun `test recordInteractWithMessage - ChatItemFeedback`() = runTest {
- mockClient.stub {
- on { this.sendChatInteractWithMessageTelemetry(any()) } doReturn mockSteResponse
- }
-
- val selectedOption = "foo"
- val comment = "bar"
-
- sut.recordInteractWithMessage(
- IncomingCwcMessage.ChatItemFeedback(
- tabId,
- selectedOption,
- comment,
- messageId,
- )
- )
-
- // TODO: STE, not implemented yet
-
- // Toolkit telemetry
- argumentCaptor {
- verify(mockBatcher, times(2)).enqueue(capture())
- val event = firstValue.data.find { it.name == "feedback_result" }
- assertNotNull(event)
- assertThat(requireNotNull(event)).matches { it.metadata["result"] == "Succeeded" }
- }
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt
deleted file mode 100644
index 01a4856b073..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/AmazonQTestBase.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.amazonq
-
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.project.modules
-import com.intellij.testFramework.DisposableRule
-import com.intellij.testFramework.replaceService
-import org.junit.Before
-import org.junit.Rule
-import org.mockito.kotlin.any
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.whenever
-import software.amazon.q.core.TokenConnectionSettings
-import software.amazon.q.core.credentials.ToolkitBearerTokenProvider
-import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager
-import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider
-import software.amazon.q.jetbrains.utils.rules.CodeInsightTestFixtureRule
-import software.amazon.q.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule
-import software.amazon.q.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
-import software.amazon.q.jetbrains.utils.rules.addModule
-import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient
-
-open class AmazonQTestBase(
- @Rule @JvmField
- val projectRule: CodeInsightTestFixtureRule = JavaCodeInsightTestFixtureRule(),
-) {
- @Rule
- @JvmField
- val disposableRule = DisposableRule()
-
- internal lateinit var project: Project
- internal lateinit var module: Module
- internal lateinit var clientAdaptorSpy: AmazonQStreamingClient
- internal lateinit var toolkitConnectionManager: ToolkitConnectionManager
-
- @Before
- open fun setup() {
- project = projectRule.project
- toolkitConnectionManager = spy(ToolkitConnectionManager.getInstance(project))
-
- val provider = mock()
-
- val mockBearerProvider = mock {
- doReturn(provider).whenever(it).delegate
- }
-
- val connectionSettingsMock = mock {
- whenever(it.tokenProvider).thenReturn(mockBearerProvider)
- }
-
- val toolkitConnection = mock {
- doReturn(connectionSettingsMock).whenever(it).getConnectionSettings()
- }
- doReturn(toolkitConnection).whenever(toolkitConnectionManager).activeConnectionForFeature(any())
-
- project.replaceService(ToolkitConnectionManager::class.java, toolkitConnectionManager, disposableRule.disposable)
-
- clientAdaptorSpy = spy(AmazonQStreamingClient.getInstance(project))
-
- project.replaceService(AmazonQStreamingClient::class.java, clientAdaptorSpy, disposableRule.disposable)
-
- module = project.modules.firstOrNull() ?: if (projectRule is HeavyJavaCodeInsightTestFixtureRule) {
- projectRule.fixture.addModule("module1")
- } else {
- TODO()
- }
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt
deleted file mode 100644
index 182f0d7292d..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.amazonq.clients
-
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.SystemInfo
-import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
-import com.intellij.testFramework.RuleChain
-import com.intellij.testFramework.replaceService
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runTest
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.doAnswer
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.stub
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
-import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStreamingAsyncClient
-import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent
-import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveRequest
-import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveResponseHandler
-import software.amazon.awssdk.services.codewhispererstreaming.model.ValidationException
-import software.amazon.awssdk.services.ssooidc.SsoOidcClient
-import software.amazon.q.core.TokenConnectionSettings
-import software.amazon.q.core.utils.test.aString
-import software.amazon.q.jetbrains.core.MockClientManagerRule
-import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection
-import software.amazon.q.jetbrains.core.credentials.ManagedSsoProfile
-import software.amazon.q.jetbrains.core.credentials.MockCredentialManagerRule
-import software.amazon.q.jetbrains.core.credentials.MockToolkitAuthManagerRule
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager
-import software.aws.toolkits.jetbrains.services.amazonq.AmazonQTestBase
-import java.util.concurrent.CompletableFuture
-
-class AmazonQStreamingClientTest : AmazonQTestBase() {
- val mockClientManagerRule = MockClientManagerRule()
- private val mockCredentialRule = MockCredentialManagerRule()
- private val authManagerRule = MockToolkitAuthManagerRule()
-
- @Rule
- @JvmField
- val ruleChain = RuleChain(projectRule, mockCredentialRule, mockClientManagerRule, disposableRule)
-
- private lateinit var streamingBearerClient: CodeWhispererStreamingAsyncClient
- private lateinit var ssoClient: SsoOidcClient
-
- private lateinit var amazonQStreamingClient: AmazonQStreamingClient
- private lateinit var connectionManager: ToolkitConnectionManager
-
- @Before
- override fun setup() {
- super.setup()
-
- // Allow Python paths on Windows for test environment (Python plugin scans for interpreters)
- if (SystemInfo.isWindows) {
- VfsRootAccess.allowRootAccess(disposableRule.disposable, "C:/Program Files")
- }
-
- amazonQStreamingClient = AmazonQStreamingClient.getInstance(projectRule.project)
- ssoClient = mockClientManagerRule.create()
-
- streamingBearerClient = mockClientManagerRule.create().stub {
- on {
- exportResultArchive(any(), any())
- } doReturn CompletableFuture.completedFuture(mock()) // void type can't be instantiated
- }
-
- val mockConnection = mock()
- whenever(mockConnection.getConnectionSettings()) doReturn mock()
-
- connectionManager = mock {
- on {
- activeConnectionForFeature(any())
- } doReturn authManagerRule.createConnection(ManagedSsoProfile("us-east-1", aString(), listOf("scopes"))) as AwsBearerTokenConnection
- }
-
- projectRule.project.replaceService(ToolkitConnectionManager::class.java, connectionManager, disposableRule.disposable)
- }
-
- @Test
- fun `check exportResultArchive`() = runTest {
- val requestCaptor = argumentCaptor()
- val handlerCaptor = argumentCaptor()
-
- amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
- argumentCaptor().apply {
- verify(streamingBearerClient).exportResultArchive(requestCaptor.capture(), handlerCaptor.capture())
- }
- }
-
- @Test
- fun `verify retry on ValidationException`(): Unit = runBlocking {
- var attemptCount = 0
- streamingBearerClient = mockClientManagerRule.create().stub {
- on {
- exportResultArchive(any(), any())
- } doAnswer {
- attemptCount++
- if (attemptCount <= 2) {
- CompletableFuture.runAsync {
- throw VALIDATION_EXCEPTION
- }
- } else {
- CompletableFuture.completedFuture(mock())
- }
- }
- }
-
- amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
-
- assertThat(attemptCount).isEqualTo(3)
- }
-
- @Test
- fun `verify retry gives up after max attempts`(): Unit = runBlocking {
- var attemptCount = 0
- streamingBearerClient = mockClientManagerRule.create().stub {
- on {
- exportResultArchive(any(), any())
- } doAnswer {
- attemptCount++
- CompletableFuture.runAsync {
- throw VALIDATION_EXCEPTION
- }
- }
- }
-
- val thrown = catchCoroutineException {
- amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
- }
-
- assertThat(attemptCount).isEqualTo(4)
- assertThat(thrown)
- .isInstanceOf(ValidationException::class.java)
- .hasMessage("Resource validation failed")
- }
-
- @Test
- fun `verify no retry on non-retryable exception`(): Unit = runBlocking {
- var attemptCount = 0
-
- streamingBearerClient = mockClientManagerRule.create().stub {
- on {
- exportResultArchive(any(), any())
- } doAnswer {
- attemptCount++
- CompletableFuture.runAsync {
- throw IllegalArgumentException("Non-retryable error")
- }
- }
- }
-
- val thrown = catchCoroutineException {
- amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
- }
-
- assertThat(attemptCount).isEqualTo(1)
- assertThat(thrown)
- .isInstanceOf(IllegalArgumentException::class.java)
- .hasMessage("Non-retryable error")
- }
-
- @Test
- fun `verify backoff timing between retries`(): Unit = runBlocking {
- var lastAttemptTime = 0L
- var minBackoffObserved = Long.MAX_VALUE
- var maxBackoffObserved = 0L
-
- streamingBearerClient = mockClientManagerRule.create().stub {
- on {
- exportResultArchive(any(), any())
- } doAnswer {
- val currentTime = System.currentTimeMillis()
- if (lastAttemptTime > 0) {
- val backoffTime = currentTime - lastAttemptTime
- minBackoffObserved = minOf(minBackoffObserved, backoffTime)
- maxBackoffObserved = maxOf(maxBackoffObserved, backoffTime)
- }
- lastAttemptTime = currentTime
-
- CompletableFuture.runAsync {
- throw VALIDATION_EXCEPTION
- }
- }
- }
-
- val thrown = catchCoroutineException {
- amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
- }
-
- assertThat(thrown)
- .isInstanceOf(ValidationException::class.java)
- .hasMessage("Resource validation failed")
- assertThat(minBackoffObserved).isGreaterThanOrEqualTo(100)
- assertThat(maxBackoffObserved).isLessThanOrEqualTo(10000)
- }
-
- @Test
- fun `verify onError callback is called with final exception`(): Unit = runBlocking {
- var errorCaught: Exception? = null
-
- streamingBearerClient = mockClientManagerRule.create().stub {
- on {
- exportResultArchive(any(), any())
- } doAnswer {
- CompletableFuture.runAsync {
- throw VALIDATION_EXCEPTION
- }
- }
- }
-
- val thrown = catchCoroutineException {
- amazonQStreamingClient.exportResultArchive(
- "test-id",
- ExportIntent.TRANSFORMATION,
- null,
- { errorCaught = it },
- {}
- )
- }
-
- assertThat(thrown)
- .isInstanceOf(ValidationException::class.java)
- .hasMessage("Resource validation failed")
- assertThat(errorCaught).isEqualTo(VALIDATION_EXCEPTION)
- }
-
- private suspend fun catchCoroutineException(block: suspend () -> Unit): Throwable {
- try {
- block()
- error("Expected exception was not thrown")
- } catch (e: Throwable) {
- return e
- }
- }
-
- companion object {
- @JvmStatic
- @BeforeClass
- fun allowWindowsPythonPaths() {
- if (SystemInfo.isWindows) {
- VfsRootAccess.allowRootAccess(Disposer.newDisposable(), "C:/Program Files")
- }
- }
-
- private val VALIDATION_EXCEPTION = ValidationException.builder()
- .message("Resource validation failed")
- .build()
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt
deleted file mode 100644
index 75a5b987e7e..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnectorTest.kt
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.amazonq.webview
-
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import com.intellij.openapi.editor.Document
-import com.intellij.openapi.fileEditor.FileDocumentManager
-import com.intellij.openapi.vfs.LocalFileSystem
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.testFramework.fixtures.CodeInsightTestFixture
-import com.intellij.testFramework.replaceService
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.mockStatic
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
-import software.amazon.q.jetbrains.utils.satisfiesKt
-import software.aws.toolkits.jetbrains.services.amazonq.AmazonQTestBase
-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.util.CodeWhispererConstants
-
-class BrowserConnectorTest : AmazonQTestBase() {
- private lateinit var browserConnector: BrowserConnector
- private lateinit var mockCodeScanManager: CodeWhispererCodeScanManager
- private lateinit var mockLocalFileSystem: LocalFileSystem
- private lateinit var mockFileDocumentManager: FileDocumentManager
- private lateinit var fixture: CodeInsightTestFixture
-
- @Before
- override fun setup() {
- super.setup()
- fixture = projectRule.fixture
-
- mockCodeScanManager = mock()
- mockLocalFileSystem = mock()
- mockFileDocumentManager = mock()
-
- project.replaceService(CodeWhispererCodeScanManager::class.java, mockCodeScanManager, disposableRule.disposable)
-
- browserConnector = spy(BrowserConnector(project = project))
- }
-
- @Test
- fun `parseFindingsMessages should handle no additionalMessages`() {
- browserConnector.parseFindingsMessages("""""")
-
- verify(mockCodeScanManager, never()).addOnDemandIssues(any(), any(), any())
- }
-
- @Test
- fun `parseFindingsMessages should handle null additionalMessages`() {
- browserConnector.parseFindingsMessages(
- """
- {
- "additionalMessages": null
- }
- """.trimIndent()
- )
-
- verify(mockCodeScanManager, never()).addOnDemandIssues(any(), any(), any())
- }
-
- @Test
- fun `parseFindingsMessages should handle empty additionalMessages`() {
- browserConnector.parseFindingsMessages(
- """
- {
- "additionalMessages": []
- }
- """.trimIndent()
- )
-
- verify(mockCodeScanManager, never()).addOnDemandIssues(any(), any(), any())
- }
-
- @Test
- fun `parseFindingsMessages should filter messages with CODE_REVIEW_FINDINGS_SUFFIX`() {
- val findingsMessage = """
- {
- "additionalMessages": [
- {
- "messageId": "test_codeReviewFindings",
- "body": "[{\"filePath\": \"/test/file.kt\", \"issues\": []}]"
- },
- {
- "messageId": "other_message",
- "body": "other content"
- }
- ]
- }
- """.trimIndent()
-
- assertThat(browserConnector.deserializeFindings(findingsMessage))
- .singleElement()
- .satisfiesKt {
- assertThat(it.messageId).isEqualTo("test_codeReviewFindings")
- assertThat(it.body).singleElement()
- .satisfiesKt { finding ->
- assertThat(finding.filePath).isEqualTo("/test/file.kt")
- }
- }
- }
-
- @Test
- fun `parseFindingsMessages should filter messages with DISPLAY_FINDINGS_SUFFIX`() {
- val findingsMessage = """
- {
- "additionalMessages": [
- {
- "messageId": "test_displayFindings",
- "body": "[{\"filePath\": \"/test/file.kt\", \"issues\": []}]"
- },
- {
- "messageId": "other_message",
- "body": "other content"
- }
- ]
- }
- """.trimIndent()
-
- assertThat(browserConnector.deserializeFindings(findingsMessage))
- .singleElement()
- .satisfiesKt {
- assertThat(it.messageId).isEqualTo("test_displayFindings")
- assertThat(it.body).singleElement()
- .satisfiesKt { finding ->
- assertThat(finding.filePath).isEqualTo("/test/file.kt")
- }
- }
- }
-
- @Test
- fun `parseFindingsMessages should process valid findings and verify mappedFindings populated`() {
- val mockVirtualFile = mock {
- on { isDirectory } doReturn false
- }
- val mockDocument = mock {
- on { lineCount } doReturn 5
- on { getLineStartOffset(0) } doReturn 0
- on { getLineEndOffset(0) } doReturn 10
- }
-
- mockStatic(LocalFileSystem::class.java).use { localFileSystemMock ->
- localFileSystemMock.`when` { LocalFileSystem.getInstance() }.thenReturn(mockLocalFileSystem)
- whenever(mockLocalFileSystem.findFileByIoFile(any())) doReturn mockVirtualFile
-
- mockStatic(FileDocumentManager::class.java).use { fileDocumentManagerMock ->
- fileDocumentManagerMock.`when` { FileDocumentManager.getInstance() } doReturn mockFileDocumentManager
- whenever(mockFileDocumentManager.getDocument(mockVirtualFile)) doReturn mockDocument
- whenever(mockCodeScanManager.isIgnoredIssue(any(), any(), any(), any())) doReturn false
-
- val issue = FlareCodeScanIssue(
- startLine = 1, endLine = 1, comment = "Test comment", title = "Test Issue",
- description = Description("Test description", "Test text"), detectorId = "test-detector",
- detectorName = "Test Detector", findingId = "test-finding-id", ruleId = "test-rule",
- relatedVulnerabilities = listOf("CVE-2023-1234"), severity = "HIGH",
- recommendation = Recommendation("Fix this", "https://example.com"),
- suggestedFixes = listOf(SuggestedFix("Fix code", "Fixed code")),
- scanJobId = "test-job-id", language = "kotlin", autoDetected = false,
- filePath = "/test/file.kt", findingContext = "test context"
- )
-
- val aggregatedIssue = AggregatedCodeScanIssue("/test/file.kt", listOf(issue))
- val findingsMessage = """
- {
- "additionalMessages": [
- {
- "messageId": "test_codeReviewFindings",
- "body": "${jacksonObjectMapper().writeValueAsString(listOf(aggregatedIssue)).replace("\"", "\\\"")}"
- },
- {
- "messageId": "other_message",
- "body": "other content"
- }
- ]
- }
- """.trimIndent()
-
- browserConnector.parseFindingsMessages(findingsMessage)
-
- val issuesCaptor = argumentCaptor>()
- verify(mockCodeScanManager).addOnDemandIssues(
- issuesCaptor.capture(),
- any(),
- eq(CodeWhispererConstants.CodeAnalysisScope.AGENTIC)
- )
-
- assertThat(issuesCaptor.firstValue.isNotEmpty())
- assertThat(issuesCaptor.firstValue[0].title == "Test Issue")
- }
- }
- }
-
- @Test
- fun `parseFindingsMessages should skip directory files and not populate mappedFindings`() {
- val mockDirectoryFile = mock { on { isDirectory } doReturn true }
-
- mockStatic(LocalFileSystem::class.java).use { localFileSystemMock ->
- localFileSystemMock.`when` { LocalFileSystem.getInstance() } doReturn mockLocalFileSystem
- whenever(mockLocalFileSystem.findFileByIoFile(any())) doReturn mockDirectoryFile
-
- val issue = FlareCodeScanIssue(
- startLine = 1, endLine = 1, comment = null, title = "Test Issue",
- description = Description("Test description", "Test text"), detectorId = "test-detector",
- detectorName = "Test Detector", findingId = "test-finding-id", ruleId = null,
- relatedVulnerabilities = emptyList(), severity = "MEDIUM",
- recommendation = Recommendation("Fix this", "https://example.com"), suggestedFixes = emptyList(),
- scanJobId = "test-job-id", language = "kotlin", autoDetected = true,
- filePath = "/test/directory", findingContext = "test context"
- )
-
- val aggregatedIssue = AggregatedCodeScanIssue("/test/directory", listOf(issue))
- val findingsMessage = """
- {
- "additionalMessages": [
- {
- "messageId": "test_displayFindings",
- "body": "${jacksonObjectMapper().writeValueAsString(listOf(aggregatedIssue)).replace("\"", "\\\"")}"
- },
- {
- "messageId": "other_message",
- "body": "other content"
- }
- ]
- }
- """.trimIndent()
-
- browserConnector.parseFindingsMessages(findingsMessage)
-
- verify(mockCodeScanManager, never()).addOnDemandIssues(
- any(),
- any(),
- eq(CodeWhispererConstants.CodeAnalysisScope.AGENTIC)
- )
- }
- }
-
- @Test
- fun `parseFindingsMessages should handle invalid JSON gracefully`() {
- val findingsMessage = """
- {
- "additionalMessages": [
- {
- "messageId": "test_codeReviewFindings",
- "body": "invalid json"
- },
- {
- "body": "invalid json"
- }
- ]
- }
- """.trimIndent()
-
- browserConnector.parseFindingsMessages(findingsMessage)
-
- verify(mockCodeScanManager, never()).addOnDemandIssues(any(), any(), any())
- }
-}
diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/workspace/context/WorkspaceTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/workspace/context/WorkspaceTest.kt
deleted file mode 100644
index daee081cff0..00000000000
--- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/workspace/context/WorkspaceTest.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.amazonq.workspace.context
-
-import com.intellij.openapi.vcs.changes.ChangeListManager
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.openapi.vfs.refreshAndFindVirtualDirectory
-import com.intellij.openapi.vfs.refreshAndFindVirtualFile
-import com.intellij.testFramework.LightPlatformTestCase
-import org.mockito.Mockito.mock
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.whenever
-import software.aws.toolkits.jetbrains.services.amazonq.project.additionalGlobalIgnoreRules
-import software.aws.toolkits.jetbrains.services.amazonq.project.additionalGlobalIgnoreRulesForStrictSources
-import software.aws.toolkits.jetbrains.services.amazonq.project.findWorkspaceRoot
-import software.aws.toolkits.jetbrains.services.amazonq.project.isContentInWorkspace
-import software.aws.toolkits.jetbrains.services.amazonq.project.isWorkspaceSourceContent
-import java.nio.file.Files
-import java.nio.file.Path
-import kotlin.io.path.createDirectories
-import kotlin.io.path.deleteExisting
-import kotlin.io.path.exists
-
-class WorkspaceTest : LightPlatformTestCase() {
- private lateinit var tempDir: Path
-
- override fun setUp() {
- super.setUp()
- tempDir = Files.createTempDirectory("workspace-test")
- }
-
- override fun tearDown() {
- if (tempDir.exists()) {
- Files.walk(tempDir)
- .sorted(Comparator.reverseOrder())
- .forEach { it.deleteExisting() }
- }
- super.tearDown()
- }
-
- private fun createDir(relativePath: String): VirtualFile {
- val normalizedPath = relativePath.removePrefix("/")
- return if (normalizedPath.isEmpty()) {
- tempDir.refreshAndFindVirtualDirectory() ?: error("Failed to create directory")
- } else {
- tempDir.resolve(normalizedPath).createDirectories().refreshAndFindVirtualDirectory() ?: error("Failed to create directory")
- }
- }
-
- private fun createFile(path: String): VirtualFile {
- val filePath = tempDir.resolve(path.removePrefix("/"))
- Files.createDirectories(filePath.parent)
- return Files.createFile(filePath).refreshAndFindVirtualFile() ?: error("Failed to create file")
- }
-
- fun `testFindWorkspaceRoot returns null when no projects`() {
- assertNull(findWorkspaceRoot(emptySet()))
- }
-
- fun `test findWorkspaceRoot returns project path for single project`() {
- val projectPath = createDir("test/project")
-
- assertEquals(projectPath, findWorkspaceRoot(setOf(projectPath)))
- }
-
- fun `test findWorkspaceRoot returns project path for project with inner modules`() {
- val path1 = createDir("test/projects/project1")
- val path2 = createDir("test/projects/project1/module")
-
- assertEquals(path1, findWorkspaceRoot(setOf(path1, path2)))
- }
-
- fun `test findWorkspaceRoot returns common root of multiple projects`() {
- val path1 = createDir("test/projects/project1")
- val path2 = createDir("test/projects/project2")
-
- assertEquals(createDir("test/projects"), findWorkspaceRoot(setOf(path1, path2)))
- }
-
- fun `test findWorkspaceRoot returns common root of a project and external modules`() {
- val projectPath = createDir("test/project")
- val modulePath = createDir("test/external/module")
-
- assertEquals(createDir("test"), findWorkspaceRoot(setOf(projectPath, modulePath)))
- }
-
- fun `test isContentInWorkspace returns false when workspace has no directories`() {
- assertFalse(isContentInWorkspace(createDir("any/path"), emptySet()))
- }
-
- fun `test isContentInWorkspace returns true for path in project`() {
- val projectPath = createDir("test/project")
- val testPath = createFile("test/project/src/file.txt")
-
- assertTrue(isContentInWorkspace(testPath, setOf(projectPath)))
- }
-
- fun `test isContentInWorkspace returns true for path in external module`() {
- val projectPath = createDir("test/project")
- val modulePath = createDir("test/external/module")
-
- val testPath = createFile("test/external/module/src/file.txt")
-
- assertTrue(isContentInWorkspace(testPath, setOf(projectPath, modulePath)))
- }
-
- fun `test isContentInWorkspace returns false for path outside project and modules`() {
- val projectPath = createDir("test/project")
- val modulePath = createDir("test/external/module")
-
- val testPath = createFile("other/path/file.txt")
-
- assertFalse(isContentInWorkspace(testPath, setOf(projectPath, modulePath)))
- }
-
- fun `test isWorkspaceSourceContent returns true for non-ignored file or directory with default ignore rules`() {
- val projectPath = createDir("test/project")
- val directoryPath = createDir("test/project/module")
- val codeFilePath = createDir("test/project/module/example.java")
- val nonCodeFilePath = createDir("test/project/module/example.bin")
-
- val changeListManager = mock().apply {
- whenever(isIgnoredFile(directoryPath)) doReturn false
- }
-
- assertTrue(
- isWorkspaceSourceContent(
- directoryPath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRules
- )
- )
-
- assertTrue(
- isWorkspaceSourceContent(
- codeFilePath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRules
- )
- )
-
- assertTrue(
- isWorkspaceSourceContent(
- nonCodeFilePath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRules
- )
- )
-
- assertTrue(
- isWorkspaceSourceContent(
- directoryPath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRulesForStrictSources
- )
- )
-
- assertTrue(
- isWorkspaceSourceContent(
- codeFilePath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRulesForStrictSources
- )
- )
- }
-
- fun `test isWorkspaceSourceContent returns false for ignored file or directory with default ignore rules`() {
- val projectPath = createDir("test/project")
- val directoryPath = createDir("test/project/node_modules")
- val nonCodeFilePath = createDir("test/project/module/example.bin")
-
- val changeListManager = mock().apply {
- whenever(isIgnoredFile(directoryPath)) doReturn false
- }
-
- assertFalse(
- isWorkspaceSourceContent(
- directoryPath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRules
- )
- )
-
- assertFalse(
- isWorkspaceSourceContent(
- directoryPath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRulesForStrictSources
- )
- )
-
- assertFalse(
- isWorkspaceSourceContent(
- nonCodeFilePath,
- setOf(projectPath),
- changeListManager,
- additionalGlobalIgnoreRulesForStrictSources
- )
- )
- }
-}
diff --git a/plugins/amazonq/codetransform/build.gradle.kts b/plugins/amazonq/codetransform/build.gradle.kts
deleted file mode 100644
index 416518f734e..00000000000
--- a/plugins/amazonq/codetransform/build.gradle.kts
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-plugins {
- id("toolkit-jvm-conventions")
-}
-
-dependencies {
- implementation(project(":plugin-amazonq:codetransform:jetbrains-community"))
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts b/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts
deleted file mode 100644
index 2fa6891f734..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/build.gradle.kts
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-import software.aws.toolkits.gradle.intellij.IdeFlavor
-
-plugins {
- id("toolkit-intellij-subplugin")
-}
-
-intellijToolkit {
- ideFlavor.set(IdeFlavor.IC)
-}
-
-dependencies {
- implementation(project(":plugin-core-q"))
-
- implementation(project(":plugin-amazonq:shared:jetbrains-community"))
- // hack because transform has a chat entrypoint
- implementation(project(":plugin-amazonq:chat:jetbrains-community"))
- // hack because everything references codewhisperer
- implementation(project(":plugin-amazonq:codewhisperer:jetbrains-community"))
-
- compileOnly(project(":plugin-core-q:jetbrains-community"))
-
- testImplementation(testFixtures(project(":plugin-core-q:jetbrains-community")))
-}
-
-// hack because our test structure currently doesn't make complete sense
-tasks.prepareTestSandbox {
- val pluginXmlJar = project(":plugin-amazonq").tasks.jar
-
- dependsOn(pluginXmlJar)
- from(pluginXmlJar) {
- into(intellijPlatform.projectName.map { "$it/lib" })
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/codetransform-ext-java.xml b/plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/codetransform-ext-java.xml
deleted file mode 100644
index d47d6b047d9..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/codetransform-ext-java.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/plugin-codetransform.xml b/plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/plugin-codetransform.xml
deleted file mode 100644
index f303ef1ecb3..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/resources/META-INF/plugin-codetransform.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- com.intellij.modules.java
- org.jetbrains.plugins.gradle
-
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt
deleted file mode 100644
index fd7a8a2396a..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt
+++ /dev/null
@@ -1,448 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.notification.NotificationAction
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.fileEditor.FileEditorManager
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vcs.changes.ChangeListManager
-import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor
-import com.intellij.openapi.vcs.changes.patch.ApplyPatchDifferentiatedDialog
-import com.intellij.openapi.vcs.changes.patch.ApplyPatchMode
-import com.intellij.openapi.vcs.changes.patch.ImportToShelfExecutor
-import com.intellij.openapi.vfs.LocalFileSystem
-import com.intellij.openapi.vfs.VirtualFile
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType
-import software.amazon.awssdk.services.ssooidc.model.SsoOidcException
-import software.amazon.q.core.utils.error
-import software.amazon.q.core.utils.exists
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.info
-import software.amazon.q.jetbrains.core.coroutines.EDT
-import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope
-import software.amazon.q.jetbrains.core.credentials.sso.bearer.NoTokenInitializedException
-import software.amazon.q.jetbrains.utils.notifyStickyInfo
-import software.amazon.q.jetbrains.utils.notifyStickyWarn
-import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW
-import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_EXPIRED
-import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
-import software.aws.toolkits.jetbrains.services.codemodernizer.constants.getDownloadedArtifactTextFromType
-import software.aws.toolkits.jetbrains.services.codemodernizer.controller.CodeTransformChatHelper
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatMessageContent
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatMessageType
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformFailureBuildLog
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadArtifactResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.ParseZipFailureReason
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.UnzipFailureReason
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilArtifactDir
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isValidCodeTransformConnection
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.openTroubleshootingGuideNotificationAction
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.zipToPath
-import software.aws.toolkits.resources.message
-import software.aws.toolkits.telemetry.CodeTransformArtifactType
-import java.io.File
-import java.nio.file.Path
-import java.time.Instant
-import java.util.concurrent.atomic.AtomicBoolean
-
-const val DOWNLOAD_PROXY_WILDCARD_ERROR: String = "Dangling meta character '*' near index 0"
-const val DOWNLOAD_SSL_HANDSHAKE_ERROR: String = "Unable to execute HTTP request: javax.net.ssl.SSLHandshakeException"
-const val INVALID_ARTIFACT_ERROR: String = "Invalid artifact"
-
-class ArtifactHandler(
- private val project: Project,
- private val clientAdaptor: GumbyClient,
- private val codeTransformChatHelper: CodeTransformChatHelper? = null,
-) {
- private val telemetry = CodeTransformTelemetryManager.getInstance(project)
- private val downloadedArtifacts = mutableMapOf()
- private val downloadedSummaries = mutableMapOf()
- private val downloadedBuildLogPath = mutableMapOf()
- private var isCurrentlyDownloading = AtomicBoolean(false)
-
- internal suspend fun displayDiff(job: JobId) {
- if (isCurrentlyDownloading.get()) return
- when (val result = downloadArtifact(job, TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS)) {
- is DownloadArtifactResult.Success -> {
- if (result.artifact !is CodeModernizerArtifact) return notifyUnableToApplyPatch("")
- displayDiffUsingPatch(result.artifact.patch, job)
- }
- is DownloadArtifactResult.ParseZipFailure -> notifyUnableToApplyPatch(result.failureReason.errorMessage)
- is DownloadArtifactResult.UnzipFailure -> notifyUnableToApplyPatch(result.failureReason.errorMessage)
- is DownloadArtifactResult.DownloadFailure -> notifyUnableToDownload(result.failureReason)
- is DownloadArtifactResult.Skipped -> {}
- }
- }
-
- suspend fun downloadHilArtifact(jobId: JobId, artifactId: String, tmpDir: File): CodeTransformHilDownloadArtifact? {
- val downloadResultsResponse = clientAdaptor.downloadExportResultArchive(jobId, artifactId)
-
- return try {
- val tmpPath = tmpDir.toPath()
- val (downloadZipFilePath, _) = zipToPath(downloadResultsResponse, tmpPath)
- LOG.info { "Successfully converted the hil artifact download to a zip at ${downloadZipFilePath.toAbsolutePath()}." }
- CodeTransformHilDownloadArtifact.create(downloadZipFilePath, getPathToHilArtifactDir(tmpPath))
- } catch (e: Exception) {
- // In case if unzip or file operations fail
- val errorMessage = "Unexpected error when saving downloaded hil artifact: ${e.localizedMessage}"
- telemetry.error(errorMessage)
- LOG.error { errorMessage }
- null
- }
- }
-
- /**
- * Downloads an artifact and returns a [DownloadArtifactResult]
- * [DownloadArtifactResult.Success] indicates success when downloading and processing artifact
- * [DownloadArtifactResult.DownloadFailure] indicates failure when downloading artifact
- * [DownloadArtifactResult.ParseZipFailure] indicates failure when parsing artifact contents
- * [DownloadArtifactResult.UnzipFailure] indicates failure when unzipping artifact contents to disk
- * [DownloadArtifactResult.Skipped] indicates a silent failure that should be skipped as [isPreFetch] is set
- */
- suspend fun downloadArtifact(
- job: JobId,
- artifactType: TransformationDownloadArtifactType,
- isPreFetch: Boolean = false,
- ): DownloadArtifactResult {
- isCurrentlyDownloading.set(true)
- val downloadStartTime = Instant.now()
- try {
- // 1. Attempt reusing previously downloaded artifact for job
- val previousArtifact = if (artifactType == TransformationDownloadArtifactType.LOGS) {
- downloadedBuildLogPath.getOrDefault(job, null)
- } else {
- downloadedArtifacts.getOrDefault(job, null)
- }
- if (previousArtifact != null && previousArtifact.exists()) {
- val zipPath = previousArtifact.toAbsolutePath().toString()
- return try {
- if (artifactType == TransformationDownloadArtifactType.LOGS) {
- DownloadArtifactResult.Success(CodeTransformFailureBuildLog.create(zipPath), zipPath)
- } else {
- val artifact = CodeModernizerArtifact.create(zipPath)
- downloadedSummaries[job] = artifact.summary
- DownloadArtifactResult.Success(artifact, zipPath)
- }
- } catch (e: RuntimeException) {
- LOG.error { e.message.toString() }
- DownloadArtifactResult.ParseZipFailure(ParseZipFailureReason(artifactType, e.message.orEmpty()))
- }
- }
-
- // 2. Download the data
- LOG.info { "Verifying user is authenticated prior to download" }
- if (!isValidCodeTransformConnection(project)) {
- CodeModernizerManager.getInstance(project).handleResumableDownloadArtifactFailure(job)
- return DownloadArtifactResult.DownloadFailure(DownloadFailureReason.CREDENTIALS_EXPIRED(artifactType))
- }
-
- LOG.info { "About to download the export result archive" }
- // only notify if downloading client instructions (upgraded code)
- if (artifactType == TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS) {
- notifyDownloadStart()
- }
- val downloadResultsResponse = if (artifactType == TransformationDownloadArtifactType.LOGS) {
- clientAdaptor.downloadExportResultArchive(job, null, TransformationDownloadArtifactType.LOGS)
- } else {
- clientAdaptor.downloadExportResultArchive(job)
- }
-
- // 3. Convert to zip
- LOG.info { "Downloaded the export result archive, about to transform to zip" }
- val path: Path
- val totalDownloadBytes: Int
- val zipPath: String
- try {
- val result = zipToPath(downloadResultsResponse)
- path = result.first
- totalDownloadBytes = result.second
- zipPath = path.toAbsolutePath().toString()
- LOG.info { "Successfully converted the download to a zip at $zipPath." }
- } catch (e: Exception) {
- LOG.error { e.message.toString() }
- return DownloadArtifactResult.UnzipFailure(UnzipFailureReason(artifactType, e.message.orEmpty()))
- }
-
- // 4. Deserialize zip
- var telemetryErrorMessage: String? = null
- return try {
- val output = if (artifactType == TransformationDownloadArtifactType.LOGS) {
- DownloadArtifactResult.Success(CodeTransformFailureBuildLog.create(zipPath), zipPath)
- } else {
- DownloadArtifactResult.Success(CodeModernizerArtifact.create(zipPath), zipPath)
- }
- if (artifactType == TransformationDownloadArtifactType.LOGS) {
- downloadedBuildLogPath[job] = path
- } else {
- downloadedArtifacts[job] = path
- if (output.artifact is CodeModernizerArtifact && output.artifact.metrics != null) {
- output.artifact.metrics.linesOfCodeSubmitted = CodeModernizerSessionState.getInstance(project).getLinesOfCodeSubmitted()
- output.artifact.metrics.programmingLanguage = CodeModernizerSessionState.getInstance(project).getTransformationLanguage()
- try {
- clientAdaptor.sendTransformTelemetryEvent(job, output.artifact.metrics)
- } catch (e: Exception) {
- // log error, but can still show diff.patch and summary.md
- LOG.error { e.message.toString() }
- telemetryErrorMessage = "Unexpected error when sending telemetry with metrics ${e.localizedMessage}"
- }
- }
- }
- output
- } catch (e: RuntimeException) {
- LOG.error { e.message.toString() }
- LOG.error { "Unable to find patch for file: $zipPath" }
- telemetryErrorMessage = "Unexpected error when downloading result ${e.localizedMessage}"
- DownloadArtifactResult.ParseZipFailure(ParseZipFailureReason(artifactType, e.message.orEmpty()))
- } finally {
- telemetry.downloadArtifact(mapArtifactTypes(artifactType), downloadStartTime, job, totalDownloadBytes, telemetryErrorMessage)
- }
- } catch (e: Exception) {
- if (isPreFetch) return DownloadArtifactResult.Skipped
- return when {
- e is SsoOidcException || e is NoTokenInitializedException -> {
- CodeModernizerManager.getInstance(project).handleResumableDownloadArtifactFailure(job)
- DownloadArtifactResult.DownloadFailure(DownloadFailureReason.CREDENTIALS_EXPIRED(artifactType))
- }
- e.message.toString().contains(DOWNLOAD_PROXY_WILDCARD_ERROR) ->
- DownloadArtifactResult.DownloadFailure(DownloadFailureReason.PROXY_WILDCARD_ERROR(artifactType))
- e.message.toString().contains(DOWNLOAD_SSL_HANDSHAKE_ERROR) ->
- DownloadArtifactResult.DownloadFailure(DownloadFailureReason.SSL_HANDSHAKE_ERROR(artifactType))
-
- e.message.toString().contains(INVALID_ARTIFACT_ERROR) ->
- DownloadArtifactResult.DownloadFailure(DownloadFailureReason.INVALID_ARTIFACT(artifactType))
-
- else -> DownloadArtifactResult.DownloadFailure(DownloadFailureReason.OTHER(artifactType, e.message.orEmpty()))
- }
- } finally {
- isCurrentlyDownloading.set(false)
- }
- }
-
- /**
- * Opens the built-in patch dialog to display the diff and allowing users to apply the changes locally.
- */
- internal suspend fun displayDiffUsingPatch(
- patchFile: VirtualFile,
- jobId: JobId,
- ) {
- withContext(EDT) {
- val dialog = ApplyPatchDifferentiatedDialog(
- project,
- ApplyPatchDefaultExecutor(project),
- listOf(ImportToShelfExecutor(project)),
- ApplyPatchMode.APPLY,
- patchFile,
- null,
- ChangeListManager.getInstance(project)
- .addChangeList(
- patchFile.name,
- ""
- ),
- null,
- null,
- null,
- false,
- )
- dialog.isModal = true
-
- if (dialog.showAndGet()) {
- telemetry.submitSelection("Submit", jobId.toString())
- val resultContent = CodeTransformChatMessageContent(
- type = CodeTransformChatMessageType.PendingAnswer,
- message = message("codemodernizer.chat.message.changes_applied"),
- )
- codeTransformChatHelper?.updateLastPendingMessage(resultContent)
- } else {
- telemetry.submitSelection("Cancel", jobId.toString())
- }
- }
- }
-
- fun notifyUnableToDownload(error: DownloadFailureReason) {
- // Inform about failure
- LOG.error { "Unable to download artifact: $error" }
- CodeTransformMessageListener.instance.onDownloadFailure(error)
-
- val artifactText = getDownloadedArtifactTextFromType(error.artifactType)
-
- // Display notification balloon if applicable
- when (error) {
- is DownloadFailureReason.PROXY_WILDCARD_ERROR -> notifyStickyWarn(
- message("codemodernizer.notification.warn.view_diff_failed.title"),
- message("codemodernizer.notification.warn.download_failed_wildcard.content", error),
- project,
- )
-
- is DownloadFailureReason.SSL_HANDSHAKE_ERROR -> notifyStickyWarn(
- message("codemodernizer.notification.warn.view_diff_failed.title"),
- message("codemodernizer.notification.warn.download_failed_ssl.content", error),
- project,
- )
-
- is DownloadFailureReason.CREDENTIALS_EXPIRED -> {
- // Inform chat that reauth is required
- CodeTransformMessageListener.instance.onCheckAuth()
-
- // Since chat content is reset on reauth, inform users with notification balloon
- notifyStickyWarn(
- message("codemodernizer.notification.warn.expired_credentials.title"),
- message("codemodernizer.notification.warn.download_failed_expired_credentials.content"),
- project,
- listOf(
- NotificationAction.createSimpleExpiring(message("codemodernizer.notification.warn.action.reauthenticate")) {
- CodeTransformMessageListener.instance.onReauthStarted()
- }
- )
- )
- }
-
- is DownloadFailureReason.INVALID_ARTIFACT -> {
- if (error.artifactType == TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS) {
- notifyStickyWarn(
- message("codemodernizer.notification.warn.download_failed_client_instructions_expired"),
- CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_EXPIRED,
- )
- } else {
- notifyStickyWarn(
- message("codemodernizer.notification.warn.download_failed_invalid_artifact", artifactText),
- CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_EXPIRED,
- )
- }
- }
-
- is DownloadFailureReason.OTHER -> {
- notifyStickyWarn(
- message("codemodernizer.notification.warn.download_failed_other.content", artifactText, error.errorMessage),
- CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW,
- )
- }
- }
- }
-
- private fun notifyDownloadStart() {
- notifyStickyInfo(
- message("codemodernizer.notification.info.download.started.title"),
- message("codemodernizer.notification.info.download.started.content"),
- project,
- )
- }
-
- fun notifyUnableToApplyPatch(errorMessage: String) = notifyStickyWarn(
- message("codemodernizer.notification.warn.view_diff_failed.title"),
- message("codemodernizer.notification.warn.view_diff_failed.content", errorMessage),
- project,
- listOf(
- openTroubleshootingGuideNotificationAction(
- CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW
- )
- ),
- )
-
- private fun notifyUnableToShowSummary() {
- LOG.error { "Unable to display summary" }
- notifyStickyWarn(
- message("codemodernizer.notification.warn.view_summary_failed.title"),
- message("codemodernizer.notification.warn.view_summary_failed.content"),
- project,
- listOf(
- openTroubleshootingGuideNotificationAction(
- CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW
- )
- ),
- )
- }
-
- private fun notifyUnableToShowBuildLog() {
- LOG.error { "Unable to display build log" }
- notifyStickyWarn(
- message("codemodernizer.notification.warn.view_build_log_failed.title"),
- message("codemodernizer.notification.warn.view_build_log_failed.content"),
- project,
- listOf(
- openTroubleshootingGuideNotificationAction(
- CODE_TRANSFORM_TROUBLESHOOT_DOC_DOWNLOAD_ERROR_OVERVIEW
- )
- ),
- )
- }
-
- fun displayDiffAction(jobId: JobId) = runReadAction {
- projectCoroutineScope(project).launch {
- displayDiff(jobId)
- }
- }
-
- fun getSummary(job: JobId) = downloadedSummaries[job]
-
- private fun showSummaryFromFile(summaryFile: File) {
- val summaryMarkdownVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(summaryFile)
- if (summaryMarkdownVirtualFile != null) {
- runInEdt {
- FileEditorManager.getInstance(project).openFile(summaryMarkdownVirtualFile, true)
- }
- }
- }
-
- fun showTransformationSummary(job: JobId) {
- if (isCurrentlyDownloading.get()) return
- runReadAction {
- projectCoroutineScope(project).launch {
- when (val result = downloadArtifact(job, TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS)) {
- is DownloadArtifactResult.Success -> {
- if (result.artifact !is CodeModernizerArtifact) return@launch notifyUnableToShowSummary()
- showSummaryFromFile(result.artifact.summaryMarkdownFile)
- }
- is DownloadArtifactResult.ParseZipFailure, is DownloadArtifactResult.UnzipFailure -> notifyUnableToShowSummary()
- is DownloadArtifactResult.DownloadFailure -> notifyUnableToDownload(result.failureReason)
- is DownloadArtifactResult.Skipped -> {}
- }
- }
- }
- }
-
- fun showBuildLog(job: JobId) {
- if (isCurrentlyDownloading.get()) return
- runReadAction {
- projectCoroutineScope(project).launch {
- when (val result = downloadArtifact(job, TransformationDownloadArtifactType.LOGS)) {
- is DownloadArtifactResult.Success -> {
- if (result.artifact !is CodeTransformFailureBuildLog) return@launch notifyUnableToShowBuildLog()
- val buildLogVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(result.artifact.logFile)
- if (buildLogVirtualFile != null) {
- runInEdt {
- FileEditorManager.getInstance(project).openFile(buildLogVirtualFile, true)
- }
- }
- }
-
- is DownloadArtifactResult.ParseZipFailure, is DownloadArtifactResult.UnzipFailure -> notifyUnableToShowBuildLog()
- is DownloadArtifactResult.DownloadFailure -> notifyUnableToDownload(result.failureReason)
- is DownloadArtifactResult.Skipped -> {}
- }
- }
- }
- }
-
- private fun mapArtifactTypes(artifactType: TransformationDownloadArtifactType): CodeTransformArtifactType =
- when (artifactType) {
- TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS -> CodeTransformArtifactType.ClientInstructions
- TransformationDownloadArtifactType.LOGS -> CodeTransformArtifactType.Logs
- TransformationDownloadArtifactType.UNKNOWN_TO_SDK_VERSION -> CodeTransformArtifactType.Unknown
- TransformationDownloadArtifactType.GENERATED_CODE -> CodeTransformArtifactType.Unknown
- }
-
- companion object {
- val LOG = getLogger()
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt
deleted file mode 100644
index e64b71c6a39..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt
+++ /dev/null
@@ -1,945 +0,0 @@
-// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.notification.NotificationAction
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.components.PersistentStateComponent
-import com.intellij.openapi.components.RoamingType
-import com.intellij.openapi.components.State
-import com.intellij.openapi.components.Storage
-import com.intellij.openapi.components.service
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.projectRoots.JavaSdkVersion
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.io.FileUtil.createTempDirectory
-import com.intellij.openapi.vfs.LocalFileSystem
-import com.intellij.openapi.wm.ToolWindowManager
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationJob
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationPlan
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus
-import software.amazon.q.core.utils.error
-import software.amazon.q.core.utils.exists
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.info
-import software.amazon.q.core.utils.warn
-import software.amazon.q.jetbrains.core.coroutines.projectCoroutineScope
-import software.amazon.q.jetbrains.utils.notifyStickyError
-import software.amazon.q.jetbrains.utils.notifyStickyInfo
-import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_MVN_FAILURE
-import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESHOOT_DOC_PROJECT_SIZE
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener
-import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
-import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_POM_FILE_NAME
-import software.aws.toolkits.jetbrains.services.codemodernizer.file.PomFileAnnotator
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerException
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerStartJobResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CustomerSelection
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.Dependency
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.InvalidTelemetryReason
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_CONFIGURATION_FILE_NAME
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenDependencyReportCommandsResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerState
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.StateFlags
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.buildState
-import software.aws.toolkits.jetbrains.services.codemodernizer.toolwindow.CodeModernizerBottomToolWindowFactory
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.STATES_WHERE_PLAN_EXIST
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.createFileCopy
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.findLineNumberByString
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getJavaModulesWithSQL
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getMavenVersion
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getModuleOrProjectNameForFile
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilArtifactPomFile
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilDependencyReport
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilDependencyReportDir
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getSupportedBuildFilesWithSupportedJdk
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getSupportedJavaMappings
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getSupportedModules
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isGradleProject
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.openTroubleshootingGuideNotificationAction
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseBuildFile
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseXmlDependenciesReport
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.setDependencyVersionInPom
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.tryGetJdk
-import software.aws.toolkits.jetbrains.ui.feedback.CodeTransformFeedbackDialog
-import software.aws.toolkits.resources.message
-import software.aws.toolkits.telemetry.CodeTransformBuildSystem
-import software.aws.toolkits.telemetry.CodeTransformCancelSrcComponents
-import software.aws.toolkits.telemetry.CodeTransformPreValidationError
-import java.io.File
-import java.nio.file.Path
-import java.time.Instant
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.io.path.pathString
-
-@State(name = "codemodernizerStates", storages = [Storage("amazonq.xml", roamingType = RoamingType.PER_OS)])
-class CodeModernizerManager(private val project: Project) : PersistentStateComponent, Disposable {
- private val telemetry = CodeTransformTelemetryManager.getInstance(project)
- private var managerState = CodeModernizerState()
- val codeModernizerBottomWindowPanelManager by lazy { CodeModernizerBottomWindowPanelManager(project) }
- private val codeModernizerBottomWindowPanelContent by lazy {
- val contentManager = getBottomToolWindow().contentManager
- contentManager.removeAllContents(true)
- contentManager.factory.createContent(
- codeModernizerBottomWindowPanelManager,
- message("codemodernizer.toolwindow.scan_display"),
- false,
- ).also {
- Disposer.register(contentManager, it)
- }
- }
- private val supportedBuildFileNames = listOf(MAVEN_CONFIGURATION_FILE_NAME)
- private val isModernizationInProgress = AtomicBoolean(false)
- private val isResumingJob = AtomicBoolean(false)
- private val isMvnRunning = AtomicBoolean(false)
- private val isJobSuccessfullyResumed = AtomicBoolean(false)
-
- private val transformationStoppedByUsr = AtomicBoolean(false)
- var codeTransformationSession: CodeModernizerSession? = null
- set(session) {
- if (session != null) {
- Disposer.register(this, session)
- }
- field = session
- }
- private val artifactHandler = ArtifactHandler(project, GumbyClient.getInstance(project))
- private val supportedJavaMappings = mapOf(
- JavaSdkVersion.JDK_1_8 to setOf(JavaSdkVersion.JDK_17, JavaSdkVersion.JDK_21),
- JavaSdkVersion.JDK_11 to setOf(JavaSdkVersion.JDK_17, JavaSdkVersion.JDK_21),
- JavaSdkVersion.JDK_17 to setOf(JavaSdkVersion.JDK_17, JavaSdkVersion.JDK_21),
- JavaSdkVersion.JDK_21 to setOf(JavaSdkVersion.JDK_21),
- )
-
- init {
- CodeModernizerSessionState.getInstance(project).setDefaults()
- initQRegionProfileSelectedListener()
- }
-
- private fun initQRegionProfileSelectedListener() {
- project.messageBus.connect(this).subscribe(
- QRegionProfileSelectedListener.TOPIC,
- object : QRegionProfileSelectedListener {
- override fun onProfileSelected(project: Project, profile: QRegionProfile?) {
- stopModernize()
- codeTransformationSession?.let {
- Disposer.dispose(it)
- }
- managerState = CodeModernizerState()
- codeTransformationSession = null
- }
- }
- )
- }
-
- fun validate(project: Project, transformationType: CodeTransformType): ValidationResult {
- fun validateCore(project: Project): ValidationResult {
- if (!isCodeTransformAvailable(project)) {
- return ValidationResult(
- false,
- InvalidTelemetryReason(
- CodeTransformPreValidationError.NonSsoLogin,
- )
- )
- }
-
- if (ProjectRootManager.getInstance(project).contentRoots.isEmpty()) {
- return ValidationResult(
- false,
- InvalidTelemetryReason(
- CodeTransformPreValidationError.EmptyProject,
- )
- )
- }
-
- if (transformationType == CodeTransformType.SQL_CONVERSION) {
- val javaModules = project.getJavaModulesWithSQL()
- return if (javaModules.isNotEmpty()) {
- ValidationResult(
- true,
- metadata = "found ${javaModules.size} modules with SQL"
- )
- } else {
- ValidationResult(
- false,
- InvalidTelemetryReason(
- CodeTransformPreValidationError.NoJavaProject,
- )
- )
- }
- }
-
- val supportedModules = project.getSupportedModules(supportedJavaMappings).toSet()
- val validProjectJdk = project.getSupportedJavaMappings(supportedJavaMappings).isNotEmpty()
- val projectJdk = project.tryGetJdk()
- if (supportedModules.isEmpty() && !validProjectJdk) {
- return ValidationResult(
- false,
- InvalidTelemetryReason(
- CodeTransformPreValidationError.UnsupportedJavaVersion,
- projectJdk.toString()
- )
- )
- }
- val validatedBuildFiles = project.getSupportedBuildFilesWithSupportedJdk(supportedBuildFileNames, supportedJavaMappings)
- return if (validatedBuildFiles.isNotEmpty()) {
- ValidationResult(
- true,
- validatedBuildFiles = validatedBuildFiles,
- buildSystem = CodeTransformBuildSystem.Maven,
- buildSystemVersion = getMavenVersion(project)
- )
- } else {
- ValidationResult(
- false,
- invalidTelemetryReason = InvalidTelemetryReason(
- CodeTransformPreValidationError.UnsupportedBuildSystem,
- if (isGradleProject(project)) "Gradle build" else "other build"
- ),
- buildSystem = if (isGradleProject(project)) CodeTransformBuildSystem.Gradle else CodeTransformBuildSystem.Unknown
- )
- }
- }
-
- val result = validateCore(project)
-
- telemetry.validateProject(result)
-
- return result
- }
-
- /**
- * The initial landing UI for the results view panel.
- * This method adds code content to the problems view if not already added.
- * When [setSelected] is true, code scan panel is set to be in focus.
- */
- fun addCodeModernizeUI(setSelected: Boolean = false, moduleOrProjectNameForFile: String? = null) = runInEdt {
- val appModernizerBottomWindow = getBottomToolWindow()
- appModernizerBottomWindow.isAvailable = true
- if (!appModernizerBottomWindow.contentManager.contents.contains(codeModernizerBottomWindowPanelContent)) {
- appModernizerBottomWindow.contentManager.addContent(codeModernizerBottomWindowPanelContent)
- }
- codeModernizerBottomWindowPanelContent.displayName = message("codemodernizer.toolwindow.scan_display")
- if (moduleOrProjectNameForFile == null) {
- appModernizerBottomWindow.stripeTitle = message("codemodernizer.toolwindow.label_no_job")
- } else {
- appModernizerBottomWindow.stripeTitle = message("codemodernizer.toolwindow.label", moduleOrProjectNameForFile)
- }
-
- if (setSelected) {
- appModernizerBottomWindow.contentManager.setSelectedContent(codeModernizerBottomWindowPanelContent)
- appModernizerBottomWindow.show()
- }
- }
-
- /**
- * @description Main function for triggering the start of elastic gumby migration.
- */
- fun initModernizationJobUI(shouldOpenBottomWindowOnStart: Boolean = true, moduleOrProjectNameForFile: String) {
- isModernizationInProgress.set(true)
- // Initialize the bottom toolkit window with content
- addCodeModernizeUI(shouldOpenBottomWindowOnStart, moduleOrProjectNameForFile)
- codeModernizerBottomWindowPanelManager.setJobStartingUI()
- }
-
- fun stopModernize() {
- if (isModernizationJobActive()) {
- userInitiatedStopCodeModernization()
- telemetry.jobIsCancelledByUser(CodeTransformCancelSrcComponents.DevToolsStopButton)
- }
- }
-
- fun runModernize(copyResult: MavenCopyCommandsResult? = null) {
- initStopParameters()
- val session = codeTransformationSession ?: return
- initModernizationJobUI(true, project.getModuleOrProjectNameForFile(session.sessionContext.configurationFile))
- launchModernizationJob(session, copyResult)
- }
-
- suspend fun resumePollingFromHil() {
- val transformationType =
- if (codeTransformationSession?.sessionContext?.sqlMetadataZip != null) CodeTransformType.SQL_CONVERSION else CodeTransformType.LANGUAGE_UPGRADE
- val result = handleJobResumedFromHil(managerState.getLatestJobId(), codeTransformationSession as CodeModernizerSession, transformationType)
- postModernizationJob(result)
- }
-
- private fun initStopParameters() {
- transformationStoppedByUsr.set(false)
- CodeModernizerSessionState.getInstance(project).currentJobStatus = TransformationStatus.UNKNOWN_TO_SDK_VERSION
- CodeModernizerSessionState.getInstance(project).currentJobCreationTime = Instant.MIN
- CodeModernizerSessionState.getInstance(project).currentJobStopTime = Instant.MIN
- }
-
- private fun notifyJobFailure(failureReason: String?, actions: Collection = listOf()) {
- val reason = failureReason ?: message("codemodernizer.notification.info.modernize_failed.unknown_failure_reason") // should not happen
- notifyStickyInfo(
- message("codemodernizer.notification.info.modernize_failed.title"),
- reason,
- project,
- listOf(displayFeedbackNotificationAction(), *actions.toTypedArray())
- )
- }
-
- internal fun notifyTransformationStopped() {
- notifyStickyInfo(
- message("codemodernizer.notification.info.transformation_stop.title"),
- message("codemodernizer.notification.info.transformation_stop.content"),
- project,
- listOf(displayFeedbackNotificationAction())
- )
- }
-
- internal fun notifyUnableToResumeJob() {
- notifyStickyInfo(
- message("codemodernizer.notification.info.transformation_resume.title"),
- message("codemodernizer.notification.info.transformation_resume.content"),
- project,
- listOf(displayFeedbackNotificationAction())
- )
- }
-
- internal fun notifyTransformationStartStopping() {
- notifyStickyInfo(
- message("codemodernizer.notification.info.transformation_start_stopping.title"),
- message("codemodernizer.notification.info.transformation_start_stopping.content"),
- project,
- )
- }
-
- internal fun notifyTransformationFailedToStop() {
- notifyStickyError(
- message("codemodernizer.notification.info.transformation_start_stopping.failed_title"),
- message("codemodernizer.notification.info.transformation_start_stopping.failed_content"),
- project,
- listOf(displayFeedbackNotificationAction())
- )
- }
-
- fun launchModernizationJob(session: CodeModernizerSession, copyResult: MavenCopyCommandsResult?) = projectCoroutineScope(project).launch {
- val result = initModernizationJob(session, copyResult)
-
- postModernizationJob(result)
- }
-
- fun resumeJob(session: CodeModernizerSession, lastJobId: JobId, currentJobResult: TransformationJob) = projectCoroutineScope(project).launch {
- isJobSuccessfullyResumed.set(true)
- CodeTransformMessageListener.instance.onTransformResuming()
- if (isModernizationJobActive()) {
- runInEdt { getBottomToolWindow().show() }
- return@launch
- }
- try {
- val plan = if (currentJobResult.status() in STATES_WHERE_PLAN_EXIST) {
- try {
- delay(1000)
- session.fetchPlan(lastJobId).transformationPlan()
- } catch (_: Exception) {
- null
- }
- } else {
- null
- }
- CodeModernizerSessionState.getInstance(project).currentJobCreationTime = currentJobResult.creationTime()
- codeTransformationSession = session
- initModernizationJobUI(false, project.getModuleOrProjectNameForFile(session.sessionContext.configurationFile))
- val transformationType = if (session.sessionContext.sqlMetadataZip != null) CodeTransformType.SQL_CONVERSION else CodeTransformType.LANGUAGE_UPGRADE
- codeModernizerBottomWindowPanelManager.setResumeJobUI(currentJobResult, plan, session.sessionContext.sourceJavaVersion, transformationType)
- session.resumeJob(currentJobResult.creationTime(), lastJobId)
- val result = handleJobStarted(lastJobId, session)
- postModernizationJob(result)
- } catch (e: Exception) {
- notifyUnableToResumeJob()
- LOG.error(e) { e.message.toString() }
- return@launch
- }
- }
-
- fun setJobOngoing(jobId: JobId, sessionContext: CodeModernizerSessionContext) {
- isModernizationInProgress.set(true)
- managerState = buildState(sessionContext, true, jobId)
- }
-
- fun setJobNotOngoing() {
- isModernizationInProgress.set(false)
- managerState.flags[StateFlags.IS_ONGOING] = false
- }
-
- fun isJobOngoingInState() = managerState.flags.getOrDefault(StateFlags.IS_ONGOING, false)
-
- fun handleLocalMavenBuildResult(mavenCopyCommandsResult: MavenCopyCommandsResult) {
- codeTransformationSession?.setLastMvnBuildResult(mavenCopyCommandsResult)
- // Send IDE notifications first
- if (mavenCopyCommandsResult is MavenCopyCommandsResult.Failure) {
- notifyStickyInfo(
- message("codemodernizer.notification.warn.maven_failed.title"),
- message("codemodernizer.notification.warn.maven_failed.content"),
- project,
- listOf(openTroubleshootingGuideNotificationAction(CODE_TRANSFORM_TROUBLESHOOT_DOC_MVN_FAILURE), displayFeedbackNotificationAction()),
- )
- } else if (mavenCopyCommandsResult is MavenCopyCommandsResult.NoJdk) {
- notifyStickyInfo(
- message("codemodernizer.notification.warn.maven_failed.title"),
- message("codemodernizer.notification.warn.validation.no_jdk"),
- project,
- listOf(openTroubleshootingGuideNotificationAction(CODE_TRANSFORM_TROUBLESHOOT_DOC_MVN_FAILURE), displayFeedbackNotificationAction()),
- )
- }
-
- CodeTransformMessageListener.instance.onMavenBuildResult(mavenCopyCommandsResult)
- }
-
- fun runLocalMavenBuild(project: Project, session: CodeModernizerSession) {
- projectCoroutineScope(project).launch {
- isMvnRunning.set(true)
- val result = session.getDependenciesUsingMaven()
- isMvnRunning.set(false)
- handleLocalMavenBuildResult(result)
- }
- }
-
- fun parseBuildFile(): String? = parseBuildFile(codeTransformationSession?.sessionContext?.configurationFile)
-
- private suspend fun initModernizationJob(session: CodeModernizerSession, copyResult: MavenCopyCommandsResult?): CodeModernizerJobCompletedResult =
- when (val result = session.createModernizationJob(copyResult)) {
- is CodeModernizerStartJobResult.ZipCreationFailed -> {
- CodeModernizerJobCompletedResult.UnableToCreateJob(
- message("codemodernizer.notification.warn.zip_creation_failed", result.reason),
- false,
- )
- }
-
- is CodeModernizerStartJobResult.UnableToStartJob -> {
- CodeModernizerJobCompletedResult.UnableToCreateJob(
- message("codemodernizer.notification.warn.unable_to_start_job", result.exception),
- true,
- )
- }
-
- is CodeModernizerStartJobResult.ZipUploadFailed -> {
- CodeModernizerJobCompletedResult.ZipUploadFailed(
- result.reason
- )
- }
-
- is CodeModernizerStartJobResult.Started -> {
- handleJobStarted(result.jobId, session)
- }
-
- is CodeModernizerStartJobResult.Disposed -> {
- CodeModernizerJobCompletedResult.ManagerDisposed
- }
-
- is CodeModernizerStartJobResult.Cancelled -> {
- CodeModernizerJobCompletedResult.JobAbortedBeforeStarting
- }
-
- is CodeModernizerStartJobResult.CancelledMissingDependencies -> {
- CodeModernizerJobCompletedResult.JobAbortedMissingDependencies
- }
-
- is CodeModernizerStartJobResult.CancelledZipTooLarge -> {
- CodeModernizerJobCompletedResult.JobAbortedZipTooLarge
- }
- }
-
- private suspend fun handleJobResumedFromHil(
- jobId: JobId,
- session: CodeModernizerSession,
- transformType: CodeTransformType,
- ): CodeModernizerJobCompletedResult = session.pollUntilJobCompletion(
- transformType,
- jobId
- ) { new, plan ->
- codeModernizerBottomWindowPanelManager.handleJobTransition(new, plan, session.sessionContext, transformType)
- }
-
- private suspend fun handleJobStarted(jobId: JobId, session: CodeModernizerSession): CodeModernizerJobCompletedResult {
- setJobOngoing(jobId, session.sessionContext)
- // Init the splitter panel to show progress and progress steps
- // https://plugins.jetbrains.com/docs/intellij/general-threading-rules.html#write-access
- ApplicationManager.getApplication().invokeLater {
- codeModernizerBottomWindowPanelManager.setJobRunningUI()
- }
-
- val transformType = if (session.sessionContext.sqlMetadataZip != null) CodeTransformType.SQL_CONVERSION else CodeTransformType.LANGUAGE_UPGRADE
-
- return session.pollUntilJobCompletion(transformType, jobId) { new, plan ->
- codeModernizerBottomWindowPanelManager.handleJobTransition(new, plan, session.sessionContext, transformType)
- }
- }
-
- private fun postModernizationJob(result: CodeModernizerJobCompletedResult) {
- codeTransformationSession?.setLastTransformResult(result)
-
- if (result is CodeModernizerJobCompletedResult.ManagerDisposed) {
- return
- }
-
- if (result is CodeModernizerJobCompletedResult.JobPaused) {
- codeTransformationSession?.setHilDownloadArtifactId(result.downloadArtifactId)
-
- CodeTransformMessageListener.instance.onStartingHil()
-
- return
- }
-
- // https://plugins.jetbrains.com/docs/intellij/general-threading-rules.html#write-access
- ApplicationManager.getApplication().invokeLater {
- setJobNotOngoing()
- if (!transformationStoppedByUsr.get()) {
- informUserOfCompletion(result)
- codeModernizerBottomWindowPanelManager.setJobFinishedUI(result)
- CodeTransformMessageListener.instance.onTransformResult(result)
- } else {
- codeModernizerBottomWindowPanelManager.userInitiatedStopCodeModernizationUI()
- notifyTransformationStopped()
- transformationStoppedByUsr.set(false)
- CodeTransformMessageListener.instance.onTransformStopped()
- }
- }
- }
-
- /**
- * Silently try to resume the job, informs users only when job successfully resumed, suppresses exceptions.
- */
- fun tryResumeJob() = projectCoroutineScope(project).launch {
- try {
- val notYetResumed = isResumingJob.compareAndSet(false, true)
- // If the job is already running, compareAndSet will return false because the expected
- // behavior is that the job is not running when trying to resume
- if (!notYetResumed) {
- return@launch
- }
-
- LOG.info { "Attempting to resume job, current state is: $managerState" }
- if (!managerState.flags.getOrDefault(StateFlags.IS_ONGOING, false)) return@launch
-
- val context = managerState.toSessionContext(project)
- val session = CodeModernizerSession(context)
- val lastJobId = managerState.getLatestJobId()
- LOG.info { "Attempting to resume job with id $lastJobId" }
- val result = session.getJobDetails(lastJobId)
- when (result.status()) {
- TransformationStatus.COMPLETED -> {
- resumeJob(session, lastJobId, result)
- setJobNotOngoing()
- }
-
- TransformationStatus.PARTIALLY_COMPLETED -> {
- resumeJob(session, lastJobId, result)
- setJobNotOngoing()
- }
-
- TransformationStatus.UNKNOWN_TO_SDK_VERSION -> {
- notifyStickyInfo(
- message("codemodernizer.notification.warn.on_resume.unknown_status_response.title"),
- message("codemodernizer.notification.warn.on_resume.unknown_status_response.content"),
- )
- setJobNotOngoing()
- }
-
- TransformationStatus.STOPPED, TransformationStatus.STOPPING -> {
- // If user stopped the last job, there is no need for us to resume the job
- setJobNotOngoing()
- }
-
- else -> {
- resumeJob(session, lastJobId, result)
- notifyStickyInfo(
- message("codemodernizer.manager.job_ongoing_title"),
- message("codemodernizer.manager.job_ongoing_content"),
- project,
- listOf(resumeJobNotificationAction(session, lastJobId, result)),
- )
- }
- }
- telemetry.jobIsResumedAfterIdeClose(lastJobId, result.status())
- } catch (e: AccessDeniedException) {
- LOG.error { "Unable to resume job as credentials are invalid" }
- // User is logged in with old or invalid credentials, nothing to do until they log in with valid credentials
- } catch (e: Exception) {
- LOG.error(e) { "Unable to resume job as an unexpected exception occurred" }
- } finally {
- isResumingJob.set(false)
- }
- }
-
- private fun resumeJobNotificationAction(session: CodeModernizerSession, lastJobId: JobId, currentJobResult: TransformationJob) =
- NotificationAction.createSimple(message("codemodernizer.notification.info.modernize_ongoing.view_status")) {
- resumeJob(session, lastJobId, currentJobResult)
- }
-
- private fun displaySummaryNotificationAction(jobId: JobId) =
- NotificationAction.createSimple(message("codemodernizer.notification.info.modernize_complete.view_summary")) {
- artifactHandler.showTransformationSummary(jobId)
- }
-
- private fun displayFeedbackNotificationAction() =
- NotificationAction.createSimple(message("codemodernizer.notification.warn.submit_feedback")) {
- CodeTransformFeedbackDialog(project).showAndGet()
- }
-
- private fun informUserOfCompletion(result: CodeModernizerJobCompletedResult) {
- var jobId: JobId? = null
- when (result) {
- is CodeModernizerJobCompletedResult.UnableToCreateJob -> notifyJobFailure(
- result.failureReason
- )
-
- is CodeModernizerJobCompletedResult.ZipUploadFailed -> {
- if (result.failureReason is UploadFailureReason.CREDENTIALS_EXPIRED) {
- setJobNotOngoing()
- CodeTransformMessageListener.instance.onCheckAuth()
- notifyJobFailure(
- message("codemodernizer.notification.warn.upload_failed_expired_credentials.content"),
- listOf(
- NotificationAction.createSimpleExpiring(message("codemodernizer.notification.warn.action.reauthenticate")) {
- CodeTransformMessageListener.instance.onReauthStarted()
- }
- )
- )
- } else {
- notifyJobFailure(
- message("codemodernizer.notification.warn.upload_failed", result.failureReason.toString()),
- )
- }
- }
-
- is CodeModernizerJobCompletedResult.RetryableFailure -> notifyJobFailure(
- result.failureReason
- )
-
- is CodeModernizerJobCompletedResult.JobFailed -> notifyJobFailure(
- result.failureReason
- )
-
- is CodeModernizerJobCompletedResult.JobFailedInitialBuild -> notifyStickyInfo(
- message("codemodernizer.builderrordialog.description.title"),
- result.failureReason,
- project,
- listOf(displayFeedbackNotificationAction())
- )
-
- is CodeModernizerJobCompletedResult.JobPartiallySucceeded -> {
- notifyStickyInfo(
- message("codemodernizer.notification.info.modernize_partial_complete.title"),
- message("codemodernizer.notification.info.modernize_partial_complete.content"),
- project,
- listOf(displaySummaryNotificationAction(result.jobId), displayFeedbackNotificationAction()),
- )
- jobId = result.jobId
- }
-
- is CodeModernizerJobCompletedResult.JobCompletedSuccessfully -> {
- notifyStickyInfo(
- message("codemodernizer.notification.info.modernize_complete.title"),
- message("codemodernizer.notification.info.modernize_complete.content"),
- project,
- listOf(displaySummaryNotificationAction(result.jobId)),
- )
- jobId = result.jobId
- }
-
- is CodeModernizerJobCompletedResult.ManagerDisposed -> LOG.warn { "Manager disposed" }
- is CodeModernizerJobCompletedResult.JobAbortedBeforeStarting -> LOG.warn { "Job was aborted" }
- is CodeModernizerJobCompletedResult.JobAbortedMissingDependencies -> notifyStickyInfo(
- message("codemodernizer.notification.warn.maven_failed.title"),
- message("codemodernizer.notification.warn.maven_failed.content"),
- project,
- listOf(openTroubleshootingGuideNotificationAction(CODE_TRANSFORM_TROUBLESHOOT_DOC_MVN_FAILURE), displayFeedbackNotificationAction()),
- )
- is CodeModernizerJobCompletedResult.JobAbortedZipTooLarge -> notifyStickyInfo(
- message("codemodernizer.notification.warn.zip_too_large.title"),
- message("codemodernizer.notification.warn.zip_too_large.content"),
- project,
- listOf(openTroubleshootingGuideNotificationAction(CODE_TRANSFORM_TROUBLESHOOT_DOC_PROJECT_SIZE), displayFeedbackNotificationAction()),
- )
- is CodeModernizerJobCompletedResult.Stopped -> notifyStickyInfo(
- message("codemodernizer.notification.info.transformation_stop.title"),
- message("codemodernizer.notification.info.transformation_stop.content"),
- project,
- listOf(displayFeedbackNotificationAction())
- )
-
- is CodeModernizerJobCompletedResult.JobPaused -> return
- }
- telemetry.totalRunTime(result.toString(), jobId)
- }
-
- fun createCodeModernizerSession(customerSelection: CustomerSelection, project: Project) {
- codeTransformationSession = CodeModernizerSession(
- CodeModernizerSessionContext(
- project = project,
- configurationFile = customerSelection.configurationFile,
- sourceJavaVersion = customerSelection.sourceJavaVersion,
- targetJavaVersion = customerSelection.targetJavaVersion,
- sourceVendor = customerSelection.sourceVendor,
- targetVendor = customerSelection.targetVendor,
- sourceServerName = customerSelection.sourceServerName,
- sqlMetadataZip = customerSelection.sqlMetadataZip,
- ),
- )
- }
-
- fun showModernizationProgressUI() = codeModernizerBottomWindowPanelManager.showUnalteredJobUI()
-
- fun showPreviousJobHistoryUI() {
- codeModernizerBottomWindowPanelManager.setPreviousJobHistoryUI(isModernizationJobActive())
- }
-
- fun userInitiatedStopCodeModernization() {
- notifyTransformationStartStopping()
- codeTransformationSession?.hilCleanup()
- if (transformationStoppedByUsr.getAndSet(true)) return
- val currentId = codeTransformationSession?.getActiveJobId()
- projectCoroutineScope(project).launch {
- try {
- val success = codeTransformationSession?.stopTransformation(currentId?.id) ?: true // no session -> no job to stop
- if (!success) {
- // This should not happen
- throw CodeModernizerException(message("codemodernizer.notification.info.transformation_start_stopping.as_no_response"))
- }
- } catch (e: Exception) {
- LOG.error(e) { e.message.toString() }
- notifyTransformationFailedToStop()
- } finally {
- telemetry.totalRunTime("JobCancelled", currentId)
- }
- }
- }
-
- fun isModernizationJobResuming(): Boolean = isResumingJob.get()
-
- fun isModernizationJobStopping(): Boolean = transformationStoppedByUsr.get()
-
- fun isModernizationJobActive(): Boolean = isModernizationInProgress.get()
-
- fun isRunningMvn(): Boolean = isMvnRunning.get()
-
- fun isJobSuccessfullyResumed(): Boolean = isJobSuccessfullyResumed.get()
-
- fun getLastMvnBuildResult(): MavenCopyCommandsResult? = codeTransformationSession?.getLastMvnBuildResult()
-
- fun getLastTransformResult(): CodeModernizerJobCompletedResult? = codeTransformationSession?.getLastTransformResult()
-
- fun getCurrentHilArtifact(): CodeTransformHilDownloadArtifact? = codeTransformationSession?.getHilDownloadArtifact()
-
- fun getBottomToolWindow() = ToolWindowManager.getInstance(project).getToolWindow(CodeModernizerBottomToolWindowFactory.id)
- ?: error(message("codemodernizer.toolwindow.problems_window_not_found"))
-
- fun getMvnBuildWindow() = ToolWindowManager.getInstance(project).getToolWindow("Run")
- ?: error(message("codemodernizer.toolwindow.problems_mvn_window_not_found"))
-
- override fun getState(): CodeModernizerState = CodeModernizerState().apply {
- lastJobContext.putAll(managerState.lastJobContext)
- flags.putAll(managerState.flags)
- }
-
- override fun loadState(state: CodeModernizerState) {
- managerState.lastJobContext.clear()
- managerState.lastJobContext.putAll(state.lastJobContext)
- managerState.flags.clear()
- managerState.flags.putAll(state.flags)
- }
-
- fun getTransformationPlan(): TransformationPlan? = codeTransformationSession?.getTransformationPlan()
- fun getTransformationSummary(): TransformationSummary? {
- val job = codeTransformationSession?.getActiveJobId() ?: return null
- return artifactHandler.getSummary(job)
- }
-
- companion object {
- fun getInstance(project: Project): CodeModernizerManager = project.service()
- val LOG = getLogger()
- }
-
- override fun dispose() {}
- fun showTransformationSummary() {
- val job = codeTransformationSession?.getActiveJobId() ?: return
- artifactHandler.showTransformationSummary(job)
- }
-
- fun showTransformationPlan() {
- codeTransformationSession?.tryOpenTransformationPlanEditor()
- }
-
- fun handleCredentialsChanged() {
- codeTransformationSession?.dispose()
- codeModernizerBottomWindowPanelManager.reset()
- isModernizationInProgress.set(false)
- }
-
- suspend fun getArtifactForHil(): CodeTransformHilDownloadArtifact? {
- if (codeTransformationSession?.getHilDownloadArtifact() != null) {
- return codeTransformationSession?.getHilDownloadArtifact()
- }
-
- val jobId = codeTransformationSession?.getActiveJobId() ?: return null
- val downloadArtifactId = codeTransformationSession?.getHilDownloadArtifactId() ?: return null
-
- val tmpDir = try {
- createTempDirectory("", null)
- } catch (e: Exception) {
- val errorMessage = "Unexpected error when creating tmp dir for HIL: ${e.localizedMessage}"
- LOG.error { errorMessage }
- return null
- }
- try {
- val hilArtifact = artifactHandler.downloadHilArtifact(jobId, downloadArtifactId, tmpDir)
- if (hilArtifact != null) {
- codeTransformationSession?.setHilTempDirectoryPath(tmpDir.toPath())
- codeTransformationSession?.setHilDownloadArtifact(hilArtifact)
- }
- return hilArtifact
- } catch (e: Exception) {
- return null
- }
- }
-
- fun createDependencyReport(hilDownloadArtifact: CodeTransformHilDownloadArtifact): MavenDependencyReportCommandsResult {
- val tmpDirPath = codeTransformationSession?.getHilTempDirectoryPath() ?: return MavenDependencyReportCommandsResult.Failure
-
- try {
- val copyPomForDependencyReport = createFileCopy(hilDownloadArtifact.pomFile, getPathToHilDependencyReportDir(tmpDirPath).resolve(HIL_POM_FILE_NAME))
- setDependencyVersionInPom(copyPomForDependencyReport, hilDownloadArtifact.manifest.sourcePomVersion)
- } catch (e: Exception) {
- val errorMessage = "Unexpected error when preparing for HIL dependency report: ${e.localizedMessage}"
- telemetry.error(errorMessage)
- LOG.error { errorMessage }
- return MavenDependencyReportCommandsResult.Failure
- }
-
- return codeTransformationSession?.createHilDependencyReportUsingMaven() as MavenDependencyReportCommandsResult
- }
-
- fun findAvailableVersionForDependency(pomGroupId: String, pomArtifactId: String): Dependency? {
- try {
- val hilTempDirPath = codeTransformationSession?.getHilTempDirectoryPath()
- ?: throw CodeModernizerException("Cannot get HIL temp directory")
-
- val report = parseXmlDependenciesReport(
- getPathToHilDependencyReport(hilTempDirPath)
- )
- return report.dependencies?.first {
- it.groupId == pomGroupId &&
- it.artifactId == pomArtifactId
- }
- } catch (e: NoSuchElementException) {
- val errorMessage = "No available versions found when parsing HIL dependency report: ${e.localizedMessage}"
- telemetry.error(errorMessage)
- LOG.error { errorMessage }
- return null
- } catch (e: Exception) {
- val errorMessage = "Unexpected error when parsing HIL dependency report: ${e.localizedMessage}"
- telemetry.error(errorMessage)
- LOG.error { errorMessage }
- return null
- }
- }
-
- fun rejectHil() {
- try {
- codeTransformationSession?.rejectHilAndContinue()
- } catch (e: Exception) {
- throw e
- } finally {
- // DO clean up
- codeTransformationSession?.hilCleanup()
- }
- }
-
- fun copyDependencyForHil(selectedVersion: String): MavenCopyCommandsResult {
- try {
- val session = codeTransformationSession ?: throw CodeModernizerException("Cannot get the current session")
-
- val tmpDirPath = session.getHilTempDirectoryPath()
- ?: throw CodeModernizerException("Cannot get HIL temp directory")
-
- val downloadedPomPath = getPathToHilArtifactPomFile(tmpDirPath)
- if (!downloadedPomPath.exists()) {
- return MavenCopyCommandsResult.Failure
- }
-
- val downloadedPomFile = File(downloadedPomPath.pathString)
- setDependencyVersionInPom(downloadedPomFile, selectedVersion)
-
- val copyDependencyResult = session.copyHilDependencyUsingMaven()
- return copyDependencyResult
- } catch (e: Exception) {
- val errorMessage = "Unexpected error when getting HIL dependency for upload: ${e.localizedMessage}"
- telemetry.error(errorMessage)
- LOG.error { errorMessage }
- return MavenCopyCommandsResult.Failure
- }
- }
-
- suspend fun tryResumeWithAlternativeVersion(selectedVersion: String) {
- try {
- val zipCreationResult = codeTransformationSession?.createHilUploadZip(selectedVersion)
- if (zipCreationResult?.payload?.exists() == true) {
- codeTransformationSession?.uploadHilPayload(zipCreationResult.payload)
-
- // Add delay between upload complete and trying to resume
- delay(500)
-
- codeTransformationSession?.resumeTransformation()
- } else {
- throw CodeModernizerException("Cannot create dependency zip for HIL")
- }
- } catch (e: Exception) {
- val errorMessage = "Unexpected error when resuming HIL: ${e.localizedMessage}"
- telemetry.error(errorMessage)
- LOG.error { errorMessage }
- } finally {
- // DO file clean up
- codeTransformationSession?.hilCleanup()
- }
- }
-
- fun showHilPomFileAnnotation(): Boolean {
- val sourceVersion = codeTransformationSession?.getHilDownloadArtifact()?.manifest?.sourcePomVersion as String
- val dependencyReportDirPath = getPathToHilDependencyReportDir(codeTransformationSession?.getHilTempDirectoryPath() as Path)
-
- val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(dependencyReportDirPath.resolve(HIL_POM_FILE_NAME).toFile())
- if (virtualFile != null) {
- val lineNumberToHighlight = findLineNumberByString(virtualFile, "$sourceVersion")
- val pomFileAnnotator = PomFileAnnotator(project, virtualFile, lineNumberToHighlight)
- pomFileAnnotator.showCustomEditor() // opens editor using Edt thread
- } else {
- return false
- }
-
- return true
- }
-
- /**
- * When customer attempts to download an artifact and it fails for some reason (credential expiry etc)
- * we need to be able to resume the job in order for customers to be able to reattempt the download.
- * This sets the job as ongoing in the persistent state so that when tryResumeJob is triggered the
- * IDE attempts to resume the job.
- */
- fun handleResumableDownloadArtifactFailure(job: JobId) {
- // handle the case when user clicks long living notification but has a new job running
- val session = this.codeTransformationSession ?: return
- if (session.getActiveJobId() != job) return
- setJobOngoing(job, session.sessionContext)
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt
deleted file mode 100644
index 7a3ddd1b6cf..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt
+++ /dev/null
@@ -1,656 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.util.Disposer
-import com.intellij.serviceContainer.AlreadyDisposedException
-import com.intellij.util.io.HttpRequests
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.withContext
-import org.apache.commons.codec.digest.DigestUtils
-import software.amazon.awssdk.core.exception.SdkClientException
-import software.amazon.awssdk.services.codewhispererruntime.model.ResumeTransformationResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.StartTransformationResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationJob
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationLanguage
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationPlan
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationProgressUpdateStatus
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationUserActionStatus
-import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext
-import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType
-import software.amazon.awssdk.services.ssooidc.model.SsoOidcException
-import software.amazon.q.core.utils.Waiters.waitUntil
-import software.amazon.q.core.utils.error
-import software.amazon.q.core.utils.exists
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.info
-import software.amazon.q.core.utils.warn
-import software.amazon.q.jetbrains.core.coroutines.getCoroutineBgContext
-import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerException
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerStartJobResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadArtifactResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenDependencyReportCommandsResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.ZipCreationResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.plan.CodeModernizerPlanEditorProvider
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.STATES_AFTER_INITIAL_BUILD
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.STATES_AFTER_STARTED
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.calculateTotalLatency
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getModuleOrProjectNameForFile
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilDependencyReportDir
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isPlanComplete
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isValidCodeTransformConnection
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.pollTransformationStatusAndPlan
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.toTransformationLanguage
-import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanSession
-import software.aws.toolkits.resources.message
-import java.io.File
-import java.io.FileInputStream
-import java.io.IOException
-import java.net.ConnectException
-import java.net.SocketTimeoutException
-import java.net.UnknownHostException
-import java.nio.file.Path
-import java.time.Duration
-import java.time.Instant
-import java.util.Base64
-import java.util.concurrent.CancellationException
-import java.util.concurrent.atomic.AtomicBoolean
-import javax.net.ssl.SSLHandshakeException
-
-const val MAX_ZIP_SIZE = 2000000000 // 2GB
-
-// constants for handling SDKClientException
-const val CONNECTION_REFUSED_ERROR: String = "Connection refused"
-const val SSL_HANDSHAKE_ERROR: String = "Unable to execute HTTP request: PKIX path building failed"
-
-class CodeModernizerSession(
- val sessionContext: CodeModernizerSessionContext,
- private val initialPollingSleepDurationMillis: Long = 2000,
- private val totalPollingSleepDurationMillis: Long = 5000,
-) : Disposable {
- private val clientAdaptor = GumbyClient.getInstance(sessionContext.project)
- private val state = CodeModernizerSessionState.getInstance(sessionContext.project)
- private val isDisposed = AtomicBoolean(false)
- private val shouldStop = AtomicBoolean(false)
- private val telemetry = CodeTransformTelemetryManager.getInstance(sessionContext.project)
- private val artifactHandler = ArtifactHandler(sessionContext.project, GumbyClient.getInstance(sessionContext.project))
-
- private var mvnBuildResult: MavenCopyCommandsResult? = null
- private var transformResult: CodeModernizerJobCompletedResult? = null
-
- private var hilDownloadArtifactId: String? = null
- private var hilTempDirectoryPath: Path? = null
- private var hilDownloadArtifact: CodeTransformHilDownloadArtifact? = null
-
- fun getHilDownloadArtifactId() = hilDownloadArtifactId
-
- fun setHilDownloadArtifactId(artifactId: String) {
- hilDownloadArtifactId = artifactId
- }
-
- fun getHilDownloadArtifact() = hilDownloadArtifact
-
- fun setHilDownloadArtifact(artifact: CodeTransformHilDownloadArtifact) {
- hilDownloadArtifact = artifact
- }
-
- fun getHilTempDirectoryPath() = hilTempDirectoryPath
-
- fun setHilTempDirectoryPath(path: Path) {
- hilTempDirectoryPath = path
- }
- fun getLastMvnBuildResult(): MavenCopyCommandsResult? = mvnBuildResult
-
- fun setLastMvnBuildResult(result: MavenCopyCommandsResult) {
- mvnBuildResult = result
- }
-
- fun getLastTransformResult(): CodeModernizerJobCompletedResult? = transformResult
-
- fun setLastTransformResult(result: CodeModernizerJobCompletedResult) {
- transformResult = result
- }
-
- fun getDependenciesUsingMaven(): MavenCopyCommandsResult = sessionContext.getDependenciesUsingMaven()
-
- fun createHilDependencyReportUsingMaven(): MavenDependencyReportCommandsResult = sessionContext.createDependencyReportUsingMaven(
- getPathToHilDependencyReportDir(hilTempDirectoryPath as Path)
- )
-
- fun copyHilDependencyUsingMaven(): MavenCopyCommandsResult = sessionContext.copyHilDependencyUsingMaven(hilTempDirectoryPath as Path)
-
- fun createHilUploadZip(selectedVersion: String) = sessionContext.createZipForHilUpload(
- hilTempDirectoryPath as Path,
- hilDownloadArtifact?.manifest,
- selectedVersion
- )
-
- /**
- * Note that this function makes network calls and needs to be run from a background thread.
- * Runs a code modernizer session which comprises the following steps:
- * 1. Generate zip file with the files in the selected module
- * 2. CreateUploadURL to upload the zip.
- * 3. Upload the zip files using the URL.
- * 4. Call startMigrationJob to start a migration job
- *
- * Based on [CodeWhispererCodeScanSession]
- */
- suspend fun createModernizationJob(copyResult: MavenCopyCommandsResult?): CodeModernizerStartJobResult {
- if (this.isDisposed.get()) {
- return CodeModernizerStartJobResult.Cancelled
- }
- LOG.info { "Compressing local project" }
- val payload: File?
- var payloadSize = 0
- val startTime = Instant.now()
- var telemetryErrorMessage: String? = null
- var dependenciesCopied = false
-
- try {
- // Generate zip file
- if (!isValidCodeTransformConnection(sessionContext.project)) {
- // Creating zip can take some time, so quit early
- telemetryErrorMessage = "Credential expired before generating zip"
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.CREDENTIALS_EXPIRED)
- }
- if (isDisposed.get()) {
- LOG.warn { "Disposed when about to create zip to upload" }
- telemetryErrorMessage = "Disposed when about to create zip"
- return CodeModernizerStartJobResult.Disposed
- }
- // for language upgrades, copyResult should always be Successful here, failure cases already handled
- val result = sessionContext.createZipWithModuleFiles(copyResult)
- sessionContext.originalUploadZipPath = result.payload.toPath()
-
- if (result is ZipCreationResult.Missing1P) {
- telemetryErrorMessage = "Missing 1p dependencies"
- return CodeModernizerStartJobResult.CancelledMissingDependencies
- } else {
- dependenciesCopied = true
- }
-
- payload = result.payload
- payloadSize = payload.length().toInt()
-
- LOG.info { "Uploading zip file with size: $payloadSize bytes" }
- if (this.isDisposed.get()) {
- return CodeModernizerStartJobResult.Cancelled
- }
-
- if (payloadSize > MAX_ZIP_SIZE) {
- telemetryErrorMessage = "Project exceeds max upload size"
- return CodeModernizerStartJobResult.CancelledZipTooLarge
- }
- } catch (e: Exception) {
- val errorMessage = "Failed to create zip"
- LOG.error(e) { errorMessage }
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = errorMessage
- return when (e) {
- is CodeModernizerException -> CodeModernizerStartJobResult.ZipCreationFailed(e.message)
- else -> CodeModernizerStartJobResult.ZipCreationFailed(message("codemodernizer.notification.warn.zip_creation_failed.reasons.unknown"))
- }
- } finally {
- // Publish metric if uploadProject failed at the zipping step, since the process will return early.
- if (!telemetryErrorMessage.isNullOrEmpty()) {
- telemetry.uploadProject(payloadSize, startTime, dependenciesCopied, telemetryErrorMessage)
- }
- }
-
- var uploadId = ""
- try {
- // Create upload url and upload zip
- if (!isValidCodeTransformConnection(sessionContext.project)) {
- telemetryErrorMessage = "Credential expired before uploading project"
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.CREDENTIALS_EXPIRED)
- }
- if (shouldStop.get() || this.isDisposed.get()) {
- LOG.warn { "Job was cancelled by user before upload was called" }
- telemetryErrorMessage = "Cancelled when about to upload project"
- return CodeModernizerStartJobResult.Cancelled
- }
- uploadId = payload?.let { uploadPayload(it) }.toString()
- } catch (e: AlreadyDisposedException) {
- LOG.warn { e.localizedMessage }
- telemetryErrorMessage = "Disposed when about to upload zip"
- return CodeModernizerStartJobResult.Disposed
- } catch (e: ConnectException) {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = e.localizedMessage
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.CONNECTION_REFUSED)
- } catch (e: SSLHandshakeException) {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = e.localizedMessage
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.SSL_HANDSHAKE_ERROR)
- } catch (e: HttpRequests.HttpStatusException) {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = "Upload failed with statusCode: ${e.statusCode}"
- return if (e.statusCode == 403) {
- CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.PRESIGNED_URL_EXPIRED)
- } else {
- CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.HTTP_ERROR(e.statusCode))
- }
- } catch (e: IOException) {
- if (shouldStop.get()) {
- // Cancelling during S3 upload will cause IOException of "not enough data written",
- // so no need to show an IDE error for it
- LOG.warn { "Job was cancelled by user before start job was called" }
- telemetryErrorMessage = "Cancelled when about to upload project"
- return CodeModernizerStartJobResult.Cancelled
- } else {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = e.localizedMessage
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.OTHER(e.localizedMessage))
- }
- } catch (e: SsoOidcException) {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = e.localizedMessage
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.CREDENTIALS_EXPIRED)
- } catch (e: SdkClientException) {
- // Errors from code whisperer client will always be thrown as SdkClientException
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = e.localizedMessage
- return if (e.message.toString().contains(CONNECTION_REFUSED_ERROR)) {
- CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.CONNECTION_REFUSED)
- } else if (e.message.toString().contains(SSL_HANDSHAKE_ERROR)) {
- CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.SSL_HANDSHAKE_ERROR)
- } else {
- CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.OTHER(e.localizedMessage))
- }
- } catch (e: Exception) {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetryErrorMessage = e.localizedMessage
- return CodeModernizerStartJobResult.ZipUploadFailed(UploadFailureReason.OTHER(e.localizedMessage))
- } finally {
- telemetry.uploadProject(payloadSize, startTime, true, telemetryErrorMessage)
- // do not delete upload ZIP; re-used for client-side build
- }
-
- // Send upload completion message to chat (only if successful)
- CodeTransformMessageListener.instance.onUploadResult()
-
- return try {
- if (shouldStop.get() || this.isDisposed.get()) {
- LOG.warn { "Job was cancelled by user before start job was called" }
- return CodeModernizerStartJobResult.Cancelled
- }
- val startJobResponse = startJob(uploadId)
- state.putJobHistory(sessionContext, TransformationStatus.STARTED, startJobResponse.transformationJobId())
- state.currentJobStatus = TransformationStatus.STARTED
- telemetry.jobStart(startTime, JobId(startJobResponse.transformationJobId()))
- CodeModernizerStartJobResult.Started(JobId(startJobResponse.transformationJobId()))
- } catch (e: Exception) {
- state.putJobHistory(sessionContext, TransformationStatus.FAILED)
- state.currentJobStatus = TransformationStatus.FAILED
- telemetry.jobStart(startTime, null, e.localizedMessage)
- CodeModernizerStartJobResult.UnableToStartJob(e.message.toString())
- }
- }
-
- private fun startJob(uploadId: String): StartTransformationResponse {
- val sourceLanguage = sessionContext.sourceJavaVersion.name.toTransformationLanguage()
- val targetLanguage = sessionContext.targetJavaVersion.name.toTransformationLanguage()
- if (sourceLanguage == TransformationLanguage.UNKNOWN_TO_SDK_VERSION) {
- throw RuntimeException("Source language is not supported")
- }
- if (targetLanguage == TransformationLanguage.UNKNOWN_TO_SDK_VERSION) {
- throw RuntimeException("Target language is not supported")
- }
- LOG.info { "Starting job with uploadId [$uploadId] for $sourceLanguage -> $targetLanguage" }
- return clientAdaptor.startCodeModernization(uploadId, sourceLanguage, targetLanguage)
- }
-
- /**
- * Will perform a single call, checking if a modernization job is finished.
- */
- fun getJobDetails(jobId: JobId): TransformationJob {
- LOG.info { "Getting job details." }
- return clientAdaptor.getCodeModernizationJob(jobId.id).transformationJob()
- }
-
- /**
- * This will resume the job, i.e. it will resume the main job loop kicked of by [createModernizationJob]
- */
- fun resumeJob(startTime: Instant, jobId: JobId) = state.putJobHistory(sessionContext, TransformationStatus.STARTED, jobId.id, startTime)
-
- fun resumeTransformation() {
- val clientAdaptor = GumbyClient.getInstance(sessionContext.project)
- clientAdaptor.resumeCodeTransformation(state.currentJobId as JobId, TransformationUserActionStatus.COMPLETED)
- getLogger().info { "Successfully resumed transformation with status of COMPLETED" }
- }
-
- fun rejectHilAndContinue(): ResumeTransformationResponse {
- val clientAdaptor = GumbyClient.getInstance(sessionContext.project)
- val jobId = state.currentJobId ?: throw CodeModernizerException("No Job ID found")
- return clientAdaptor.resumeCodeTransformation(jobId, TransformationUserActionStatus.REJECTED)
- }
-
- fun uploadHilPayload(payload: File): String {
- val sha256checksum: String = Base64.getEncoder().encodeToString(DigestUtils.sha256(FileInputStream(payload)))
- if (isDisposed.get()) {
- throw AlreadyDisposedException("Disposed when about to create upload URL")
- }
- val jobId = state.currentJobId ?: throw CodeModernizerException("No Job ID found")
- val clientAdaptor = GumbyClient.getInstance(sessionContext.project)
- val createUploadUrlResponse = clientAdaptor.createHilUploadUrl(sha256checksum, jobId = jobId)
-
- LOG.info {
- "Uploading hil artifact with checksum $sha256checksum using uploadId: ${
- createUploadUrlResponse.uploadId()
- } and size ${(payload.length() / 1000).toInt()}kB"
- }
- if (isDisposed.get()) {
- throw AlreadyDisposedException("Disposed when about to upload hil artifact to s3")
- }
- val uploadStartTime = Instant.now()
- try {
- clientAdaptor.uploadArtifactToS3(
- createUploadUrlResponse.uploadUrl(),
- payload,
- sha256checksum,
- createUploadUrlResponse.kmsKeyArn().orEmpty(),
- ) { shouldStop.get() }
- } catch (e: Exception) {
- LOG.error { "Unexpected error when uploading hil artifact to S3: ${e.localizedMessage}" }
- throw e
- }
- if (!shouldStop.get()) {
- LOG.info { "Uploaded hil artifact. Latency: ${calculateTotalLatency(uploadStartTime, Instant.now())}ms" }
- }
- return createUploadUrlResponse.uploadId()
- }
-
- /**
- * Adapted from [CodeWhispererCodeScanSession]
- */
- suspend fun uploadPayload(payload: File, uploadContext: UploadContext? = null): String {
- val sha256checksum: String = Base64.getEncoder().encodeToString(
- withContext(getCoroutineBgContext()) {
- DigestUtils.sha256(FileInputStream(payload))
- }
- )
- if (isDisposed.get()) {
- throw AlreadyDisposedException("Disposed when about to create upload URL")
- }
- val clientAdaptor = GumbyClient.getInstance(sessionContext.project)
- val createUploadUrlResponse = clientAdaptor.createGumbyUploadUrl(sha256checksum, uploadContext)
-
- LOG.info {
- "Uploading project artifact at ${payload.path} with checksum $sha256checksum using uploadId: ${
- createUploadUrlResponse.uploadId()
- } and size ${(payload.length() / 1000).toInt()}kB"
- }
- if (isDisposed.get()) {
- throw AlreadyDisposedException("Disposed when about to upload project artifact to s3")
- }
- val uploadStartTime = Instant.now()
- waitUntil(
- exceptionsToIgnore = setOf(
- UnknownHostException::class,
- SocketTimeoutException::class,
- HttpRequests.HttpStatusException::class,
- ConnectException::class,
- IOException::class,
- ),
- maxDuration = Duration.ofMinutes(5)
- ) {
- clientAdaptor.uploadArtifactToS3(
- createUploadUrlResponse.uploadUrl(),
- payload,
- sha256checksum,
- createUploadUrlResponse.kmsKeyArn().orEmpty(),
- ) { shouldStop.get() }
- }
- LOG.info { "Upload of ${payload.path} to S3 succeeded with upload context of $uploadContext" }
- if (!shouldStop.get()) {
- LOG.info { "Uploaded artifact. Latency: ${calculateTotalLatency(uploadStartTime, Instant.now())} ms" }
- }
- return createUploadUrlResponse.uploadId()
- }
-
- suspend fun pollUntilJobCompletion(
- transformType: CodeTransformType,
- jobId: JobId,
- jobTransitionHandler: (currentStatus: TransformationStatus, migrationPlan: TransformationPlan?) -> Unit,
- ): CodeModernizerJobCompletedResult {
- try {
- state.currentJobId = jobId
-
- // add delay to avoid the throttling error
- delay(1000)
-
- var isTransformationPlanEditorOpened = false
- var passedBuild = false
- var passedStart = false
-
- val result = jobId.pollTransformationStatusAndPlan(
- transformType,
- succeedOn = setOf(
- TransformationStatus.COMPLETED,
- TransformationStatus.PAUSED,
- TransformationStatus.STOPPED,
- TransformationStatus.PARTIALLY_COMPLETED,
- ),
- failOn = setOf(
- TransformationStatus.FAILED,
- TransformationStatus.UNKNOWN_TO_SDK_VERSION,
- ),
- clientAdaptor,
- initialPollingSleepDurationMillis,
- totalPollingSleepDurationMillis,
- isDisposed,
- sessionContext.project,
- ) { old, new, plan ->
- // Always refresh the dev tool tree so status will be up-to-date
- state.currentJobStatus = new
- state.transformationPlan = plan
-
- if (state.currentJobStatus == TransformationStatus.PAUSED) {
- val pausedUpdate =
- state.transformationPlan
- ?.transformationSteps()
- ?.flatMap { step -> step.progressUpdates() }
- ?.filter { update -> update.status() == TransformationProgressUpdateStatus.PAUSED }
- if (pausedUpdate?.isNotEmpty() == true) {
- state.currentHilArtifactId = pausedUpdate[0].downloadArtifacts()[0].downloadArtifactId()
- }
- }
-
- if (!isTransformationPlanEditorOpened && transformType == CodeTransformType.LANGUAGE_UPGRADE) {
- val isPlanComplete = isPlanComplete(state.transformationPlan, sessionContext)
- if (isPlanComplete && state.transformationPlan != null) {
- tryOpenTransformationPlanEditor()
- isTransformationPlanEditorOpened = true
- }
- }
- val instant = Instant.now()
- // Set the job start time
- if (state.currentJobCreationTime == Instant.MIN) {
- state.currentJobCreationTime = instant
- }
- state.updateJobHistory(sessionContext, new, instant)
- setCurrentJobStopTime(new, instant)
- setCurrentJobSummary(new)
-
- if (!passedStart && new in STATES_AFTER_STARTED) {
- passedStart = true
- }
- if (!passedBuild && new in STATES_AFTER_INITIAL_BUILD) {
- passedBuild = true
- }
-
- jobTransitionHandler(new, plan)
- LOG.info { "Waiting for Modernization Job [$jobId] to complete. State changed for job: $old -> $new" }
- }
- return when {
- result.state == TransformationStatus.STOPPED -> CodeModernizerJobCompletedResult.Stopped
-
- result.state == TransformationStatus.PAUSED -> CodeModernizerJobCompletedResult.JobPaused(jobId, state.currentHilArtifactId.orEmpty())
-
- result.state == TransformationStatus.UNKNOWN_TO_SDK_VERSION -> CodeModernizerJobCompletedResult.JobFailed(
- jobId,
- message("codemodernizer.notification.warn.unknown_status_response")
- )
-
- result.state == TransformationStatus.PARTIALLY_COMPLETED -> CodeModernizerJobCompletedResult.JobPartiallySucceeded(jobId)
-
- result.state == TransformationStatus.FAILED -> {
- if (!passedStart) {
- val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.unknown_start_failure")
- return CodeModernizerJobCompletedResult.JobFailed(jobId, failureReason)
- } else if (!passedBuild) {
- // This is a short term solution to check if build log is available by attempting to download it.
- // In the long term, we should check if build log is available from transformation metadata.
- val downloadArtifactResult = artifactHandler.downloadArtifact(jobId, TransformationDownloadArtifactType.LOGS, true)
- if (downloadArtifactResult is DownloadArtifactResult.Success) {
- val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.maven_failed.content")
- return CodeModernizerJobCompletedResult.JobFailedInitialBuild(jobId, failureReason, true)
- } else {
- val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.maven_failed.content")
- return CodeModernizerJobCompletedResult.JobFailedInitialBuild(jobId, failureReason, false)
- }
- } else {
- val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.unknown_status_response")
- return CodeModernizerJobCompletedResult.JobFailed(jobId, failureReason)
- }
- }
-
- result.state == TransformationStatus.COMPLETED -> {
- CodeModernizerJobCompletedResult.JobCompletedSuccessfully(jobId)
- }
-
- // Should not happen
- else -> CodeModernizerJobCompletedResult.JobFailed(jobId, result.jobDetails?.reason().orEmpty())
- }
- } catch (e: Exception) {
- return when (e) {
- is AlreadyDisposedException, is CancellationException -> {
- LOG.error(e) { "The session was disposed while polling for job details." }
- CodeModernizerJobCompletedResult.ManagerDisposed
- }
-
- else -> {
- LOG.error(e) { e.message.toString() }
- LOG.info { "Stopping transformation job [$jobId] due to unexpected error." }
- stopTransformation(jobId.id)
- CodeModernizerJobCompletedResult.RetryableFailure(
- jobId,
- message("codemodernizer.notification.info.modernize_failed.connection_failed", e.message.orEmpty()),
- )
- }
- }
- }
- }
-
- fun stopTransformation(transformationId: String?): Boolean {
- shouldStop.set(true) // allows the zipping and upload to cancel
- return if (transformationId != null) {
- // Means job exists in backend, and we have to call the stop api
- clientAdaptor.stopTransformation(transformationId)
- return true
- } else {
- true // We did not yet call the start API so no need to call the stop job api
- }
- }
-
- fun getTransformationPlan(): TransformationPlan? = state.transformationPlan
-
- fun setCurrentJobStopTime(status: TransformationStatus, instant: Instant) {
- if (status in setOf(
- TransformationStatus.COMPLETED, // successfully transformed
- TransformationStatus.STOPPED, // manually stopped,
- TransformationStatus.PARTIALLY_COMPLETED, // partially successfully transformed
- TransformationStatus.FAILED, // unable to generate transformation plan
- TransformationStatus.UNKNOWN_TO_SDK_VERSION,
- )
- ) {
- state.currentJobStopTime = instant
- }
- }
-
- private fun setCurrentJobSummary(status: TransformationStatus) {
- state.transformationSummary ?: return
- if (status in setOf(
- TransformationStatus.COMPLETED,
- TransformationStatus.STOPPED,
- TransformationStatus.PARTIALLY_COMPLETED,
- TransformationStatus.FAILED,
- )
- ) {
- val summary = TransformationSummary(
- """
- # Transformation summary
-
- This is pretty
- and now it has been updated from api call...
- """.trimIndent()
- )
- state.transformationSummary = summary
- }
- }
-
- fun tryOpenTransformationPlanEditor() {
- val transformationPlan = getTransformationPlan()
- if (transformationPlan != null) {
- runInEdt {
- CodeModernizerPlanEditorProvider.openEditor(
- sessionContext.project,
- transformationPlan,
- sessionContext.project.getModuleOrProjectNameForFile(sessionContext.configurationFile),
- sessionContext.sourceJavaVersion.description,
- )
- }
- }
- }
-
- companion object {
- private val LOG = getLogger()
- }
-
- override fun dispose() {
- isDisposed.set(true)
- shouldStop.set(true)
- Disposer.dispose(sessionContext)
- }
-
- fun getActiveJobId() = state.currentJobId
- fun fetchPlan(lastJobId: JobId) = clientAdaptor.getCodeModernizationPlan(lastJobId)
-
- fun hilCleanup() {
- hilDownloadArtifactId = null
- hilDownloadArtifact = null
- if (hilTempDirectoryPath?.exists() == true) {
- try {
- (hilTempDirectoryPath as Path).toFile().deleteRecursively()
- } catch (e: Exception) {
- val errorMessage = "Unexpected error when cleaning up HIL files: ${e.localizedMessage}"
- LOG.error { errorMessage }
- telemetry.error(errorMessage)
- return
- } finally {
- hilTempDirectoryPath = null
- }
- }
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerStartupActivity.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerStartupActivity.kt
deleted file mode 100644
index ff54b2c240c..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerStartupActivity.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.startup.StartupActivity
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
-
-class CodeModernizerStartupActivity : StartupActivity.DumbAware {
-
- /**
- * Will be run on startup of the IDE
- * Prompts users of jobs that finished while IDE was closed.
- */
- override fun runActivity(project: Project) {
- if (!isCodeTransformAvailable(project)) return
- CodeModernizerManager.getInstance(project).tryResumeJob()
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt
deleted file mode 100644
index 3138c20026e..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.project.Project
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.launch
-import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener
-import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState
-import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
-import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQApp
-import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
-import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
-import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener
-import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformActionMessage
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
-import software.aws.toolkits.jetbrains.services.codemodernizer.controller.CodeTransformChatController
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.AuthenticationNeededExceptionMessage
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.AuthenticationUpdateMessage
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CODE_TRANSFORM_TAB_NAME
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.IncomingCodeTransformMessage
-import software.aws.toolkits.jetbrains.services.codemodernizer.session.ChatSessionStorage
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getQTokenProvider
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
-import java.util.concurrent.atomic.AtomicBoolean
-
-private enum class CodeTransformMessageTypes(val type: String) {
- TabCreated("new-tab-was-created"),
- TabRemoved("tab-was-removed"),
- Transform("transform"),
- ChatPrompt("chat-prompt"), // for getting the transformation objective
- CodeTransformStart("codetransform-start"),
- CodeTransformSelectSQLMetadata("codetransform-select-sql-metadata"),
- CodeTransformConfirmCustomDependencyVersions("codetransform-input-confirm-custom-dependency-versions"),
- CodeTransformSelectSQLModuleSchema("codetransform-select-sql-module-schema"),
- CodeTransformStop("codetransform-stop"),
- CodeTransformCancel("codetransform-cancel"),
- CodeTransformContinue("codetransform-continue"),
- CodeTransformConfirmSkipTests("codetransform-confirm-skip-tests"),
- CodeTransformNew("codetransform-new"),
- CodeTransformOpenTransformHub("codetransform-open-transform-hub"),
- CodeTransformOpenMvnBuild("codetransform-open-mvn-build"),
- ViewDiff("codetransform-view-diff"),
- ViewSummary("codetransform-view-summary"),
- ViewBuildLog("codetransform-view-build-log"),
- AuthFollowUpWasClicked("auth-follow-up-was-clicked"),
- BodyLinkClicked("response-body-link-click"),
- ConfirmHilSelection("codetransform-confirm-hil-selection"),
- RejectHilSelection("codetransform-reject-hil-selection"),
- OpenPomFileHilClicked("codetransform-pom-file-open-click"),
-}
-
-class CodeTransformChatApp : AmazonQApp {
- private val scope = disposableCoroutineScope(this)
- private val isProcessingAuthChanged = AtomicBoolean(false)
- override val tabTypes = listOf(CODE_TRANSFORM_TAB_NAME)
-
- override fun init(context: AmazonQAppInitContext) {
- val chatSessionStorage = ChatSessionStorage()
- val inboundAppMessagesHandler: InboundAppMessagesHandler = CodeTransformChatController(context, chatSessionStorage)
-
- context.messageTypeRegistry.register(
- CodeTransformMessageTypes.TabCreated.type to IncomingCodeTransformMessage.TabCreated::class,
- CodeTransformMessageTypes.TabRemoved.type to IncomingCodeTransformMessage.TabRemoved::class,
- CodeTransformMessageTypes.Transform.type to IncomingCodeTransformMessage.Transform::class,
- CodeTransformMessageTypes.CodeTransformStart.type to IncomingCodeTransformMessage.CodeTransformStart::class,
- CodeTransformMessageTypes.CodeTransformSelectSQLMetadata.type to IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata::class,
- CodeTransformMessageTypes.CodeTransformConfirmCustomDependencyVersions.type to
- IncomingCodeTransformMessage.CodeTransformConfirmCustomDependencyVersions::class,
- CodeTransformMessageTypes.CodeTransformSelectSQLModuleSchema.type to IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema::class,
- CodeTransformMessageTypes.CodeTransformStop.type to IncomingCodeTransformMessage.CodeTransformStop::class,
- CodeTransformMessageTypes.CodeTransformCancel.type to IncomingCodeTransformMessage.CodeTransformCancel::class,
- CodeTransformMessageTypes.CodeTransformContinue.type to IncomingCodeTransformMessage.CodeTransformContinue::class,
- CodeTransformMessageTypes.ChatPrompt.type to IncomingCodeTransformMessage.ChatPrompt::class,
- CodeTransformMessageTypes.CodeTransformConfirmSkipTests.type to IncomingCodeTransformMessage.CodeTransformConfirmSkipTests::class,
- CodeTransformMessageTypes.CodeTransformNew.type to IncomingCodeTransformMessage.CodeTransformNew::class,
- CodeTransformMessageTypes.CodeTransformOpenTransformHub.type to IncomingCodeTransformMessage.CodeTransformOpenTransformHub::class,
- CodeTransformMessageTypes.CodeTransformOpenMvnBuild.type to IncomingCodeTransformMessage.CodeTransformOpenMvnBuild::class,
- CodeTransformMessageTypes.ViewDiff.type to IncomingCodeTransformMessage.CodeTransformViewDiff::class,
- CodeTransformMessageTypes.ViewSummary.type to IncomingCodeTransformMessage.CodeTransformViewSummary::class,
- CodeTransformMessageTypes.ViewBuildLog.type to IncomingCodeTransformMessage.CodeTransformViewBuildLog::class,
- CodeTransformMessageTypes.AuthFollowUpWasClicked.type to IncomingCodeTransformMessage.AuthFollowUpWasClicked::class,
- CodeTransformMessageTypes.BodyLinkClicked.type to IncomingCodeTransformMessage.BodyLinkClicked::class,
- CodeTransformMessageTypes.ConfirmHilSelection.type to IncomingCodeTransformMessage.ConfirmHilSelection::class,
- CodeTransformMessageTypes.RejectHilSelection.type to IncomingCodeTransformMessage.RejectHilSelection::class,
- CodeTransformMessageTypes.OpenPomFileHilClicked.type to IncomingCodeTransformMessage.OpenPomFileHilClicked::class,
- )
-
- scope.launch {
- merge(CodeTransformMessageListener.instance.flow, context.messagesFromUiToApp.flow).collect { message ->
- // Launch a new coroutine to handle each message
- scope.launch { handleMessage(message, inboundAppMessagesHandler) }
- }
- }
-
- fun authChanged() {
- val isAnotherThreadProcessing = !isProcessingAuthChanged.compareAndSet(false, true)
- if (isAnotherThreadProcessing) return
- scope.launch {
- val authController = AuthController()
- val credentialState = authController.getAuthNeededStates(context.project).amazonQ
- if (credentialState == null) {
- // Notify tabs about restoring authentication
- context.messagesFromAppToUi.publish(
- AuthenticationUpdateMessage(
- codeTransformEnabled = isCodeTransformAvailable(context.project),
- codeScanEnabled = isCodeScanAvailable(context.project),
- authenticatingTabIDs = chatSessionStorage.getAuthenticatingSessions().map { it.tabId },
- )
- )
-
- chatSessionStorage.changeAuthenticationNeeded(false)
- chatSessionStorage.changeAuthenticationNeededNotified(false)
- CodeTransformMessageListener.instance.onAuthRestored()
- } else {
- chatSessionStorage.changeAuthenticationNeeded(true)
-
- // Ask for reauth
- chatSessionStorage.getAuthenticatingSessions().filter { !it.authNeededNotified }.forEach {
- context.messagesFromAppToUi.publish(
- AuthenticationNeededExceptionMessage(
- tabId = it.tabId,
- authType = credentialState.authType,
- message = credentialState.message,
- )
- )
- }
-
- // Prevent multiple calls to activeConnectionChanged
- chatSessionStorage.changeAuthenticationNeededNotified(true)
- }
- isProcessingAuthChanged.set(false)
- }
- }
-
- ApplicationManager.getApplication().messageBus.connect(this).subscribe(
- BearerTokenProviderListener.TOPIC,
- object : BearerTokenProviderListener {
- override fun onProviderChange(providerId: String, newScopes: List?) {
- val qProvider = getQTokenProvider(context.project)
- val isQ = qProvider?.id == providerId
- val isAuthorized = qProvider?.state() == BearerTokenAuthState.AUTHORIZED
- if (!isQ || !isAuthorized) return
- authChanged()
- }
- }
- )
-
- ApplicationManager.getApplication().messageBus.connect(this).subscribe(
- ToolkitConnectionManagerListener.TOPIC,
- object : ToolkitConnectionManagerListener {
- override fun activeConnectionChanged(newConnection: ToolkitConnection?) {
- authChanged()
- }
- }
- )
-
- context.project.messageBus.connect(this).subscribe(
- QRegionProfileSelectedListener.TOPIC,
- object : QRegionProfileSelectedListener {
- override fun onProfileSelected(project: Project, profile: QRegionProfile?) {
- chatSessionStorage.deleteAllSessions()
- }
- }
- )
- }
-
- private suspend fun handleMessage(message: AmazonQMessage, inboundAppMessagesHandler: InboundAppMessagesHandler) {
- when (message) {
- is IncomingCodeTransformMessage.Transform -> inboundAppMessagesHandler.processTransformQuickAction(message)
- is IncomingCodeTransformMessage.CodeTransformStart -> inboundAppMessagesHandler.processCodeTransformStartAction(message)
- is IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata -> inboundAppMessagesHandler.processCodeTransformSelectSQLMetadataAction(message)
- is IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema ->
- inboundAppMessagesHandler.processCodeTransformSelectSQLModuleSchemaAction(message)
- is IncomingCodeTransformMessage.CodeTransformConfirmCustomDependencyVersions ->
- inboundAppMessagesHandler.processCodeTransformCustomDependencyVersions(message)
- is IncomingCodeTransformMessage.CodeTransformCancel -> inboundAppMessagesHandler.processCodeTransformCancelAction(message)
- is IncomingCodeTransformMessage.CodeTransformStop -> inboundAppMessagesHandler.processCodeTransformStopAction(message.tabId)
- is IncomingCodeTransformMessage.ChatPrompt -> inboundAppMessagesHandler.processChatPromptMessage(message)
- is IncomingCodeTransformMessage.CodeTransformConfirmSkipTests -> inboundAppMessagesHandler.processCodeTransformConfirmSkipTests(message)
- is IncomingCodeTransformMessage.CodeTransformContinue -> inboundAppMessagesHandler.processCodeTransformContinueAction(message)
- is IncomingCodeTransformMessage.CodeTransformNew -> inboundAppMessagesHandler.processCodeTransformNewAction(message)
- is IncomingCodeTransformMessage.CodeTransformOpenTransformHub -> inboundAppMessagesHandler.processCodeTransformOpenTransformHub(message)
- is IncomingCodeTransformMessage.CodeTransformOpenMvnBuild -> inboundAppMessagesHandler.processCodeTransformOpenMvnBuild(message)
- is IncomingCodeTransformMessage.CodeTransformViewDiff -> inboundAppMessagesHandler.processCodeTransformViewDiff(message)
- is IncomingCodeTransformMessage.CodeTransformViewSummary -> inboundAppMessagesHandler.processCodeTransformViewSummary(message)
- is IncomingCodeTransformMessage.CodeTransformViewBuildLog -> inboundAppMessagesHandler.processCodeTransformViewBuildLog(message)
- is IncomingCodeTransformMessage.TabCreated -> inboundAppMessagesHandler.processTabCreated(message)
- is IncomingCodeTransformMessage.TabRemoved -> inboundAppMessagesHandler.processTabRemoved(message)
- is IncomingCodeTransformMessage.AuthFollowUpWasClicked -> inboundAppMessagesHandler.processAuthFollowUpClick(message)
- is IncomingCodeTransformMessage.BodyLinkClicked -> inboundAppMessagesHandler.processBodyLinkClicked(message)
- is IncomingCodeTransformMessage.ConfirmHilSelection -> inboundAppMessagesHandler.processConfirmHilSelection(message)
- is IncomingCodeTransformMessage.RejectHilSelection -> inboundAppMessagesHandler.processRejectHilSelection(message)
- is CodeTransformActionMessage -> inboundAppMessagesHandler.processCodeTransformCommand(message)
- is IncomingCodeTransformMessage.OpenPomFileHilClicked -> inboundAppMessagesHandler.processOpenPomFileHilClicked(message)
- }
- }
-
- override fun dispose() {
- // nothing to do
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatAppFactory.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatAppFactory.kt
deleted file mode 100644
index 436e3c4da27..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatAppFactory.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.openapi.project.Project
-import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppFactory
-
-class CodeTransformChatAppFactory : AmazonQAppFactory {
- override fun createApp(project: Project) = CodeTransformChatApp()
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt
deleted file mode 100644
index c05108800e4..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformProjectStartupSettingListener.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.wm.ex.ToolWindowManagerListener
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnection
-import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener
-import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
-import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
-import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererActivationChangedListener
-
-class CodeTransformProjectStartupSettingListener(private val project: Project) :
- CodeWhispererActivationChangedListener,
- ToolWindowManagerListener,
- ToolkitConnectionManagerListener,
- BearerTokenProviderListener {
-
- override fun activeConnectionChanged(newConnection: ToolkitConnection?) {
- runInEdt {
- val isAvailable = isCodeTransformAvailable(project)
- CodeModernizerBottomWindowPanelManager.getInstance(project).toolWindow?.isAvailable = isAvailable
- CodeModernizerManager.getInstance(project).handleCredentialsChanged()
- if (isAvailable) {
- CodeModernizerManager.getInstance(project).tryResumeJob()
- }
- }
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt
deleted file mode 100644
index 0f247685f09..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.components.service
-import com.intellij.openapi.project.Project
-import org.apache.commons.codec.digest.DigestUtils
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CustomerSelection
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult
-import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeTransformTelemetryState
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.calculateTotalLatency
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getAuthType
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getJavaVersionFromProjectSetting
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getMavenVersion
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.tryGetJdk
-import software.aws.toolkits.telemetry.CodeTransformArtifactType
-import software.aws.toolkits.telemetry.CodeTransformBuildCommand
-import software.aws.toolkits.telemetry.CodeTransformCancelSrcComponents
-import software.aws.toolkits.telemetry.CodeTransformJavaSourceVersionsAllowed
-import software.aws.toolkits.telemetry.CodeTransformJavaTargetVersionsAllowed
-import software.aws.toolkits.telemetry.CodeTransformPreValidationError
-import software.aws.toolkits.telemetry.CodetransformTelemetry
-import software.aws.toolkits.telemetry.MetricResult
-import software.aws.toolkits.telemetry.Result
-import java.time.Instant
-import java.util.Base64
-
-/**
- * CodeModernizerTelemetry contains g functions for common operations that require telemetry.
- */
-@Service(Service.Level.PROJECT)
-class CodeTransformTelemetryManager(private val project: Project) {
- private val sessionId get() = CodeTransformTelemetryState.instance.getSessionId()
-
- fun initiateTransform(telemetryErrorMessage: String? = null) {
- CodetransformTelemetry.initiateTransform(
- codeTransformSessionId = sessionId,
- credentialSourceId = getAuthType(project),
- result = if (telemetryErrorMessage.isNullOrEmpty()) Result.Succeeded else Result.Failed,
- reason = telemetryErrorMessage,
- )
- }
-
- fun validateProject(validationResult: ValidationResult) {
- val validationError = if (validationResult.valid) {
- null
- } else {
- validationResult.invalidTelemetryReason.category ?: CodeTransformPreValidationError.Unknown
- }
-
- CodetransformTelemetry.validateProject(
- buildSystemVersion = validationResult.buildSystemVersion,
- codeTransformLocalJavaVersion = project.tryGetJdk().toString(),
- codeTransformPreValidationError = validationError,
- codeTransformBuildSystem = validationResult.buildSystem,
- codeTransformSessionId = sessionId,
- codeTransformMetadata = validationResult.metadata,
- result = if (validationResult.valid) Result.Succeeded else Result.Failed,
- reason = if (validationResult.valid) null else validationResult.invalidTelemetryReason.additionalInfo,
- )
- }
-
- fun submitSelection(userChoice: String, jobId: String? = null, customerSelection: CustomerSelection? = null, telemetryErrorMessage: String? = null) {
- CodetransformTelemetry.submitSelection(
- // TODO: remove the below 2 lines (JavaSource / JavaTarget) once BI is updated to use source / target
- codeTransformJavaSourceVersionsAllowed = CodeTransformJavaSourceVersionsAllowed.from(customerSelection?.sourceJavaVersion?.name.orEmpty()),
- codeTransformJavaTargetVersionsAllowed = CodeTransformJavaTargetVersionsAllowed.from(customerSelection?.targetJavaVersion?.name.orEmpty()),
- codeTransformSessionId = sessionId,
- codeTransformProjectId = customerSelection?.let { getProjectHash(it) },
- codeTransformJobId = jobId,
- source = if (userChoice == "Confirm-Java") customerSelection?.sourceJavaVersion?.name.orEmpty() else customerSelection?.sourceVendor.orEmpty(),
- target = if (userChoice == "Confirm-Java") customerSelection?.targetJavaVersion?.name.orEmpty() else customerSelection?.targetVendor.orEmpty(),
- userChoice = userChoice,
- result = if (telemetryErrorMessage.isNullOrEmpty()) Result.Succeeded else Result.Failed,
- reason = telemetryErrorMessage,
- )
- }
-
- // Replace the input as needed to support Gradle and other transformation types.
- fun localBuildProject(buildCommand: CodeTransformBuildCommand, localBuildResult: Result, telemetryErrorMessage: String?) {
- CodetransformTelemetry.localBuildProject(
- codeTransformBuildCommand = buildCommand,
- codeTransformSessionId = sessionId,
- result = localBuildResult,
- reason = if (telemetryErrorMessage.isNullOrEmpty()) null else telemetryErrorMessage,
- )
- }
-
- fun uploadProject(payloadSize: Int, startTime: Instant, dependenciesCopied: Boolean = false, telemetryErrorMessage: String? = null) {
- CodetransformTelemetry.uploadProject(
- codeTransformRunTimeLatency = calculateTotalLatency(startTime, Instant.now()).toLong(),
- codeTransformSessionId = sessionId,
- codeTransformTotalByteSize = payloadSize.toLong(),
- codeTransformDependenciesCopied = dependenciesCopied,
- result = if (telemetryErrorMessage.isNullOrEmpty()) Result.Succeeded else Result.Failed,
- reason = telemetryErrorMessage,
- )
- }
-
- fun jobStart(transformStartTime: Instant, jobId: JobId?, telemetryErrorMessage: String? = null) = CodetransformTelemetry.jobStart(
- codeTransformSessionId = sessionId,
- codeTransformJobId = jobId?.id.orEmpty(),
- codeTransformRunTimeLatency = calculateTotalLatency(transformStartTime, Instant.now()).toLong(), // subtract current time by project start time
- result = if (telemetryErrorMessage.isNullOrEmpty()) Result.Succeeded else Result.Failed,
- reason = telemetryErrorMessage,
- )
-
- fun downloadArtifact(
- artifactType: CodeTransformArtifactType,
- downloadStartTime: Instant,
- jobId: JobId,
- totalDownloadBytes: Int,
- telemetryErrorMessage: String?,
- ) {
- CodetransformTelemetry.downloadArtifact(
- codeTransformArtifactType = artifactType,
- codeTransformJobId = jobId.id,
- codeTransformRuntimeError = telemetryErrorMessage,
- codeTransformRunTimeLatency = calculateTotalLatency(downloadStartTime, Instant.now()).toLong(),
- codeTransformSessionId = sessionId,
- codeTransformTotalByteSize = totalDownloadBytes.toLong(),
- result = if (telemetryErrorMessage.isNullOrEmpty()) Result.Succeeded else Result.Failed,
- reason = telemetryErrorMessage,
- )
- }
-
- fun getProjectHash(customerSelection: CustomerSelection) = Base64.getEncoder().encodeToString(
- DigestUtils.sha256(customerSelection.configurationFile?.toNioPath()?.toAbsolutePath().toString())
- )
-
- /**
- * Will be the first invokation per job submission.
- * Should contain relevant initialization for proper telemetry emission.
- */
- fun prepareForNewJobSubmission() {
- CodeTransformTelemetryState.instance.setSessionId()
- }
-
- fun jobIsCancelledByUser(srcComponent: CodeTransformCancelSrcComponents) = CodetransformTelemetry.jobIsCancelledByUser(
- codeTransformCancelSrcComponents = srcComponent,
- codeTransformSessionId = sessionId
- )
-
- fun jobIsResumedAfterIdeClose(lastJobId: JobId, status: TransformationStatus) = CodetransformTelemetry.jobIsResumedAfterIdeClose(
- codeTransformSessionId = sessionId,
- codeTransformJobId = lastJobId.id,
- codeTransformStatus = status.toString()
- )
-
- fun totalRunTime(codeTransformResultStatusMessage: String, jobId: JobId?) = CodetransformTelemetry.totalRunTime(
- buildSystemVersion = getMavenVersion(project),
- codeTransformJobId = jobId?.toString(),
- codeTransformSessionId = sessionId,
- codeTransformResultStatusMessage = codeTransformResultStatusMessage,
- codeTransformRunTimeLatency = calculateTotalLatency(
- CodeTransformTelemetryState.instance.getStartTime(),
- Instant.now()
- ).toLong(),
- codeTransformLocalJavaVersion = getJavaVersionFromProjectSetting(project),
- )
-
- fun error(errorMessage: String) = CodetransformTelemetry.logGeneralError(
- reason = errorMessage,
- codeTransformSessionId = sessionId,
- )
-
- fun jobStatusChanged(jobId: JobId, newStatus: String, previousStatus: String) = CodetransformTelemetry.jobStatusChanged(
- codeTransformPreviousStatus = previousStatus,
- codeTransformSessionId = sessionId,
- codeTransformJobId = jobId.id,
- codeTransformStatus = newStatus
- )
-
- fun logHil(jobId: String, metaData: HilTelemetryMetaData, success: Boolean, reason: String) {
- CodetransformTelemetry.humanInTheLoop(
- project = project,
- codeTransformJobId = jobId,
- codeTransformMetadata = metaData.toString(),
- codeTransformSessionId = sessionId,
- reason = reason,
- result = if (success) MetricResult.Succeeded else MetricResult.Failed,
- )
- }
-
- companion object {
- fun getInstance(project: Project): CodeTransformTelemetryManager = project.service()
- }
-}
-
-data class HilTelemetryMetaData(
- val dependencyVersionSelected: String? = null,
- val cancelledFromChat: Boolean = false,
-)
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt
deleted file mode 100644
index e300b595a30..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformActionMessage
-import software.aws.toolkits.jetbrains.services.codemodernizer.messages.IncomingCodeTransformMessage
-
-interface InboundAppMessagesHandler {
- suspend fun processTransformQuickAction(message: IncomingCodeTransformMessage.Transform)
-
- suspend fun processCodeTransformCancelAction(message: IncomingCodeTransformMessage.CodeTransformCancel)
-
- suspend fun processCodeTransformContinueAction(message: IncomingCodeTransformMessage.CodeTransformContinue)
-
- suspend fun processCodeTransformStartAction(message: IncomingCodeTransformMessage.CodeTransformStart)
-
- suspend fun processCodeTransformSelectSQLMetadataAction(message: IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata)
-
- suspend fun processCodeTransformSelectSQLModuleSchemaAction(message: IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema)
-
- suspend fun processCodeTransformCustomDependencyVersions(message: IncomingCodeTransformMessage.CodeTransformConfirmCustomDependencyVersions)
-
- suspend fun processCodeTransformStopAction(tabId: String)
-
- suspend fun processChatPromptMessage(message: IncomingCodeTransformMessage.ChatPrompt)
-
- suspend fun processCodeTransformConfirmSkipTests(message: IncomingCodeTransformMessage.CodeTransformConfirmSkipTests)
-
- suspend fun processCodeTransformOpenTransformHub(message: IncomingCodeTransformMessage.CodeTransformOpenTransformHub)
-
- suspend fun processCodeTransformOpenMvnBuild(message: IncomingCodeTransformMessage.CodeTransformOpenMvnBuild)
-
- suspend fun processCodeTransformViewDiff(message: IncomingCodeTransformMessage.CodeTransformViewDiff)
-
- suspend fun processCodeTransformViewSummary(message: IncomingCodeTransformMessage.CodeTransformViewSummary)
-
- suspend fun processCodeTransformViewBuildLog(message: IncomingCodeTransformMessage.CodeTransformViewBuildLog)
-
- suspend fun processCodeTransformNewAction(message: IncomingCodeTransformMessage.CodeTransformNew)
-
- suspend fun processCodeTransformCommand(message: CodeTransformActionMessage)
-
- suspend fun processTabCreated(message: IncomingCodeTransformMessage.TabCreated)
-
- suspend fun processTabRemoved(message: IncomingCodeTransformMessage.TabRemoved)
-
- suspend fun processAuthFollowUpClick(message: IncomingCodeTransformMessage.AuthFollowUpWasClicked)
-
- suspend fun processBodyLinkClicked(message: IncomingCodeTransformMessage.BodyLinkClicked)
-
- suspend fun processConfirmHilSelection(message: IncomingCodeTransformMessage.ConfirmHilSelection)
-
- suspend fun processRejectHilSelection(message: IncomingCodeTransformMessage.RejectHilSelection)
-
- suspend fun processOpenPomFileHilClicked(message: IncomingCodeTransformMessage.OpenPomFileHilClicked)
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/TransformationSummary.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/TransformationSummary.kt
deleted file mode 100644
index e4a2a6f099c..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/TransformationSummary.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer
-
-data class TransformationSummary(val content: String)
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowJobStatusAction.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowJobStatusAction.kt
deleted file mode 100644
index 5903d0e7a36..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowJobStatusAction.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer.actions
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.project.DumbAware
-import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager
-import software.aws.toolkits.resources.message
-
-class CodeModernizerShowJobStatusAction :
- AnAction(
- message("codemodernizer.explorer.show_job_status"),
- message("codemodernizer.explorer.show_job_status_description"),
- AllIcons.Vcs.History
- ),
- DumbAware {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun update(event: AnActionEvent) {
- event.presentation.icon = AllIcons.Vcs.History
- }
-
- override fun actionPerformed(event: AnActionEvent) {
- val project = event.project ?: return
- val codeModernizerManager = CodeModernizerManager.getInstance(project)
- codeModernizerManager.showPreviousJobHistoryUI()
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationPlanAction.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationPlanAction.kt
deleted file mode 100644
index 2bb04ca3c63..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationPlanAction.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer.actions
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.project.DumbAware
-import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager
-import software.aws.toolkits.resources.message
-
-class CodeModernizerShowTransformationPlanAction :
- AnAction(
- message("codemodernizer.explorer.show_transformation_plan_title"),
- null,
- AllIcons.Actions.Annotate
- ),
- DumbAware {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun update(event: AnActionEvent) {
- val project = event.project ?: return
- val codeModernizerManager = CodeModernizerManager.getInstance(project)
- event.presentation.isEnabled = codeModernizerManager.getTransformationPlan() != null
- event.presentation.icon = AllIcons.Actions.Annotate
- }
-
- override fun actionPerformed(event: AnActionEvent) {
- val project = event.project ?: return
- CodeModernizerManager.getInstance(project).showTransformationPlan()
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationStatusAction.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationStatusAction.kt
deleted file mode 100644
index 0f11430f949..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationStatusAction.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer.actions
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.project.DumbAware
-import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager
-import software.aws.toolkits.resources.message
-
-class CodeModernizerShowTransformationStatusAction :
- AnAction(
- message("codemodernizer.explorer.show_transformation_status"),
- message("codemodernizer.explorer.show_transformation_status_description"),
- AllIcons.Vcs.Changelist
- ),
- DumbAware {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun update(event: AnActionEvent) {
- event.presentation.icon = AllIcons.Vcs.Changelist
- }
-
- override fun actionPerformed(event: AnActionEvent) {
- val project = event.project ?: return
- CodeModernizerManager.getInstance(project).showModernizationProgressUI()
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationSummaryAction.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationSummaryAction.kt
deleted file mode 100644
index 7edaa38f6b9..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerShowTransformationSummaryAction.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer.actions
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.project.DumbAware
-import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager
-import software.aws.toolkits.resources.message
-
-class CodeModernizerShowTransformationSummaryAction :
- AnAction(
- message("codemodernizer.explorer.show_transformation_summary_title"),
- null,
- AllIcons.Actions.Annotate
- ),
- DumbAware {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun update(event: AnActionEvent) {
- val project = event.project ?: return
- val codeModernizerManager = CodeModernizerManager.getInstance(project)
- event.presentation.isEnabled = codeModernizerManager.getTransformationSummary() != null
- }
-
- override fun actionPerformed(event: AnActionEvent) {
- val project = event.project ?: return
- val codeModernizerManager = CodeModernizerManager.getInstance(project)
- codeModernizerManager.showTransformationSummary()
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerStopModernizerAction.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerStopModernizerAction.kt
deleted file mode 100644
index 72e41325e5c..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/actions/CodeModernizerStopModernizerAction.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer.actions
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.project.DumbAware
-import com.intellij.openapi.wm.ToolWindowManager
-import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
-import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager
-import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager
-import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
-import software.aws.toolkits.resources.message
-import software.aws.toolkits.telemetry.CodeTransformCancelSrcComponents
-
-class CodeModernizerStopModernizerAction :
- AnAction(
- message("codemodernizer.explorer.stop_migration_job"),
- null,
- AllIcons.Actions.Suspend
- ),
- DumbAware {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun update(event: AnActionEvent) {
- val project = event.project ?: return
- val codeModernizerManager = CodeModernizerManager.getInstance(project)
- event.presentation.isVisible = codeModernizerManager.isModernizationJobActive()
- event.presentation.isEnabled = codeModernizerManager.isModernizationJobActive() && !codeModernizerManager.isModernizationJobStopping()
- }
-
- override fun actionPerformed(event: AnActionEvent) {
- val project = event.project ?: return
-
- runInEdt {
- val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID)
- toolWindow?.show()
- }
- CodeTransformMessageListener.instance.onStopClicked()
- CodeTransformTelemetryManager.getInstance(project)
- .jobIsCancelledByUser(CodeTransformCancelSrcComponents.BottomPanelSideNavButton)
- }
-}
diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt
deleted file mode 100644
index 14facdaceb6..00000000000
--- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt
+++ /dev/null
@@ -1,276 +0,0 @@
-// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codemodernizer.client
-
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.components.service
-import com.intellij.openapi.project.Project
-import com.intellij.util.io.HttpRequests
-import software.amazon.awssdk.core.exception.SdkException
-import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient
-import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.ContentChecksumType
-import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.GetTransformationPlanRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.GetTransformationPlanResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.GetTransformationRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.GetTransformationResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.IdeCategory
-import software.amazon.awssdk.services.codewhispererruntime.model.ResumeTransformationRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.ResumeTransformationResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.StartTransformationRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.StartTransformationResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.StopTransformationRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.StopTransformationResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationLanguage
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationType
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationUploadArtifactType
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationUploadContext
-import software.amazon.awssdk.services.codewhispererruntime.model.TransformationUserActionStatus
-import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext
-import software.amazon.awssdk.services.codewhispererruntime.model.UploadIntent
-import software.amazon.awssdk.services.codewhispererstreaming.model.ExportContext
-import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent
-import software.amazon.awssdk.services.codewhispererstreaming.model.ThrottlingException
-import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType
-import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationExportContext
-import software.amazon.awssdk.services.codewhispererstreaming.model.ValidationException
-import software.amazon.q.core.utils.error
-import software.amazon.q.core.utils.getLogger
-import software.amazon.q.core.utils.info
-import software.amazon.q.jetbrains.core.AwsClientManager
-import software.aws.toolkits.jetbrains.services.amazonq.APPLICATION_ZIP
-import software.aws.toolkits.jetbrains.services.amazonq.AWS_KMS
-import software.aws.toolkits.jetbrains.services.amazonq.CONTENT_SHA256
-import software.aws.toolkits.jetbrains.services.amazonq.RetryableOperation
-import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION
-import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID
-import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient
-import software.aws.toolkits.jetbrains.services.amazonq.codeWhispererUserContext
-import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerMetrics
-import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
-import software.aws.toolkits.jetbrains.services.codemodernizer.utils.calculateTotalLatency
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getTelemetryOptOutPreference
-import java.io.File
-import java.net.HttpURLConnection
-import java.net.SocketTimeoutException
-import java.time.Instant
-import java.util.concurrent.TimeoutException
-
-@Service(Service.Level.PROJECT)
-class GumbyClient(private val project: Project) {
- private fun bearerClient() = QRegionProfileManager.getInstance().getQClient(project)
-
- private val amazonQStreamingClient
- get() = AmazonQStreamingClient.getInstance(project)
-
- fun createGumbyUploadUrl(sha256Checksum: String, context: UploadContext? = null): CreateUploadUrlResponse {
- val request = CreateUploadUrlRequest.builder()
- .contentChecksumType(ContentChecksumType.SHA_256)
- .contentChecksum(sha256Checksum)
- .uploadIntent(UploadIntent.TRANSFORMATION)
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .uploadContext(context)
- .build()
- return callApi({ bearerClient().createUploadUrl(request) }, apiName = "CreateUploadUrl")
- }
-
- fun createHilUploadUrl(sha256Checksum: String, jobId: JobId): CreateUploadUrlResponse {
- val request = CreateUploadUrlRequest.builder()
- .contentChecksumType(ContentChecksumType.SHA_256)
- .contentChecksum(sha256Checksum)
- .uploadIntent(UploadIntent.TRANSFORMATION)
- .uploadContext(
- UploadContext
- .builder()
- .transformationUploadContext(
- TransformationUploadContext
- .builder()
- .uploadArtifactType(TransformationUploadArtifactType.DEPENDENCIES)
- .jobId(jobId.id)
- .build()
- )
- .build()
- )
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .build()
- return callApi({ bearerClient().createUploadUrl(request) }, apiName = "CreateUploadUrl")
- }
-
- fun getCodeModernizationJob(jobId: String): GetTransformationResponse {
- val request = GetTransformationRequest.builder()
- .transformationJobId(jobId)
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .build()
- return callApi({ bearerClient().getTransformation(request) }, apiName = "GetTransformation")
- }
-
- fun startCodeModernization(
- uploadId: String,
- sourceLanguage: TransformationLanguage,
- targetLanguage: TransformationLanguage,
- ): StartTransformationResponse {
- val request = StartTransformationRequest.builder()
- .workspaceState { state ->
- state.programmingLanguage { it.languageName("java") }
- .uploadId(uploadId)
- }
- .transformationSpec { spec ->
- spec.transformationType(TransformationType.LANGUAGE_UPGRADE)
- .source { it.language(sourceLanguage) }
- .target { it.language(targetLanguage) }
- }
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .build()
- return callApi({ bearerClient().startTransformation(request) }, apiName = "StartTransformation")
- }
-
- fun resumeCodeTransformation(
- jobId: JobId,
- userActionStatus: TransformationUserActionStatus,
- ): ResumeTransformationResponse {
- val request = ResumeTransformationRequest.builder()
- .transformationJobId(jobId.id)
- .userActionStatus(userActionStatus)
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .build()
- return callApi({ bearerClient().resumeTransformation(request) }, apiName = "ResumeTransformation")
- }
-
- fun getCodeModernizationPlan(jobId: JobId): GetTransformationPlanResponse {
- val request = GetTransformationPlanRequest.builder()
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .transformationJobId(jobId.id).build()
- return callApi({ bearerClient().getTransformationPlan(request) }, apiName = "GetTransformationPlan")
- }
-
- fun stopTransformation(transformationJobId: String): StopTransformationResponse {
- val request = StopTransformationRequest.builder().transformationJobId(transformationJobId)
- .profileArn(QRegionProfileManager.getInstance().activeProfile(project)?.arn)
- .build()
- return callApi({ bearerClient().stopTransformation(request) }, apiName = "StopTransformation")
- }
-
- private fun callApi(
- apiCall: () -> T,
- apiName: String,
- ): T {
- var result: T? = null
- try {
- RetryableOperation().execute(
- operation = {
- result = apiCall()
- },
- isRetryable = { e ->
- when (e) {
- is ValidationException,
- is ThrottlingException,
- is SdkException,
- is TimeoutException,
- is SocketTimeoutException,
- -> true
- else -> false
- }
- },
- errorHandler = { e, attempts ->
- LOG.error(e) { "After $attempts attempts, $apiName failed: ${e.message}" }
- throw e
- }
- )
- } catch (e: Exception) {
- LOG.error(e) { "$apiName failed: ${e.message}; may have been retried up to 3 times" }
- throw e
- }
- LOG.info { "$apiName request ID: ${result?.responseMetadata()?.requestId()}" }
- return result ?: error("$apiName failed")
- }
-
- suspend fun downloadExportResultArchive(
- jobId: JobId,
- downloadArtifactId: String? = null,
- downloadArtifactType: TransformationDownloadArtifactType? = TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS,
- ): MutableList