Skip to content

Commit eb02344

Browse files
authored
fix(amazonq): add progressUpdates to TransformationHub (#4602
Problem Needed to add progress updates to the Transformation Hub. Builds off of this old PR (#4575) which I closed because a lot of that work became unneeded & there were many merge conflicts; was easier to start over and add my desired changes on top of the latest work.
1 parent 287f12b commit eb02344

File tree

10 files changed

+106
-59
lines changed

10 files changed

+106
-59
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": "Amazon Q Code Transformation: show detailed progress updates in Transformation Hub"
4+
}

packages/core/package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@
743743
"type": "webview",
744744
"id": "aws.amazonq.transformationHub",
745745
"name": "Status",
746-
"when": "gumby.isTransformAvailable || gumby.isStopButtonAvailable"
746+
"when": "gumby.wasQCodeTransformationUsed"
747747
},
748748
{
749749
"id": "aws.amazonq.transformationProposedChangesTree",
@@ -3753,12 +3753,6 @@
37533753
}
37543754
}
37553755
},
3756-
{
3757-
"command": "aws.amazonq.startTransformationInHub",
3758-
"title": "Start Transformation",
3759-
"icon": "$(play)",
3760-
"enablement": "gumby.isTransformAvailable"
3761-
},
37623756
{
37633757
"command": "aws.amazonq.stopTransformationInHub",
37643758
"title": "Stop Transformation",

packages/core/src/amazonq/explorer/amazonQChildrenNodes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export const createTransformByQ = () => {
7777
const prefix = transformByQState.getPrefixTextForButton()
7878
let status = transformByQState.getPolledJobStatus().toLowerCase()
7979
if (transformByQState.isRunning()) {
80-
void vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', false)
8180
if (status === '') {
8281
// job is running but polling has not started yet, so display generic message
8382
status = CodeWhispererConstants.transformByQStateRunningMessage

packages/core/src/amazonq/explorer/amazonQTreeNode.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,8 @@ export class AmazonQNode implements TreeNode {
4444
}
4545

4646
private getDescription(): string {
47-
void vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', false)
4847
if (AuthUtil.instance.isConnectionValid()) {
4948
if (AuthUtil.instance.isEnterpriseSsoInUse()) {
50-
void vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', true)
5149
return 'IAM Identity Center Connected'
5250
} else if (AuthUtil.instance.isBuilderIdInUse()) {
5351
return 'AWS Builder ID Connected'
@@ -61,7 +59,6 @@ export class AmazonQNode implements TreeNode {
6159
}
6260

6361
public getChildren() {
64-
void vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', false)
6562
if (AuthUtil.instance.isConnectionExpired()) {
6663
return [createReconnect('tree'), createLearnMoreNode()]
6764
}
@@ -82,7 +79,6 @@ export class AmazonQNode implements TreeNode {
8279

8380
const transformNode = []
8481
if (AuthUtil.instance.isValidCodeTransformationAuthUser()) {
85-
void vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', true)
8682
transformNode.push(createTransformByQ())
8783
}
8884

packages/core/src/amazonqGumby/activation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { AuthUtil } from '../codewhisperer/util/authUtil'
1818
import { validateAndLogProjectDetails } from '../codewhisperer/service/transformByQHandler'
1919

2020
export async function activate(context: ExtContext) {
21+
void vscode.commands.executeCommand('setContext', 'gumby.wasQCodeTransformationUsed', false)
2122
// If the user is codewhisperer eligible, activate the plugin
2223
if (AuthUtil.instance.isValidCodeTransformationAuthUser()) {
2324
const transformationHubViewProvider = new TransformationHubViewProvider()

packages/core/src/codewhisperer/commands/startTransformByQ.ts

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import * as fs from 'fs'
99
import * as os from 'os'
1010
import { getLogger } from '../../shared/logger'
1111
import * as CodeWhispererConstants from '../models/constants'
12-
import { transformByQState, StepProgress, TransformByQReviewStatus, JDKVersion } from '../models/model'
12+
import {
13+
transformByQState,
14+
StepProgress,
15+
TransformByQReviewStatus,
16+
JDKVersion,
17+
sessionPlanProgress,
18+
} from '../models/model'
1319
import { convertToTimeString, convertDateToTimestamp } from '../../shared/utilities/textUtilities'
1420
import {
1521
throwIfCancelled,
@@ -55,18 +61,6 @@ export const stopTransformByQButton = localize('aws.codewhisperer.stop.transform
5561

5662
let sessionJobHistory: { timestamp: string; module: string; status: string; duration: string; id: string }[] = []
5763

58-
const sessionPlanProgress: {
59-
uploadCode: StepProgress
60-
buildCode: StepProgress
61-
transformCode: StepProgress
62-
returnCode: StepProgress
63-
} = {
64-
uploadCode: StepProgress.NotStarted,
65-
buildCode: StepProgress.NotStarted,
66-
transformCode: StepProgress.NotStarted,
67-
returnCode: StepProgress.NotStarted,
68-
}
69-
7064
export async function startTransformByQWithProgress() {
7165
await startTransformByQ()
7266
}
@@ -158,7 +152,7 @@ export async function startTransformByQ() {
158152
'aws.amazonq.showPlanProgressInHub',
159153
codeTransformTelemetryState.getStartTime()
160154
)
161-
}, CodeWhispererConstants.progressIntervalMs)
155+
}, CodeWhispererConstants.transformationJobPollingIntervalSeconds * 1000)
162156

163157
// step 1: CreateCodeUploadUrl and upload code
164158
const uploadId = await preTransformationUploadCode()
@@ -173,6 +167,7 @@ export async function startTransformByQ() {
173167
const status = await pollTransformationStatusUntilComplete(jobId)
174168

175169
// Set the result state variables for our store and the UI
170+
// At this point job should be completed or partially completed
176171
await finalizeTransformationJob(status)
177172
} catch (error: any) {
178173
await transformationJobErrorHandler(error)
@@ -204,7 +199,6 @@ export async function preTransformationUploadCode() {
204199
})
205200
throw err
206201
}
207-
sessionPlanProgress['uploadCode'] = StepProgress.Succeeded
208202

209203
await sleep(2000) // sleep before starting job to prevent ThrottlingException
210204
throwIfCancelled()
@@ -238,7 +232,7 @@ export async function startTransformationJob(uploadId: string) {
238232

239233
export async function pollTransformationStatusUntilPlanReady(jobId: string) {
240234
try {
241-
await pollTransformationJob(jobId, CodeWhispererConstants.validStatesForGettingPlan)
235+
await pollTransformationJob(jobId, CodeWhispererConstants.validStatesForPlanGenerated)
242236
} catch (error) {
243237
const errorMessage = CodeWhispererConstants.failedToCompleteJobMessage
244238
getLogger().error(`CodeTransformation: ${errorMessage}`, error)
@@ -254,7 +248,7 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string) {
254248
transformByQState.setJobFailureErrorMessage(errorMessage)
255249
throw new Error('Get plan failed')
256250
}
257-
sessionPlanProgress['buildCode'] = StepProgress.Succeeded
251+
258252
const planFilePath = path.join(os.tmpdir(), 'transformation-plan.md')
259253
fs.writeFileSync(planFilePath, plan)
260254
await vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(planFilePath))
@@ -286,7 +280,6 @@ export async function finalizeTransformationJob(status: string) {
286280
throw new Error('Job was not successful nor partially successful')
287281
}
288282

289-
sessionPlanProgress['transformCode'] = StepProgress.Succeeded
290283
transformByQState.setToSucceeded()
291284
if (status === 'PARTIALLY_COMPLETED') {
292285
transformByQState.setToPartiallySucceeded()
@@ -298,11 +291,7 @@ export async function finalizeTransformationJob(status: string) {
298291
await vscode.commands.executeCommand('aws.amazonq.transformationHub.reviewChanges.reveal')
299292
await vscode.commands.executeCommand('aws.amazonq.refresh')
300293

301-
transformByQState.getChatControllers()?.transformationFinished.fire({
302-
jobStatus: status,
303-
tabID: ChatSessionManager.Instance.getSession().tabID,
304-
})
305-
sessionPlanProgress['returnCode'] = StepProgress.Succeeded
294+
sessionPlanProgress['transformCode'] = StepProgress.Succeeded
306295
}
307296

308297
export async function getValidCandidateProjects(): Promise<TransformationCandidateProject[]> {
@@ -311,11 +300,13 @@ export async function getValidCandidateProjects(): Promise<TransformationCandida
311300
}
312301

313302
export async function setTransformationToRunningState() {
303+
await setContextVariables()
304+
314305
transformByQState.setToRunning()
315-
sessionPlanProgress['uploadCode'] = StepProgress.Pending
306+
sessionPlanProgress['startJob'] = StepProgress.Pending
316307
sessionPlanProgress['buildCode'] = StepProgress.Pending
308+
sessionPlanProgress['generatePlan'] = StepProgress.Pending
317309
sessionPlanProgress['transformCode'] = StepProgress.Pending
318-
sessionPlanProgress['returnCode'] = StepProgress.Pending
319310

320311
codeTransformTelemetryState.setStartTime()
321312

@@ -343,13 +334,14 @@ export async function setTransformationToRunningState() {
343334
codeTransformTelemetryState.getStartTime()
344335
)
345336

346-
await setContextVariables()
347-
348337
await vscode.commands.executeCommand('aws.amazonq.refresh')
349338
}
350339

351340
export async function postTransformationJob() {
352-
await vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', true)
341+
transformByQState.getChatControllers()?.transformationFinished.fire({
342+
jobStatus: transformByQState.getPolledJobStatus(),
343+
tabID: ChatSessionManager.Instance.getSession().tabID,
344+
})
353345
const durationInMs = calculateTotalLatency(codeTransformTelemetryState.getStartTime())
354346
const resultStatusMessage = codeTransformTelemetryState.getResultStatus()
355347

@@ -441,18 +433,18 @@ export async function transformationJobErrorHandler(error: any) {
441433
}
442434
})
443435
}
444-
if (sessionPlanProgress['uploadCode'] !== StepProgress.Succeeded) {
445-
sessionPlanProgress['uploadCode'] = StepProgress.Failed
436+
if (sessionPlanProgress['startJob'] !== StepProgress.Succeeded) {
437+
sessionPlanProgress['startJob'] = StepProgress.Failed
446438
}
447439
if (sessionPlanProgress['buildCode'] !== StepProgress.Succeeded) {
448440
sessionPlanProgress['buildCode'] = StepProgress.Failed
449441
}
442+
if (sessionPlanProgress['generatePlan'] !== StepProgress.Succeeded) {
443+
sessionPlanProgress['generatePlan'] = StepProgress.Failed
444+
}
450445
if (sessionPlanProgress['transformCode'] !== StepProgress.Succeeded) {
451446
sessionPlanProgress['transformCode'] = StepProgress.Failed
452447
}
453-
if (sessionPlanProgress['returnCode'] !== StepProgress.Succeeded) {
454-
sessionPlanProgress['returnCode'] = StepProgress.Failed
455-
}
456448
getLogger().error(`CodeTransformation: ${error.message}`)
457449
}
458450

@@ -509,8 +501,8 @@ export async function stopTransformByQ(
509501
}
510502

511503
async function setContextVariables() {
504+
await vscode.commands.executeCommand('setContext', 'gumby.wasQCodeTransformationUsed', true)
512505
await vscode.commands.executeCommand('setContext', 'gumby.isStopButtonAvailable', true)
513-
await vscode.commands.executeCommand('setContext', 'gumby.isTransformAvailable', false)
514506
await vscode.commands.executeCommand('setContext', 'gumby.isPlanAvailable', false)
515507
await vscode.commands.executeCommand('setContext', 'gumby.isSummaryAvailable', false)
516508
await vscode.commands.executeCommand('setContext', 'gumby.reviewState', TransformByQReviewStatus.NotStarted)

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,10 @@ export const transformByQStatePartialSuccessMessage = 'partially succeeded'
401401

402402
export const transformByQStoppedState = 'STOPPED'
403403

404-
export const transformationJobPollingIntervalSeconds = 10
404+
export const transformationJobPollingIntervalSeconds = 5
405405

406406
export const transformationJobTimeoutSeconds = 60 * 60 // 1 hour, to match backend
407407

408-
export const progressIntervalMs = 1000
409-
410408
export const defaultLanguage = 'Java'
411409

412410
export const contentChecksumType = 'SHA_256'
@@ -415,13 +413,26 @@ export const uploadIntent = 'TRANSFORMATION'
415413

416414
export const transformationType = 'LANGUAGE_UPGRADE'
417415

418-
// when in one of these states, we can definitely say the plan is available
419-
// in other states, we keep polling/waiting
420-
export const validStatesForGettingPlan = ['COMPLETED', 'PARTIALLY_COMPLETED', 'PLANNED', 'TRANSFORMING', 'TRANSFORMED']
416+
// job successfully started
417+
export const validStatesForJobStarted = [
418+
'STARTED',
419+
'PREPARING',
420+
'PREPARED',
421+
'PLANNING',
422+
'PLANNED',
423+
'TRANSFORMING',
424+
'TRANSFORMED',
425+
]
426+
427+
// initial build succeeded
428+
export const validStatesForBuildSucceeded = ['PREPARED', 'PLANNING', 'PLANNED', 'TRANSFORMING', 'TRANSFORMED']
429+
430+
// plan must be available
431+
export const validStatesForPlanGenerated = ['PLANNED', 'TRANSFORMING', 'TRANSFORMED']
421432

422433
export const failureStates = ['FAILED', 'STOPPING', 'STOPPED', 'REJECTED']
423434

424-
// similarly, when in one of these states, we can stop polling, and if status is COMPLETED or PARTIALLY_COMPLETED we can download artifacts
435+
// if status is COMPLETED or PARTIALLY_COMPLETED we can download artifacts
425436
export const validStatesForCheckingDownloadUrl = [
426437
'COMPLETED',
427438
'PARTIALLY_COMPLETED',

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,18 @@ export enum DropdownStep {
267267
STEP_2 = 2,
268268
}
269269

270+
export const sessionPlanProgress: {
271+
startJob: StepProgress
272+
buildCode: StepProgress
273+
generatePlan: StepProgress
274+
transformCode: StepProgress
275+
} = {
276+
startJob: StepProgress.NotStarted,
277+
buildCode: StepProgress.NotStarted,
278+
generatePlan: StepProgress.NotStarted,
279+
transformCode: StepProgress.NotStarted,
280+
}
281+
270282
export class TransformByQState {
271283
private transformByQState: TransformByQStatus = TransformByQStatus.NotStarted
272284

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { BuildSystem, JDKVersion, transformByQState, TransformByQStoppedError, ZipManifest } from '../models/model'
6+
import {
7+
BuildSystem,
8+
JDKVersion,
9+
sessionPlanProgress,
10+
StepProgress,
11+
transformByQState,
12+
TransformByQStoppedError,
13+
ZipManifest,
14+
} from '../models/model'
715
import * as codeWhisperer from '../client/codewhisperer'
816
import * as crypto from 'crypto'
917
import { getLogger } from '../../shared/logger'
@@ -846,6 +854,16 @@ export async function pollTransformationJob(jobId: string, validStates: string[]
846854
result: MetadataResult.Pass,
847855
})
848856
status = response.transformationJob.status!
857+
// must be series of ifs, not else ifs
858+
if (CodeWhispererConstants.validStatesForJobStarted.includes(status)) {
859+
sessionPlanProgress['startJob'] = StepProgress.Succeeded
860+
}
861+
if (CodeWhispererConstants.validStatesForBuildSucceeded.includes(status)) {
862+
sessionPlanProgress['buildCode'] = StepProgress.Succeeded
863+
}
864+
if (CodeWhispererConstants.validStatesForPlanGenerated.includes(status)) {
865+
sessionPlanProgress['generatePlan'] = StepProgress.Succeeded
866+
}
849867
// emit metric when job status changes
850868
if (status !== transformByQState.getPolledJobStatus()) {
851869
telemetry.codeTransform_jobStatusChanged.emit({

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,24 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
116116
private async showPlanProgress(startTime: number): Promise<string> {
117117
const planProgress = getPlanProgress()
118118
let planSteps = undefined
119-
if (planProgress['buildCode'] === StepProgress.Succeeded) {
119+
if (planProgress['generatePlan'] === StepProgress.Succeeded) {
120120
planSteps = await getTransformationSteps(transformByQState.getJobId())
121121
}
122122
let progressHtml = `<p><b>Transformation Status</b></p><p>No job ongoing</p>`
123-
if (planProgress['returnCode'] !== StepProgress.NotStarted) {
123+
if (planProgress['transformCode'] !== StepProgress.NotStarted) {
124124
progressHtml = `<p><b>Transformation Status</b></p>`
125-
progressHtml += `<p> ${this.getProgressIconMarkup(planProgress['uploadCode'])} Waiting for job to start</p>`
126-
if (planProgress['uploadCode'] === StepProgress.Succeeded) {
125+
progressHtml += `<p> ${this.getProgressIconMarkup(planProgress['startJob'])} Waiting for job to start</p>`
126+
if (planProgress['startJob'] === StepProgress.Succeeded) {
127127
progressHtml += `<p> ${this.getProgressIconMarkup(
128128
planProgress['buildCode']
129-
)} Build uploaded code in secure build environment and generate transformation plan</p>`
129+
)} Build uploaded code in secure build environment</p>`
130130
}
131131
if (planProgress['buildCode'] === StepProgress.Succeeded) {
132+
progressHtml += `<p> ${this.getProgressIconMarkup(
133+
planProgress['generatePlan']
134+
)} Generate transformation plan</p>`
135+
}
136+
if (planProgress['generatePlan'] === StepProgress.Succeeded) {
132137
progressHtml += `<p> ${this.getProgressIconMarkup(
133138
planProgress['transformCode']
134139
)} Transform your code to Java 17 using transformation plan</p>`
@@ -172,6 +177,11 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
172177
if (step.progressUpdates) {
173178
for (const subStep of step.progressUpdates) {
174179
progressHtml += `<p style="margin-left: 40px">- ${subStep.name}</p>`
180+
if (subStep.description) {
181+
progressHtml += `<p style="margin-left: 60px; color:${this.getFontColorForSubStep(
182+
subStep.status
183+
)}">- ${subStep.description}</p>`
184+
}
175185
}
176186
}
177187
}
@@ -248,4 +258,14 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
248258
return `<span style="color: grey"> ✓ </span>`
249259
}
250260
}
261+
262+
private getFontColorForSubStep(status: string) {
263+
if (status === 'COMPLETED') {
264+
return 'green'
265+
} else if (status === 'FAILED') {
266+
return 'red'
267+
} else {
268+
return '' // uses VS Code's default color
269+
}
270+
}
251271
}

0 commit comments

Comments
 (0)