@@ -51,6 +51,7 @@ import { encodeHTML } from '../../../shared/utilities/textUtilities'
5151import { convertToTimeString } from '../../../shared/datetime'
5252import { getAuthType } from '../../../auth/utils'
5353import { UserWrittenCodeTracker } from '../../tracker/userWrittenCodeTracker'
54+ import { setContext } from '../../../shared/vscode/setContext'
5455import { AuthUtil } from '../../util/authUtil'
5556import { DiffModel } from './transformationResultsViewProvider'
5657import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports
@@ -521,20 +522,33 @@ export function getFormattedString(s: string) {
521522 return CodeWhispererConstants . formattedStringMap . get ( s ) ?? s
522523}
523524
524- export function addTableMarkdown ( plan : string , stepId : string , tableMapping : { [ key : string ] : string } ) {
525- const tableObj = tableMapping [ stepId ]
526- if ( ! tableObj ) {
527- // no table present for this step
525+ export function addTableMarkdown ( plan : string , stepId : string , tableMapping : { [ key : string ] : string [ ] } ) {
526+ const tableObjects = tableMapping [ stepId ]
527+ if ( ! tableObjects || tableObjects . length === 0 || tableObjects . every ( ( table : string ) => table === '' ) ) {
528+ // no tables for this stepId
528529 return plan
529530 }
530- const table = JSON . parse ( tableObj )
531- if ( table . rows . length === 0 ) {
532- // empty table
533- plan += `\n\nThere are no ${ table . name . toLowerCase ( ) } to display.\n\n`
531+ const tables : any [ ] = [ ]
532+ // eslint-disable-next-line unicorn/no-array-for-each
533+ tableObjects . forEach ( ( tableObj : string ) => {
534+ try {
535+ const table = JSON . parse ( tableObj )
536+ if ( table ) {
537+ tables . push ( table )
538+ }
539+ } catch ( e ) {
540+ getLogger ( ) . error ( `CodeTransformation: Failed to parse table JSON, skipping: ${ e } ` )
541+ }
542+ } )
543+
544+ if ( tables . every ( ( table : any ) => table . rows . length === 0 ) ) {
545+ // empty tables for this stepId
546+ plan += `\n\nThere are no ${ tables [ 0 ] . name . toLowerCase ( ) } to display.\n\n`
534547 return plan
535548 }
536- plan += `\n\n\n${ table . name } \n|`
537- const columns = table . columnNames
549+ // table name and columns are shared, so only add to plan once
550+ plan += `\n\n\n${ tables [ 0 ] . name } \n|`
551+ const columns = tables [ 0 ] . columnNames
538552 // eslint-disable-next-line unicorn/no-array-for-each
539553 columns . forEach ( ( columnName : string ) => {
540554 plan += ` ${ getFormattedString ( columnName ) } |`
@@ -544,28 +558,35 @@ export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [
544558 columns . forEach ( ( _ : any ) => {
545559 plan += '-----|'
546560 } )
561+ // add all rows of all tables
547562 // eslint-disable-next-line unicorn/no-array-for-each
548- table . rows . forEach ( ( row : any ) => {
549- plan += '\n|'
563+ tables . forEach ( ( table : any ) => {
550564 // eslint-disable-next-line unicorn/no-array-for-each
551- columns . forEach ( ( columnName : string ) => {
552- if ( columnName === 'relativePath' ) {
553- plan += ` [${ row [ columnName ] } ](${ row [ columnName ] } ) |` // add MD link only for files
554- } else {
555- plan += ` ${ row [ columnName ] } |`
556- }
565+ table . rows . forEach ( ( row : any ) => {
566+ plan += '\n|'
567+ // eslint-disable-next-line unicorn/no-array-for-each
568+ columns . forEach ( ( columnName : string ) => {
569+ if ( columnName === 'relativePath' ) {
570+ // add markdown link only for file paths
571+ plan += ` [${ row [ columnName ] } ](${ row [ columnName ] } ) |`
572+ } else {
573+ plan += ` ${ row [ columnName ] } |`
574+ }
575+ } )
557576 } )
558577 } )
559578 plan += '\n\n'
560579 return plan
561580}
562581
563582export function getTableMapping ( stepZeroProgressUpdates : ProgressUpdates ) {
564- const map : { [ key : string ] : string } = { }
583+ const map : { [ key : string ] : string [ ] } = { }
565584 for ( const update of stepZeroProgressUpdates ) {
566- // description should never be undefined since even if no data we show an empty table
567- // but just in case, empty string allows us to skip this table without errors when rendering
568- map [ update . name ] = update . description ?? ''
585+ if ( ! map [ update . name ] ) {
586+ map [ update . name ] = [ ]
587+ }
588+ // empty string allows us to skip this table when rendering
589+ map [ update . name ] . push ( update . description ?? '' )
569590 }
570591 return map
571592}
@@ -604,7 +625,7 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil
604625 // gets a mapping between the ID ('name' field) of each progressUpdate (substep) and the associated table
605626 const tableMapping = getTableMapping ( stepZeroProgressUpdates )
606627
607- const jobStatistics = JSON . parse ( tableMapping [ '0' ] ) . rows // ID of '0' reserved for job statistics table
628+ const jobStatistics = JSON . parse ( tableMapping [ '0' ] [ 0 ] ) . rows // ID of '0' reserved for job statistics table; only 1 table there
608629
609630 // get logo directly since we only use one logo regardless of color theme
610631 const logoIcon = getTransformationIcon ( 'transformLogo' )
@@ -631,7 +652,7 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil
631652 }
632653 plan += `</div><br>`
633654 plan += `<p style="font-size: 18px; margin-bottom: 4px;"><b>Appendix</b><br><a href="#top" style="float: right; font-size: 14px;">Scroll to top <img src="${ arrowIcon } " style="vertical-align: middle;"></a></p><br>`
634- plan = addTableMarkdown ( plan , '-1' , tableMapping ) // ID of '-1' reserved for appendix table
655+ plan = addTableMarkdown ( plan , '-1' , tableMapping ) // ID of '-1' reserved for appendix table; only 1 table there
635656 return plan
636657 } catch ( e : any ) {
637658 const errorMessage = ( e as Error ) . message
@@ -663,6 +684,7 @@ export async function getTransformationSteps(jobId: string, profile: RegionProfi
663684
664685export async function pollTransformationJob ( jobId : string , validStates : string [ ] , profile : RegionProfile | undefined ) {
665686 let status : string = ''
687+ let isPlanComplete = false
666688 while ( true ) {
667689 throwIfCancelled ( )
668690 try {
@@ -699,6 +721,19 @@ export async function pollTransformationJob(jobId: string, validStates: string[]
699721 `${ CodeWhispererConstants . failedToCompleteJobGenericNotification } ${ errorMessage } `
700722 )
701723 }
724+
725+ if (
726+ CodeWhispererConstants . validStatesForPlanGenerated . includes ( status ) &&
727+ transformByQState . getTransformationType ( ) === TransformationType . LANGUAGE_UPGRADE &&
728+ ! isPlanComplete
729+ ) {
730+ const plan = await openTransformationPlan ( jobId , profile )
731+ if ( plan ?. toLowerCase ( ) . includes ( 'dependency changes' ) ) {
732+ // final plan is complete; show to user
733+ isPlanComplete = true
734+ }
735+ }
736+
702737 if ( validStates . includes ( status ) ) {
703738 break
704739 }
@@ -738,6 +773,32 @@ export async function pollTransformationJob(jobId: string, validStates: string[]
738773 return status
739774}
740775
776+ async function openTransformationPlan ( jobId : string , profile ?: RegionProfile ) {
777+ let plan = undefined
778+ try {
779+ plan = await getTransformationPlan ( jobId , profile )
780+ } catch ( error ) {
781+ // means API call failed
782+ getLogger ( ) . error ( `CodeTransformation: ${ CodeWhispererConstants . failedToCompleteJobNotification } ` , error )
783+ transformByQState . setJobFailureErrorNotification (
784+ `${ CodeWhispererConstants . failedToGetPlanNotification } ${ ( error as Error ) . message } `
785+ )
786+ transformByQState . setJobFailureErrorChatMessage (
787+ `${ CodeWhispererConstants . failedToGetPlanChatMessage } ${ ( error as Error ) . message } `
788+ )
789+ throw new Error ( 'Get plan failed' )
790+ }
791+
792+ if ( plan ) {
793+ const planFilePath = path . join ( transformByQState . getProjectPath ( ) , 'transformation-plan.md' )
794+ nodefs . writeFileSync ( planFilePath , plan )
795+ await vscode . commands . executeCommand ( 'markdown.showPreview' , vscode . Uri . file ( planFilePath ) )
796+ transformByQState . setPlanFilePath ( planFilePath )
797+ await setContext ( 'gumby.isPlanAvailable' , true )
798+ }
799+ return plan
800+ }
801+
741802async function attemptLocalBuild ( ) {
742803 const jobId = transformByQState . getJobId ( )
743804 let artifactId
0 commit comments