Skip to content

Commit 40db8b8

Browse files
authored
feat(amazonq): display test plan summary and refactor shortAnswer to accommodate API changes (aws#6747)
## Problem - API has been refactored with new fields - shortAnswer will eventually be deprecated - new field jobSummary which has a summary about tests that were generated ## Solution - implemented new field: packageInfoList and its subfields - display test summary in chat https://github.com/user-attachments/assets/23dcdcab-fe08-4f6a-88b6-74940d9ceb57 #### TODO: - integrate new API fields: buildCommand (once build exec is released), packageSummary, fileSummary, etc - remove all unused instances of shortAnswer --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent edb6b04 commit 40db8b8

File tree

7 files changed

+137
-43
lines changed

7 files changed

+137
-43
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "/test: display test plan summary in chat after generating tests"
4+
}

packages/core/src/amazonqTest/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function init(appContext: AmazonQAppInitContext) {
2323
authClicked: new vscode.EventEmitter<any>(),
2424
startTestGen: new vscode.EventEmitter<any>(),
2525
processHumanChatMessage: new vscode.EventEmitter<any>(),
26-
updateShortAnswer: new vscode.EventEmitter<any>(),
26+
updateTargetFileInfo: new vscode.EventEmitter<any>(),
2727
showCodeGenerationResults: new vscode.EventEmitter<any>(),
2828
openDiff: new vscode.EventEmitter<any>(),
2929
formActionClicked: new vscode.EventEmitter<any>(),

packages/core/src/amazonqTest/chat/controller/controller.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { randomUUID } from '../../../shared/crypto'
5353
import { tempDirPath, testGenerationLogsDir } from '../../../shared/filesystemUtilities'
5454
import { CodeReference } from '../../../codewhispererChat/view/connector/connector'
5555
import { TelemetryHelper } from '../../../codewhisperer/util/telemetryHelper'
56-
import { ShortAnswer, ShortAnswerReference, testGenState } from '../../../codewhisperer/models/model'
56+
import { Reference, testGenState } from '../../../codewhisperer/models/model'
5757
import {
5858
referenceLogText,
5959
TestGenerationBuildStep,
@@ -63,6 +63,7 @@ import {
6363
} from '../../../codewhisperer/models/constants'
6464
import { UserWrittenCodeTracker } from '../../../codewhisperer/tracker/userWrittenCodeTracker'
6565
import { ReferenceLogViewProvider } from '../../../codewhisperer/service/referenceLogViewProvider'
66+
import { TargetFileInfo } from '../../../codewhisperer/client/codewhispereruserclient'
6667
import { submitFeedback } from '../../../feedback/vue/submitFeedback'
6768
import { placeholder } from '../../../shared/vscode/commands2'
6869
import { Auth } from '../../../auth/auth'
@@ -73,7 +74,7 @@ export interface TestChatControllerEventEmitters {
7374
readonly authClicked: vscode.EventEmitter<any>
7475
readonly startTestGen: vscode.EventEmitter<any>
7576
readonly processHumanChatMessage: vscode.EventEmitter<any>
76-
readonly updateShortAnswer: vscode.EventEmitter<any>
77+
readonly updateTargetFileInfo: vscode.EventEmitter<any>
7778
readonly showCodeGenerationResults: vscode.EventEmitter<any>
7879
readonly openDiff: vscode.EventEmitter<any>
7980
readonly formActionClicked: vscode.EventEmitter<any>
@@ -134,8 +135,8 @@ export class TestController {
134135
return this.handleFormActionClicked(data)
135136
})
136137

137-
this.chatControllerMessageListeners.updateShortAnswer.event((data) => {
138-
return this.updateShortAnswer(data)
138+
this.chatControllerMessageListeners.updateTargetFileInfo.event((data) => {
139+
return this.updateTargetFileInfo(data)
139140
})
140141

141142
this.chatControllerMessageListeners.showCodeGenerationResults.event((data) => {
@@ -638,10 +639,9 @@ export class TestController {
638639
}
639640
}
640641

641-
private async updateShortAnswer(message: {
642+
private async updateTargetFileInfo(message: {
642643
tabID: string
643-
status: string
644-
shortAnswer?: ShortAnswer
644+
targetFileInfo?: TargetFileInfo
645645
testGenerationJobGroupName: string
646646
testGenerationJobId: string
647647
type: ChatItemType
@@ -651,11 +651,11 @@ export class TestController {
651651
type: 'answer',
652652
tabID: message.tabID,
653653
message: testGenSummaryMessage(
654-
path.basename(message.shortAnswer?.sourceFilePath ?? message.filePath),
655-
message.shortAnswer?.planSummary?.replaceAll('```', '')
654+
path.basename(message.targetFileInfo?.filePath ?? message.filePath),
655+
message.targetFileInfo?.filePlan?.replaceAll('```', '')
656656
),
657657
canBeVoted: true,
658-
filePath: message.shortAnswer?.testFilePath,
658+
filePath: message.targetFileInfo?.testFilePath,
659659
})
660660
}
661661

@@ -705,7 +705,7 @@ export class TestController {
705705
tabID: data.tabID,
706706
messageType: 'answer',
707707
codeGenerationId: '',
708-
message: `Please see the unit tests generated below. Click “View diff” to review the changes in the code editor.`,
708+
message: `${session.jobSummary}\n\n Please see the unit tests generated below. Click “View diff” to review the changes in the code editor.`,
709709
canBeVoted: true,
710710
messageId: '',
711711
followUps,
@@ -715,7 +715,7 @@ export class TestController {
715715
filePaths: [data.filePath],
716716
},
717717
codeReference: session.references.map(
718-
(ref: ShortAnswerReference) =>
718+
(ref: Reference) =>
719719
({
720720
...ref,
721721
information: `${ref.licenseName} - <a href="${ref.url}">${ref.repository}</a>`,

packages/core/src/amazonqTest/chat/session/session.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { ShortAnswer, ShortAnswerReference } from '../../../codewhisperer/models/model'
7-
import { TestGenerationJob } from '../../../codewhisperer/client/codewhispereruserclient'
6+
import { ShortAnswer, Reference } from '../../../codewhisperer/models/model'
7+
import { TargetFileInfo, TestGenerationJob } from '../../../codewhisperer/client/codewhispereruserclient'
88

99
export enum ConversationState {
1010
IDLE,
@@ -43,6 +43,8 @@ export class Session {
4343
public projectRootPath: string = ''
4444
public fileLanguage: string | undefined = 'plaintext'
4545
public stopIteration: boolean = false
46+
public targetFileInfo: TargetFileInfo | undefined
47+
public jobSummary: string = ''
4648

4749
// Telemetry
4850
public testGenerationStartTime: number = 0
@@ -65,7 +67,7 @@ export class Session {
6567
public testCoveragePercentage: number = 90
6668
public isInProgress: boolean = false
6769
public acceptedJobId = ''
68-
public references: ShortAnswerReference[] = []
70+
public references: Reference[] = []
6971

7072
constructor() {}
7173

packages/core/src/codewhisperer/client/user-service-2.json

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,39 @@
17601760
"type": "string",
17611761
"enum": ["OPTIN", "OPTOUT"]
17621762
},
1763+
"PackageInfo": {
1764+
"type": "structure",
1765+
"members": {
1766+
"executionCommand": { "shape": "SensitiveString" },
1767+
"buildCommand": { "shape": "SensitiveString" },
1768+
"buildOrder": { "shape": "PackageInfoBuildOrderInteger" },
1769+
"testFramework": { "shape": "String" },
1770+
"packageSummary": { "shape": "PackageInfoPackageSummaryString" },
1771+
"packagePlan": { "shape": "PackageInfoPackagePlanString" },
1772+
"targetFileInfoList": { "shape": "TargetFileInfoList" }
1773+
}
1774+
},
1775+
"PackageInfoBuildOrderInteger": {
1776+
"type": "integer",
1777+
"box": true,
1778+
"min": 0
1779+
},
1780+
"PackageInfoList": {
1781+
"type": "list",
1782+
"member": { "shape": "PackageInfo" }
1783+
},
1784+
"PackageInfoPackagePlanString": {
1785+
"type": "string",
1786+
"max": 30720,
1787+
"min": 0,
1788+
"sensitive": true
1789+
},
1790+
"PackageInfoPackageSummaryString": {
1791+
"type": "string",
1792+
"max": 30720,
1793+
"min": 0,
1794+
"sensitive": true
1795+
},
17631796
"PaginationToken": {
17641797
"type": "string",
17651798
"max": 2048,
@@ -2418,6 +2451,45 @@
24182451
"min": 1,
24192452
"sensitive": true
24202453
},
2454+
"TargetFileInfo": {
2455+
"type": "structure",
2456+
"members": {
2457+
"filePath": { "shape": "SensitiveString" },
2458+
"testFilePath": { "shape": "SensitiveString" },
2459+
"testCoverage": { "shape": "TargetFileInfoTestCoverageInteger" },
2460+
"fileSummary": { "shape": "TargetFileInfoFileSummaryString" },
2461+
"filePlan": { "shape": "TargetFileInfoFilePlanString" },
2462+
"codeReferences": { "shape": "References" },
2463+
"numberOfTestMethods": { "shape": "TargetFileInfoNumberOfTestMethodsInteger" }
2464+
}
2465+
},
2466+
"TargetFileInfoFilePlanString": {
2467+
"type": "string",
2468+
"max": 30720,
2469+
"min": 0,
2470+
"sensitive": true
2471+
},
2472+
"TargetFileInfoFileSummaryString": {
2473+
"type": "string",
2474+
"max": 30720,
2475+
"min": 0,
2476+
"sensitive": true
2477+
},
2478+
"TargetFileInfoList": {
2479+
"type": "list",
2480+
"member": { "shape": "TargetFileInfo" }
2481+
},
2482+
"TargetFileInfoNumberOfTestMethodsInteger": {
2483+
"type": "integer",
2484+
"box": true,
2485+
"min": 0
2486+
},
2487+
"TargetFileInfoTestCoverageInteger": {
2488+
"type": "integer",
2489+
"box": true,
2490+
"max": 100,
2491+
"min": 0
2492+
},
24212493
"TaskAssistPlan": {
24222494
"type": "list",
24232495
"member": { "shape": "TaskAssistPlanStep" },
@@ -2556,7 +2628,11 @@
25562628
"status": { "shape": "TestGenerationJobStatus" },
25572629
"shortAnswer": { "shape": "SensitiveString" },
25582630
"creationTime": { "shape": "Timestamp" },
2559-
"progressRate": { "shape": "TestGenerationJobProgressRateInteger" }
2631+
"progressRate": { "shape": "TestGenerationJobProgressRateInteger" },
2632+
"jobStatusReason": { "shape": "String" },
2633+
"jobSummary": { "shape": "TestGenerationJobJobSummaryString" },
2634+
"jobPlan": { "shape": "TestGenerationJobJobPlanString" },
2635+
"packageInfoList": { "shape": "PackageInfoList" }
25602636
},
25612637
"documentation": "<p>Represents a test generation job</p>"
25622638
},
@@ -2567,6 +2643,18 @@
25672643
"min": 1,
25682644
"pattern": "[a-zA-Z0-9-_]+"
25692645
},
2646+
"TestGenerationJobJobPlanString": {
2647+
"type": "string",
2648+
"max": 30720,
2649+
"min": 0,
2650+
"sensitive": true
2651+
},
2652+
"TestGenerationJobJobSummaryString": {
2653+
"type": "string",
2654+
"max": 30720,
2655+
"min": 0,
2656+
"sensitive": true
2657+
},
25702658
"TestGenerationJobProgressRateInteger": {
25712659
"type": "integer",
25722660
"box": true,

packages/core/src/codewhisperer/models/model.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ export interface FolderInfo {
11651165
name: string
11661166
}
11671167

1168-
export interface ShortAnswerReference {
1168+
export interface Reference {
11691169
licenseName?: string
11701170
repository?: string
11711171
url?: string
@@ -1175,6 +1175,7 @@ export interface ShortAnswerReference {
11751175
}
11761176
}
11771177

1178+
// TODO: remove ShortAnswer because it will be deprecated
11781179
export interface ShortAnswer {
11791180
testFilePath: string
11801181
buildCommands: string[]
@@ -1185,6 +1186,6 @@ export interface ShortAnswer {
11851186
testCoverage?: number
11861187
stopIteration?: string
11871188
errorMessage?: string
1188-
codeReferences?: ShortAnswerReference[]
1189+
codeReferences?: References
11891190
numberOfTestMethods?: number
11901191
}

packages/core/src/codewhisperer/service/testGenHandler.ts

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import {
1818
CreateUploadUrlError,
1919
ExportResultsArchiveError,
2020
InvalidSourceZipError,
21-
TestGenFailedError,
2221
TestGenStoppedError,
2322
TestGenTimedOutError,
2423
} from '../../amazonqTest/error'
2524
import { getMd5, uploadArtifactToS3 } from './securityScanHandler'
26-
import { ShortAnswer, testGenState } from '../models/model'
25+
import { testGenState, Reference } from '../models/model'
2726
import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession'
2827
import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient'
2928
import { downloadExportResultArchive } from '../../shared/utilities/download'
@@ -156,44 +155,44 @@ export async function pollTestJobStatus(
156155
status: 'InProgress',
157156
progressRate,
158157
})
159-
const shortAnswerString = resp.testGenerationJob?.shortAnswer
160-
if (shortAnswerString) {
161-
const parsedShortAnswer = JSON.parse(shortAnswerString)
162-
const shortAnswer: ShortAnswer = JSON.parse(parsedShortAnswer)
163-
// Stop the Unit test generation workflow if IDE receive stopIteration = true
164-
if (shortAnswer.stopIteration === 'true') {
165-
session.stopIteration = true
166-
throw new TestGenFailedError(shortAnswer.planSummary)
167-
}
168-
if (shortAnswer.numberOfTestMethods) {
169-
session.numberOfTestsGenerated = Number(shortAnswer.numberOfTestMethods)
158+
const jobSummary = resp.testGenerationJob?.jobSummary ?? ''
159+
const jobSummaryNoBackticks = jobSummary.replace(/^`+|`+$/g, '')
160+
ChatSessionManager.Instance.getSession().jobSummary = jobSummaryNoBackticks
161+
const packageInfoList = resp.testGenerationJob?.packageInfoList ?? []
162+
const packageInfo = packageInfoList[0]
163+
const targetFileInfo = packageInfo?.targetFileInfoList?.[0]
164+
165+
if (packageInfo) {
166+
// TODO: will need some fields from packageInfo such as buildCommand, packagePlan, packageSummary
167+
}
168+
if (targetFileInfo) {
169+
if (targetFileInfo.numberOfTestMethods) {
170+
session.numberOfTestsGenerated = Number(targetFileInfo.numberOfTestMethods)
170171
}
171-
if (shortAnswer.codeReferences) {
172-
session.references = shortAnswer.codeReferences
172+
if (targetFileInfo.codeReferences) {
173+
session.references = targetFileInfo.codeReferences as Reference[]
173174
}
174175
if (initialExecution) {
175-
session.generatedFilePath = shortAnswer?.testFilePath ?? ''
176-
const currentPlanSummary = session.shortAnswer?.planSummary
177-
const newPlanSummary = shortAnswer?.planSummary
178-
const status = shortAnswer.stopIteration
176+
session.generatedFilePath = targetFileInfo.testFilePath ?? ''
177+
const currentPlanSummary = session.targetFileInfo?.filePlan
178+
const newPlanSummary = targetFileInfo?.filePlan
179179

180180
if (currentPlanSummary !== newPlanSummary && newPlanSummary) {
181181
const chatControllers = testGenState.getChatControllers()
182182
if (chatControllers) {
183183
const currentSession = ChatSessionManager.Instance.getSession()
184-
chatControllers.updateShortAnswer.fire({
184+
chatControllers.updateTargetFileInfo.fire({
185185
tabID: currentSession.tabID,
186-
status,
187-
shortAnswer,
186+
targetFileInfo,
188187
testGenerationJobGroupName: resp.testGenerationJob?.testGenerationJobGroupName,
189188
testGenerationJobId: resp.testGenerationJob?.testGenerationJobId,
190189
filePath,
191190
})
192191
}
193192
}
194193
}
195-
ChatSessionManager.Instance.getSession().shortAnswer = shortAnswer
196194
}
195+
ChatSessionManager.Instance.getSession().targetFileInfo = targetFileInfo
197196
if (resp.testGenerationJob?.status !== CodeWhispererConstants.TestGenerationJobStatus.IN_PROGRESS) {
198197
// This can be FAILED or COMPLETED
199198
status = resp.testGenerationJob?.status as CodeWhispererConstants.TestGenerationJobStatus
@@ -243,7 +242,7 @@ export async function exportResultsArchive(
243242
const zip = new AdmZip(pathToArchive)
244243
zip.extractAllTo(pathToArchiveDir, true)
245244

246-
const testFilePathFromResponse = session?.shortAnswer?.testFilePath
245+
const testFilePathFromResponse = session?.targetFileInfo?.testFilePath
247246
const testFilePath = testFilePathFromResponse
248247
? testFilePathFromResponse.split('/').slice(1).join('/') // remove the project name
249248
: await getTestFilePathFromZip(pathToArchiveDir)

0 commit comments

Comments
 (0)