@@ -14,12 +14,13 @@ import {
1414 transformByQState ,
1515} from '../../models/model'
1616import { getLogger } from '../../../shared/logger/logger'
17- import { getTransformationSteps } from './transformApiHandler'
17+ import { getTransformationSteps , downloadAndExtractResultArchive } from './transformApiHandler'
1818import {
1919 TransformationSteps ,
2020 ProgressUpdates ,
2121 TransformationStatus ,
2222} from '../../../codewhisperer/client/codewhispereruserclient'
23+ import { codeWhispererClient } from '../../../codewhisperer/client/codewhisperer'
2324import { startInterval } from '../../commands/startTransformByQ'
2425import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState'
2526import { convertToTimeString } from '../../../shared/datetime'
@@ -72,6 +73,14 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
7273 ) : void | Thenable < void > {
7374 this . _view = webviewView
7475
76+ this . _view . webview . onDidReceiveMessage ( ( message ) => {
77+ if ( message . command === 'refreshJob' ) {
78+ this . refreshJob ( message . jobId , message . currentStatus , message . projectName ) . catch ( ( error ) => {
79+ getLogger ( ) . error ( 'refreshJob failed: %s' , ( error as Error ) . message )
80+ } )
81+ }
82+ } )
83+
7584 this . _view . webview . options = {
7685 enableScripts : true ,
7786 localResourceRoots : [ this . _extensionUri ] ,
@@ -150,6 +159,23 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
150159 ? `<p>${ CodeWhispererConstants . nothingToShowMessage } </p>`
151160 : this . getTableMarkup ( history )
152161 }
162+ <script>
163+ const vscode = acquireVsCodeApi();
164+
165+ document.addEventListener('click', (event) => {
166+ if (event.target.classList.contains('refresh-btn')) {
167+ const jobId = event.target.getAttribute('row-id');
168+ const projectName = event.target.getAttribute('proj-name');
169+ const status = event.target.getAttribute('status');
170+ vscode.postMessage({
171+ command: 'refreshJob',
172+ jobId: jobId,
173+ projectName: projectName,
174+ currentStatus: status
175+ });
176+ }
177+ });
178+ </script>
153179 </body>
154180 </html>`
155181 }
@@ -165,6 +191,18 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
165191 } [ ]
166192 ) {
167193 return `
194+ <style>
195+ .refresh-btn {
196+ border: none;
197+ background: none;
198+ cursor: pointer;
199+ font-size: 16px;
200+ }
201+ .refresh-btn:disabled {
202+ opacity: 0.3;
203+ cursor: not-allowed;
204+ }
205+ </style>
168206 <table border="1" style="border-collapse:collapse">
169207 <thead>
170208 <tr>
@@ -187,6 +225,17 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
187225 <td>${ job . duration } </td>
188226 <td><a href="vscode://file${ job . diffPath } ">${ job . diffPath } </a></td>
189227 <td>${ job . jobId } </td>
228+ <td>
229+ <button
230+ class="refresh-btn"
231+ row-id="${ job . jobId } "
232+ proj-name="${ job . projectName } "
233+ status="${ job . status } "
234+ ${ ! CodeWhispererConstants . validStatesForCheckingDownloadUrl . includes ( job . status ) ? 'disabled' : '' }
235+ >
236+ ↻
237+ </button>
238+ </td>
190239 </tr>
191240 `
192241 )
@@ -196,6 +245,124 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
196245 `
197246 }
198247
248+ private async refreshJob ( jobId : string , currentStatus : string , projectName : string ) {
249+ console . log ( 'refreshing job id: %s' , jobId )
250+
251+ // fetch status from server
252+ let status = ''
253+ let duration = ''
254+ try {
255+ const response = await codeWhispererClient . codeModernizerGetCodeTransformation ( {
256+ transformationJobId : jobId ,
257+ profileArn : undefined ,
258+ } )
259+ status = response . transformationJob . status !
260+ if ( response . transformationJob . endExecutionTime && response . transformationJob . creationTime ) {
261+ duration = convertToTimeString (
262+ response . transformationJob . endExecutionTime . getTime ( ) -
263+ response . transformationJob . creationTime . getTime ( )
264+ )
265+ }
266+ // status = await pollTransformationJob(jobId, CodeWhispererConstants.validStatesForCheckingDownloadUrl, undefined)
267+
268+ console . log ( 'status returned: %s' , status )
269+ console . log ( 'duration returned: %s' , duration )
270+ } catch ( error ) {
271+ console . error ( 'error fetching status: %s' , ( error as Error ) . message )
272+ return
273+ }
274+
275+ // retrieve artifacts and updated duration if available
276+ let jobDiffPath : string = ''
277+ if (
278+ CodeWhispererConstants . validStatesForCheckingDownloadUrl . includes ( status ) &&
279+ ! CodeWhispererConstants . failureStates . includes ( status )
280+ ) {
281+ // status is COMPLETED or PARTIALLY_COMPLETED on sertver side
282+ console . log ( 'valid successful status' )
283+
284+ // artifacts should be available to download
285+ jobDiffPath = await this . retrieveArtifacts ( jobId , projectName )
286+ } else {
287+ console . log ( 'no artifacts available' )
288+ }
289+
290+ if ( status === currentStatus && ! jobDiffPath ) {
291+ // no changes, no need to update file/table
292+ return
293+ }
294+
295+ // update local file and history table
296+ this . updateHistoryFile ( status , duration , jobDiffPath , jobId )
297+ }
298+
299+ private async retrieveArtifacts ( jobId : string , projectName : string ) {
300+ const resultsPath = path . join ( homedir ( ) , '.aws' , 'transform' , projectName , 'results' )
301+ let jobDiffPath = path . join ( homedir ( ) , '.aws' , 'transform' , projectName , jobId , 'diff.patch' )
302+
303+ if ( fs . existsSync ( jobDiffPath ) ) {
304+ console . log ( 'diff patch already exists' )
305+ jobDiffPath = ''
306+ } else {
307+ try {
308+ await downloadAndExtractResultArchive ( jobId , resultsPath )
309+ console . log ( 'artifacts downloaded' )
310+
311+ if ( ! fs . existsSync ( path . dirname ( jobDiffPath ) ) ) {
312+ fs . mkdirSync ( path . dirname ( jobDiffPath ) , { recursive : true } )
313+ }
314+ fs . copyFileSync ( path . join ( resultsPath , 'patch' , 'diff.patch' ) , jobDiffPath )
315+ } catch ( error ) {
316+ console . error ( 'error downloading artifacts: %s' , ( error as Error ) . message )
317+ jobDiffPath = ''
318+ } finally {
319+ if ( fs . existsSync ( resultsPath ) ) {
320+ fs . rmSync ( resultsPath , { recursive : true , force : true } )
321+ }
322+ console . log ( 'deleted temporary extraction directory' )
323+ }
324+ }
325+ return jobDiffPath
326+ }
327+
328+ private updateHistoryFile ( status : string , duration : string , diffPath : string , jobId : string ) {
329+ const history : string [ ] [ ] = [ ]
330+ const jobHistoryFilePath = path . join ( homedir ( ) , '.aws' , 'transform' , 'transformation-history.tsv' )
331+ if ( fs . existsSync ( jobHistoryFilePath ) ) {
332+ const historyFile = fs . readFileSync ( jobHistoryFilePath , { encoding : 'utf8' , flag : 'r' } )
333+ const jobs = historyFile . split ( '\n' )
334+ jobs . shift ( ) // removes headers
335+ if ( jobs . length > 0 ) {
336+ jobs . forEach ( ( job ) => {
337+ if ( job ) {
338+ const jobInfo = job . split ( '\t' )
339+ // startTime: jobInfo[0], projectName: jobInfo[1], status: jobInfo[2], duration: jobInfo[3], diffPath: jobInfo[4], jobId: jobInfo[5]
340+ if ( jobInfo [ 5 ] === jobId ) {
341+ // update any values if applicable
342+ jobInfo [ 2 ] = status
343+ if ( duration ) {
344+ jobInfo [ 3 ] = duration
345+ }
346+ if ( diffPath ) {
347+ jobInfo [ 4 ] = diffPath
348+ }
349+ }
350+ history . push ( jobInfo )
351+ }
352+ } )
353+ }
354+ }
355+ if ( history . length > 0 ) {
356+ // rewrite file
357+ fs . writeFileSync ( jobHistoryFilePath , 'date\tproject_name\tstatus\tduration\tdiff_path\tjob_id\n' )
358+ const tsvContent = history . map ( ( row ) => row . join ( '\t' ) ) . join ( '\n' )
359+ fs . writeFileSync ( jobHistoryFilePath , tsvContent , { flag : 'a' } )
360+
361+ // update table content
362+ this . updateContent ( 'job history' )
363+ }
364+ }
365+
199366 private generateTransformationStepMarkup (
200367 name : string ,
201368 startTime : Date | undefined ,
0 commit comments