@@ -5,6 +5,7 @@ package software.aws.toolkits.jetbrains.services.amazonqCodeTest
55
66import com.fasterxml.jackson.core.JsonParseException
77import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
8+ import com.fasterxml.jackson.module.kotlin.readValue
89import com.intellij.openapi.application.ApplicationManager
910import com.intellij.openapi.components.Service
1011import com.intellij.openapi.components.service
@@ -34,7 +35,11 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.controller.CodeT
3435import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.Button
3536import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.CodeTestChatMessageContent
3637import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.PreviousUTGIterationContext
37- import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.ShortAnswer
38+ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.PackageInfoList
39+ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.PackageInfo
40+ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.TargetFileInfoList
41+ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.TargetFileInfo
42+ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.model.CodeReferenceInfo
3843import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.BuildAndExecuteProgressStatus
3944import software.aws.toolkits.jetbrains.services.amazonqCodeTest.session.Session
4045import software.aws.toolkits.jetbrains.services.amazonqCodeTest.utils.combineBuildAndExecuteLogFiles
@@ -172,7 +177,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
172177 var finished = false
173178 var testGenerationResponse: GetTestGenerationResponse ? = null
174179
175- var shortAnswer = ShortAnswer ()
180+ var packageInfoList = PackageInfoList ()
176181 LOG .debug {
177182 " Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()} : " +
178183 " polling result for id: ${job.testGenerationJobId()} , group name: ${job.testGenerationJobGroupName()} , " +
@@ -187,23 +192,25 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
187192 if (status == TestGenerationJobStatus .COMPLETED ) {
188193 LOG .debug {
189194 " Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()} : " +
190- " Test generation completed, short answer string : ${testGenerationResponse.testGenerationJob().shortAnswer ()} "
195+ " Test generation completed, package info : ${testGenerationResponse.testGenerationJob().packageInfoList ()} "
191196 }
192197 finished = true
193- if (testGenerationResponse.testGenerationJob().shortAnswer() != null ) {
194- shortAnswer = parseShortAnswerString(testGenerationResponse.testGenerationJob().shortAnswer())
198+ if (testGenerationResponse.testGenerationJob().packageInfoList() != null ) {
199+ packageInfoList = parsePackageInfoList(testGenerationResponse.testGenerationJob().packageInfoList())
200+ session.packageInfoList = packageInfoList
195201
196- val testFileName = shortAnswer.testFilePath?.let { File (it).name }.orEmpty()
202+ val targetFileInfo = packageInfoList.member?.targetFileInfoList?.member?.firstOrNull()
203+ val testFileName = targetFileInfo?.testFilePath?.let { File (it).name }.orEmpty()
197204 session.testFileName = testFileName
198205 // Setting default value to 0 if the value is null or invalid
199- session.numberOfUnitTestCasesGenerated = shortAnswer .numberOfTestMethods
200- session.testFileRelativePathToProjectRoot = getTestFilePathRelativeToRoot(shortAnswer )
206+ session.numberOfUnitTestCasesGenerated = targetFileInfo? .numberOfTestMethods ? : 0
207+ session.testFileRelativePathToProjectRoot = getTestFilePathRelativeToRoot(targetFileInfo )
201208
202209 // update test summary card in success case
203210 if (previousIterationContext == null ) {
204211 codeTestChatHelper.updateAnswer(
205212 CodeTestChatMessageContent (
206- message = generateSummaryMessage(path.fileName.toString()) + shortAnswer.planSummary ,
213+ message = generateSummaryMessage(path.fileName.toString()) + (packageInfoList.member?.packageSummary ? : " " ) ,
207214 type = ChatMessageType .Answer ,
208215 footer = listOf (testFileName)
209216 ),
@@ -222,14 +229,17 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
222229 } else if (status == TestGenerationJobStatus .FAILED ) {
223230 LOG .debug {
224231 " Q TestGen session: ${codeTestChatHelper.getActiveCodeTestTabId()} : " +
225- " Test generation failed, short answer string: ${testGenerationResponse.testGenerationJob().shortAnswer()} "
226- }
227- if (testGenerationResponse.testGenerationJob().shortAnswer() != null ) {
228- shortAnswer = parseShortAnswerString(testGenerationResponse.testGenerationJob().shortAnswer())
229- if (shortAnswer.stopIteration == " true" ) {
230- throw CodeTestException (" TestGenFailedError: ${shortAnswer.planSummary} " , " TestGenFailedError" , shortAnswer.planSummary)
231- }
232+ " Test generation failed, package info: ${testGenerationResponse.testGenerationJob().packageInfoList()} "
232233 }
234+ // if (testGenerationResponse.testGenerationJob().packageInfoList() != null) {
235+ // shortAnswer = parseShortAnswerString(testGenerationResponse.testGenerationJob().shortAnswer())
236+ // if (shortAnswer.stopIteration == "true") {
237+ // throw CodeTestException("TestGenFailedError: ${shortAnswer.planSummary}", "TestGenFailedError", shortAnswer.planSummary)
238+ // }
239+ // }
240+
241+ // TO DO
242+ // add TestGenerationJobStatus.STOPPED status
233243
234244 // If job status is Failed and has no ShortAnswer then there might be some issue in the backend.
235245 throw CodeTestException (
@@ -245,15 +255,13 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
245255 }
246256 val progressRate = testGenerationResponse.testGenerationJob().progressRate() ? : 0
247257
248- if (previousIterationContext == null && testGenerationResponse.testGenerationJob().shortAnswer() != null ) {
249- shortAnswer = parseShortAnswerString(testGenerationResponse.testGenerationJob().shortAnswer())
250- if (shortAnswer.stopIteration == " true" ) {
251- throw CodeTestException (" TestGenFailedError: ${shortAnswer.planSummary} " , " TestGenFailedError" , shortAnswer.planSummary)
252- }
253- val fileName = shortAnswer.sourceFilePath?.let { Path .of(it).fileName.toString() } ? : path.fileName.toString()
258+ if (previousIterationContext == null && testGenerationResponse.testGenerationJob().packageInfoList() != null ) {
259+ packageInfoList = parsePackageInfoList(testGenerationResponse.testGenerationJob().packageInfoList())
260+ val targetFileInfo = packageInfoList.member?.targetFileInfoList?.member?.firstOrNull()
261+ val fileName = targetFileInfo?.filePath?.let { Path .of(it).fileName.toString() }? : path.fileName.toString()
254262 codeTestChatHelper.updateAnswer(
255263 CodeTestChatMessageContent (
256- message = generateSummaryMessage(fileName) + shortAnswer.planSummary ,
264+ message = generateSummaryMessage(fileName) + (packageInfoList.member?.packagePlan ? : " " ) ,
257265 type = ChatMessageType .Answer
258266 ),
259267 messageIdOverride = codeTestResponseContext.testSummaryMessageId
@@ -305,14 +313,15 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
305313 return
306314 }
307315
308- val codeReference = shortAnswer.codeReferences?.map { ref ->
316+ val targetFileInfo = packageInfoList.member?.targetFileInfoList?.member?.firstOrNull()
317+ val codeReference = targetFileInfo?.codeReferences?.map { ref ->
309318 CodeReference (
310319 licenseName = ref.licenseName,
311320 url = ref.url,
312321 information = " ${ref.licenseName} - <a href=\" ${ref.url} \" >${ref.repository} </a>"
313322 )
314323 }
315- shortAnswer .codeReferences?.let { session.codeReferences = it }
324+ targetFileInfo? .codeReferences?.let { session.codeReferences = it }
316325 val isReferenceAllowed = CodeWhispererSettings .getInstance().isIncludeCodeWithReference()
317326 if (! isReferenceAllowed && codeReference?.isNotEmpty() == true ) {
318327 codeTestChatHelper.addAnswer(
@@ -333,7 +342,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
333342 """ .trimIndent(),
334343 type = ChatMessageType .Answer ,
335344 buttons = listOf (Button (" utg_view_diff" , " View Diff" , keepCardAfterClick = true , position = " outside" , status = " info" )),
336- fileList = listOf (getTestFilePathRelativeToRoot(shortAnswer )),
345+ fileList = listOf (getTestFilePathRelativeToRoot(targetFileInfo )),
337346 projectRootName = project.name,
338347 canBeVoted = true ,
339348 codeReference = codeReference
@@ -350,7 +359,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
350359 CodeTestChatMessageContent (
351360 type = ChatMessageType .Answer ,
352361 buttons = listOf (Button (" utg_view_diff" , " View Diff" , keepCardAfterClick = true , position = " outside" , status = " info" )),
353- fileList = listOf (getTestFilePathRelativeToRoot(shortAnswer )),
362+ fileList = listOf (getTestFilePathRelativeToRoot(targetFileInfo )),
354363 projectRootName = project.name,
355364 codeReference = codeReference
356365 ),
@@ -374,42 +383,58 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
374383
375384 // Input: test file path relative to project root's parent .
376385 // Output: test file path relative to project root.
377- // shortAnswer .testFilePath has a format of <projectName>/<test file path relative to project root>.
386+ // targetFileInfo .testFilePath has a format of <projectName>/<test file path relative to project root>.
378387 // test file path in generatedTestDiffs map has a format of resultArtifacts/<test file path relative to project root>.
379388 // both needs to be handled the same way which is remove the first sub-directory
380- private fun getTestFilePathRelativeToRoot (shortAnswer : ShortAnswer ): String {
381- val pathString = shortAnswer .testFilePath ? : generatedTestDiffs.keys.firstOrNull() ? : throw RuntimeException (" No test file path found" )
389+ private fun getTestFilePathRelativeToRoot (targetFileInfo : TargetFileInfo ? ): String {
390+ val pathString = targetFileInfo? .testFilePath ? : generatedTestDiffs.keys.firstOrNull() ? : throw RuntimeException (" No test file path found" )
382391 val path = Paths .get(pathString)
383392 val updatedPath = path.subpath(1 , path.nameCount).toString()
384393 return updatedPath
385394 }
386395
387- private fun parseShortAnswerString (shortAnswerString : String ): ShortAnswer {
388- // Step 1: Replace single quotes with double quotes
389- var jsonString = shortAnswerString.replace(" '" , " \" " ).replace(" ```" , " " )
396+ private fun parsePackageInfoList (packageInfoData : Any ): PackageInfoList {
397+ if (packageInfoData is PackageInfoList ){
398+ return packageInfoData
399+ }
390400
391- // Step 2: Replace Python's None with JSON's null
392- jsonString = jsonString.replace(" : None" , " : null" )
401+ if (packageInfoData is String ){
402+ // Step 1: Replace single quotes with double quotes
403+ var jsonString = packageInfoData.replace(" '" , " \" " ).replace(" ```" , " " )
404+
405+ // Step 2: Replace Python's None with JSON's null
406+ jsonString = jsonString.replace(" : None" , " : null" )
407+
408+ // Step 3: remove extra quotes in the head and tail
409+ if (jsonString.startsWith(" \" " ) && jsonString.endsWith(" \" " )) {
410+ jsonString = jsonString.substring(1 , jsonString.length - 1 ) // Remove the first and last quote
411+ }
412+
413+ // Step 4: unescape it
414+ jsonString = jsonString.replace(" \\\" " , " \" " )
415+ .replace(" \\\\ " , " \\ " )
416+ // Deserialize JSON to Kotlin data class
417+ try {
418+ val packageInfoList: PackageInfoList = mapper.readValue(jsonString, PackageInfoList ::class .java)
419+ return packageInfoList
420+ } catch (e: JsonParseException ) {
421+ LOG .debug(e) { " Test Generation JSON parsing error: ${e.message} " }
422+ throw e
423+ } catch (e: Exception ) {
424+ LOG .debug(e) { " Error parsing JSON" }
425+ throw e
426+ }
393427
394- // Step 3: remove extra quotes in the head and tail
395- if (jsonString.startsWith(" \" " ) && jsonString.endsWith(" \" " )) {
396- jsonString = jsonString.substring(1 , jsonString.length - 1 ) // Remove the first and last quote
397428 }
398429
399- // Step 4: unescape it
400- jsonString = jsonString.replace(" \\\" " , " \" " )
401- .replace(" \\\\ " , " \\ " )
402- // Deserialize JSON to Kotlin data class
403- try {
404- val shortAnswer: ShortAnswer = mapper.readValue(jsonString, ShortAnswer ::class .java)
405- return shortAnswer
406- } catch (e: JsonParseException ) {
407- LOG .debug(e) { " Test Generation JSON parsing error: ${e.message} " }
408- throw e
409- } catch (e: Exception ) {
410- LOG .debug(e) { " Error parsing JSON" }
430+ try {
431+ return mapper.convertValue(packageInfoData,PackageInfoList ::class .java)
432+ }catch (e: JsonParseException ) {
433+ LOG .debug(e) { " Failed to covert packageInfoData to PackageInfoList" }
411434 throw e
412435 }
436+
437+
413438 }
414439
415440 private fun storeGeneratedTestDiffs (byteArray : ByteArray , session : Session ) {
@@ -507,7 +532,7 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
507532 private fun resetTestGenFlowSession (session : Session ) {
508533 // session.selectedFile doesn't need to be reset since it will remain unchanged
509534 session.conversationState = ConversationState .IN_PROGRESS
510- session.shortAnswer = ShortAnswer ()
535+ session.packageInfoList = PackageInfoList ()
511536 session.openedDiffFile = null
512537 session.testFileRelativePathToProjectRoot = " "
513538 session.testFileName = " "
0 commit comments