Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,7 @@ class DocApp : AmazonQApp {
is IncomingDocMessage.TabRemoved -> inboundAppMessagesHandler.processTabRemovedMessage(message)
is IncomingDocMessage.AuthFollowUpWasClicked -> inboundAppMessagesHandler.processAuthFollowUpClick(message)
is IncomingDocMessage.FollowupClicked -> inboundAppMessagesHandler.processFollowupClickedMessage(message)
is IncomingDocMessage.ChatItemVotedMessage -> inboundAppMessagesHandler.processChatItemVotedMessage(message)
is IncomingDocMessage.ChatItemFeedbackMessage -> inboundAppMessagesHandler.processChatItemFeedbackMessage(message)
is IncomingDocMessage.ClickedLink -> inboundAppMessagesHandler.processLinkClick(message)
is IncomingDocMessage.InsertCodeAtCursorPosition -> inboundAppMessagesHandler.processInsertCodeAtCursorPosition(message)
is IncomingDocMessage.OpenDiff -> inboundAppMessagesHandler.processOpenDiff(message)
is IncomingDocMessage.FileClicked -> inboundAppMessagesHandler.processFileClicked(message)
is IncomingDocMessage.StopDocGeneration -> inboundAppMessagesHandler.processStopDocGeneration(message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ interface InboundAppMessagesHandler {
suspend fun processTabRemovedMessage(message: IncomingDocMessage.TabRemoved)
suspend fun processAuthFollowUpClick(message: IncomingDocMessage.AuthFollowUpWasClicked)
suspend fun processFollowupClickedMessage(message: IncomingDocMessage.FollowupClicked)
suspend fun processChatItemVotedMessage(message: IncomingDocMessage.ChatItemVotedMessage)
suspend fun processChatItemFeedbackMessage(message: IncomingDocMessage.ChatItemFeedbackMessage)
suspend fun processLinkClick(message: IncomingDocMessage.ClickedLink)
suspend fun processInsertCodeAtCursorPosition(message: IncomingDocMessage.InsertCodeAtCursorPosition)
suspend fun processOpenDiff(message: IncomingDocMessage.OpenDiff)
suspend fun processFileClicked(message: IncomingDocMessage.FileClicked)
suspend fun processStopDocGeneration(message: IncomingDocMessage.StopDocGeneration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,19 @@

package software.aws.toolkits.jetbrains.services.amazonqDoc.controller

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.intellij.diff.DiffContentFactory
import com.intellij.diff.DiffManager
import com.intellij.diff.contents.EmptyContent
import com.intellij.diff.requests.SimpleDiffRequest
import com.intellij.diff.util.DiffUserDataKeys
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.wm.ToolWindowManager
import kotlinx.coroutines.withContext
import software.amazon.awssdk.services.codewhispererruntime.model.DocGenerationFolderLevel
import software.amazon.awssdk.services.codewhispererruntime.model.DocGenerationInteractionType
import software.amazon.awssdk.services.codewhispererruntime.model.DocGenerationUserDecision
import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.error
import software.aws.toolkits.core.utils.getLogger
Expand All @@ -37,7 +31,6 @@ import software.aws.toolkits.jetbrains.services.amazonqDoc.DEFAULT_RETRY_LIMIT
import software.aws.toolkits.jetbrains.services.amazonqDoc.DocException
import software.aws.toolkits.jetbrains.services.amazonqDoc.FEATURE_NAME
import software.aws.toolkits.jetbrains.services.amazonqDoc.InboundAppMessagesHandler
import software.aws.toolkits.jetbrains.services.amazonqDoc.ModifySourceFolderErrorReason
import software.aws.toolkits.jetbrains.services.amazonqDoc.ZipFileError
import software.aws.toolkits.jetbrains.services.amazonqDoc.cancellingProgressField
import software.aws.toolkits.jetbrains.services.amazonqDoc.createUserFacingErrorMessage
Expand Down Expand Up @@ -74,13 +67,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.Delete
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.NewFileZipInfo
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.SessionStatePhase
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.CancellationTokenSource
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.FeedbackComment
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
import software.aws.toolkits.jetbrains.utils.notifyError
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.AmazonqTelemetry
import software.aws.toolkits.telemetry.Result
import java.util.UUID

enum class DocGenerationStep {
Expand Down Expand Up @@ -343,84 +330,13 @@ class DocController(
}
}

override suspend fun processChatItemVotedMessage(message: IncomingDocMessage.ChatItemVotedMessage) {
logger.debug { "$FEATURE_NAME: Processing ChatItemVotedMessage: $message" }

val session = chatSessionStorage.getSession(message.tabId, context.project)
when (message.vote) {
"upvote" -> {
AmazonqTelemetry.codeGenerationThumbsUp(
amazonqConversationId = session.conversationId,
credentialStartUrl = getStartUrl(project = context.project)
)
}

"downvote" -> {
AmazonqTelemetry.codeGenerationThumbsDown(
amazonqConversationId = session.conversationId,
credentialStartUrl = getStartUrl(project = context.project)
)
}
}
}

override suspend fun processChatItemFeedbackMessage(message: IncomingDocMessage.ChatItemFeedbackMessage) {
logger.debug { "$FEATURE_NAME: Processing ChatItemFeedbackMessage: ${message.comment}" }

val session = getSessionInfo(message.tabId)

val comment = FeedbackComment(
conversationId = session.conversationId,
userComment = message.comment.orEmpty(),
reason = message.selectedOption,
messageId = message.messageId,
type = "doc-chat-answer-feedback"
)

try {
TelemetryService.getInstance().sendFeedback(
sentiment = Sentiment.NEGATIVE,
comment = objectMapper.writeValueAsString(comment),
)
logger.info { "$FEATURE_NAME answer feedback sent: \"Negative\"" }
} catch (e: Throwable) {
e.notifyError(message("feedback.submit_failed", e))
logger.warn(e) { "Failed to submit feedback" }
return
}
}

override suspend fun processLinkClick(message: IncomingDocMessage.ClickedLink) {
BrowserUtil.browse(message.link)
}

override suspend fun processInsertCodeAtCursorPosition(message: IncomingDocMessage.InsertCodeAtCursorPosition) {
logger.debug { "$FEATURE_NAME: Processing InsertCodeAtCursorPosition: $message" }

withContext(EDT) {
val editor: Editor = FileEditorManager.getInstance(context.project).selectedTextEditor ?: return@withContext

val caret: Caret = editor.caretModel.primaryCaret
val offset: Int = caret.offset

WriteCommandAction.runWriteCommandAction(context.project) {
if (caret.hasSelection()) {
editor.document.deleteString(caret.selectionStart, caret.selectionEnd)
}
editor.document.insertString(offset, message.code)
}
}
}

override suspend fun processOpenDiff(message: IncomingDocMessage.OpenDiff) {
val session = getSessionInfo(message.tabId)

AmazonqTelemetry.isReviewedChanges(
amazonqConversationId = session.conversationId,
enabled = true,
credentialStartUrl = getStartUrl(project = context.project)
)

val project = context.project
val sessionState = session.sessionState

Expand Down Expand Up @@ -528,13 +444,6 @@ class DocController(
}
}

AmazonqTelemetry.isAcceptedCodeChanges(
amazonqNumberOfFilesAccepted = (filePaths.filterNot { it.rejected }.size + deletedFiles.filterNot { it.rejected }.size) * 1.0,
amazonqConversationId = session.conversationId,
enabled = true,
credentialStartUrl = getStartUrl(project = context.project)
)

session.insertChanges(
filePaths = filePaths.filterNot { it.rejected },
deletedFiles = deletedFiles.filterNot { it.rejected }
Expand Down Expand Up @@ -572,14 +481,7 @@ class DocController(
}

private suspend fun newTask(tabId: String) {
val session = getSessionInfo(tabId)
val sessionLatency = System.currentTimeMillis() - session.sessionStartTime
docGenerationTask = DocGenerationTask()
AmazonqTelemetry.endChat(
amazonqConversationId = session.conversationId,
amazonqEndOfTheConversationLatency = sessionLatency.toDouble(),
credentialStartUrl = getStartUrl(project = context.project)
)
chatSessionStorage.deleteSession(tabId)

messenger.sendAnswer(
Expand Down Expand Up @@ -627,25 +529,9 @@ class DocController(

messenger.sendChatInputEnabledMessage(tabId = tabId, enabled = false)
docGenerationTask.reset()

val session = getSessionInfo(tabId)
val sessionLatency = System.currentTimeMillis() - session.sessionStartTime
AmazonqTelemetry.endChat(
amazonqConversationId = session.conversationId,
amazonqEndOfTheConversationLatency = sessionLatency.toDouble(),
credentialStartUrl = getStartUrl(project = context.project)
)
}

private suspend fun provideFeedbackAndRegenerateCode(tabId: String) {
val session = getSessionInfo(tabId)

AmazonqTelemetry.isProvideFeedbackForCodeGen(
amazonqConversationId = session.conversationId,
enabled = true,
credentialStartUrl = getStartUrl(project = context.project)
)

// Unblock the message button
messenger.sendAsyncEventProgress(tabId = tabId, inProgress = false)

Expand Down Expand Up @@ -1007,16 +893,11 @@ class DocController(
val currentSourceFolder = session.context.selectedSourceFolder
val projectRoot = session.context.projectRoot

var result: Result = Result.Failed
var reason: ModifySourceFolderErrorReason? = null

withContext(EDT) {
val selectedFolder = selectFolder(context.project, currentSourceFolder)
// No folder was selected
if (selectedFolder == null) {
logger.info { "Cancelled dialog and not selected any folder" }

reason = ModifySourceFolderErrorReason.ClosedBeforeSelection
return@withContext
}

Expand All @@ -1029,8 +910,6 @@ class DocController(
messageType = DocMessageType.Answer,
message = message("amazonqFeatureDev.follow_up.incorrect_source_folder"),
)

reason = ModifySourceFolderErrorReason.NotInWorkspaceFolder
return@withContext
}
if (selectedFolder.path == projectRoot.path) {
Expand All @@ -1042,21 +921,13 @@ class DocController(
logger.info { "Selected correct folder inside workspace: ${selectedFolder.path}" }

session.context.selectedSourceFolder = selectedFolder
result = Result.Succeeded

promptForDocTarget(tabId)

messenger.sendChatInputEnabledMessage(tabId, enabled = false)

messenger.sendUpdatePlaceholder(tabId = tabId, newPlaceholder = message("amazonqDoc.prompt.placeholder"))
}

AmazonqTelemetry.modifySourceFolder(
amazonqConversationId = session.conversationId,
credentialStartUrl = getStartUrl(project = context.project),
result = result,
reason = reason?.toString()
)
}

private fun sendDocGenerationTelemetry(tabId: String) {
Expand Down Expand Up @@ -1086,7 +957,5 @@ class DocController(

companion object {
private val logger = getLogger<DocController>()

private val objectMapper = jacksonObjectMapper()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.Sessio
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.registerDeletedFiles
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.registerNewFiles
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.CancellationTokenSource
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.AmazonqTelemetry
import software.aws.toolkits.telemetry.MetricResult

private val logger = getLogger<DocGenerationState>()

Expand All @@ -43,20 +40,13 @@ class DocGenerationState(
val config: SessionStateConfig,
val uploadId: String,
val currentIteration: Int,
val repositorySize: Double,
val messenger: MessagePublisher,
var codeGenerationRemainingIterationCount: Int? = null,
var codeGenerationTotalIterationCount: Int? = null,
override val phase: SessionStatePhase?,
override var token: CancellationTokenSource?,
) : SessionState {
override suspend fun interact(action: SessionStateAction): SessionStateInteraction<SessionState> {
val startTime = System.currentTimeMillis()
var result: MetricResult = MetricResult.Succeeded
var failureReason: String? = null
var codeGenerationWorkflowStatus: CodeGenerationWorkflowStatus = CodeGenerationWorkflowStatus.COMPLETE
var numberOfReferencesGenerated: Int? = null
var numberOfFilesGenerated: Int? = null
try {
val response = config.amazonQCodeGenService.startTaskAssistCodeGeneration(
conversationId = config.conversationId,
Expand All @@ -66,8 +56,6 @@ class DocGenerationState(
)
val mode = if (action.msg == message("amazonqDoc.session.create")) Mode.CREATE else null
val codeGenerationResult = generateCode(codeGenerationId = response.codeGenerationId(), mode, token)
numberOfReferencesGenerated = codeGenerationResult.references.size
numberOfFilesGenerated = codeGenerationResult.newFiles.size
codeGenerationRemainingIterationCount = codeGenerationResult.codeGenerationRemainingIterationCount
codeGenerationTotalIterationCount = codeGenerationResult.codeGenerationTotalIterationCount

Expand All @@ -94,25 +82,7 @@ class DocGenerationState(
)
} catch (e: Exception) {
logger.warn(e) { "$FEATURE_NAME: Code generation failed: ${e.message}" }
result = MetricResult.Failed
failureReason = e.javaClass.simpleName
codeGenerationWorkflowStatus = CodeGenerationWorkflowStatus.FAILED

throw e
} finally {
AmazonqTelemetry.codeGenerationInvoke(
amazonqConversationId = config.conversationId,
amazonqCodeGenerationResult = codeGenerationWorkflowStatus.toString(),
amazonqGenerateCodeIteration = currentIteration.toDouble(),
amazonqNumberOfReferences = numberOfReferencesGenerated?.toDouble(),
amazonqGenerateCodeResponseLatency = (System.currentTimeMillis() - startTime).toDouble(),
amazonqNumberOfFilesGenerated = numberOfFilesGenerated?.toDouble(),
amazonqRepositorySize = repositorySize,
result = result,
reason = failureReason,
duration = (System.currentTimeMillis() - startTime).toDouble(),
credentialStartUrl = getStartUrl(config.amazonQCodeGenService.project)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.Sessio
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.CancellationTokenSource
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.deleteUploadArtifact
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.uploadArtifactToS3
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
import software.aws.toolkits.telemetry.AmazonqTelemetry
import software.aws.toolkits.telemetry.AmazonqUploadIntent
import software.aws.toolkits.telemetry.Result

private val logger = getLogger<PrepareDocGenerationState>()

Expand All @@ -40,10 +36,6 @@ class PrepareDocGenerationState(
) : SessionState {
override val phase = SessionStatePhase.CODEGEN
override suspend fun interact(action: SessionStateAction): SessionStateInteraction<SessionState> {
val startTime = System.currentTimeMillis()
var result: Result = Result.Succeeded
var failureReason: String? = null
var failureReasonDesc: String? = null
var zipFileLength: Long? = null
val nextState: SessionState
try {
Expand Down Expand Up @@ -76,30 +68,14 @@ class PrepareDocGenerationState(
config = this.config,
uploadId = this.uploadId,
currentIteration = this.currentIteration,
repositorySize = zipFileLength.toDouble(),
messenger = messenger,
phase = phase,
token = this.token
)
} catch (e: Exception) {
result = Result.Failed
failureReason = e.javaClass.simpleName
failureReasonDesc = e.message
logger.warn(e) { "$FEATURE_NAME: Code uploading failed: ${e.message}" }
throw e
} finally {
AmazonqTelemetry.createUpload(
amazonqConversationId = config.conversationId,
amazonqRepositorySize = zipFileLength?.toDouble(),
amazonqUploadIntent = AmazonqUploadIntent.TASKASSISTPLANNING,
result = result,
reason = failureReason,
reasonDesc = failureReasonDesc,
duration = (System.currentTimeMillis() - startTime).toDouble(),
credentialStartUrl = getStartUrl(config.amazonQCodeGenService.project)
)
}
// It is essential to interact with the next state outside of try-catch block for the telemetry to capture events for the states separately
return nextState.interact(action)
}
}
Loading