Skip to content

Commit 1a3d1a2

Browse files
committed
Refresh button original functionalilty (updates status and downloads diff patch if available), also added isWithin30Days util func for future use
1 parent 7f74b39 commit 1a3d1a2

File tree

2 files changed

+190
-1
lines changed

2 files changed

+190
-1
lines changed

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

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ import {
1414
transformByQState,
1515
} from '../../models/model'
1616
import { getLogger } from '../../../shared/logger/logger'
17-
import { getTransformationSteps } from './transformApiHandler'
17+
import { getTransformationSteps, downloadAndExtractResultArchive } from './transformApiHandler'
1818
import {
1919
TransformationSteps,
2020
ProgressUpdates,
2121
TransformationStatus,
2222
} from '../../../codewhisperer/client/codewhispereruserclient'
23+
import { codeWhispererClient } from '../../../codewhisperer/client/codewhisperer'
2324
import { startInterval } from '../../commands/startTransformByQ'
2425
import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState'
2526
import { 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,

packages/core/src/shared/datetime.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,25 @@ export function formatDateTimestamp(forceUTC: boolean, d: Date = new Date()): st
154154
// trim 'Z' (last char of iso string) and add offset string
155155
return `${iso.substring(0, iso.length - 1)}${offsetString}`
156156
}
157+
158+
/**
159+
* Checks if a given timestamp is within 30 days of the current day
160+
* @param timeStamp
161+
* @returns true if timeStamp is within 30 days, false otherwise
162+
*/
163+
export function isWithin30Days(timeStamp: string): boolean {
164+
if (!timeStamp) {
165+
return false // No timestamp given
166+
}
167+
168+
const startDate = new Date(timeStamp)
169+
const currentDate = new Date()
170+
171+
// Calculate the difference in milliseconds
172+
const timeDifference = currentDate.getTime() - startDate.getTime()
173+
174+
// Convert milliseconds to days (1000ms * 60s * 60min * 24hr)
175+
const daysDifference = timeDifference / (1000 * 60 * 60 * 24)
176+
177+
return daysDifference <= 30
178+
}

0 commit comments

Comments
 (0)