From 65a3ba9dc0a6cabeebe7998ac709290bd41757d1 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 15 May 2025 15:10:37 -0700 Subject: [PATCH 01/10] parse plan --- .../core/src/codewhisperer/models/model.ts | 3 + .../transformByQ/transformApiHandler.ts | 142 ++++++++++++++---- .../commands/transformByQ.test.ts | 14 +- 3 files changed, 127 insertions(+), 32 deletions(-) diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index 28072249371..974923e6ef6 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -686,6 +686,9 @@ export class ZipManifest { version: string = '1.0' hilCapabilities: string[] = ['HIL_1pDependency_VersionUpgrade'] // TO-DO: add 'CLIENT_SIDE_BUILD' here when releasing + + // TO-DO: add a new capability here for AGENTIC_PLAN_V1? + transformCapabilities: string[] = ['EXPLAINABILITY_V1'] customBuildCommand: string = 'clean test' requestedConversions?: { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 476123f2d6d..8739f5f75e0 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -521,39 +521,51 @@ export function getFormattedString(s: string) { return CodeWhispererConstants.formattedStringMap.get(s) ?? s } -export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [key: string]: string }) { - const tableObj = tableMapping[stepId] - if (!tableObj) { - // no table present for this step +export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [key: string]: string[] }) { + const tableObjects = tableMapping[stepId] + if (!tableObjects || tableObjects.length === 0 || tableObjects.every((table: string) => table === '')) { + // no tables for this stepId return plan } - const table = JSON.parse(tableObj) - if (table.rows.length === 0) { - // empty table - plan += `\n\nThere are no ${table.name.toLowerCase()} to display.\n\n` + const tables: any[] = [] + tableObjects.forEach((tableObj: string) => { + try { + const table = JSON.parse(tableObj) + if (table) { + tables.push(table) + } + } catch (e) { + getLogger().error(`CodeTransformation: Failed to parse table JSON, skipping: ${e}`) + } + }) + + if (tables.every((table: any) => table.rows.length === 0)) { + // empty tables for this stepId + plan += `\n\nThere are no ${tables[0].name.toLowerCase()} to display.\n\n` return plan } - plan += `\n\n\n${table.name}\n|` - const columns = table.columnNames - // eslint-disable-next-line unicorn/no-array-for-each + // table name and columns are shared, so only add to plan once + plan += `\n\n\n${tables[0].name}\n|` + const columns = tables[0].columnNames columns.forEach((columnName: string) => { plan += ` ${getFormattedString(columnName)} |` }) plan += '\n|' - // eslint-disable-next-line unicorn/no-array-for-each columns.forEach((_: any) => { plan += '-----|' }) - // eslint-disable-next-line unicorn/no-array-for-each - table.rows.forEach((row: any) => { - plan += '\n|' - // eslint-disable-next-line unicorn/no-array-for-each - columns.forEach((columnName: string) => { - if (columnName === 'relativePath') { - plan += ` [${row[columnName]}](${row[columnName]}) |` // add MD link only for files - } else { - plan += ` ${row[columnName]} |` - } + // add all rows of all tables + tables.forEach((table: any) => { + table.rows.forEach((row: any) => { + plan += '\n|' + columns.forEach((columnName: string) => { + if (columnName === 'relativePath') { + // add markdown link only for file paths + plan += ` [${row[columnName]}](${row[columnName]}) |` + } else { + plan += ` ${row[columnName]} |` + } + }) }) }) plan += '\n\n' @@ -561,11 +573,13 @@ export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [ } export function getTableMapping(stepZeroProgressUpdates: ProgressUpdates) { - const map: { [key: string]: string } = {} + const map: { [key: string]: string[] } = {} for (const update of stepZeroProgressUpdates) { - // description should never be undefined since even if no data we show an empty table - // but just in case, empty string allows us to skip this table without errors when rendering - map[update.name] = update.description ?? '' + if (!map[update.name]) { + map[update.name] = [] + } + // empty string allows us to skip this table when rendering + map[update.name].push(update.description ?? '') } return map } @@ -594,6 +608,78 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil profileArn: profile?.arn, }) + // TO-DO: remove this mocked API response + response = { + transformationPlan: { + transformationSteps: [ + { + id: '0', + name: 'Supplement Info', + status: 'COMPLETED', + progressUpdates: [ + { + name: '0', + status: 'COMPLETED', + description: + '{"type":"STATISTICS","name":"Plan Statistics","description":null,"columnNames":["name","value"],"rows":[{"name":"linesOfCode","value":"2532"},{"name":"plannedDependencyChanges","value":"4"},{"name":"plannedDeprecatedApiChanges","value":"0"},{"name":"plannedFileChanges","value":"7"}]}', + }, + { + name: '1', + status: 'COMPLETED', + description: + '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[]}', + }, + { + name: '1', + status: 'COMPLETED', + description: + '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"junit:junit","action":"REMOVE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', + }, + { + name: '1', + status: 'FAILED', + description: + '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"com.squareup.okio:okio","action":"UPDATE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', + }, + { + name: '1', + status: 'FAILED', + description: + '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"com.squareup.okio:okio","action":"UPDATE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', + }, + { + name: '1', + status: 'COMPLETED', + description: + '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"com.squareup.okio:okio","action":"UPDATE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', + }, + { + name: '-1', + status: 'COMPLETED', + description: + '{"type":"FILES","name":"File Changes","description":null,"columnNames":["relativePath","action"],"rows":[{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelInterfaceTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelEnumerationTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelClassTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelAnnotationTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/ParameterTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/MethodTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/FieldTest.java","action":"UPDATE"}]}', + }, + ], + }, + { + id: '1', + name: 'Step 1 - Update JDK version, dependencies and related code (David)', + description: + 'Amazon Q will attempt to update the JDK version and change the following dependencies and related code.', + status: 'CREATED', + progressUpdates: [], + }, + { + id: '2', + name: 'Step 2 - Finalize code changes', + description: 'Amazon Q will attempt to replace the following instances of deprecated code.', + status: 'CREATED', + progressUpdates: [], + }, + ], + }, + } + const stepZeroProgressUpdates = response.transformationPlan.transformationSteps[0].progressUpdates if (!stepZeroProgressUpdates || stepZeroProgressUpdates.length === 0) { @@ -604,7 +690,7 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil // gets a mapping between the ID ('name' field) of each progressUpdate (substep) and the associated table const tableMapping = getTableMapping(stepZeroProgressUpdates) - const jobStatistics = JSON.parse(tableMapping['0']).rows // ID of '0' reserved for job statistics table + const jobStatistics = JSON.parse(tableMapping['0'][0]).rows // ID of '0' reserved for job statistics table; only 1 table there // get logo directly since we only use one logo regardless of color theme const logoIcon = getTransformationIcon('transformLogo') @@ -631,7 +717,7 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil } plan += `
` plan += `

Appendix
Scroll to top


` - plan = addTableMarkdown(plan, '-1', tableMapping) // ID of '-1' reserved for appendix table + plan = addTableMarkdown(plan, '-1', tableMapping) // ID of '-1' reserved for appendix table; only 1 table there return plan } catch (e: any) { const errorMessage = (e as Error).message diff --git a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts index 4b478e1876e..5b84773e3d8 100644 --- a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts +++ b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts @@ -488,12 +488,18 @@ dependencyManagement: const actual = getTableMapping(stepZeroProgressUpdates) const expected = { - '0': '{"columnNames":["name","value"],"rows":[{"name":"Lines of code in your application","value":"3000"},{"name":"Dependencies to be replaced","value":"5"},{"name":"Deprecated code instances to be replaced","value":"10"},{"name":"Files to be updated","value":"7"}]}', - '1-dependency-change-abc': + '0': [ + '{"columnNames":["name","value"],"rows":[{"name":"Lines of code in your application","value":"3000"},{"name":"Dependencies to be replaced","value":"5"},{"name":"Deprecated code instances to be replaced","value":"10"},{"name":"Files to be updated","value":"7"}]}', + ], + '1-dependency-change-abc': [ '{"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"org.springboot.com","action":"Update","currentVersion":"2.1","targetVersion":"2.4"}, {"dependencyName":"com.lombok.java","action":"Remove","currentVersion":"1.7","targetVersion":"-"}]}', - '2-deprecated-code-xyz': + ], + '2-deprecated-code-xyz': [ '{"columnNames":["apiFullyQualifiedName","numChangedFiles"],“rows”:[{"apiFullyQualifiedName":"java.lang.Thread.stop()","numChangedFiles":"6"}, {"apiFullyQualifiedName":"java.math.bad()","numChangedFiles":"3"}]}', - '-1': '{"columnNames":["relativePath","action"],"rows":[{"relativePath":"pom.xml","action":"Update"}, {"relativePath":"src/main/java/com/bhoruka/bloodbank/BloodbankApplication.java","action":"Update"}]}', + ], + '-1': [ + '{"columnNames":["relativePath","action"],"rows":[{"relativePath":"pom.xml","action":"Update"}, {"relativePath":"src/main/java/com/bhoruka/bloodbank/BloodbankApplication.java","action":"Update"}]}', + ], } assert.deepStrictEqual(actual, expected) }) From e8c516cd5757c83abb5aec75787f69d0cc72a04f Mon Sep 17 00:00:00 2001 From: David Hasani Date: Sat, 17 May 2025 18:27:12 -0700 Subject: [PATCH 02/10] open plan periodically --- .../core/src/codewhisperer/commands/startTransformByQ.ts | 7 +++++-- .../service/transformByQ/transformApiHandler.ts | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index eb31839686d..cb0cac0a103 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -554,6 +554,11 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string, prof // for now, no plan shown with SQL conversions. later, we may add one return } + jobPlanProgress['generatePlan'] = StepProgress.Succeeded + throwIfCancelled() +} + +export async function openTransformationPlan(jobId: string, profile?: RegionProfile) { let plan = undefined try { plan = await getTransformationPlan(jobId, profile) @@ -576,8 +581,6 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string, prof transformByQState.setPlanFilePath(planFilePath) await setContext('gumby.isPlanAvailable', true) } - jobPlanProgress['generatePlan'] = StepProgress.Succeeded - throwIfCancelled() } export async function pollTransformationStatusUntilComplete(jobId: string, profile: RegionProfile | undefined) { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 8739f5f75e0..085f02c321a 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -55,6 +55,7 @@ import { AuthUtil } from '../../util/authUtil' import { DiffModel } from './transformationResultsViewProvider' import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports import { isClientSideBuildEnabled } from '../../../dev/config' +import { openTransformationPlan } from '../../commands/startTransformByQ' export function getSha256(buffer: Buffer) { const hasher = crypto.createHash('sha256') @@ -785,6 +786,11 @@ export async function pollTransformationJob(jobId: string, validStates: string[] `${CodeWhispererConstants.failedToCompleteJobGenericNotification} ${errorMessage}` ) } + + if (CodeWhispererConstants.validStatesForPlanGenerated.includes(status)) { + await openTransformationPlan(jobId, profile) + } + if (validStates.includes(status)) { break } From daeb93e2a501c47995c274b9e963422f96841954 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Sat, 17 May 2025 18:50:30 -0700 Subject: [PATCH 03/10] cleanup --- .../core/src/codewhisperer/models/model.ts | 4 +- .../transformByQ/transformApiHandler.ts | 72 ------------------- 2 files changed, 1 insertion(+), 75 deletions(-) diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index 974923e6ef6..9c48838afe3 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -686,9 +686,7 @@ export class ZipManifest { version: string = '1.0' hilCapabilities: string[] = ['HIL_1pDependency_VersionUpgrade'] // TO-DO: add 'CLIENT_SIDE_BUILD' here when releasing - - // TO-DO: add a new capability here for AGENTIC_PLAN_V1? - + // TO-DO: add something like AGENTIC_PLAN_V1 here when BE allowlists everyone transformCapabilities: string[] = ['EXPLAINABILITY_V1'] customBuildCommand: string = 'clean test' requestedConversions?: { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 085f02c321a..a368ddb41d3 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -609,78 +609,6 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil profileArn: profile?.arn, }) - // TO-DO: remove this mocked API response - response = { - transformationPlan: { - transformationSteps: [ - { - id: '0', - name: 'Supplement Info', - status: 'COMPLETED', - progressUpdates: [ - { - name: '0', - status: 'COMPLETED', - description: - '{"type":"STATISTICS","name":"Plan Statistics","description":null,"columnNames":["name","value"],"rows":[{"name":"linesOfCode","value":"2532"},{"name":"plannedDependencyChanges","value":"4"},{"name":"plannedDeprecatedApiChanges","value":"0"},{"name":"plannedFileChanges","value":"7"}]}', - }, - { - name: '1', - status: 'COMPLETED', - description: - '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[]}', - }, - { - name: '1', - status: 'COMPLETED', - description: - '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"junit:junit","action":"REMOVE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', - }, - { - name: '1', - status: 'FAILED', - description: - '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"com.squareup.okio:okio","action":"UPDATE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', - }, - { - name: '1', - status: 'FAILED', - description: - '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"com.squareup.okio:okio","action":"UPDATE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', - }, - { - name: '1', - status: 'COMPLETED', - description: - '{"type":"DEPENDENCIES","name":"Dependency Changes","description":null,"columnNames":["dependencyName","action","currentVersion","targetVersion"],"rows":[{"dependencyName":"com.squareup.okio:okio","action":"UPDATE","currentVersion":"4.12","targetVersion":"-"},{"dependencyName":"org.apache.logging.log4j:log4j-slf4j-impl","action":"ADD","currentVersion":"-","targetVersion":"2.x"},{"dependencyName":"org.apache.maven.plugins:maven-surefire-plugin","action":"UPDATE","currentVersion":"2.18.1","targetVersion":"3.1.x"},{"dependencyName":"org.slf4j:slf4j-log4j12","action":"REMOVE","currentVersion":"1.8.0-beta0","targetVersion":"-"}]}', - }, - { - name: '-1', - status: 'COMPLETED', - description: - '{"type":"FILES","name":"File Changes","description":null,"columnNames":["relativePath","action"],"rows":[{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelInterfaceTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelEnumerationTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelClassTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/TopLevelAnnotationTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/ParameterTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/MethodTest.java","action":"UPDATE"},{"relativePath":"src/main/test/com/nxllxn/plantuml/java/FieldTest.java","action":"UPDATE"}]}', - }, - ], - }, - { - id: '1', - name: 'Step 1 - Update JDK version, dependencies and related code (David)', - description: - 'Amazon Q will attempt to update the JDK version and change the following dependencies and related code.', - status: 'CREATED', - progressUpdates: [], - }, - { - id: '2', - name: 'Step 2 - Finalize code changes', - description: 'Amazon Q will attempt to replace the following instances of deprecated code.', - status: 'CREATED', - progressUpdates: [], - }, - ], - }, - } - const stepZeroProgressUpdates = response.transformationPlan.transformationSteps[0].progressUpdates if (!stepZeroProgressUpdates || stepZeroProgressUpdates.length === 0) { From abb43b73cd7da29edabaf2b05a07e64f06a697c6 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Sat, 17 May 2025 19:26:34 -0700 Subject: [PATCH 04/10] fix circular imports --- .../commands/startTransformByQ.ts | 26 ------------------ .../transformByQ/transformApiHandler.ts | 27 ++++++++++++++++++- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index cb0cac0a103..88171dd5b83 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -27,7 +27,6 @@ import { downloadHilResultArchive, findDownloadArtifactStep, getArtifactsFromProgressUpdate, - getTransformationPlan, getTransformationSteps, pollTransformationJob, resumeTransformationJob, @@ -558,31 +557,6 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string, prof throwIfCancelled() } -export async function openTransformationPlan(jobId: string, profile?: RegionProfile) { - let plan = undefined - try { - plan = await getTransformationPlan(jobId, profile) - } catch (error) { - // means API call failed - getLogger().error(`CodeTransformation: ${CodeWhispererConstants.failedToCompleteJobNotification}`, error) - transformByQState.setJobFailureErrorNotification( - `${CodeWhispererConstants.failedToGetPlanNotification} ${(error as Error).message}` - ) - transformByQState.setJobFailureErrorChatMessage( - `${CodeWhispererConstants.failedToGetPlanChatMessage} ${(error as Error).message}` - ) - throw new Error('Get plan failed') - } - - if (plan !== undefined) { - const planFilePath = path.join(transformByQState.getProjectPath(), 'transformation-plan.md') - fs.writeFileSync(planFilePath, plan) - await vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(planFilePath)) - transformByQState.setPlanFilePath(planFilePath) - await setContext('gumby.isPlanAvailable', true) - } -} - export async function pollTransformationStatusUntilComplete(jobId: string, profile: RegionProfile | undefined) { let status = '' try { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index a368ddb41d3..494d706b36a 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -51,11 +51,11 @@ import { encodeHTML } from '../../../shared/utilities/textUtilities' import { convertToTimeString } from '../../../shared/datetime' import { getAuthType } from '../../../auth/utils' import { UserWrittenCodeTracker } from '../../tracker/userWrittenCodeTracker' +import { setContext } from '../../../shared/vscode/setContext' import { AuthUtil } from '../../util/authUtil' import { DiffModel } from './transformationResultsViewProvider' import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports import { isClientSideBuildEnabled } from '../../../dev/config' -import { openTransformationPlan } from '../../commands/startTransformByQ' export function getSha256(buffer: Buffer) { const hasher = crypto.createHash('sha256') @@ -758,6 +758,31 @@ export async function pollTransformationJob(jobId: string, validStates: string[] return status } +async function openTransformationPlan(jobId: string, profile?: RegionProfile) { + let plan = undefined + try { + plan = await getTransformationPlan(jobId, profile) + } catch (error) { + // means API call failed + getLogger().error(`CodeTransformation: ${CodeWhispererConstants.failedToCompleteJobNotification}`, error) + transformByQState.setJobFailureErrorNotification( + `${CodeWhispererConstants.failedToGetPlanNotification} ${(error as Error).message}` + ) + transformByQState.setJobFailureErrorChatMessage( + `${CodeWhispererConstants.failedToGetPlanChatMessage} ${(error as Error).message}` + ) + throw new Error('Get plan failed') + } + + if (plan) { + const planFilePath = path.join(transformByQState.getProjectPath(), 'transformation-plan.md') + nodefs.writeFileSync(planFilePath, plan) + await vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(planFilePath)) + transformByQState.setPlanFilePath(planFilePath) + await setContext('gumby.isPlanAvailable', true) + } +} + async function attemptLocalBuild() { const jobId = transformByQState.getJobId() let artifactId From 84e7ea94dadb539709529b5f639e7588d10ef358 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Sat, 17 May 2025 19:43:50 -0700 Subject: [PATCH 05/10] fix test --- .../commands/transformByQ.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts index 5b84773e3d8..ea2aefce277 100644 --- a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts +++ b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts @@ -247,7 +247,25 @@ dependencyManagement: }, transformationJob: { status: 'COMPLETED' }, } + const mockPlanResponse = { + $response: { + data: { + transformationPlan: { transformationSteps: [] }, + }, + requestId: 'requestId', + hasNextPage: () => false, + error: undefined, + nextPage: () => null, // eslint-disable-line unicorn/no-null + redirectCount: 0, + retryCount: 0, + httpResponse: new HttpResponse(), + }, + transformationPlan: { transformationSteps: [] }, + } sinon.stub(codeWhisperer.codeWhispererClient, 'codeModernizerGetCodeTransformation').resolves(mockJobResponse) + sinon + .stub(codeWhisperer.codeWhispererClient, 'codeModernizerGetCodeTransformationPlan') + .resolves(mockPlanResponse) transformByQState.setToSucceeded() const status = await pollTransformationJob( 'dummyId', From 6670f1969e4b859ba66f04a6094247ef8e857faa Mon Sep 17 00:00:00 2001 From: David Hasani Date: Sat, 17 May 2025 19:55:16 -0700 Subject: [PATCH 06/10] fix lint --- .../service/transformByQ/transformApiHandler.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 494d706b36a..029c8e2ec12 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -529,6 +529,7 @@ export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [ return plan } const tables: any[] = [] + // eslint-disable-next-line unicorn/no-array-for-each tableObjects.forEach((tableObj: string) => { try { const table = JSON.parse(tableObj) @@ -548,17 +549,22 @@ export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [ // table name and columns are shared, so only add to plan once plan += `\n\n\n${tables[0].name}\n|` const columns = tables[0].columnNames + // eslint-disable-next-line unicorn/no-array-for-each columns.forEach((columnName: string) => { plan += ` ${getFormattedString(columnName)} |` }) plan += '\n|' + // eslint-disable-next-line unicorn/no-array-for-each columns.forEach((_: any) => { plan += '-----|' }) // add all rows of all tables + // eslint-disable-next-line unicorn/no-array-for-each tables.forEach((table: any) => { + // eslint-disable-next-line unicorn/no-array-for-each table.rows.forEach((row: any) => { plan += '\n|' + // eslint-disable-next-line unicorn/no-array-for-each columns.forEach((columnName: string) => { if (columnName === 'relativePath') { // add markdown link only for file paths From ca82e1a3fd6eeaa91760b26299fb931c3003770b Mon Sep 17 00:00:00 2001 From: David Hasani Date: Mon, 19 May 2025 14:08:23 -0700 Subject: [PATCH 07/10] add transformation type check --- .../service/transformByQ/transformApiHandler.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 029c8e2ec12..e2847a02ccc 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -617,6 +617,8 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil const stepZeroProgressUpdates = response.transformationPlan.transformationSteps[0].progressUpdates + console.log('stepZeroProgressUpdates', JSON.stringify(stepZeroProgressUpdates)) + if (!stepZeroProgressUpdates || stepZeroProgressUpdates.length === 0) { // means backend API response wrong and table data is missing throw new Error('No progress updates found in step 0') @@ -721,7 +723,10 @@ export async function pollTransformationJob(jobId: string, validStates: string[] ) } - if (CodeWhispererConstants.validStatesForPlanGenerated.includes(status)) { + if ( + CodeWhispererConstants.validStatesForPlanGenerated.includes(status) && + transformByQState.getTransformationType() === TransformationType.LANGUAGE_UPGRADE + ) { await openTransformationPlan(jobId, profile) } From f1b70a890a812e99494814d1a5d0cf3122eadeb8 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Mon, 19 May 2025 14:20:52 -0700 Subject: [PATCH 08/10] remove log --- packages/core/src/auth/connection.ts | 8 ++++---- .../core/src/codewhisperer/region/regionProfileManager.ts | 4 ++-- .../service/transformByQ/transformApiHandler.ts | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/core/src/auth/connection.ts b/packages/core/src/auth/connection.ts index 3e7752dd8e9..d27a8254fcc 100644 --- a/packages/core/src/auth/connection.ts +++ b/packages/core/src/auth/connection.ts @@ -28,10 +28,10 @@ const warnOnce = onceChanged((s: string, url: string) => { export const scopesCodeCatalyst = ['codecatalyst:read_write'] export const scopesSsoAccountAccess = ['sso:account:access'] /** These are the non-chat scopes for CW. */ -export const scopesCodeWhispererCore = ['codewhisperer:completions', 'codewhisperer:analysis'] -export const scopesCodeWhispererChat = ['codewhisperer:conversations'] -export const scopesFeatureDev = ['codewhisperer:taskassist'] -export const scopesGumby = ['codewhisperer:transformations'] +export const scopesCodeWhispererCore = ['codewhisperer_internal:completions', 'codewhisperer_internal:analysis'] +export const scopesCodeWhispererChat = ['codewhisperer_internal:conversations'] +export const scopesFeatureDev = ['codewhisperer_internal:taskassist'] +export const scopesGumby = ['codewhisperer_internal:transformations'] export const defaultSsoRegion = 'us-east-1' diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index 149a78391f8..134740c5df8 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -34,12 +34,12 @@ import { CachedResource } from '../../shared/utilities/resourceCache' // TODO: is there a better way to manage all endpoint strings in one place? export const defaultServiceConfig: CodeWhispererConfig = { region: 'us-east-1', - endpoint: 'https://codewhisperer.us-east-1.amazonaws.com/', + endpoint: 'https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/', } // Hack until we have a single discovery endpoint. We will call each endpoint one by one to fetch profile before then. const endpoints = createConstantMap({ - 'us-east-1': 'https://q.us-east-1.amazonaws.com/', + 'us-east-1': 'https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/', 'eu-central-1': 'https://q.eu-central-1.amazonaws.com/', }) diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index e2847a02ccc..94ba5c95631 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -617,8 +617,6 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil const stepZeroProgressUpdates = response.transformationPlan.transformationSteps[0].progressUpdates - console.log('stepZeroProgressUpdates', JSON.stringify(stepZeroProgressUpdates)) - if (!stepZeroProgressUpdates || stepZeroProgressUpdates.length === 0) { // means backend API response wrong and table data is missing throw new Error('No progress updates found in step 0') From 803bb4710791c829978b8995ffc680407df5b0b3 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Mon, 19 May 2025 14:22:46 -0700 Subject: [PATCH 09/10] undo --- packages/core/src/auth/connection.ts | 8 ++++---- .../core/src/codewhisperer/region/regionProfileManager.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/auth/connection.ts b/packages/core/src/auth/connection.ts index d27a8254fcc..3e7752dd8e9 100644 --- a/packages/core/src/auth/connection.ts +++ b/packages/core/src/auth/connection.ts @@ -28,10 +28,10 @@ const warnOnce = onceChanged((s: string, url: string) => { export const scopesCodeCatalyst = ['codecatalyst:read_write'] export const scopesSsoAccountAccess = ['sso:account:access'] /** These are the non-chat scopes for CW. */ -export const scopesCodeWhispererCore = ['codewhisperer_internal:completions', 'codewhisperer_internal:analysis'] -export const scopesCodeWhispererChat = ['codewhisperer_internal:conversations'] -export const scopesFeatureDev = ['codewhisperer_internal:taskassist'] -export const scopesGumby = ['codewhisperer_internal:transformations'] +export const scopesCodeWhispererCore = ['codewhisperer:completions', 'codewhisperer:analysis'] +export const scopesCodeWhispererChat = ['codewhisperer:conversations'] +export const scopesFeatureDev = ['codewhisperer:taskassist'] +export const scopesGumby = ['codewhisperer:transformations'] export const defaultSsoRegion = 'us-east-1' diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index 134740c5df8..149a78391f8 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -34,12 +34,12 @@ import { CachedResource } from '../../shared/utilities/resourceCache' // TODO: is there a better way to manage all endpoint strings in one place? export const defaultServiceConfig: CodeWhispererConfig = { region: 'us-east-1', - endpoint: 'https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/', + endpoint: 'https://codewhisperer.us-east-1.amazonaws.com/', } // Hack until we have a single discovery endpoint. We will call each endpoint one by one to fetch profile before then. const endpoints = createConstantMap({ - 'us-east-1': 'https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/', + 'us-east-1': 'https://q.us-east-1.amazonaws.com/', 'eu-central-1': 'https://q.eu-central-1.amazonaws.com/', }) From 68485efb297d303435f9feac6d32872445388c1a Mon Sep 17 00:00:00 2001 From: David Hasani Date: Mon, 19 May 2025 20:31:21 -0700 Subject: [PATCH 10/10] only show plan when ready --- .../service/transformByQ/transformApiHandler.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 94ba5c95631..9214fb23572 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -684,6 +684,7 @@ export async function getTransformationSteps(jobId: string, profile: RegionProfi export async function pollTransformationJob(jobId: string, validStates: string[], profile: RegionProfile | undefined) { let status: string = '' + let isPlanComplete = false while (true) { throwIfCancelled() try { @@ -723,9 +724,14 @@ export async function pollTransformationJob(jobId: string, validStates: string[] if ( CodeWhispererConstants.validStatesForPlanGenerated.includes(status) && - transformByQState.getTransformationType() === TransformationType.LANGUAGE_UPGRADE + transformByQState.getTransformationType() === TransformationType.LANGUAGE_UPGRADE && + !isPlanComplete ) { - await openTransformationPlan(jobId, profile) + const plan = await openTransformationPlan(jobId, profile) + if (plan?.toLowerCase().includes('dependency changes')) { + // final plan is complete; show to user + isPlanComplete = true + } } if (validStates.includes(status)) { @@ -790,6 +796,7 @@ async function openTransformationPlan(jobId: string, profile?: RegionProfile) { transformByQState.setPlanFilePath(planFilePath) await setContext('gumby.isPlanAvailable', true) } + return plan } async function attemptLocalBuild() {