From e81d0042329a3c9ccf0dcd582df65adc099780b8 Mon Sep 17 00:00:00 2001 From: tgodara-aws Date: Fri, 8 Aug 2025 14:11:21 -0700 Subject: [PATCH 01/24] refactor(amazonq): reorganize transformation history code (#7843) ## Problem Job history-related code is scattered throughout various files, making it difficult to review and understand. ## Solution Centralize existing history functions in a new file and add helper functions to declutter transformation flow. Update and simplify unit tests accordingly. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../chat/controller/controller.ts | 2 +- .../commands/startTransformByQ.ts | 88 +-- .../src/codewhisperer/models/constants.ts | 12 +- .../transformByQ/transformFileHandler.ts | 39 ++ .../transformationHistoryHandler.ts | 389 +++++++++++ .../transformationHubViewProvider.ts | 291 +------- .../transformationResultsViewProvider.ts | 22 +- .../transformationJobHistory.test.ts | 633 +++++++----------- 8 files changed, 718 insertions(+), 758 deletions(-) create mode 100644 packages/core/src/codewhisperer/service/transformByQ/transformationHistoryHandler.ts diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 90706cbf731..a3d047bbbdc 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -58,7 +58,7 @@ import { import { getAuthType } from '../../../auth/utils' import fs from '../../../shared/fs/fs' import { setContext } from '../../../shared/vscode/setContext' -import { readHistoryFile } from '../../../codewhisperer/service/transformByQ/transformationHubViewProvider' +import { readHistoryFile } from '../../../codewhisperer/service/transformByQ/transformationHistoryHandler' // These events can be interactions within the chat, // or elsewhere in the IDE diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 209b9628a73..aa8bea11da2 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -5,7 +5,6 @@ import * as vscode from 'vscode' import * as fs from 'fs' // eslint-disable-line no-restricted-imports -import os from 'os' import path from 'path' import { getLogger } from '../../shared/logger/logger' import * as CodeWhispererConstants from '../models/constants' @@ -79,6 +78,12 @@ import { convertDateToTimestamp } from '../../shared/datetime' import { findStringInDirectory } from '../../shared/utilities/workspaceUtils' import { makeTemporaryToolkitFolder } from '../../shared/filesystemUtilities' import { AuthUtil } from '../util/authUtil' +import { + cleanupTempJobFiles, + createMetadataFile, + JobMetadata, + writeToHistoryFile, +} from '../service/transformByQ/transformationHistoryHandler' export function getFeedbackCommentData() { const jobId = transformByQState.getJobId() @@ -477,28 +482,21 @@ export async function startTransformationJob( }) // create local history folder(s) and store metadata - const jobHistoryPath = path.join(os.homedir(), '.aws', 'transform', transformByQState.getProjectName(), jobId) - if (!fs.existsSync(jobHistoryPath)) { - fs.mkdirSync(jobHistoryPath, { recursive: true }) + const metadata: JobMetadata = { + jobId: jobId, + projectName: transformByQState.getProjectName(), + transformationType: transformByQState.getTransformationType() ?? TransformationType.LANGUAGE_UPGRADE, + sourceJDKVersion: transformByQState.getSourceJDKVersion() ?? JDKVersion.JDK8, + targetJDKVersion: transformByQState.getTargetJDKVersion() ?? JDKVersion.JDK17, + customDependencyVersionFilePath: transformByQState.getCustomDependencyVersionFilePath(), + customBuildCommand: transformByQState.getCustomBuildCommand(), + targetJavaHome: transformByQState.getTargetJavaHome() ?? '', + projectPath: transformByQState.getProjectPath(), + startTime: transformByQState.getStartTime(), } - transformByQState.setJobHistoryPath(jobHistoryPath) - // save a copy of the upload zip - fs.copyFileSync(transformByQState.getPayloadFilePath(), path.join(jobHistoryPath, 'zipped-code.zip')) - const fields = [ - jobId, - transformByQState.getTransformationType(), - transformByQState.getSourceJDKVersion(), - transformByQState.getTargetJDKVersion(), - transformByQState.getCustomDependencyVersionFilePath(), - transformByQState.getCustomBuildCommand(), - transformByQState.getTargetJavaHome(), - transformByQState.getProjectPath(), - transformByQState.getStartTime(), - ] - - const jobDetails = fields.join('\t') - fs.writeFileSync(path.join(jobHistoryPath, 'metadata.txt'), jobDetails) + const jobHistoryPath = await createMetadataFile(transformByQState.getPayloadFilePath(), metadata) + transformByQState.setJobHistoryPath(jobHistoryPath) } catch (error) { getLogger().error(`CodeTransformation: ${CodeWhispererConstants.failedToStartJobNotification}`, error) const errorMessage = (error as Error).message.toLowerCase() @@ -749,24 +747,11 @@ export async function postTransformationJob() { }) } - // delete original upload ZIP at very end of transformation - fs.rmSync(transformByQState.getPayloadFilePath(), { force: true }) - - if ( - transformByQState.isSucceeded() || - transformByQState.isPartiallySucceeded() || - transformByQState.isCancelled() - ) { - // delete the copy of the upload ZIP - fs.rmSync(path.join(transformByQState.getJobHistoryPath(), 'zipped-code.zip'), { force: true }) - // delete transformation job metadata file (no longer needed) - fs.rmSync(path.join(transformByQState.getJobHistoryPath(), 'metadata.txt'), { force: true }) - } - // delete temporary build logs file - const logFilePath = path.join(os.tmpdir(), 'build-logs.txt') - if (fs.existsSync(logFilePath)) { - fs.rmSync(logFilePath, { force: true }) - } + await cleanupTempJobFiles( + transformByQState.getJobHistoryPath(), + transformByQState.getPolledJobStatus(), + transformByQState.getPayloadFilePath() + ) // attempt download for user // TODO: refactor as explained here https://github.com/aws/aws-toolkit-vscode/pull/6519/files#r1946873107 @@ -777,35 +762,14 @@ export async function postTransformationJob() { // store job details and diff path locally (history) // TODO: ideally when job is cancelled, should be stored as CANCELLED instead of FAILED (remove this if statement after bug is fixed) if (!transformByQState.isCancelled()) { - const historyLogFilePath = path.join(os.homedir(), '.aws', 'transform', 'transformation_history.tsv') - // create transform folder if necessary - if (!fs.existsSync(historyLogFilePath)) { - fs.mkdirSync(path.dirname(historyLogFilePath), { recursive: true }) - // create headers of new transformation history file - fs.writeFileSync(historyLogFilePath, 'date\tproject_name\tstatus\tduration\tdiff_patch\tsummary\tjob_id\n') - } const latest = sessionJobHistory[transformByQState.getJobId()] - const fields = [ + await writeToHistoryFile( latest.startTime, latest.projectName, latest.status, latest.duration, - transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded() - ? path.join(transformByQState.getJobHistoryPath(), 'diff.patch') - : '', - transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded() - ? path.join(transformByQState.getJobHistoryPath(), 'summary', 'summary.md') - : '', transformByQState.getJobId(), - ] - - const jobDetails = fields.join('\t') + '\n' - fs.writeFileSync(historyLogFilePath, jobDetails, { flag: 'a' }) // 'a' flag used to append to file - await vscode.commands.executeCommand( - 'aws.amazonq.transformationHub.updateContent', - 'job history', - undefined, - true + transformByQState.getJobHistoryPath() ) } } diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 3e72ca1de19..f3bbfb07d85 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -547,7 +547,7 @@ export const noChangesMadeMessage = "I didn't make any changes for this transfor export const noOngoingJobMessage = 'No ongoing job.' -export const nothingToShowMessage = 'Nothing to show' +export const noJobHistoryMessage = 'No job history' export const jobStartedNotification = 'Amazon Q is transforming your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your project. To monitor progress, go to the Transformation Hub.' @@ -941,13 +941,3 @@ export const displayFindingsSuffix = '_displayFindings' export const displayFindingsDetectorName = 'DisplayFindings' export const findingsSuffix = '_codeReviewFindings' - -export interface HistoryObject { - startTime: string - projectName: string - status: string - duration: string - diffPath: string - summaryPath: string - jobId: string -} diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts index 6aad4fd15f6..400acd5fa7a 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts @@ -17,6 +17,8 @@ import { AbsolutePathDetectedError } from '../../../amazonqGumby/errors' import { getLogger } from '../../../shared/logger/logger' import AdmZip from 'adm-zip' import { IManifestFile } from './humanInTheLoopManager' +import { ExportResultArchiveStructure } from '../../../shared/utilities/download' +import { isFileNotFoundError } from '../../../shared/errors' export async function getDependenciesFolderInfo(): Promise { const dependencyFolderName = `${CodeWhispererConstants.dependencyFolderName}${globals.clock.Date.now()}` @@ -348,3 +350,40 @@ export async function parseVersionsListFromPomFile(xmlString: string): Promise { + const history: HistoryObject[] = [] + const jobHistoryFilePath = path.join(os.homedir(), '.aws', 'transform', 'transformation_history.tsv') + + if (!(await fs.existsFile(jobHistoryFilePath))) { + return history + } + + const historyFile = await fs.readFileText(jobHistoryFilePath) + const jobs = historyFile.split('\n') + jobs.shift() // removes headers + + // Process from end, stop at 10 valid entries + for (let i = jobs.length - 1; i >= 0 && history.length < 10; i--) { + const job = jobs[i] + if (job && isWithin30Days(job.split('\t')[0])) { + const jobInfo = job.split('\t') + history.push({ + startTime: jobInfo[0], + projectName: jobInfo[1], + status: jobInfo[2], + duration: jobInfo[3], + diffPath: jobInfo[4], + summaryPath: jobInfo[5], + jobId: jobInfo[6], + }) + } + } + return history +} + +/** + * Creates temporary metadata JSON file with transformation config info and saves a copy of upload zip + * + * These files are used when a job is resumed after interruption + * + * @param payloadFilePath path to upload zip + * @param metadata + * @returns + */ +export async function createMetadataFile(payloadFilePath: string, metadata: JobMetadata): Promise { + const jobHistoryPath = path.join(os.homedir(), '.aws', 'transform', metadata.projectName, metadata.jobId) + + // create job history folders + await fs.mkdir(jobHistoryPath) + + // save a copy of the upload zip + try { + await fs.copy(payloadFilePath, path.join(jobHistoryPath, 'zipped-code.zip')) + } catch (error) { + getLogger().error('Code Transformation: error saving copy of upload zip: %s', (error as Error).message) + } + + // create metadata file with transformation config info + try { + await fs.writeFile(path.join(jobHistoryPath, 'metadata.json'), JSON.stringify(metadata)) + } catch (error) { + getLogger().error('Code Transformation: error creating metadata file: %s', (error as Error).message) + } + + return jobHistoryPath +} + +/** + * Writes job details to history file + * + * @param startTime job start timestamp (ex. "01/01/23, 12:00 AM") + * @param projectName + * @param status + * @param duration job duration in hr / min / sec format (ex. "1 hr 15 min") + * @param jobId + * @param jobHistoryPath path to where job's history details are stored (ex. "~/.aws/transform/proj_name/job_id") + */ +export async function writeToHistoryFile( + startTime: string, + projectName: string, + status: string, + duration: string, + jobId: string, + jobHistoryPath: string +) { + const historyLogFilePath = path.join(os.homedir(), '.aws', 'transform', 'transformation_history.tsv') + // create transform folder if necessary + if (!(await fs.existsFile(historyLogFilePath))) { + await fs.mkdir(path.dirname(historyLogFilePath)) + // create headers of new transformation history file + await fs.writeFile(historyLogFilePath, 'date\tproject_name\tstatus\tduration\tdiff_patch\tsummary\tjob_id\n') + } + const artifactsExist = status === 'COMPLETED' || status === 'PARTIALLY_COMPLETED' + const fields = [ + startTime, + projectName, + status, + duration, + artifactsExist ? path.join(jobHistoryPath, 'diff.patch') : '', + artifactsExist ? path.join(jobHistoryPath, 'summary', 'summary.md') : '', + jobId, + ] + + const jobDetails = fields.join('\t') + '\n' + await fs.appendFile(historyLogFilePath, jobDetails) + + // update Transformation Hub table + await vscode.commands.executeCommand('aws.amazonq.transformationHub.updateContent', 'job history', undefined, true) +} + +/** + * Delete temporary files at the end of a transformation + * + * @param jobHistoryPath path to history directory for this job + * @param jobStatus final transformation status + * @param payloadFilePath path to original upload zip; providing this param will also delete any temp build logs + */ +export async function cleanupTempJobFiles(jobHistoryPath: string, jobStatus: string, payloadFilePath?: string) { + if (payloadFilePath) { + // delete original upload ZIP + await fs.delete(payloadFilePath, { force: true }) + // delete temporary build logs file + const logFilePath = path.join(os.tmpdir(), 'build-logs.txt') + await fs.delete(logFilePath, { force: true }) + } + + // delete metadata file and upload zip copy if no longer need them (i.e. will not be resuming) + if (jobStatus !== 'FAILED') { + await fs.delete(path.join(jobHistoryPath, 'metadata.json'), { force: true }) + await fs.delete(path.join(jobHistoryPath, 'zipped-code.zip'), { force: true }) + } +} + +/* Job refresh-related functions */ + +export async function refreshJob(jobId: string, currentStatus: string, projectName: string) { + // fetch status from server + let status = '' + let duration = '' + if (currentStatus === 'COMPLETED' || currentStatus === 'PARTIALLY_COMPLETED') { + // job is already completed, no need to fetch status + status = currentStatus + } else { + try { + const response = await codeWhispererClient.codeModernizerGetCodeTransformation({ + transformationJobId: jobId, + profileArn: undefined, + }) + status = response.transformationJob.status ?? currentStatus + if (response.transformationJob.endExecutionTime && response.transformationJob.creationTime) { + duration = convertToTimeString( + response.transformationJob.endExecutionTime.getTime() - + response.transformationJob.creationTime.getTime() + ) + } + + getLogger().debug( + 'Code Transformation: Job refresh - Fetched status for job id: %s\n{Status: %s; Duration: %s}', + jobId, + status, + duration + ) + } catch (error) { + const errorMessage = (error as Error).message + getLogger().error('Code Transformation: Error fetching status (job id: %s): %s', jobId, errorMessage) + if (errorMessage.includes('not authorized to make this call')) { + // job not available on backend + status = 'FAILED' // won't allow retries for this job + } else { + // some other error (e.g. network error) + return + } + } + } + + // retrieve artifacts and updated duration if available + let jobHistoryPath: string = '' + if (status === 'COMPLETED' || status === 'PARTIALLY_COMPLETED') { + // artifacts should be available to download + jobHistoryPath = await retrieveArtifacts(jobId, projectName) + + await cleanupTempJobFiles(path.join(os.homedir(), '.aws', 'transform', projectName, jobId), status) + } else if (CodeWhispererConstants.validStatesForBuildSucceeded.includes(status)) { + // still in progress on server side + if (transformByQState.isRunning()) { + getLogger().warn( + 'Code Transformation: There is a job currently running (id: %s). Cannot resume another job (id: %s)', + transformByQState.getJobId(), + jobId + ) + return + } + transformByQState.setRefreshInProgress(true) + const messenger = transformByQState.getChatMessenger() + const tabID = ChatSessionManager.Instance.getSession().tabID + messenger?.sendJobRefreshInProgressMessage(tabID!, jobId) + await vscode.commands.executeCommand('aws.amazonq.transformationHub.updateContent', 'job history') // refreshing the table disables all jobs' refresh buttons while this one is resuming + + // resume job and bring to completion + try { + status = await resumeJob(jobId, projectName, status) + } catch (error) { + getLogger().error('Code Transformation: Error resuming job (id: %s): %s', jobId, (error as Error).message) + transformByQState.setJobDefaults() + messenger?.sendJobFinishedMessage(tabID!, CodeWhispererConstants.refreshErrorChatMessage) + void vscode.window.showErrorMessage(CodeWhispererConstants.refreshErrorNotification(jobId)) + await vscode.commands.executeCommand('aws.amazonq.transformationHub.updateContent', 'job history') + return + } + + // download artifacts if available + if ( + CodeWhispererConstants.validStatesForCheckingDownloadUrl.includes(status) && + !CodeWhispererConstants.failureStates.includes(status) + ) { + duration = convertToTimeString(Date.now() - new Date(transformByQState.getStartTime()).getTime()) + jobHistoryPath = await retrieveArtifacts(jobId, projectName) + } + + // reset state + transformByQState.setJobDefaults() + messenger?.sendJobFinishedMessage(tabID!, CodeWhispererConstants.refreshCompletedChatMessage) + } else { + // FAILED or STOPPED job + getLogger().info('Code Transformation: No artifacts available to download (job status = %s)', status) + if (status === 'FAILED') { + // if job failed on backend, mark it to disable the refresh button + status = 'FAILED_BE' // this will be truncated to just 'FAILED' in the table + } + await cleanupTempJobFiles(path.join(os.homedir(), '.aws', 'transform', projectName, jobId), status) + } + + if (status === currentStatus && !jobHistoryPath) { + // no changes, no need to update file/table + void vscode.window.showInformationMessage(CodeWhispererConstants.refreshNoUpdatesNotification(jobId)) + return + } + + void vscode.window.showInformationMessage(CodeWhispererConstants.refreshCompletedNotification(jobId)) + // update local file and history table + + await updateHistoryFile(status, duration, jobHistoryPath, jobId) +} + +async function retrieveArtifacts(jobId: string, projectName: string) { + const resultsPath = path.join(os.homedir(), '.aws', 'transform', projectName, 'results') // temporary directory for extraction + let jobHistoryPath = path.join(os.homedir(), '.aws', 'transform', projectName, jobId) + + if (await fs.existsFile(path.join(jobHistoryPath, 'diff.patch'))) { + getLogger().info('Code Transformation: Diff patch already exists for job id: %s', jobId) + jobHistoryPath = '' + } else { + try { + await downloadAndExtractResultArchive(jobId, resultsPath) + await copyArtifacts(resultsPath, jobHistoryPath) + } catch (error) { + jobHistoryPath = '' + } finally { + // delete temporary extraction directory + await fs.delete(resultsPath, { recursive: true, force: true }) + } + } + return jobHistoryPath +} + +async function updateHistoryFile(status: string, duration: string, jobHistoryPath: string, jobId: string) { + const history: string[][] = [] + const historyLogFilePath = path.join(os.homedir(), '.aws', 'transform', 'transformation_history.tsv') + if (await fs.existsFile(historyLogFilePath)) { + const historyFile = await fs.readFileText(historyLogFilePath) + const jobs = historyFile.split('\n') + jobs.shift() // removes headers + if (jobs.length > 0) { + for (const job of jobs) { + if (job) { + const jobInfo = job.split('\t') + // startTime: jobInfo[0], projectName: jobInfo[1], status: jobInfo[2], duration: jobInfo[3], diffPath: jobInfo[4], summaryPath: jobInfo[5], jobId: jobInfo[6] + if (jobInfo[6] === jobId) { + // update any values if applicable + jobInfo[2] = status + if (duration) { + jobInfo[3] = duration + } + if (jobHistoryPath) { + jobInfo[4] = path.join(jobHistoryPath, 'diff.patch') + jobInfo[5] = path.join(jobHistoryPath, 'summary', 'summary.md') + } + } + history.push(jobInfo) + } + } + } + } + + if (history.length === 0) { + return + } + + // rewrite file + await fs.writeFile(historyLogFilePath, 'date\tproject_name\tstatus\tduration\tdiff_patch\tsummary\tjob_id\n') + const tsvContent = history.map((row) => row.join('\t')).join('\n') + '\n' + await fs.appendFile(historyLogFilePath, tsvContent) + + // update table content + await vscode.commands.executeCommand('aws.amazonq.transformationHub.updateContent', 'job history', undefined, true) +} + +async function resumeJob(jobId: string, projectName: string, status: string) { + // set state to prepare to resume job + await setupTransformationState(jobId, projectName, status) + // resume polling the job + return await pollAndCompleteTransformation(jobId) +} + +async function setupTransformationState(jobId: string, projectName: string, status: string) { + transformByQState.setJobId(jobId) + transformByQState.setPolledJobStatus(status) + transformByQState.setJobHistoryPath(path.join(os.homedir(), '.aws', 'transform', projectName, jobId)) + + const metadata: JobMetadata = JSON.parse( + await fs.readFileText(path.join(transformByQState.getJobHistoryPath(), 'metadata.json')) + ) + transformByQState.setTransformationType(metadata.transformationType) + transformByQState.setSourceJDKVersion(metadata.sourceJDKVersion) + transformByQState.setTargetJDKVersion(metadata.targetJDKVersion) + transformByQState.setCustomDependencyVersionFilePath(metadata.customDependencyVersionFilePath) + transformByQState.setPayloadFilePath( + path.join(os.homedir(), '.aws', 'transform', projectName, jobId, 'zipped-code.zip') + ) + setMaven() + transformByQState.setCustomBuildCommand(metadata.customBuildCommand) + transformByQState.setTargetJavaHome(metadata.targetJavaHome) + transformByQState.setProjectPath(metadata.projectPath) + transformByQState.setStartTime(metadata.startTime) +} + +async function pollAndCompleteTransformation(jobId: string) { + const status = await pollTransformationJob( + jobId, + CodeWhispererConstants.validStatesForCheckingDownloadUrl, + AuthUtil.instance.regionProfileManager.activeRegionProfile + ) + await cleanupTempJobFiles(transformByQState.getJobHistoryPath(), status, transformByQState.getPayloadFilePath()) + return status +} diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts index fe09e203919..35e8319ab46 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts @@ -7,7 +7,6 @@ import * as vscode from 'vscode' import globals from '../../../shared/extensionGlobals' import * as CodeWhispererConstants from '../../models/constants' import { - JDKVersion, StepProgress, TransformationType, jobPlanProgress, @@ -15,29 +14,24 @@ import { transformByQState, } from '../../models/model' import { getLogger } from '../../../shared/logger/logger' -import { getTransformationSteps, downloadAndExtractResultArchive } from './transformApiHandler' +import { getTransformationSteps } from './transformApiHandler' import { TransformationSteps, ProgressUpdates, TransformationStatus, } from '../../../codewhisperer/client/codewhispereruserclient' -import { codeWhispererClient } from '../../../codewhisperer/client/codewhisperer' -import { startInterval, pollTransformationStatusUntilComplete } from '../../commands/startTransformByQ' +import { startInterval } from '../../commands/startTransformByQ' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' -import { convertToTimeString, isWithin30Days } from '../../../shared/datetime' +import { convertToTimeString } from '../../../shared/datetime' import { AuthUtil } from '../../util/authUtil' -import fs from '../../../shared/fs/fs' -import path from 'path' -import os from 'os' -import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' -import { setMaven } from './transformFileHandler' +import { refreshJob, readHistoryFile, HistoryObject } from './transformationHistoryHandler' export class TransformationHubViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'aws.amazonq.transformationHub' private _view?: vscode.WebviewView private lastClickedButton: string = '' private _extensionUri: vscode.Uri = globals.context.extensionUri - private transformationHistory: CodeWhispererConstants.HistoryObject[] = [] + private transformationHistory: HistoryObject[] = [] constructor() {} static #instance: TransformationHubViewProvider @@ -84,7 +78,7 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider this._view.webview.onDidReceiveMessage((message) => { switch (message.command) { case 'refreshJob': - void this.refreshJob(message.jobId, message.currentStatus, message.projectName) + void refreshJob(message.jobId, message.currentStatus, message.projectName) break case 'openSummaryPreview': void vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(message.filePath)) @@ -115,7 +109,7 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider } private showJobHistory(): string { - const jobsToDisplay: CodeWhispererConstants.HistoryObject[] = [...this.transformationHistory] + const jobsToDisplay: HistoryObject[] = [...this.transformationHistory] if (transformByQState.isRunning()) { const current = sessionJobHistory[transformByQState.getJobId()] jobsToDisplay.unshift({ @@ -143,7 +137,7 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider

${CodeWhispererConstants.transformationHistoryTableDescription}

${ jobsToDisplay.length === 0 - ? `


${CodeWhispererConstants.nothingToShowMessage}

` + ? `


${CodeWhispererConstants.noJobHistoryMessage}

` : this.getTableMarkup(jobsToDisplay) }