From 24a0832af005b9a321453066f4ebdd20b133734c Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Fri, 21 Feb 2025 13:43:53 -0800 Subject: [PATCH 01/25] add build-execute code --- .../CodeWhispererUTGChatManager.kt | 33 ++-- .../controller/CodeTestChatController.kt | 156 +++++++++++------- .../model/BuildAndExecuteStatusIcon.kt | 13 +- .../model/PreviousUTGIterationContext.kt | 2 +- .../amazonqCodeTest/session/Session.kt | 2 + .../amazonqCodeTest/utils/UTGChatUtil.kt | 28 +++- .../sessionconfig/CodeTestSessionConfig.kt | 11 +- .../credentials/CodeWhispererClientAdaptor.kt | 16 +- 8 files changed, 174 insertions(+), 87 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index 8b02c019c2..bbcd79199a 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -37,7 +37,6 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.PreviousUT import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.ShortAnswer import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteProgressStatus import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.Session -import software.aws.toolkits.jetbrains.services.amazonqCodeTest.utils.combineBuildAndExecuteLogFiles import software.aws.toolkits.jetbrains.services.codemodernizer.utils.calculateTotalLatency import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.CodeTestException import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.fileTooLarge @@ -59,10 +58,10 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException -import java.nio.file.Path import java.nio.file.Paths import java.time.Duration import java.time.Instant +import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean import java.util.zip.ZipInputStream @@ -89,6 +88,10 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin val session = codeTestChatHelper.getActiveSession() session.isGeneratingTests = true session.iteration++ + if (session.testGenerationJobGroupName.isNullOrEmpty()) { + session.testGenerationJobGroupName = UUID.randomUUID().toString() + } + val final = session.testGenerationJobGroupName // Set the Progress bar to "Generating unit tests..." codeTestChatHelper.updateUI( @@ -137,7 +140,8 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin ) .build() ), - userInput = prompt + userInput = prompt, + testGenerationJobGroupName = final ) delay(200) response?.testGenerationJob() != null @@ -175,7 +179,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin var shortAnswer = ShortAnswer() LOG.debug { "Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()}: " + - "polling result for id: ${job.testGenerationJobId()}, group name: ${job.testGenerationJobGroupName()}, " + + "polling result for id: ${job.testGenerationJobId()}, group name: ${session.testGenerationJobGroupName}, " + "request id: ${startTestGenerationResponse.responseMetadata().requestId()}" } @@ -198,6 +202,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin // Setting default value to 0 if the value is null or invalid session.numberOfUnitTestCasesGenerated = shortAnswer.numberOfTestMethods session.testFileRelativePathToProjectRoot = getTestFilePathRelativeToRoot(shortAnswer) + session.shortAnswer = shortAnswer // update test summary card in success case if (previousIterationContext == null) { @@ -250,10 +255,9 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin if (shortAnswer.stopIteration == "true") { throw CodeTestException("TestGenFailedError: ${shortAnswer.planSummary}", "TestGenFailedError", shortAnswer.planSummary) } - val fileName = shortAnswer.sourceFilePath?.let { Path.of(it).fileName.toString() } ?: path.fileName.toString() codeTestChatHelper.updateAnswer( CodeTestChatMessageContent( - message = generateSummaryMessage(fileName) + shortAnswer.planSummary, + message = generateSummaryMessage(path.fileName.toString()) + shortAnswer.planSummary, type = ChatMessageType.Answer ), messageIdOverride = codeTestResponseContext.testSummaryMessageId @@ -297,7 +301,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin // TODO: Modify text according to FnF codeTestChatHelper.addAnswer( CodeTestChatMessageContent( - message = message("testgen.error.generic_technical_error_message"), + message = message("testgen.message.failed"), type = ChatMessageType.Answer, canBeVoted = true ) @@ -469,10 +473,8 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin previousIterationContext.selectedFile } - val combinedBuildAndExecuteLogFile = combineBuildAndExecuteLogFiles( - previousIterationContext?.buildLogFile, - previousIterationContext?.testLogFile - ) + val combinedBuildAndExecuteLogFile = + previousIterationContext?.buildLogFile val codeTestSessionConfig = CodeTestSessionConfig(file, project, combinedBuildAndExecuteLogFile) codeTestChatHelper.getActiveSession().projectRoot = codeTestSessionConfig.projectRoot.path @@ -481,8 +483,13 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin return codeWhispererCodeTestSession.run(codeTestChatHelper, previousIterationContext) } - private fun startTestGeneration(uploadId: String, targetCode: List, userInput: String): StartTestGenerationResponse = - CodeWhispererClientAdaptor.getInstance(project).startTestGeneration(uploadId, targetCode, userInput) + private fun startTestGeneration( + uploadId: String, + targetCode: List, + userInput: String, + testGenerationJobGroupName: String, + ): StartTestGenerationResponse = + CodeWhispererClientAdaptor.getInstance(project).startTestGeneration(uploadId, targetCode, userInput, testGenerationJobGroupName) private fun getTestGenerationStatus(jobId: String, jobGroupName: String): GetTestGenerationResponse = CodeWhispererClientAdaptor.getInstance(project).getTestGeneration(jobId, jobGroupName) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index 2807e2cf9c..b4ffee5bc9 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -53,6 +53,7 @@ import software.aws.toolkits.jetbrains.core.AwsClientManager import software.aws.toolkits.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection +import software.aws.toolkits.jetbrains.core.credentials.sono.isInternalUser 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.project.RelevantDocument @@ -67,6 +68,7 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.Incomin import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.PreviousUTGIterationContext import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteProgressStatus import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.Session +import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.UTG_CHAT_MAX_ITERATION import software.aws.toolkits.jetbrains.services.amazonqCodeTest.storage.ChatSessionStorage import software.aws.toolkits.jetbrains.services.amazonqCodeTest.utils.constructBuildAndExecutionSummaryText import software.aws.toolkits.jetbrains.services.amazonqCodeTest.utils.runBuildOrTestCommand @@ -617,6 +619,8 @@ class CodeTestChatController( "utg_accept" -> { // open the file at test path relative to the project root val testFileAbsolutePath = Paths.get(session.projectRoot, session.testFileRelativePathToProjectRoot) + val startUrl = getStartUrl(context.project) + val isInternalUser = isInternalUser(startUrl) openOrCreateTestFileAndApplyDiff(context.project, testFileAbsolutePath, session.generatedTestDiffs.values.first(), session.openedDiffFile) session.codeReferences?.let { references -> LOG.debug { "Accepted unit tests with references: $references" } @@ -691,16 +695,20 @@ class CodeTestChatController( canBeVoted = false ) ) - sessionCleanUp(session.tabId) + if (!isInternalUser) { + sessionCleanUp(message.tabId) + return + } + codeTestChatHelper.updateUI( promptInputDisabledState = false, promptInputPlaceholder = message("testgen.placeholder.enter_slash_quick_actions"), ) - /* + val taskContext = session.buildAndExecuteTaskContext if (session.iteration < 2) { - taskContext.buildCommand = getBuildCommand(message.tabId) - taskContext.executionCommand = getExecutionCommand(message.tabId) + taskContext.buildCommand = codeTestChatHelper.getSession(message.tabId).shortAnswer.buildCommand.toString() + codeTestChatHelper.addAnswer( CodeTestChatMessageContent( message = """ @@ -708,7 +716,6 @@ class CodeTestChatController( ```sh ${taskContext.buildCommand} - ${taskContext.executionCommand} ``` """.trimIndent(), type = ChatMessageType.Answer, @@ -790,10 +797,9 @@ class CodeTestChatController( promptInputPlaceholder = message("testgen.placeholder.newtab") ) } - */ } - /* - //TODO: this is for unit test regeneration build iteration loop + + // TODO: this is for unit test regeneration build iteration loop "utg_regenerate" -> { // close the existing open diff in the editor. ApplicationManager.getApplication().invokeLater { @@ -810,6 +816,7 @@ class CodeTestChatController( session.testGenerationJob, session.testGenerationJobGroupName, session.programmingLanguage, + IdeCategory.JETBRAINS, session.numberOfUnitTestCasesGenerated, 0, session.linesOfCodeGenerated, @@ -821,13 +828,12 @@ class CodeTestChatController( "Successfully sent test generation telemetry. RequestId: ${ testGenerationEventResponse.responseMetadata().requestId()}" } - sessionCleanUp(session.tabId) + // sessionCleanUp(session.tabId) codeTestChatHelper.updateUI( promptInputDisabledState = false, promptInputPlaceholder = message("testgen.placeholder.waiting_on_your_inputs"), ) } - */ "utg_reject" -> { ApplicationManager.getApplication().invokeLater { @@ -917,43 +923,14 @@ class CodeTestChatController( "tmpFile for build logs:\n ${buildLogsFile.path}" } - runBuildOrTestCommand(taskContext.buildCommand, buildLogsFile, context.project, isBuildCommand = true, taskContext) + runBuildOrTestCommand(taskContext.buildCommand, buildLogsFile, context.project, isBuildCommand = true, taskContext, session.projectRoot) while (taskContext.buildExitCode < 0) { // wait until build command finished delay(1000) } - // TODO: only go to future iterations when buildExitCode or testExitCode > 0, right now iterate regardless - if (taskContext.buildExitCode > 0) { - // TODO: handle build failure case - // ... -// return - } - taskContext.progressStatus = BuildAndExecuteProgressStatus.RUN_EXECUTION_TESTS - updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) - - val testLogsFile = VirtualFileManager.getInstance().findFileByNioPath( - withContext(currentCoroutineContext()) { - Files.createTempFile(null, null) - } - ) - if (testLogsFile == null) { - // TODO: handle no log file case - return - } - LOG.debug { - "Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()}: " + - "tmpFile for test logs:\n ${buildLogsFile.path}" - } - delay(1000) - runBuildOrTestCommand(taskContext.executionCommand, testLogsFile, context.project, isBuildCommand = false, taskContext) - while (taskContext.testExitCode < 0) { - // wait until test command finished - delay(1000) - } - - if (taskContext.testExitCode == 0) { - taskContext.progressStatus = BuildAndExecuteProgressStatus.TESTS_EXECUTED + if (taskContext.buildExitCode == 0) { + taskContext.progressStatus = BuildAndExecuteProgressStatus.PROCESS_TEST_RESULTS updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) codeTestChatHelper.addAnswer( CodeTestChatMessageContent( @@ -966,23 +943,90 @@ class CodeTestChatController( return } - // has test failure, we will zip the latest project and invoke backend again - taskContext.progressStatus = BuildAndExecuteProgressStatus.FIXING_TEST_CASES - val buildAndExecuteMessageId = updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) + // TODO: only go to future iterations when buildExitCode or testExitCode > 0, right now iterate regardless + if (taskContext.buildExitCode > 0) { + // handle build failure case - val previousUTGIterationContext = PreviousUTGIterationContext( - buildLogFile = buildLogsFile, - testLogFile = testLogsFile, - selectedFile = session.selectedFile, - buildAndExecuteMessageId = buildAndExecuteMessageId - ) + taskContext.progressStatus = BuildAndExecuteProgressStatus.BUILD_FAILED + updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) + taskContext.progressStatus = BuildAndExecuteProgressStatus.FIXING_TEST_CASES + val buildAndExecuteMessageId = updateBuildAndExecuteProgressCard( + taskContext.progressStatus, + messageId, + session.iteration + 1 + ) + + val previousUTGIterationContext = PreviousUTGIterationContext( + buildLogFile = buildLogsFile, + testLogFile = null, + selectedFile = session.selectedFile, + buildAndExecuteMessageId = buildAndExecuteMessageId + ) + + session.isGeneratingTests = false + session.conversationState = ConversationState.IDLE - val job = CodeWhispererUTGChatManager.getInstance(context.project).generateTests("", codeTestChatHelper, previousUTGIterationContext, null) - job?.join() + val job = CodeWhispererUTGChatManager.getInstance( + context.project + ).generateTests(session.userPrompt, codeTestChatHelper, previousUTGIterationContext, null) + job?.join() - taskContext.progressStatus = BuildAndExecuteProgressStatus.PROCESS_TEST_RESULTS - // session.iteration already updated in generateTests - updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration - 1) + return + } +// taskContext.progressStatus = BuildAndExecuteProgressStatus.RUN_EXECUTION_TESTS +// updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) +// +// val testLogsFile = VirtualFileManager.getInstance().findFileByNioPath( +// withContext(currentCoroutineContext()) { +// Files.createTempFile(null, null) +// } +// ) +// if (testLogsFile == null) { +// // TODO: handle no log file case +// return +// } +// LOG.debug { +// "Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()}: " + +// "tmpFile for test logs:\n ${buildLogsFile.path}" +// } +// delay(1000) +// runBuildOrTestCommand(taskContext.executionCommand, testLogsFile, context.project, isBuildCommand = false, taskContext) +// while (taskContext.testExitCode < 0) { +// // wait until test command finished +// delay(1000) +// } +// +// if (taskContext.testExitCode == 0) { +// taskContext.progressStatus = BuildAndExecuteProgressStatus.TESTS_EXECUTED +// updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) +// codeTestChatHelper.addAnswer( +// CodeTestChatMessageContent( +// message = message("testgen.message.success"), +// type = ChatMessageType.Answer, +// canBeVoted = false +// ) +// ) +// // sessionCleanUp(message.tabId) +// return +// } +// +// // has test failure, we will zip the latest project and invoke backend again +// taskContext.progressStatus = BuildAndExecuteProgressStatus.FIXING_TEST_CASES +// val buildAndExecuteMessageId = updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) +// +// val previousUTGIterationContext = PreviousUTGIterationContext( +// buildLogFile = buildLogsFile, +// testLogFile = testLogsFile, +// selectedFile = session.selectedFile, +// buildAndExecuteMessageId = buildAndExecuteMessageId +// ) +// +// val job = CodeWhispererUTGChatManager.getInstance(context.project).generateTests("", codeTestChatHelper, previousUTGIterationContext, null) +// job?.join() +// +// taskContext.progressStatus = BuildAndExecuteProgressStatus.PROCESS_TEST_RESULTS +// // session.iteration already updated in generateTests +// updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration - 1) } "utg_modify_command" -> { // TODO allow user input to modify the command diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/BuildAndExecuteStatusIcon.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/BuildAndExecuteStatusIcon.kt index 0847cec649..641a715fc4 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/BuildAndExecuteStatusIcon.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/BuildAndExecuteStatusIcon.kt @@ -9,6 +9,7 @@ enum class BuildAndExecuteStatusIcon(val icon: String) { WAIT(""), CURRENT(""), DONE(""), + FAILED(""), } fun getBuildIcon(progressStatus: BuildAndExecuteProgressStatus) = @@ -16,6 +17,8 @@ fun getBuildIcon(progressStatus: BuildAndExecuteProgressStatus) = BuildAndExecuteStatusIcon.WAIT.icon } else if (progressStatus == BuildAndExecuteProgressStatus.RUN_BUILD) { BuildAndExecuteStatusIcon.CURRENT.icon + } else if (progressStatus == BuildAndExecuteProgressStatus.BUILD_FAILED || progressStatus == BuildAndExecuteProgressStatus.FIXING_TEST_CASES) { + BuildAndExecuteStatusIcon.FAILED.icon } else { BuildAndExecuteStatusIcon.DONE.icon } @@ -25,15 +28,19 @@ fun getExecutionIcon(progressStatus: BuildAndExecuteProgressStatus) = BuildAndExecuteStatusIcon.WAIT.icon } else if (progressStatus == BuildAndExecuteProgressStatus.RUN_EXECUTION_TESTS) { BuildAndExecuteStatusIcon.CURRENT.icon + } else if (progressStatus == BuildAndExecuteProgressStatus.BUILD_FAILED || progressStatus == BuildAndExecuteProgressStatus.FIXING_TEST_CASES) { + BuildAndExecuteStatusIcon.FAILED.icon } else { BuildAndExecuteStatusIcon.DONE.icon } fun getFixingTestCasesIcon(progressStatus: BuildAndExecuteProgressStatus) = - if (progressStatus < BuildAndExecuteProgressStatus.FIXING_TEST_CASES) { - BuildAndExecuteStatusIcon.WAIT.icon + if (progressStatus == BuildAndExecuteProgressStatus.BUILD_FAILED) { + BuildAndExecuteStatusIcon.FAILED.icon } else if (progressStatus == BuildAndExecuteProgressStatus.FIXING_TEST_CASES) { BuildAndExecuteStatusIcon.CURRENT.icon - } else { + } else if (progressStatus >= BuildAndExecuteProgressStatus.PROCESS_TEST_RESULTS) { BuildAndExecuteStatusIcon.DONE.icon + } else { + BuildAndExecuteStatusIcon.WAIT.icon } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/PreviousUTGIterationContext.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/PreviousUTGIterationContext.kt index 4576a0ea0c..9ee4c0fe33 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/PreviousUTGIterationContext.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/model/PreviousUTGIterationContext.kt @@ -7,7 +7,7 @@ import com.intellij.openapi.vfs.VirtualFile data class PreviousUTGIterationContext( val buildLogFile: VirtualFile, - val testLogFile: VirtualFile, + val testLogFile: VirtualFile?, val selectedFile: VirtualFile?, val buildAndExecuteMessageId: String?, ) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt index f7c067b5b8..a7b66a2f46 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt @@ -35,6 +35,7 @@ data class Session(val tabId: String) { var artifactUploadDuration: Long = 0 // First iteration will have a value of 1 + var userPrompt: String = "" var iteration: Int = 0 var projectRoot: String = "/" var shortAnswer: ShortAnswer = ShortAnswer() @@ -62,6 +63,7 @@ enum class BuildAndExecuteProgressStatus { START_STEP, INSTALL_DEPENDENCIES, RUN_BUILD, + BUILD_FAILED, RUN_EXECUTION_TESTS, TESTS_EXECUTED, FIXING_TEST_CASES, diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt index 9b1615c461..d66ab66e3c 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt @@ -26,6 +26,7 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getFixingT import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteProgressStatus import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteTaskContext import java.io.File +import java.io.FileWriter import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -33,7 +34,11 @@ fun constructBuildAndExecutionSummaryText(currentStatus: BuildAndExecuteProgress val progressMessages = mutableListOf() if (currentStatus >= BuildAndExecuteProgressStatus.RUN_BUILD) { - val verb = if (currentStatus == BuildAndExecuteProgressStatus.RUN_BUILD) "in progress" else "complete" + val verb = when (currentStatus) { + BuildAndExecuteProgressStatus.RUN_BUILD -> "in progress" + BuildAndExecuteProgressStatus.BUILD_FAILED -> "failed" + else -> "complete" + } progressMessages.add("${getBuildIcon(currentStatus)}: Build $verb") } @@ -42,7 +47,7 @@ fun constructBuildAndExecutionSummaryText(currentStatus: BuildAndExecuteProgress progressMessages.add("${getExecutionIcon(currentStatus)}: $verb passed tests") } - if (currentStatus >= BuildAndExecuteProgressStatus.FIXING_TEST_CASES) { + if (currentStatus >= BuildAndExecuteProgressStatus.FIXING_TEST_CASES || currentStatus == BuildAndExecuteProgressStatus.BUILD_FAILED) { val verb = if (currentStatus == BuildAndExecuteProgressStatus.FIXING_TEST_CASES) "Fixing" else "Fixed" progressMessages.add("${getFixingTestCasesIcon(currentStatus)}: $verb errors in tests") } @@ -85,12 +90,13 @@ fun runBuildOrTestCommand( project: Project, isBuildCommand: Boolean, buildAndExecuteTaskContext: BuildAndExecuteTaskContext, + projectRoot: String, ) { if (localCommand.isEmpty()) { buildAndExecuteTaskContext.testExitCode = 0 return } - val repositoryPath = project.basePath ?: return + // val repositoryPath = project.basePath ?: return val commandParts = localCommand.split(" ") val command = commandParts.first() val args = commandParts.drop(1) @@ -109,10 +115,14 @@ fun runBuildOrTestCommand( } val processBuilder = ProcessBuilder() - .command(listOf(command) + args) - .directory(File(repositoryPath)) + .command(listOf("zsh", "-c", "source ~/.zshrc && $command ${args.joinToString(" ")}")) + .directory(File(projectRoot)) .redirectErrorStream(true) + val env = processBuilder.environment() + + env["PATH"] = System.getenv("PATH") + try { val process = processBuilder.start() val processHandler: ProcessHandler = OSProcessHandler(process, localCommand, null) @@ -121,10 +131,12 @@ fun runBuildOrTestCommand( processHandler.addProcessListener(object : ProcessAdapter() { override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { val cleanedText = cleanText(event.text) + FileWriter(file, true).use { writer -> + writer.append(cleanedText) + writer.append("\n") + } ApplicationManager.getApplication().invokeLater { - ApplicationManager.getApplication().runWriteAction { - file.appendText(cleanedText) - } + VirtualFileManager.getInstance().refreshAndFindFileByNioPath(file.toPath())?.refresh(false, false) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt index a565da482c..28ae02f60b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt @@ -29,7 +29,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.noFileOpe import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_CREATE_PAYLOAD_TIMEOUT_IN_SECONDS import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_CODE_SCAN_TIMEOUT_IN_SECONDS import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_PAYLOAD_LIMIT_IN_BYTES @@ -156,7 +155,7 @@ class CodeTestSessionConfig( it.putNextEntry(utgEntry) // 3. Add the three empty subdirectories - val buildAndExecuteLogDir = "buildAndExecuteLogDir" + val buildAndExecuteLogDir = "buildAndExecuteLogDir/" val subDirs = listOf(buildAndExecuteLogDir, "repoMapData", "testCoverageDir") subDirs.forEach { subDir -> val subDirPathString = Path.of(utgDir, subDir).toString() + "/" // Added trailing slash similar to utgRequiredArtifactsDir @@ -165,7 +164,11 @@ class CodeTestSessionConfig( it.putNextEntry(zipEntry) } if (buildAndExecuteLogFile != null) { - it.putNextEntry(Path.of(utgDir, buildAndExecuteLogDir, "buildAndExecuteLog").name, buildAndExecuteLogFile.inputStream) + val buildLogFilePath = Path.of(utgDir, buildAndExecuteLogDir, "buildAndExecuteLog") + .toString().replace("\\", "/") // Ensures consistent path format + + LOG.debug { "Adding build logs to ZIP at: $buildLogFilePath" } + it.putNextEntry(buildLogFilePath, buildAndExecuteLogFile.inputStream) } if (payloadMetadata.payloadLimitCrossed == true) { // write payloadMetadata.payloadManifest into a json file in repoMapData directory. manifest is Set> write into file first or ina byte stream @@ -218,7 +221,7 @@ class CodeTestSessionConfig( for (module in project.modules) { val changeListManager = ChangeListManager.getInstance(module.project) module.guessModuleDir()?.let { moduleDir -> - val gitIgnoreFilteringUtil = GitIgnoreFilteringUtil(moduleDir, CodeWhispererConstants.FeatureName.TEST_GENERATION) + val gitIgnoreFilteringUtil = GitIgnoreFilteringUtil(moduleDir) stack.push(moduleDir) while (stack.isNotEmpty()) { val current = stack.pop() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt index e2e55b8643..a373e95c7e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt @@ -77,6 +77,10 @@ interface CodeWhispererClientAdaptor : Disposable { firstRequest: GenerateCompletionsRequest, ): Sequence + fun generateCompletions( + firstRequest: GenerateCompletionsRequest, + ): GenerateCompletionsResponse + fun createUploadUrl( request: CreateUploadUrlRequest, ): CreateUploadUrlResponse @@ -102,7 +106,7 @@ interface CodeWhispererClientAdaptor : Disposable { fun listAvailableCustomizations(): List - fun startTestGeneration(uploadId: String, targetCode: List, userInput: String): StartTestGenerationResponse + fun startTestGeneration(uploadId: String, targetCode: List, userInput: String, testGenerationJobGroupName: String): StartTestGenerationResponse fun getTestGeneration(jobId: String, jobGroupName: String): GetTestGenerationResponse @@ -323,6 +327,8 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW yield(response) } while (!nextToken.isNullOrEmpty()) } + override fun generateCompletions(firstRequest: GenerateCompletionsRequest): GenerateCompletionsResponse = + bearerClient().generateCompletions(firstRequest) override fun createUploadUrl(request: CreateUploadUrlRequest): CreateUploadUrlResponse = bearerClient().createUploadUrl(request) @@ -372,11 +378,17 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW } } - override fun startTestGeneration(uploadId: String, targetCode: List, userInput: String): StartTestGenerationResponse = + override fun startTestGeneration( + uploadId: String, + targetCode: List, + userInput: String, + testGenerationJobGroupName: String, + ): StartTestGenerationResponse = bearerClient().startTestGeneration { builder -> builder.uploadId(uploadId) builder.targetCodeList(targetCode) builder.userInput(userInput) + builder.testGenerationJobGroupName(testGenerationJobGroupName) // TODO: client token } From 4168e7838a59c84bd1d458feb90649d2c70fc831 Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Fri, 21 Feb 2025 13:59:35 -0800 Subject: [PATCH 02/25] address comment --- .../services/amazonqCodeTest/CodeWhispererUTGChatManager.kt | 2 +- .../amazonqCodeTest/controller/CodeTestChatController.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index bbcd79199a..b4a5d9e2c1 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -301,7 +301,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin // TODO: Modify text according to FnF codeTestChatHelper.addAnswer( CodeTestChatMessageContent( - message = message("testgen.message.failed"), + message = message("testgen.error.generic_technical_error_message"), type = ChatMessageType.Answer, canBeVoted = true ) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index b4ffee5bc9..a1890bf82f 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -828,7 +828,6 @@ class CodeTestChatController( "Successfully sent test generation telemetry. RequestId: ${ testGenerationEventResponse.responseMetadata().requestId()}" } - // sessionCleanUp(session.tabId) codeTestChatHelper.updateUI( promptInputDisabledState = false, promptInputPlaceholder = message("testgen.placeholder.waiting_on_your_inputs"), From b5de261c268a49d9fdf6435cb48c571f66f203a3 Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Fri, 21 Feb 2025 14:35:14 -0800 Subject: [PATCH 03/25] create progress bar for fixing build failure --- ...-272cb837-f551-485a-9f37-43ca37aef75c.json | 4 +++ .../amazonqCodeTest/CodeTestChatItems.kt | 6 ++++ .../CodeWhispererUTGChatManager.kt | 32 ++++++++++++++----- .../resources/MessagesBundle.properties | 1 + 4 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 .changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json diff --git a/.changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json b/.changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json new file mode 100644 index 0000000000..752ab588a5 --- /dev/null +++ b/.changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Add back build and execute for Internal Amazon Customers." +} \ No newline at end of file diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt index 4ed35e3506..5c419eee68 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt @@ -36,3 +36,9 @@ fun testGenProgressField(value: Int) = ProgressField( valueText = "$value%", actions = listOf(cancelTestGenButton) ) + +val buildAndExecuteProgrogressField = ProgressField( + status = "default", + text = message("testgen.progressbar.build_and_execute"), + actions = listOf(cancelTestGenButton) +) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index b4a5d9e2c1..db6d46e155 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -93,11 +93,20 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin } val final = session.testGenerationJobGroupName + if(session.iteration == 1){ + codeTestChatHelper.updateUI( + promptInputDisabledState = true, + promptInputProgress = testGenProgressField(0), + ) + }else{ + codeTestChatHelper.updateUI( + promptInputDisabledState = true, + promptInputProgress = buildAndExecuteProgrogressField, + ) + } + // Set the Progress bar to "Generating unit tests..." - codeTestChatHelper.updateUI( - promptInputDisabledState = true, - promptInputProgress = testGenProgressField(0), - ) + val codeTestResponseContext = createUploadUrl(codeTestChatHelper, previousIterationContext) session.srcPayloadSize = codeTestResponseContext.payloadContext.srcPayloadSize @@ -263,10 +272,17 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin messageIdOverride = codeTestResponseContext.testSummaryMessageId ) } - codeTestChatHelper.updateUI( - promptInputDisabledState = true, - promptInputProgress = testGenProgressField(progressRate), - ) + if(session.iteration == 1){ + codeTestChatHelper.updateUI( + promptInputDisabledState = true, + promptInputProgress = testGenProgressField(0), + ) + }else{ + codeTestChatHelper.updateUI( + promptInputDisabledState = true, + promptInputProgress = buildAndExecuteProgrogressField, + ) + } } // polling every 2 seconds to reduce # of API calls diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index ed823f2b89..65c55f6cbd 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -2078,6 +2078,7 @@ testgen.placeholder.select_an_option = Please select an action to proceed (Accep testgen.placeholder.view_diff=Select View Diff to see the generated unit tests testgen.placeholder.waiting_on_your_inputs=Waiting on your inputs... testgen.progressbar.generate_unit_tests=Generating unit tests... +testgen.progressbar.build_and_execute=Fixing test errors... toolkit.login.aws_builder_id.already_connected.cancel=Use existing AWS Builder ID toolkit.login.aws_builder_id.already_connected.message=You already signed in with an AWS Builder ID.\nSign out to add another? toolkit.login.aws_builder_id.already_connected.reconnect=Sign out From 9cf17862b9a722da703c3c3fefe80c9705808a6e Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Mon, 24 Feb 2025 15:28:04 -0800 Subject: [PATCH 04/25] fix the bug which might cause user can not execute the build command & add telemetry for build&execute --- gradle/libs.versions.toml | 2 +- .../CodeWhispererUTGChatManager.kt | 57 +++++-- .../controller/CodeTestChatController.kt | 157 ++++++++++++------ .../amazonqCodeTest/session/Session.kt | 1 + .../amazonqCodeTest/utils/UTGChatUtil.kt | 19 ++- 5 files changed, 168 insertions(+), 68 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 00c7655154..5e9e32f120 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ mockitoKotlin = "5.4.0" mockk = "1.13.10" nimbus-jose-jwt = "9.40" node-gradle = "7.0.2" -telemetryGenerator = "1.0.297" +telemetryGenerator = "1.0.299" testLogger = "4.0.0" testRetry = "1.5.10" # test-only; platform provides slf4j transitively at runtime diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index db6d46e155..c740621ec8 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -589,25 +589,46 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin canBeVoted = false ) ) + if(session.iteration == 1){ + AmazonqTelemetry.utgGenerateTests( + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isFileInWorkspace = true, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + result = if (e.message == message("testgen.message.cancelled")) MetricResult.Cancelled else MetricResult.Failed, + reason = (e as CodeTestException).code ?: "DefaultError", + reasonDesc = if (e.message == message("testgen.message.cancelled")) "${e.code}: ${e.message}" else e.message, + perfClientLatency = (Instant.now().toEpochMilli() - session.startTimeOfTestGeneration), + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildPayloadBytes = session.srcPayloadSize, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId + ) + + }else{ + AmazonqTelemetry.unitTestGeneration( + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + result = if (e.message == message("testgen.message.cancelled")) MetricResult.Cancelled else MetricResult.Failed, + reason = (e as CodeTestException).code ?: "DefaultError", + reasonDesc = if (e.message == message("testgen.message.cancelled")) "${e.code}: ${e.message}" else e.message, + perfClientLatency = (Instant.now().toEpochMilli() - session.startTimeOfTestGeneration), + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId + ) + } + - AmazonqTelemetry.utgGenerateTests( - cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, - hasUserPromptSupplied = session.hasUserPromptSupplied, - isFileInWorkspace = true, - isSupportedLanguage = true, - credentialStartUrl = getStartUrl(project), - jobGroup = session.testGenerationJobGroupName, - jobId = session.testGenerationJob, - result = if (e.message == message("testgen.message.cancelled")) MetricResult.Cancelled else MetricResult.Failed, - reason = (e as CodeTestException).code ?: "DefaultError", - reasonDesc = if (e.message == message("testgen.message.cancelled")) "${e.code}: ${e.message}" else e.message, - perfClientLatency = (Instant.now().toEpochMilli() - session.startTimeOfTestGeneration), - isCodeBlockSelected = session.isCodeBlockSelected, - artifactsUploadDuration = session.artifactUploadDuration, - buildPayloadBytes = session.srcPayloadSize, - buildZipFileBytes = session.srcZipFileSize, - requestId = session.startTestGenerationRequestId - ) session.isGeneratingTests = false } finally { // Reset the flow if there is any error diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index a1890bf82f..ad28a6d8bc 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -116,6 +116,7 @@ class CodeTestChatController( private val authController: AuthController = AuthController(), private val cs: CoroutineScope, ) : InboundAppMessagesHandler { + var buildResult = false val messenger = context.messagesFromAppToUi private val codeTestChatHelper = CodeTestChatHelper(context.messagesFromAppToUi, chatSessionStorage) private val supportedLanguage = setOf("python", "java") @@ -666,28 +667,59 @@ class CodeTestChatController( UiTelemetry.click(null as Project?, "unitTestGeneration_acceptDiff") - AmazonqTelemetry.utgGenerateTests( - cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, - hasUserPromptSupplied = session.hasUserPromptSupplied, - isFileInWorkspace = true, - isSupportedLanguage = true, - credentialStartUrl = getStartUrl(project = context.project), - jobGroup = session.testGenerationJobGroupName, - jobId = session.testGenerationJob, - acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), - generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), - acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), - generatedLinesCount = session.linesOfCodeGenerated?.toLong(), - acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), - generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = MetricResult.Succeeded, - perfClientLatency = session.latencyOfTestGeneration, - isCodeBlockSelected = session.isCodeBlockSelected, - artifactsUploadDuration = session.artifactUploadDuration, - buildPayloadBytes = session.srcPayloadSize, - buildZipFileBytes = session.srcZipFileSize, - requestId = session.startTestGenerationRequestId - ) + if(session.iteration == 1){ + AmazonqTelemetry.utgGenerateTests( + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isFileInWorkspace = true, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = MetricResult.Succeeded, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildPayloadBytes = session.srcPayloadSize, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId + ) + }else{ + AmazonqTelemetry.unitTestGeneration( + count = session.iteration.toLong() - 1, + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = if(buildResult){ + MetricResult.Succeeded + }else{ + MetricResult.Failed + }, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId, + update = session.updateBuildCommands, + ) + } + + codeTestChatHelper.addAnswer( CodeTestChatMessageContent( message = message("testgen.message.success"), @@ -784,11 +816,10 @@ class CodeTestChatController( ) } else { // TODO: change this hardcoded string - val monthlyLimitString = "25 out of 30" codeTestChatHelper.addAnswer( CodeTestChatMessageContent( message = """ - You have gone through all three iterations and this unit test generation workflow is complete. You have $monthlyLimitString Amazon Q Developer Agent invocations left this month. + You have gone through both iterations and this unit test generation workflow is complete. """.trimIndent(), type = ChatMessageType.Answer, ) @@ -863,28 +894,58 @@ class CodeTestChatController( } UiTelemetry.click(null as Project?, "unitTestGeneration_rejectDiff") - AmazonqTelemetry.utgGenerateTests( - cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, - hasUserPromptSupplied = session.hasUserPromptSupplied, - isFileInWorkspace = true, - isSupportedLanguage = true, - credentialStartUrl = getStartUrl(project = context.project), - jobGroup = session.testGenerationJobGroupName, - jobId = session.testGenerationJob, - acceptedCount = 0, - generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), - acceptedLinesCount = 0, - generatedLinesCount = session.linesOfCodeGenerated?.toLong(), - acceptedCharactersCount = 0, - generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = MetricResult.Succeeded, - perfClientLatency = session.latencyOfTestGeneration, - isCodeBlockSelected = session.isCodeBlockSelected, - artifactsUploadDuration = session.artifactUploadDuration, - buildPayloadBytes = session.srcPayloadSize, - buildZipFileBytes = session.srcZipFileSize, - requestId = session.startTestGenerationRequestId - ) + if(session.iteration == 1){ + AmazonqTelemetry.utgGenerateTests( + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isFileInWorkspace = true, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = 0, + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = 0, + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = 0, + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = MetricResult.Succeeded, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildPayloadBytes = session.srcPayloadSize, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId + ) + }else{ + AmazonqTelemetry.unitTestGeneration( + count = session.iteration.toLong() - 1, + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = 0, + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = 0, + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = 0, + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = if(buildResult){ + MetricResult.Succeeded + }else{ + MetricResult.Failed + }, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId, + update = session.updateBuildCommands, + ) + } + sessionCleanUp(message.tabId) } "utg_skip_and_finish" -> { @@ -922,7 +983,7 @@ class CodeTestChatController( "tmpFile for build logs:\n ${buildLogsFile.path}" } - runBuildOrTestCommand(taskContext.buildCommand, buildLogsFile, context.project, isBuildCommand = true, taskContext, session.projectRoot) + runBuildOrTestCommand(taskContext.buildCommand, buildLogsFile, context.project, isBuildCommand = true, taskContext, session.testFileRelativePathToProjectRoot) while (taskContext.buildExitCode < 0) { // wait until build command finished delay(1000) @@ -938,6 +999,7 @@ class CodeTestChatController( canBeVoted = false ) ) + buildResult = true sessionCleanUp(message.tabId) return } @@ -1029,6 +1091,7 @@ class CodeTestChatController( } "utg_modify_command" -> { // TODO allow user input to modify the command + session.updateBuildCommands = true codeTestChatHelper.addAnswer( CodeTestChatMessageContent( message = """ diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt index a7b66a2f46..1f4b943418 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt @@ -33,6 +33,7 @@ data class Session(val tabId: String) { var srcPayloadSize: Long = 0 var srcZipFileSize: Long = 0 var artifactUploadDuration: Long = 0 + var updateBuildCommands : Boolean = false // First iteration will have a value of 1 var userPrompt: String = "" diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt index d66ab66e3c..556aba9ab9 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt @@ -90,12 +90,27 @@ fun runBuildOrTestCommand( project: Project, isBuildCommand: Boolean, buildAndExecuteTaskContext: BuildAndExecuteTaskContext, - projectRoot: String, + testFileRelativePathToProjectRoot: String, ) { if (localCommand.isEmpty()) { buildAndExecuteTaskContext.testExitCode = 0 return } + val projectRoot = File(project.basePath ?: return) + val testFileAbsolutePath = File(projectRoot, testFileRelativePathToProjectRoot) + + // Find the nearest Gradle root directory + var packageRoot: File? = testFileAbsolutePath.parentFile + while (packageRoot != null && packageRoot != projectRoot) { + if (File(packageRoot, "settings.gradle.kts").exists() || File(packageRoot, "build.gradle.kts").exists()) { + break // top when we find a valid Gradle project root + } + packageRoot = packageRoot.parentFile + } + + // If no valid Gradle directory is found, fallback to the project root + val workingDir = packageRoot ?: projectRoot + println("Running command in directory: ${workingDir.absolutePath}") // val repositoryPath = project.basePath ?: return val commandParts = localCommand.split(" ") val command = commandParts.first() @@ -116,7 +131,7 @@ fun runBuildOrTestCommand( val processBuilder = ProcessBuilder() .command(listOf("zsh", "-c", "source ~/.zshrc && $command ${args.joinToString(" ")}")) - .directory(File(projectRoot)) + .directory(packageRoot) .redirectErrorStream(true) val env = processBuilder.environment() From ca2c1284ede1abf25f2d85bb38b5cc9aaa88b85c Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Mon, 24 Feb 2025 15:33:47 -0800 Subject: [PATCH 05/25] fix format error --- .../CodeWhispererUTGChatManager.kt | 17 +++++------ .../controller/CodeTestChatController.kt | 30 +++++++++++-------- .../amazonqCodeTest/session/Session.kt | 2 +- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index c740621ec8..5a533fb0b7 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -93,21 +93,20 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin } val final = session.testGenerationJobGroupName - if(session.iteration == 1){ + if (session.iteration == 1) { codeTestChatHelper.updateUI( promptInputDisabledState = true, promptInputProgress = testGenProgressField(0), ) - }else{ + } else { codeTestChatHelper.updateUI( promptInputDisabledState = true, promptInputProgress = buildAndExecuteProgrogressField, - ) + ) } // Set the Progress bar to "Generating unit tests..." - val codeTestResponseContext = createUploadUrl(codeTestChatHelper, previousIterationContext) session.srcPayloadSize = codeTestResponseContext.payloadContext.srcPayloadSize session.srcZipFileSize = codeTestResponseContext.payloadContext.srcZipFileSize @@ -272,12 +271,12 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin messageIdOverride = codeTestResponseContext.testSummaryMessageId ) } - if(session.iteration == 1){ + if (session.iteration == 1) { codeTestChatHelper.updateUI( promptInputDisabledState = true, promptInputProgress = testGenProgressField(0), ) - }else{ + } else { codeTestChatHelper.updateUI( promptInputDisabledState = true, promptInputProgress = buildAndExecuteProgrogressField, @@ -589,7 +588,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin canBeVoted = false ) ) - if(session.iteration == 1){ + if (session.iteration == 1) { AmazonqTelemetry.utgGenerateTests( cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, hasUserPromptSupplied = session.hasUserPromptSupplied, @@ -608,8 +607,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin buildZipFileBytes = session.srcZipFileSize, requestId = session.startTestGenerationRequestId ) - - }else{ + } else { AmazonqTelemetry.unitTestGeneration( cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, hasUserPromptSupplied = session.hasUserPromptSupplied, @@ -628,7 +626,6 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin ) } - session.isGeneratingTests = false } finally { // Reset the flow if there is any error diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index ad28a6d8bc..cc78ca1e47 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -667,7 +667,7 @@ class CodeTestChatController( UiTelemetry.click(null as Project?, "unitTestGeneration_acceptDiff") - if(session.iteration == 1){ + if (session.iteration == 1) { AmazonqTelemetry.utgGenerateTests( cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, hasUserPromptSupplied = session.hasUserPromptSupplied, @@ -690,7 +690,7 @@ class CodeTestChatController( buildZipFileBytes = session.srcZipFileSize, requestId = session.startTestGenerationRequestId ) - }else{ + } else { AmazonqTelemetry.unitTestGeneration( count = session.iteration.toLong() - 1, cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, @@ -705,10 +705,10 @@ class CodeTestChatController( generatedLinesCount = session.linesOfCodeGenerated?.toLong(), acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if(buildResult){ - MetricResult.Succeeded - }else{ - MetricResult.Failed + result = if (buildResult) { + MetricResult.Succeeded + } else { + MetricResult.Failed }, perfClientLatency = session.latencyOfTestGeneration, isCodeBlockSelected = session.isCodeBlockSelected, @@ -719,7 +719,6 @@ class CodeTestChatController( ) } - codeTestChatHelper.addAnswer( CodeTestChatMessageContent( message = message("testgen.message.success"), @@ -894,7 +893,7 @@ class CodeTestChatController( } UiTelemetry.click(null as Project?, "unitTestGeneration_rejectDiff") - if(session.iteration == 1){ + if (session.iteration == 1) { AmazonqTelemetry.utgGenerateTests( cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, hasUserPromptSupplied = session.hasUserPromptSupplied, @@ -917,7 +916,7 @@ class CodeTestChatController( buildZipFileBytes = session.srcZipFileSize, requestId = session.startTestGenerationRequestId ) - }else{ + } else { AmazonqTelemetry.unitTestGeneration( count = session.iteration.toLong() - 1, cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, @@ -932,9 +931,9 @@ class CodeTestChatController( generatedLinesCount = session.linesOfCodeGenerated?.toLong(), acceptedCharactersCount = 0, generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if(buildResult){ + result = if (buildResult) { MetricResult.Succeeded - }else{ + } else { MetricResult.Failed }, perfClientLatency = session.latencyOfTestGeneration, @@ -983,7 +982,14 @@ class CodeTestChatController( "tmpFile for build logs:\n ${buildLogsFile.path}" } - runBuildOrTestCommand(taskContext.buildCommand, buildLogsFile, context.project, isBuildCommand = true, taskContext, session.testFileRelativePathToProjectRoot) + runBuildOrTestCommand( + taskContext.buildCommand, + buildLogsFile, + context.project, + isBuildCommand = true, + taskContext, + session.testFileRelativePathToProjectRoot + ) while (taskContext.buildExitCode < 0) { // wait until build command finished delay(1000) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt index 1f4b943418..d126e2fd8a 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/session/Session.kt @@ -33,7 +33,7 @@ data class Session(val tabId: String) { var srcPayloadSize: Long = 0 var srcZipFileSize: Long = 0 var artifactUploadDuration: Long = 0 - var updateBuildCommands : Boolean = false + var updateBuildCommands: Boolean = false // First iteration will have a value of 1 var userPrompt: String = "" From 9151dce8d64902cfb45205a7b44d14aa5a931562 Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Mon, 24 Feb 2025 16:19:15 -0800 Subject: [PATCH 06/25] correct the logic to send the telemetry --- .../controller/CodeTestChatController.kt | 105 +++++++++++++----- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index cc78ca1e47..08ce14a851 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -690,33 +690,6 @@ class CodeTestChatController( buildZipFileBytes = session.srcZipFileSize, requestId = session.startTestGenerationRequestId ) - } else { - AmazonqTelemetry.unitTestGeneration( - count = session.iteration.toLong() - 1, - cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, - hasUserPromptSupplied = session.hasUserPromptSupplied, - isSupportedLanguage = true, - credentialStartUrl = getStartUrl(project = context.project), - jobGroup = session.testGenerationJobGroupName, - jobId = session.testGenerationJob, - acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), - generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), - acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), - generatedLinesCount = session.linesOfCodeGenerated?.toLong(), - acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), - generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if (buildResult) { - MetricResult.Succeeded - } else { - MetricResult.Failed - }, - perfClientLatency = session.latencyOfTestGeneration, - isCodeBlockSelected = session.isCodeBlockSelected, - artifactsUploadDuration = session.artifactUploadDuration, - buildZipFileBytes = session.srcZipFileSize, - requestId = session.startTestGenerationRequestId, - update = session.updateBuildCommands, - ) } codeTestChatHelper.addAnswer( @@ -826,6 +799,32 @@ class CodeTestChatController( codeTestChatHelper.updateUI( promptInputPlaceholder = message("testgen.placeholder.newtab") ) + AmazonqTelemetry.unitTestGeneration( + count = session.iteration.toLong() - 1, + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = if (buildResult) { + MetricResult.Succeeded + } else { + MetricResult.Failed + }, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId, + update = session.updateBuildCommands, + ) } } @@ -955,6 +954,32 @@ class CodeTestChatController( canBeVoted = false ) ) + AmazonqTelemetry.unitTestGeneration( + count = session.iteration.toLong() - 1, + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = if (buildResult) { + MetricResult.Succeeded + } else { + MetricResult.Failed + }, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId, + update = session.updateBuildCommands, + ) sessionCleanUp(message.tabId) } "utg_proceed", "utg_build_and_execute" -> { @@ -1006,6 +1031,32 @@ class CodeTestChatController( ) ) buildResult = true + AmazonqTelemetry.unitTestGeneration( + count = session.iteration.toLong() - 1, + cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, + hasUserPromptSupplied = session.hasUserPromptSupplied, + isSupportedLanguage = true, + credentialStartUrl = getStartUrl(project = context.project), + jobGroup = session.testGenerationJobGroupName, + jobId = session.testGenerationJob, + acceptedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + generatedCount = session.numberOfUnitTestCasesGenerated?.toLong(), + acceptedLinesCount = session.linesOfCodeGenerated?.toLong(), + generatedLinesCount = session.linesOfCodeGenerated?.toLong(), + acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), + generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), + result = if (buildResult) { + MetricResult.Succeeded + } else { + MetricResult.Failed + }, + perfClientLatency = session.latencyOfTestGeneration, + isCodeBlockSelected = session.isCodeBlockSelected, + artifactsUploadDuration = session.artifactUploadDuration, + buildZipFileBytes = session.srcZipFileSize, + requestId = session.startTestGenerationRequestId, + update = session.updateBuildCommands, + ) sessionCleanUp(message.tabId) return } From b5792bcb7a5b4ad5b120628bcc91ea0b6cdd641e Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Tue, 25 Feb 2025 12:39:25 -0800 Subject: [PATCH 07/25] addressed comment --- .../controller/CodeTestChatController.kt | 80 ++----------------- .../sessionconfig/CodeTestSessionConfig.kt | 2 - .../resources/MessagesBundle.properties | 2 +- 3 files changed, 6 insertions(+), 78 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index 08ce14a851..cc54a939e6 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -813,11 +813,7 @@ class CodeTestChatController( generatedLinesCount = session.linesOfCodeGenerated?.toLong(), acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if (buildResult) { - MetricResult.Succeeded - } else { - MetricResult.Failed - }, + result = MetricResult.Failed, perfClientLatency = session.latencyOfTestGeneration, isCodeBlockSelected = session.isCodeBlockSelected, artifactsUploadDuration = session.artifactUploadDuration, @@ -930,11 +926,7 @@ class CodeTestChatController( generatedLinesCount = session.linesOfCodeGenerated?.toLong(), acceptedCharactersCount = 0, generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if (buildResult) { - MetricResult.Succeeded - } else { - MetricResult.Failed - }, + result = MetricResult.Succeeded, perfClientLatency = session.latencyOfTestGeneration, isCodeBlockSelected = session.isCodeBlockSelected, artifactsUploadDuration = session.artifactUploadDuration, @@ -968,11 +960,7 @@ class CodeTestChatController( generatedLinesCount = session.linesOfCodeGenerated?.toLong(), acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if (buildResult) { - MetricResult.Succeeded - } else { - MetricResult.Failed - }, + result = MetricResult.Failed, perfClientLatency = session.latencyOfTestGeneration, isCodeBlockSelected = session.isCodeBlockSelected, artifactsUploadDuration = session.artifactUploadDuration, @@ -1030,7 +1018,6 @@ class CodeTestChatController( canBeVoted = false ) ) - buildResult = true AmazonqTelemetry.unitTestGeneration( count = session.iteration.toLong() - 1, cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, @@ -1045,11 +1032,7 @@ class CodeTestChatController( generatedLinesCount = session.linesOfCodeGenerated?.toLong(), acceptedCharactersCount = session.charsOfCodeGenerated?.toLong(), generatedCharactersCount = session.charsOfCodeGenerated?.toLong(), - result = if (buildResult) { - MetricResult.Succeeded - } else { - MetricResult.Failed - }, + result = MetricResult.Succeeded, perfClientLatency = session.latencyOfTestGeneration, isCodeBlockSelected = session.isCodeBlockSelected, artifactsUploadDuration = session.artifactUploadDuration, @@ -1091,60 +1074,7 @@ class CodeTestChatController( return } -// taskContext.progressStatus = BuildAndExecuteProgressStatus.RUN_EXECUTION_TESTS -// updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) -// -// val testLogsFile = VirtualFileManager.getInstance().findFileByNioPath( -// withContext(currentCoroutineContext()) { -// Files.createTempFile(null, null) -// } -// ) -// if (testLogsFile == null) { -// // TODO: handle no log file case -// return -// } -// LOG.debug { -// "Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()}: " + -// "tmpFile for test logs:\n ${buildLogsFile.path}" -// } -// delay(1000) -// runBuildOrTestCommand(taskContext.executionCommand, testLogsFile, context.project, isBuildCommand = false, taskContext) -// while (taskContext.testExitCode < 0) { -// // wait until test command finished -// delay(1000) -// } -// -// if (taskContext.testExitCode == 0) { -// taskContext.progressStatus = BuildAndExecuteProgressStatus.TESTS_EXECUTED -// updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) -// codeTestChatHelper.addAnswer( -// CodeTestChatMessageContent( -// message = message("testgen.message.success"), -// type = ChatMessageType.Answer, -// canBeVoted = false -// ) -// ) -// // sessionCleanUp(message.tabId) -// return -// } -// -// // has test failure, we will zip the latest project and invoke backend again -// taskContext.progressStatus = BuildAndExecuteProgressStatus.FIXING_TEST_CASES -// val buildAndExecuteMessageId = updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration) -// -// val previousUTGIterationContext = PreviousUTGIterationContext( -// buildLogFile = buildLogsFile, -// testLogFile = testLogsFile, -// selectedFile = session.selectedFile, -// buildAndExecuteMessageId = buildAndExecuteMessageId -// ) -// -// val job = CodeWhispererUTGChatManager.getInstance(context.project).generateTests("", codeTestChatHelper, previousUTGIterationContext, null) -// job?.join() -// -// taskContext.progressStatus = BuildAndExecuteProgressStatus.PROCESS_TEST_RESULTS -// // session.iteration already updated in generateTests -// updateBuildAndExecuteProgressCard(taskContext.progressStatus, messageId, session.iteration - 1) + // TO-DO ADD execute loop in the furture } "utg_modify_command" -> { // TODO allow user input to modify the command diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt index 28ae02f60b..6f49ed5532 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codetest/sessionconfig/CodeTestSessionConfig.kt @@ -166,8 +166,6 @@ class CodeTestSessionConfig( if (buildAndExecuteLogFile != null) { val buildLogFilePath = Path.of(utgDir, buildAndExecuteLogDir, "buildAndExecuteLog") .toString().replace("\\", "/") // Ensures consistent path format - - LOG.debug { "Adding build logs to ZIP at: $buildLogFilePath" } it.putNextEntry(buildLogFilePath, buildAndExecuteLogFile.inputStream) } if (payloadMetadata.payloadLimitCrossed == true) { diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 65c55f6cbd..fbd7dc82f0 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -2077,8 +2077,8 @@ testgen.placeholder.newtab=Ask any coding question or type \u0022/\u0022 for act testgen.placeholder.select_an_option = Please select an action to proceed (Accept or Reject) testgen.placeholder.view_diff=Select View Diff to see the generated unit tests testgen.placeholder.waiting_on_your_inputs=Waiting on your inputs... -testgen.progressbar.generate_unit_tests=Generating unit tests... testgen.progressbar.build_and_execute=Fixing test errors... +testgen.progressbar.generate_unit_tests=Generating unit tests... toolkit.login.aws_builder_id.already_connected.cancel=Use existing AWS Builder ID toolkit.login.aws_builder_id.already_connected.message=You already signed in with an AWS Builder ID.\nSign out to add another? toolkit.login.aws_builder_id.already_connected.reconnect=Sign out From 523178997ff915213ecef5bff93c03b6b7d601c3 Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Tue, 25 Feb 2025 12:40:49 -0800 Subject: [PATCH 08/25] nit --- .../services/amazonqCodeTest/CodeWhispererUTGChatManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index 5a533fb0b7..e8a091bc03 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -187,7 +187,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin var shortAnswer = ShortAnswer() LOG.debug { "Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()}: " + - "polling result for id: ${job.testGenerationJobId()}, group name: ${session.testGenerationJobGroupName}, " + + "polling result for id: ${job.testGenerationJobId()}, group name: ${job.testGenerationJobGroupName()}, " + "request id: ${startTestGenerationResponse.responseMetadata().requestId()}" } From 9e323fe42aea66ca5535e78ad227f396e394298d Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Tue, 25 Feb 2025 14:36:48 -0800 Subject: [PATCH 09/25] addressing comments --- .../CodeWhispererUTGChatManager.kt | 23 +++++++++--------- .../amazonqCodeTest/utils/UTGChatUtil.kt | 24 ------------------- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index e8a091bc03..d33b86fa53 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -58,6 +58,7 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException +import java.nio.file.Path import java.nio.file.Paths import java.time.Duration import java.time.Instant @@ -263,25 +264,23 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin if (shortAnswer.stopIteration == "true") { throw CodeTestException("TestGenFailedError: ${shortAnswer.planSummary}", "TestGenFailedError", shortAnswer.planSummary) } + val fileName = shortAnswer.sourceFilePath?.let { Path.of(it).fileName.toString() } ?: path.fileName.toString() codeTestChatHelper.updateAnswer( CodeTestChatMessageContent( - message = generateSummaryMessage(path.fileName.toString()) + shortAnswer.planSummary, + message = generateSummaryMessage(fileName) + shortAnswer.planSummary, type = ChatMessageType.Answer ), messageIdOverride = codeTestResponseContext.testSummaryMessageId ) } - if (session.iteration == 1) { - codeTestChatHelper.updateUI( - promptInputDisabledState = true, - promptInputProgress = testGenProgressField(0), - ) - } else { - codeTestChatHelper.updateUI( - promptInputDisabledState = true, - promptInputProgress = buildAndExecuteProgrogressField, - ) - } + codeTestChatHelper.updateUI( + promptInputDisabledState = true, + promptInputProgress = if (session.iteration == 1) { + testGenProgressField(0) + } else { + buildAndExecuteProgrogressField + } + ) } // polling every 2 seconds to reduce # of API calls diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt index 556aba9ab9..63366aa00e 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt @@ -201,27 +201,3 @@ private fun cleanText(input: String): String { return cleaned.toString() } -suspend fun combineBuildAndExecuteLogFiles( - buildLogFile: VirtualFile?, - testLogFile: VirtualFile?, -): VirtualFile? { - if (buildLogFile == null || testLogFile == null) return null - val buildLogFileContent = String(buildLogFile.contentsToByteArray(), StandardCharsets.UTF_8) - val testLogFileContent = String(testLogFile.contentsToByteArray(), StandardCharsets.UTF_8) - - val combinedContent = "Build Output:\n$buildLogFileContent\nTest Execution Output:\n$testLogFileContent" - - // Create a new virtual file and write combined content - val newFile = VirtualFileManager.getInstance().findFileByNioPath( - withContext(currentCoroutineContext()) { - Files.createTempFile(null, null) - } - ) - withContext(EDT) { - ApplicationManager.getApplication().runWriteAction { - newFile?.setBinaryContent(combinedContent.toByteArray(StandardCharsets.UTF_8)) - } - } - - return newFile -} From 3601d499cc815a87cf213b7530b07b89684fe4f5 Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Thu, 27 Feb 2025 16:37:59 -0800 Subject: [PATCH 10/25] add PATH --- .../amazonqCodeTest/utils/UTGChatUtil.kt | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt index 63366aa00e..13cd67ba33 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt @@ -4,11 +4,11 @@ package software.aws.toolkits.jetbrains.services.amazonqCodeTest.utils import com.intellij.build.BuildContentManager +import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.impl.ConsoleViewImpl import com.intellij.execution.process.OSProcessHandler import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent -import com.intellij.execution.process.ProcessHandler import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.ApplicationManager @@ -17,9 +17,6 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.ui.content.impl.ContentImpl -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.withContext -import software.aws.toolkits.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getBuildIcon import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getExecutionIcon import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getFixingTestCasesIcon @@ -27,8 +24,6 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAnd import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteTaskContext import java.io.File import java.io.FileWriter -import java.nio.charset.StandardCharsets -import java.nio.file.Files fun constructBuildAndExecutionSummaryText(currentStatus: BuildAndExecuteProgressStatus, iterationNum: Int): String { val progressMessages = mutableListOf() @@ -92,12 +87,14 @@ fun runBuildOrTestCommand( buildAndExecuteTaskContext: BuildAndExecuteTaskContext, testFileRelativePathToProjectRoot: String, ) { + val brazilPath = "${System.getProperty("user.home")}/.toolbox/bin:/usr/local/bin:/usr/bin:/bin:/sbin" if (localCommand.isEmpty()) { buildAndExecuteTaskContext.testExitCode = 0 return } val projectRoot = File(project.basePath ?: return) val testFileAbsolutePath = File(projectRoot, testFileRelativePathToProjectRoot) + val file = File(tmpFile.path) // Find the nearest Gradle root directory var packageRoot: File? = testFileAbsolutePath.parentFile @@ -107,17 +104,9 @@ fun runBuildOrTestCommand( } packageRoot = packageRoot.parentFile } - // If no valid Gradle directory is found, fallback to the project root - val workingDir = packageRoot ?: projectRoot - println("Running command in directory: ${workingDir.absolutePath}") - // val repositoryPath = project.basePath ?: return - val commandParts = localCommand.split(" ") - val command = commandParts.first() - val args = commandParts.drop(1) - val file = File(tmpFile.path) - - // Create Console View for Build Output + val gradleWrapper = File(packageRoot, "gradlew") + val workingDir = if (gradleWrapper.exists()) packageRoot else projectRoot val console: ConsoleView = ConsoleViewImpl(project, true) // Attach Console View to Build Tool Window @@ -125,22 +114,21 @@ fun runBuildOrTestCommand( val tabName = if (isBuildCommand) "Q TestGen Build Output" else "Q Test Gen Test Execution Output" val content = ContentImpl(console.component, tabName, true) BuildContentManager.getInstance(project).addContent(content) - // TODO: remove these tabs when they are not needed BuildContentManager.getInstance(project).setSelectedContent(content, false, false, true, null) } - val processBuilder = ProcessBuilder() - .command(listOf("zsh", "-c", "source ~/.zshrc && $command ${args.joinToString(" ")}")) - .directory(packageRoot) - .redirectErrorStream(true) - - val env = processBuilder.environment() - - env["PATH"] = System.getenv("PATH") + val commandLine = when { + System.getProperty("os.name").lowercase().contains("win") -> { + GeneralCommandLine("cmd.exe", "/c", "set PATH=%PATH%;$brazilPath && $localCommand") + } + else -> { + GeneralCommandLine("sh", "-c", "export PATH=\"$brazilPath\" && $localCommand") + } + }.withWorkDirectory(workingDir) try { - val process = processBuilder.start() - val processHandler: ProcessHandler = OSProcessHandler(process, localCommand, null) + // val process = processBuilder.start() + val processHandler = OSProcessHandler(commandLine) // Attach Process Listener for Output Handling processHandler.addProcessListener(object : ProcessAdapter() { @@ -200,4 +188,3 @@ private fun cleanText(input: String): String { } return cleaned.toString() } - From fc8d07bea6a4b1ee5e13866758acb6be8c4c53a4 Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Fri, 28 Feb 2025 11:43:39 -0800 Subject: [PATCH 11/25] add gradle file for groovy version of the gradle build script --- .../jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt index 13cd67ba33..ec3cd5da2b 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt @@ -99,8 +99,10 @@ fun runBuildOrTestCommand( // Find the nearest Gradle root directory var packageRoot: File? = testFileAbsolutePath.parentFile while (packageRoot != null && packageRoot != projectRoot) { - if (File(packageRoot, "settings.gradle.kts").exists() || File(packageRoot, "build.gradle.kts").exists()) { - break // top when we find a valid Gradle project root + if (File(packageRoot, "settings.gradle.kts").exists() || File(packageRoot, "build.gradle.kts").exists() || + File(packageRoot, "settings.gradle").exists() || File(packageRoot, "build.gradle").exists() + ) { + break // Stop when we find a valid Gradle project root } packageRoot = packageRoot.parentFile } From d195b1afc9819c31800d62c7fb48ac24cce3e5da Mon Sep 17 00:00:00 2001 From: Randall-Jiang Date: Sun, 2 Mar 2025 21:49:48 -0800 Subject: [PATCH 12/25] fix UI error --- .../services/amazonqCodeTest/utils/UTGChatUtil.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt index ec3cd5da2b..f06dd73394 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/utils/UTGChatUtil.kt @@ -17,6 +17,7 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.ui.content.impl.ContentImpl +import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.BuildAndExecuteStatusIcon import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getBuildIcon import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getExecutionIcon import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.getFixingTestCasesIcon @@ -34,12 +35,12 @@ fun constructBuildAndExecutionSummaryText(currentStatus: BuildAndExecuteProgress BuildAndExecuteProgressStatus.BUILD_FAILED -> "failed" else -> "complete" } - progressMessages.add("${getBuildIcon(currentStatus)}: Build $verb") + progressMessages.add("${getBuildIcon(currentStatus)}: Project compiled $verb") } if (currentStatus >= BuildAndExecuteProgressStatus.RUN_EXECUTION_TESTS) { val verb = if (currentStatus == BuildAndExecuteProgressStatus.RUN_EXECUTION_TESTS) "Executing" else "Executed" - progressMessages.add("${getExecutionIcon(currentStatus)}: $verb passed tests") + progressMessages.add("${getExecutionIcon(currentStatus)}: $verb Ran tests") } if (currentStatus >= BuildAndExecuteProgressStatus.FIXING_TEST_CASES || currentStatus == BuildAndExecuteProgressStatus.BUILD_FAILED) { @@ -51,9 +52,8 @@ fun constructBuildAndExecutionSummaryText(currentStatus: BuildAndExecuteProgress progressMessages.add("\n") progressMessages.add("**Test case summary**") progressMessages.add("\n") - progressMessages.add("Unit test coverage X%") - progressMessages.add("Build fails Y") - progressMessages.add("Assertion fails Z") + progressMessages.add(BuildAndExecuteStatusIcon.DONE.icon + "Build Success") + progressMessages.add(BuildAndExecuteStatusIcon.DONE.icon + "Assertion Success") } val prefix = From 7e6a36710ca6a0470d7e19e35a129f3fbafe33e0 Mon Sep 17 00:00:00 2001 From: laileni Date: Mon, 3 Mar 2025 12:05:51 -0800 Subject: [PATCH 13/25] Fix lint errors --- .../services/amazonqCodeTest/CodeWhispererUTGChatManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index d33b86fa53..6a7105fc2b 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -89,7 +89,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin val session = codeTestChatHelper.getActiveSession() session.isGeneratingTests = true session.iteration++ - if (session.testGenerationJobGroupName.isNullOrEmpty()) { + if (session.testGenerationJobGroupName.isEmpty()) { session.testGenerationJobGroupName = UUID.randomUUID().toString() } val final = session.testGenerationJobGroupName From 43dced69d5070c0e59734630b6f39baebbced4a0 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:35:56 -0800 Subject: [PATCH 14/25] Merge from Feature/build-execute * fix(amazonq):fix cross-tab data interference * fix(amazonq):fix cross-tab data interference Co-authored-by: Kevin Ding <102239785+KevinDing1@users.noreply.github.com> Co-authored-by: aws-toolkit-automation <43144436+aws-toolkit-automation@users.noreply.github.com> --- ...-79596dfc-37a0-44c8-adf2-a6c87ba806ac.json | 4 ++ .../amazonqDoc/controller/DocController.kt | 50 +++++++++++-------- .../controller/DocControllerExtensions.kt | 4 +- .../controller/DocGenerationTask.kt | 16 +++++- 4 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 .changes/next-release/bugfix-79596dfc-37a0-44c8-adf2-a6c87ba806ac.json diff --git a/.changes/next-release/bugfix-79596dfc-37a0-44c8-adf2-a6c87ba806ac.json b/.changes/next-release/bugfix-79596dfc-37a0-44c8-adf2-a6c87ba806ac.json new file mode 100644 index 0000000000..898768d930 --- /dev/null +++ b/.changes/next-release/bugfix-79596dfc-37a0-44c8-adf2-a6c87ba806ac.json @@ -0,0 +1,4 @@ +{ + "type" : "bugfix", + "description" : "Amazon Q: Fix data isolation between tabs to prevent interference when using /doc in multiple tabs" +} \ No newline at end of file diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt index 44aedfbbbf..8d1da5c0bb 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt @@ -132,9 +132,8 @@ class DocController( private val authController: AuthController = AuthController(), ) : InboundAppMessagesHandler { val messenger = context.messagesFromAppToUi - var mode: Mode = Mode.CREATE val toolWindow = ToolWindowManager.getInstance(context.project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID) - var docGenerationTask = DocGenerationTask() + private val docGenerationTasks = DocGenerationTasks() override suspend fun processPromptChatMessage(message: IncomingDocMessage.ChatPrompt) { handleChat( @@ -148,7 +147,7 @@ class DocController( } override suspend fun processTabRemovedMessage(message: IncomingDocMessage.TabRemoved) { - docGenerationTask.reset() + docGenerationTasks.deleteTask(message.tabId) chatSessionStorage.deleteSession(message.tabId) } @@ -160,6 +159,7 @@ class DocController( override suspend fun processFollowupClickedMessage(message: IncomingDocMessage.FollowupClicked) { val session = getSessionInfo(message.tabId) + val docGenerationTask = docGenerationTasks.getTask(message.tabId) session.preloader(message.followUp.pillText, messenger) // also stores message in session history @@ -173,7 +173,7 @@ class DocController( FollowUpTypes.CLOSE_SESSION -> closeSession(message.tabId) FollowUpTypes.CREATE_DOCUMENTATION -> { docGenerationTask.interactionType = DocInteractionType.GENERATE_README - mode = Mode.CREATE + docGenerationTask.mode = Mode.CREATE promptForDocTarget(message.tabId) } @@ -183,11 +183,11 @@ class DocController( } FollowUpTypes.CANCEL_FOLDER_SELECTION -> { - docGenerationTask.folderLevel = DocFolderLevel.ENTIRE_WORKSPACE + docGenerationTask.reset() newTask(message.tabId) } - FollowUpTypes.PROCEED_FOLDER_SELECTION -> if (mode == Mode.EDIT) makeChanges(message.tabId) else onDocsGeneration(message) + FollowUpTypes.PROCEED_FOLDER_SELECTION -> if (docGenerationTask.mode == Mode.EDIT) makeChanges(message.tabId) else onDocsGeneration(message) FollowUpTypes.ACCEPT_CHANGES -> { docGenerationTask.userDecision = DocUserDecision.ACCEPT sendDocAcceptanceTelemetry(message.tabId) @@ -195,7 +195,7 @@ class DocController( } FollowUpTypes.MAKE_CHANGES -> { - mode = Mode.EDIT + docGenerationTask.mode = Mode.EDIT makeChanges(message.tabId) } @@ -206,12 +206,12 @@ class DocController( } FollowUpTypes.SYNCHRONIZE_DOCUMENTATION -> { - mode = Mode.SYNC + docGenerationTask.mode = Mode.SYNC promptForDocTarget(message.tabId) } FollowUpTypes.EDIT_DOCUMENTATION -> { - mode = Mode.EDIT + docGenerationTask.mode = Mode.EDIT docGenerationTask.interactionType = DocInteractionType.EDIT_README promptForDocTarget(message.tabId) } @@ -241,7 +241,6 @@ class DocController( session.sessionState.token?.cancel() } - docGenerationTask.reset() newTask(message.tabId) } @@ -307,13 +306,14 @@ class DocController( private suspend fun promptForDocTarget(tabId: String) { val session = getSessionInfo(tabId) + val docGenerationTask = docGenerationTasks.getTask(tabId) val currentSourceFolder = session.context.selectedSourceFolder try { messenger.sendFolderConfirmationMessage( tabId = tabId, - message = if (mode == Mode.CREATE) message("amazonqDoc.prompt.create.confirmation") else message("amazonqDoc.prompt.update"), + message = if (docGenerationTask.mode == Mode.CREATE) message("amazonqDoc.prompt.create.confirmation") else message("amazonqDoc.prompt.update"), folderPath = currentSourceFolder.name, followUps = listOf( FollowUp( @@ -452,6 +452,9 @@ class DocController( var session: DocSession? = null try { session = getSessionInfo(tabId) + val docGenerationTask = docGenerationTasks.getTask(tabId) + docGenerationTask.mode = Mode.NONE + logger.debug { "$FEATURE_NAME: Session created with id: ${session.tabID}" } val credentialState = authController.getAuthNeededStates(context.project).amazonQ @@ -528,7 +531,7 @@ class DocController( } private suspend fun newTask(tabId: String) { - docGenerationTask = DocGenerationTask() + docGenerationTasks.deleteTask(tabId) chatSessionStorage.deleteSession(tabId) messenger.sendAnswer( @@ -577,7 +580,7 @@ class DocController( ) messenger.sendChatInputEnabledMessage(tabId = tabId, enabled = false) - docGenerationTask.reset() + docGenerationTasks.deleteTask(tabId) } private suspend fun provideFeedbackAndRegenerateCode(tabId: String) { @@ -728,6 +731,7 @@ class DocController( message: String, ) { var session: DocSession? = null + val docGenerationTask = docGenerationTasks.getTask(tabId) try { logger.debug { "$FEATURE_NAME: Processing message: $message" } session = getSessionInfo(tabId) @@ -746,7 +750,7 @@ class DocController( when (session.sessionState.phase) { SessionStatePhase.CODEGEN -> { - onCodeGeneration(session, message, tabId, mode) + onCodeGeneration(session, message, tabId, docGenerationTask.mode) } else -> null @@ -756,7 +760,7 @@ class DocController( is PrepareDocGenerationState -> state.filePaths else -> emptyList() } - sendDocGenerationTelemetry(filePaths, session) + sendDocGenerationTelemetry(filePaths, session, docGenerationTask) broadcastQEvent(QFeatureEvent.INVOCATION) if (filePaths.isNotEmpty()) { @@ -767,7 +771,7 @@ class DocController( } catch (err: Exception) { // For non edit mode lock the chat input until they explicitly click one of the follow-ups var isEnableChatInput = false - if (err is DocException && Mode.EDIT == mode) { + if (err is DocException && docGenerationTask.mode == Mode.EDIT) { isEnableChatInput = err.remainingIterations != null && err.remainingIterations > 0 } @@ -779,15 +783,16 @@ class DocController( messenger.sendUpdatePromptProgress(tabId = followUpMessage.tabId, inProgress(progress = 10, message("amazonqDoc.progress_message.scanning"))) val session = getSessionInfo(followUpMessage.tabId) + val docGenerationTask = docGenerationTasks.getTask(followUpMessage.tabId) messenger.sendAnswer( - message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, this.mode), + message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, docGenerationTask.mode), messageType = DocMessageType.AnswerPart, tabId = followUpMessage.tabId, ) try { - val sessionMessage: String = when (mode) { + val sessionMessage: String = when (docGenerationTask.mode) { Mode.CREATE -> message("amazonqDoc.session.create") else -> message("amazonqDoc.session.sync") } @@ -821,10 +826,10 @@ class DocController( return } - sendDocGenerationTelemetry(filePaths, session) + sendDocGenerationTelemetry(filePaths, session, docGenerationTask) messenger.sendAnswer( - message = docGenerationProgressMessage(DocGenerationStep.COMPLETE, mode), + message = docGenerationProgressMessage(DocGenerationStep.COMPLETE, docGenerationTask.mode), messageType = DocMessageType.AnswerPart, tabId = followUpMessage.tabId, ) @@ -907,7 +912,6 @@ class DocController( private suspend fun retryRequests(tabId: String) { var session: DocSession? = null - docGenerationTask = DocGenerationTask() try { messenger.sendAsyncEventProgress( tabId = tabId, @@ -954,6 +958,7 @@ class DocController( val session = getSessionInfo(tabId) val currentSourceFolder = session.context.selectedSourceFolder val projectRoot = session.context.projectRoot + val docGenerationTask = docGenerationTasks.getTask(tabId) withContext(EDT) { messenger.sendAnswer( @@ -1017,7 +1022,7 @@ class DocController( } } - private fun sendDocGenerationTelemetry(filePaths: List, session: DocSession) { + private fun sendDocGenerationTelemetry(filePaths: List, session: DocSession, docGenerationTask: DocGenerationTask) { docGenerationTask.conversationId = session.conversationId val (totalGeneratedChars, totalGeneratedLines, totalGeneratedFiles) = session.countedGeneratedContent(filePaths, docGenerationTask.interactionType) docGenerationTask.numberOfGeneratedChars = totalGeneratedChars @@ -1030,6 +1035,7 @@ class DocController( private fun sendDocAcceptanceTelemetry(tabId: String) { val session = getSessionInfo(tabId) + val docGenerationTask = docGenerationTasks.getTask(tabId) var filePaths: List = emptyList() when (val state = session.sessionState) { diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocControllerExtensions.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocControllerExtensions.kt index f16503e2c0..69fd1b032e 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocControllerExtensions.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocControllerExtensions.kt @@ -31,7 +31,7 @@ suspend fun DocController.onCodeGeneration(session: DocSession, message: String, messenger.sendAsyncEventProgress(tabId, inProgress = true) messenger.sendUpdatePromptProgress(tabId, inProgress(progress = 10, message("amazonqDoc.progress_message.scanning"))) messenger.sendAnswer( - message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, this.mode), + message = docGenerationProgressMessage(DocGenerationStep.UPLOAD_TO_S3, mode), messageType = DocMessageType.AnswerPart, tabId = tabId, ) @@ -108,7 +108,7 @@ suspend fun DocController.onCodeGeneration(session: DocSession, message: String, messenger.sendAnswer( tabId = tabId, messageType = DocMessageType.Answer, - message = if (this.mode === Mode.CREATE) { + message = if (mode === Mode.CREATE) { message("amazonqDoc.answer.readmeCreated") } else { "${message("amazonqDoc.answer.readmeUpdated")} ${message("amazonqDoc.answer.codeResult")}" diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocGenerationTask.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocGenerationTask.kt index d03b0b3d3b..bb2b751d7a 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocGenerationTask.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocGenerationTask.kt @@ -11,7 +11,19 @@ import software.amazon.awssdk.services.codewhispererruntime.model.DocV2Generatio import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger +class DocGenerationTasks { + private val tasks: MutableMap = mutableMapOf() + + fun getTask(tabId: String): DocGenerationTask = tasks.getOrPut(tabId) { DocGenerationTask() } + + fun deleteTask(tabId: String) { + tasks.remove(tabId) + } +} + class DocGenerationTask { + var mode: Mode = Mode.NONE + // Telemetry fields var conversationId: String? = null var numberOfAddedChars: Int? = null @@ -22,8 +34,8 @@ class DocGenerationTask { var numberOfGeneratedFiles: Int? = null var userDecision: DocUserDecision? = null var interactionType: DocInteractionType? = null - var numberOfNavigations = 0 - var folderLevel: DocFolderLevel? = DocFolderLevel.ENTIRE_WORKSPACE + var numberOfNavigations: Int = 0 + var folderLevel: DocFolderLevel = DocFolderLevel.ENTIRE_WORKSPACE fun docGenerationEventBase(): DocV2GenerationEvent { val undefinedProps = this::class.java.declaredFields .filter { it.get(this) == null } From 3c6c33c11f46b6a0f03d99a05dbc93fdb1032d76 Mon Sep 17 00:00:00 2001 From: laileni Date: Tue, 4 Mar 2025 13:30:55 -0800 Subject: [PATCH 15/25] Refactoring Build and execute --- ...-272cb837-f551-485a-9f37-43ca37aef75c.json | 4 - .../amazonqCodeTest/CodeTestChatItems.kt | 8 +- .../CodeWhispererUTGChatManager.kt | 45 ++--- .../controller/CodeTestChatController.kt | 165 +++++++++--------- .../messages/CodeTestMessage.kt | 1 + .../model/BuildAndExecuteStatusIcon.kt | 2 +- .../amazonqCodeTest/session/Session.kt | 8 + .../amazonqCodeTest/utils/UTGChatUtil.kt | 40 ++--- .../src/AWS.Psi/Protocol/ModelZoneMarket.cs | 16 -- 9 files changed, 132 insertions(+), 157 deletions(-) delete mode 100644 .changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json delete mode 100644 plugins/toolkit/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Protocol/ModelZoneMarket.cs diff --git a/.changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json b/.changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json deleted file mode 100644 index 752ab588a5..0000000000 --- a/.changes/next-release/feature-272cb837-f551-485a-9f37-43ca37aef75c.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type" : "feature", - "description" : "Add back build and execute for Internal Amazon Customers." -} \ No newline at end of file diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt index 5c419eee68..2c165c36f2 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeTestChatItems.kt @@ -29,6 +29,12 @@ val cancelTestGenButton = Button( icon = "cancel" ) +val cancelTestGenBuildAndExecuteButton = Button( + id = CodeTestButtonId.StopTestGenBuildAndExecution.id, + text = message("general.cancel"), + icon = "cancel" +) + fun testGenProgressField(value: Int) = ProgressField( status = "default", text = message("testgen.progressbar.generate_unit_tests"), @@ -40,5 +46,5 @@ fun testGenProgressField(value: Int) = ProgressField( val buildAndExecuteProgrogressField = ProgressField( status = "default", text = message("testgen.progressbar.build_and_execute"), - actions = listOf(cancelTestGenButton) + actions = listOf(cancelTestGenBuildAndExecuteButton) ) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt index 6a7105fc2b..d20e614d07 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt @@ -87,26 +87,16 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin ) { // 1st API call: Zip project and call CreateUploadUrl val session = codeTestChatHelper.getActiveSession() - session.isGeneratingTests = true - session.iteration++ if (session.testGenerationJobGroupName.isEmpty()) { session.testGenerationJobGroupName = UUID.randomUUID().toString() } - val final = session.testGenerationJobGroupName - if (session.iteration == 1) { - codeTestChatHelper.updateUI( - promptInputDisabledState = true, - promptInputProgress = testGenProgressField(0), - ) - } else { - codeTestChatHelper.updateUI( - promptInputDisabledState = true, - promptInputProgress = buildAndExecuteProgrogressField, - ) - } - - // Set the Progress bar to "Generating unit tests..." + codeTestChatHelper.updateUI( + promptInputDisabledState = true, + promptInputProgress = session.listOfTestGenerationJobId.takeUnless { it.isEmpty() } + ?.let { buildAndExecuteProgrogressField } + ?: testGenProgressField(0) + ) val codeTestResponseContext = createUploadUrl(codeTestChatHelper, previousIterationContext) session.srcPayloadSize = codeTestResponseContext.payloadContext.srcPayloadSize @@ -117,7 +107,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin fileTooLarge() } - val createUploadUrlResponse = codeTestResponseContext.createUploadUrlResponse ?: return + val createUploadUrlResponse = codeTestResponseContext.createUploadUrlResponse throwIfCancelled(session) LOG.debug { @@ -150,7 +140,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin .build() ), userInput = prompt, - testGenerationJobGroupName = final + testGenerationJobGroupName = session.testGenerationJobGroupName ) delay(200) response?.testGenerationJob() != null @@ -179,11 +169,12 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin session.startTestGenerationRequestId = startTestGenerationResponse.responseMetadata().requestId() session.testGenerationJobGroupName = job.testGenerationJobGroupName() session.testGenerationJob = job.testGenerationJobId() + session.listOfTestGenerationJobId += job.testGenerationJobId() throwIfCancelled(session) // 3rd API call: Step 3: Polling mechanism on test job status with getTestGenStatus getTestGeneration var finished = false - var testGenerationResponse: GetTestGenerationResponse? = null + var testGenerationResponse: GetTestGenerationResponse? var shortAnswer = ShortAnswer() LOG.debug { @@ -275,8 +266,8 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin } codeTestChatHelper.updateUI( promptInputDisabledState = true, - promptInputProgress = if (session.iteration == 1) { - testGenProgressField(0) + promptInputProgress = if (session.listOfTestGenerationJobId.size == 1) { + testGenProgressField(progressRate) } else { buildAndExecuteProgrogressField } @@ -312,7 +303,6 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin val result = byteArray.reduce { acc, next -> acc + next } // To map the result it is needed to combine the full byte array storeGeneratedTestDiffs(result, session) if (!session.isGeneratingTests) { - // TODO: Modify text according to FnF codeTestChatHelper.addAnswer( CodeTestChatMessageContent( message = message("testgen.error.generic_technical_error_message"), @@ -360,7 +350,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin session.viewDiffMessageId = viewDiffMessageId codeTestChatHelper.updateUI( promptInputDisabledState = false, - promptInputPlaceholder = "Specify a function(s) in the current file(optional)", +// promptInputPlaceholder = "Specify a function(s) in the current file(optional)", promptInputProgress = testGenCompletedField, ) } else { @@ -486,10 +476,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin } else { previousIterationContext.selectedFile } - - val combinedBuildAndExecuteLogFile = - previousIterationContext?.buildLogFile - val codeTestSessionConfig = CodeTestSessionConfig(file, project, combinedBuildAndExecuteLogFile) + val codeTestSessionConfig = CodeTestSessionConfig(file, project, previousIterationContext?.buildLogFile) codeTestChatHelper.getActiveSession().projectRoot = codeTestSessionConfig.projectRoot.path val codeTestSessionContext = CodeTestSessionContext(project, codeTestSessionConfig) @@ -587,7 +574,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin canBeVoted = false ) ) - if (session.iteration == 1) { + if (session.listOfTestGenerationJobId.size < 2) { AmazonqTelemetry.utgGenerateTests( cwsprChatProgrammingLanguage = session.programmingLanguage.languageId, hasUserPromptSupplied = session.hasUserPromptSupplied, @@ -624,10 +611,10 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin requestId = session.startTestGenerationRequestId ) } - session.isGeneratingTests = false } finally { // Reset the flow if there is any error + codeTestChatHelper.sendUpdatePromptProgress(session.tabId, null) if (!session.isGeneratingTests) { codeTestChatHelper.updateUI( promptInputProgress = cancellingProgressField diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt index cc54a939e6..ed9354de0d 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt @@ -67,6 +67,7 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.CodeTes import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.IncomingCodeTestMessage import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.PreviousUTGIterationContext import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteProgressStatus +import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildStatus import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.Session import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.UTG_CHAT_MAX_ITERATION import software.aws.toolkits.jetbrains.services.amazonqCodeTest.storage.ChatSessionStorage @@ -116,7 +117,6 @@ class CodeTestChatController( private val authController: AuthController = AuthController(), private val cs: CoroutineScope, ) : InboundAppMessagesHandler { - var buildResult = false val messenger = context.messagesFromAppToUi private val codeTestChatHelper = CodeTestChatHelper(context.messagesFromAppToUi, chatSessionStorage) private val supportedLanguage = setOf("python", "java") @@ -178,13 +178,6 @@ class CodeTestChatController( return } session.startTimeOfTestGeneration = Instant.now().toEpochMilli().toDouble() - session.isGeneratingTests = true - - var requestId: String = "" - var statusCode: Int = 0 - var conversationId: String? = null - var testResponseMessageId: String? = null - var testResponseText: String = "" val userMessage = when { message.prompt != "" -> { @@ -220,6 +213,11 @@ class CodeTestChatController( CodeWhispererUTGChatManager.getInstance(project).generateTests(userPrompt, codeTestChatHelper, null, selectionRange) } else { // Not adding a progress bar to unsupported language cases + var requestId: String = "" + var statusCode: Int = 0 + var conversationId: String? = null + var testResponseMessageId: String? = null + var testResponseText: String = "" val responseHandler = GenerateAssistantResponseResponseHandler.builder() .onResponse { requestId = it.responseMetadata().requestId() @@ -566,7 +564,14 @@ class CodeTestChatController( session.linesOfCodeGenerated = lineDifference.coerceAtLeast(0) session.charsOfCodeGenerated = charDifference.coerceAtLeast(0) session.latencyOfTestGeneration = (Instant.now().toEpochMilli() - session.startTimeOfTestGeneration) - UiTelemetry.click(null as Project?, "unitTestGeneration_viewDiff") + UiTelemetry.click( + null as Project?, + if (session.listOfTestGenerationJobId.size == 1) { + "unitTestGeneration_viewDiff" + } else { + "unitTestGeneration_viewDiff_Iteration" + } + ) val buttonList = mutableListOf