@@ -51,6 +51,7 @@ import { encodeHTML } from '../../../shared/utilities/textUtilities'
51
51
import { convertToTimeString } from '../../../shared/datetime'
52
52
import { getAuthType } from '../../../auth/utils'
53
53
import { UserWrittenCodeTracker } from '../../tracker/userWrittenCodeTracker'
54
+ import { setContext } from '../../../shared/vscode/setContext'
54
55
import { AuthUtil } from '../../util/authUtil'
55
56
import { DiffModel } from './transformationResultsViewProvider'
56
57
import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports
@@ -521,20 +522,33 @@ export function getFormattedString(s: string) {
521
522
return CodeWhispererConstants . formattedStringMap . get ( s ) ?? s
522
523
}
523
524
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
528
529
return plan
529
530
}
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`
534
547
return plan
535
548
}
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
538
552
// eslint-disable-next-line unicorn/no-array-for-each
539
553
columns . forEach ( ( columnName : string ) => {
540
554
plan += ` ${ getFormattedString ( columnName ) } |`
@@ -544,28 +558,35 @@ export function addTableMarkdown(plan: string, stepId: string, tableMapping: { [
544
558
columns . forEach ( ( _ : any ) => {
545
559
plan += '-----|'
546
560
} )
561
+ // add all rows of all tables
547
562
// eslint-disable-next-line unicorn/no-array-for-each
548
- table . rows . forEach ( ( row : any ) => {
549
- plan += '\n|'
563
+ tables . forEach ( ( table : any ) => {
550
564
// 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
+ } )
557
576
} )
558
577
} )
559
578
plan += '\n\n'
560
579
return plan
561
580
}
562
581
563
582
export function getTableMapping ( stepZeroProgressUpdates : ProgressUpdates ) {
564
- const map : { [ key : string ] : string } = { }
583
+ const map : { [ key : string ] : string [ ] } = { }
565
584
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 ?? '' )
569
590
}
570
591
return map
571
592
}
@@ -604,7 +625,7 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil
604
625
// gets a mapping between the ID ('name' field) of each progressUpdate (substep) and the associated table
605
626
const tableMapping = getTableMapping ( stepZeroProgressUpdates )
606
627
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
608
629
609
630
// get logo directly since we only use one logo regardless of color theme
610
631
const logoIcon = getTransformationIcon ( 'transformLogo' )
@@ -631,7 +652,7 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil
631
652
}
632
653
plan += `</div><br>`
633
654
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
635
656
return plan
636
657
} catch ( e : any ) {
637
658
const errorMessage = ( e as Error ) . message
@@ -663,6 +684,7 @@ export async function getTransformationSteps(jobId: string, profile: RegionProfi
663
684
664
685
export async function pollTransformationJob ( jobId : string , validStates : string [ ] , profile : RegionProfile | undefined ) {
665
686
let status : string = ''
687
+ let isPlanComplete = false
666
688
while ( true ) {
667
689
throwIfCancelled ( )
668
690
try {
@@ -699,6 +721,19 @@ export async function pollTransformationJob(jobId: string, validStates: string[]
699
721
`${ CodeWhispererConstants . failedToCompleteJobGenericNotification } ${ errorMessage } `
700
722
)
701
723
}
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
+
702
737
if ( validStates . includes ( status ) ) {
703
738
break
704
739
}
@@ -738,6 +773,32 @@ export async function pollTransformationJob(jobId: string, validStates: string[]
738
773
return status
739
774
}
740
775
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
+
741
802
async function attemptLocalBuild ( ) {
742
803
const jobId = transformByQState . getJobId ( )
743
804
let artifactId
0 commit comments