Skip to content

Commit 0e09993

Browse files
authored
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.
1 parent 4d56741 commit 0e09993

File tree

8 files changed

+718
-758
lines changed

8 files changed

+718
-758
lines changed

packages/core/src/amazonqGumby/chat/controller/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {
5858
import { getAuthType } from '../../../auth/utils'
5959
import fs from '../../../shared/fs/fs'
6060
import { setContext } from '../../../shared/vscode/setContext'
61-
import { readHistoryFile } from '../../../codewhisperer/service/transformByQ/transformationHubViewProvider'
61+
import { readHistoryFile } from '../../../codewhisperer/service/transformByQ/transformationHistoryHandler'
6262

6363
// These events can be interactions within the chat,
6464
// or elsewhere in the IDE

packages/core/src/codewhisperer/commands/startTransformByQ.ts

Lines changed: 26 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as vscode from 'vscode'
77
import * as fs from 'fs' // eslint-disable-line no-restricted-imports
8-
import os from 'os'
98
import path from 'path'
109
import { getLogger } from '../../shared/logger/logger'
1110
import * as CodeWhispererConstants from '../models/constants'
@@ -79,6 +78,12 @@ import { convertDateToTimestamp } from '../../shared/datetime'
7978
import { findStringInDirectory } from '../../shared/utilities/workspaceUtils'
8079
import { makeTemporaryToolkitFolder } from '../../shared/filesystemUtilities'
8180
import { AuthUtil } from '../util/authUtil'
81+
import {
82+
cleanupTempJobFiles,
83+
createMetadataFile,
84+
JobMetadata,
85+
writeToHistoryFile,
86+
} from '../service/transformByQ/transformationHistoryHandler'
8287

8388
export function getFeedbackCommentData() {
8489
const jobId = transformByQState.getJobId()
@@ -477,28 +482,21 @@ export async function startTransformationJob(
477482
})
478483

479484
// create local history folder(s) and store metadata
480-
const jobHistoryPath = path.join(os.homedir(), '.aws', 'transform', transformByQState.getProjectName(), jobId)
481-
if (!fs.existsSync(jobHistoryPath)) {
482-
fs.mkdirSync(jobHistoryPath, { recursive: true })
485+
const metadata: JobMetadata = {
486+
jobId: jobId,
487+
projectName: transformByQState.getProjectName(),
488+
transformationType: transformByQState.getTransformationType() ?? TransformationType.LANGUAGE_UPGRADE,
489+
sourceJDKVersion: transformByQState.getSourceJDKVersion() ?? JDKVersion.JDK8,
490+
targetJDKVersion: transformByQState.getTargetJDKVersion() ?? JDKVersion.JDK17,
491+
customDependencyVersionFilePath: transformByQState.getCustomDependencyVersionFilePath(),
492+
customBuildCommand: transformByQState.getCustomBuildCommand(),
493+
targetJavaHome: transformByQState.getTargetJavaHome() ?? '',
494+
projectPath: transformByQState.getProjectPath(),
495+
startTime: transformByQState.getStartTime(),
483496
}
484-
transformByQState.setJobHistoryPath(jobHistoryPath)
485-
// save a copy of the upload zip
486-
fs.copyFileSync(transformByQState.getPayloadFilePath(), path.join(jobHistoryPath, 'zipped-code.zip'))
487497

488-
const fields = [
489-
jobId,
490-
transformByQState.getTransformationType(),
491-
transformByQState.getSourceJDKVersion(),
492-
transformByQState.getTargetJDKVersion(),
493-
transformByQState.getCustomDependencyVersionFilePath(),
494-
transformByQState.getCustomBuildCommand(),
495-
transformByQState.getTargetJavaHome(),
496-
transformByQState.getProjectPath(),
497-
transformByQState.getStartTime(),
498-
]
499-
500-
const jobDetails = fields.join('\t')
501-
fs.writeFileSync(path.join(jobHistoryPath, 'metadata.txt'), jobDetails)
498+
const jobHistoryPath = await createMetadataFile(transformByQState.getPayloadFilePath(), metadata)
499+
transformByQState.setJobHistoryPath(jobHistoryPath)
502500
} catch (error) {
503501
getLogger().error(`CodeTransformation: ${CodeWhispererConstants.failedToStartJobNotification}`, error)
504502
const errorMessage = (error as Error).message.toLowerCase()
@@ -749,24 +747,11 @@ export async function postTransformationJob() {
749747
})
750748
}
751749

752-
// delete original upload ZIP at very end of transformation
753-
fs.rmSync(transformByQState.getPayloadFilePath(), { force: true })
754-
755-
if (
756-
transformByQState.isSucceeded() ||
757-
transformByQState.isPartiallySucceeded() ||
758-
transformByQState.isCancelled()
759-
) {
760-
// delete the copy of the upload ZIP
761-
fs.rmSync(path.join(transformByQState.getJobHistoryPath(), 'zipped-code.zip'), { force: true })
762-
// delete transformation job metadata file (no longer needed)
763-
fs.rmSync(path.join(transformByQState.getJobHistoryPath(), 'metadata.txt'), { force: true })
764-
}
765-
// delete temporary build logs file
766-
const logFilePath = path.join(os.tmpdir(), 'build-logs.txt')
767-
if (fs.existsSync(logFilePath)) {
768-
fs.rmSync(logFilePath, { force: true })
769-
}
750+
await cleanupTempJobFiles(
751+
transformByQState.getJobHistoryPath(),
752+
transformByQState.getPolledJobStatus(),
753+
transformByQState.getPayloadFilePath()
754+
)
770755

771756
// attempt download for user
772757
// TODO: refactor as explained here https://github.com/aws/aws-toolkit-vscode/pull/6519/files#r1946873107
@@ -777,35 +762,14 @@ export async function postTransformationJob() {
777762
// store job details and diff path locally (history)
778763
// TODO: ideally when job is cancelled, should be stored as CANCELLED instead of FAILED (remove this if statement after bug is fixed)
779764
if (!transformByQState.isCancelled()) {
780-
const historyLogFilePath = path.join(os.homedir(), '.aws', 'transform', 'transformation_history.tsv')
781-
// create transform folder if necessary
782-
if (!fs.existsSync(historyLogFilePath)) {
783-
fs.mkdirSync(path.dirname(historyLogFilePath), { recursive: true })
784-
// create headers of new transformation history file
785-
fs.writeFileSync(historyLogFilePath, 'date\tproject_name\tstatus\tduration\tdiff_patch\tsummary\tjob_id\n')
786-
}
787765
const latest = sessionJobHistory[transformByQState.getJobId()]
788-
const fields = [
766+
await writeToHistoryFile(
789767
latest.startTime,
790768
latest.projectName,
791769
latest.status,
792770
latest.duration,
793-
transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded()
794-
? path.join(transformByQState.getJobHistoryPath(), 'diff.patch')
795-
: '',
796-
transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded()
797-
? path.join(transformByQState.getJobHistoryPath(), 'summary', 'summary.md')
798-
: '',
799771
transformByQState.getJobId(),
800-
]
801-
802-
const jobDetails = fields.join('\t') + '\n'
803-
fs.writeFileSync(historyLogFilePath, jobDetails, { flag: 'a' }) // 'a' flag used to append to file
804-
await vscode.commands.executeCommand(
805-
'aws.amazonq.transformationHub.updateContent',
806-
'job history',
807-
undefined,
808-
true
772+
transformByQState.getJobHistoryPath()
809773
)
810774
}
811775
}

packages/core/src/codewhisperer/models/constants.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ export const noChangesMadeMessage = "I didn't make any changes for this transfor
547547

548548
export const noOngoingJobMessage = 'No ongoing job.'
549549

550-
export const nothingToShowMessage = 'Nothing to show'
550+
export const noJobHistoryMessage = 'No job history'
551551

552552
export const jobStartedNotification =
553553
'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'
941941

942942
export const displayFindingsDetectorName = 'DisplayFindings'
943943
export const findingsSuffix = '_codeReviewFindings'
944-
945-
export interface HistoryObject {
946-
startTime: string
947-
projectName: string
948-
status: string
949-
duration: string
950-
diffPath: string
951-
summaryPath: string
952-
jobId: string
953-
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { AbsolutePathDetectedError } from '../../../amazonqGumby/errors'
1717
import { getLogger } from '../../../shared/logger/logger'
1818
import AdmZip from 'adm-zip'
1919
import { IManifestFile } from './humanInTheLoopManager'
20+
import { ExportResultArchiveStructure } from '../../../shared/utilities/download'
21+
import { isFileNotFoundError } from '../../../shared/errors'
2022

2123
export async function getDependenciesFolderInfo(): Promise<FolderInfo> {
2224
const dependencyFolderName = `${CodeWhispererConstants.dependencyFolderName}${globals.clock.Date.now()}`
@@ -348,3 +350,40 @@ export async function parseVersionsListFromPomFile(xmlString: string): Promise<I
348350

349351
return { latestVersion, majorVersions, minorVersions, status }
350352
}
353+
354+
/**
355+
* Saves a copy of the diff patch, summary, and build logs (if any) locally
356+
*
357+
* @param pathToArchiveDir path to the archive directory where the artifacts are unzipped
358+
* @param pathToDestinationDir destination directory (will create directories if path doesn't exist already)
359+
*/
360+
export async function copyArtifacts(pathToArchiveDir: string, pathToDestinationDir: string) {
361+
// create destination path if doesn't exist already
362+
// mkdir() will not raise an error if path exists
363+
await fs.mkdir(pathToDestinationDir)
364+
365+
const diffPath = path.join(pathToArchiveDir, ExportResultArchiveStructure.PathToDiffPatch)
366+
const summaryPath = path.join(pathToArchiveDir, ExportResultArchiveStructure.PathToSummary)
367+
368+
try {
369+
await fs.copy(diffPath, path.join(pathToDestinationDir, 'diff.patch'))
370+
// make summary directory if needed
371+
await fs.mkdir(path.join(pathToDestinationDir, 'summary'))
372+
await fs.copy(summaryPath, path.join(pathToDestinationDir, 'summary', 'summary.md'))
373+
} catch (error) {
374+
getLogger().error('Code Transformation: Error saving local copy of artifacts: %s', (error as Error).message)
375+
}
376+
377+
const buildLogsPath = path.join(path.dirname(summaryPath), 'buildCommandOutput.log')
378+
try {
379+
await fs.copy(buildLogsPath, path.join(pathToDestinationDir, 'summary', 'buildCommandOutput.log'))
380+
} catch (error) {
381+
// build logs won't exist for SQL conversions (not an error)
382+
if (!isFileNotFoundError(error)) {
383+
getLogger().error(
384+
'Code Transformation: Error saving local copy of build logs: %s',
385+
(error as Error).message
386+
)
387+
}
388+
}
389+
}

0 commit comments

Comments
 (0)