From f04eb9ce82cca18d9f6781b10bf425dc3689ef32 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Thu, 17 Oct 2024 17:24:01 -0700 Subject: [PATCH 01/22] adding functionality to allow jdk17 as source and display multiple git patches for selective transformation --- package-lock.json | 2 +- .../transformationResultsHandler.test.ts | 30 ++- .../chat/controller/controller.ts | 4 +- .../commands/startTransformByQ.ts | 13 +- .../src/codewhisperer/models/constants.ts | 3 + .../transformationResultsViewProvider.ts | 190 +++++++++++++----- 6 files changed, 172 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9aeb32cddf6..583b51d9f08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20139,7 +20139,7 @@ }, "engines": { "npm": "^10.1.0", - "vscode": "^1.83.0" + "vscode": "^1.68.0" } }, "packages/core/node_modules/@types/node": { diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index f7e0b29a35e..f0b0df1fda2 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -10,11 +10,21 @@ import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' +type PatchDescription = { + name: string + fileName: string + isSuccessful: boolean +} + describe('DiffModel', function () { afterEach(() => { sinon.restore() }) + const testDescription = + '{"name": "Added file", "fileName": "resources/files/addedFile.diffs", "isSuccessful": true}' + const parsedTestDescription: PatchDescription = JSON.parse(testDescription) + it('WHEN parsing a diff patch where a file was added THEN returns an array representing the added file', async function () { const testDiffModel = new DiffModel() @@ -29,10 +39,14 @@ describe('DiffModel', function () { return true }) - testDiffModel.parseDiff(getTestResourceFilePath('resources/files/addedFile.diff'), workspacePath) + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/addedFile.diff'), + workspacePath, + parsedTestDescription + ) - assert.strictEqual(testDiffModel.changes.length, 1) - const change = testDiffModel.changes[0] + assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) + const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) }) @@ -49,10 +63,14 @@ describe('DiffModel', function () { 'This guide walks you through using Gradle to build a simple Java project.' ) - testDiffModel.parseDiff(getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath) + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/modifiedFile.diff'), + workspacePath, + parsedTestDescription + ) - assert.strictEqual(testDiffModel.changes.length, 1) - const change = testDiffModel.changes[0] + assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) + const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 37d139f5f1d..7249dd86aec 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -17,7 +17,7 @@ import { featureName } from '../../models/constants' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { cleanupTransformationJob, - compileProject, + // compileProject, finishHumanInTheLoop, getValidLanguageUpgradeCandidateProjects, openBuildLogFile, @@ -499,7 +499,7 @@ export class GumbyController { try { this.sessionStorage.getSession().conversationState = ConversationState.COMPILING this.messenger.sendCompilationInProgress(message.tabID) - await compileProject() + // await compileProject() } catch (err: any) { this.messenger.sendUnrecoverableErrorResponse('could-not-compile-project', message.tabID) // reset state to allow "Start a new transformation" button to work diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 089eadd5ba1..bed7bc336d1 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -225,7 +225,7 @@ export function startInterval() { export async function startTransformByQ() { // Set the default state variables for our store and the UI - const transformStartTime = globals.clock.Date.now() + // const transformStartTime = globals.clock.Date.now() await setTransformationToRunningState() try { @@ -233,16 +233,16 @@ export async function startTransformByQ() { startInterval() // step 1: CreateUploadUrl and upload code - const uploadId = await preTransformationUploadCode() + // const uploadId = await preTransformationUploadCode() // step 2: StartJob and store the returned jobId in TransformByQState - const jobId = await startTransformationJob(uploadId, transformStartTime) + // const jobId = await startTransformationJob(uploadId, transformStartTime) // step 3 (intermediate step): show transformation-plan.md file - await pollTransformationStatusUntilPlanReady(jobId) + // await pollTransformationStatusUntilPlanReady(jobId) // step 4: poll until artifacts are ready to download - await humanInTheLoopRetryLogic(jobId) + await humanInTheLoopRetryLogic('jobId') } catch (error: any) { await transformationJobErrorHandler(error) } finally { @@ -261,7 +261,8 @@ export async function startTransformByQ() { export async function humanInTheLoopRetryLogic(jobId: string) { let status = '' try { - status = await pollTransformationStatusUntilComplete(jobId) + // status = await pollTransformationStatusUntilComplete(jobId) + status = 'COMPLETED' if (status === 'PAUSED') { const hilStatusFailure = await initiateHumanInTheLoopPrompt(jobId) if (hilStatusFailure) { diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index d39771bc8ba..ebb2d75fd7f 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -661,6 +661,9 @@ export const windowsJavaHomeHelpChatMessage = export const macJavaVersionHomeHelpChatMessage = (version: number) => `To find the JDK path, run the following command in a new terminal: \`/usr/libexec/java_home -v ${version}\`` +export const macJava17HomeHelpChatMessage = + 'To find the JDK path, run the following command in a new terminal: `/usr/libexec/java_home -v 17`' + export const linuxJavaHomeHelpChatMessage = 'To find the JDK path, run the following command in a new terminal: `update-java-alternatives --list`' diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 8bea3486bd3..dc2340aa06f 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import AdmZip from 'adm-zip' +// import AdmZip from 'adm-zip' import os from 'os' import fs from 'fs' // eslint-disable-line no-restricted-imports import { parsePatch, applyPatches, ParsedDiff } from 'diff' @@ -17,11 +17,17 @@ import { telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' import { MetadataResult } from '../../../shared/telemetry/telemetryClient' import * as CodeWhispererConstants from '../../models/constants' -import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' +// import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' import { setContext } from '../../../shared/vscode/setContext' import * as codeWhisperer from '../../client/codewhisperer' +type PatchDescription = { + name: string + fileName: string + isSuccessful: boolean +} + export abstract class ProposedChangeNode { abstract readonly resourcePath: string @@ -107,6 +113,17 @@ export class AddedChangeNode extends ProposedChangeNode { } } +export class PatchFileNode { + readonly label: string + readonly patchFilePath: string + children: ProposedChangeNode[] = [] + + constructor(patchFilePath: string) { + this.patchFilePath = patchFilePath + this.label = path.basename(patchFilePath) + } +} + enum ReviewState { ToReview, Reviewed_Accepted, @@ -114,7 +131,8 @@ enum ReviewState { } export class DiffModel { - changes: ProposedChangeNode[] = [] + patchFileNodes: PatchFileNode[] = [] + currentPatchIndex: number = 0 /** * This function creates a copy of the changed files of the user's project so that the diff.patch can be applied to them @@ -143,7 +161,8 @@ export class DiffModel { * @param pathToWorkspace Path to the project that was transformed * @returns List of nodes containing the paths of files that were modified, added, or removed */ - public parseDiff(pathToDiff: string, pathToWorkspace: string): ProposedChangeNode[] { + public parseDiff(pathToDiff: string, pathToWorkspace: string, diffDescription: PatchDescription): PatchFileNode { + this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') if (!diffContents.trim()) { @@ -184,7 +203,8 @@ export class DiffModel { } }, }) - this.changes = changedFiles.flatMap((file) => { + const patchFileNode = new PatchFileNode(diffDescription.name) + patchFileNode.children = changedFiles.flatMap((file) => { /* ex. file.oldFileName = 'a/src/java/com/project/component/MyFile.java' * ex. file.newFileName = 'b/src/java/com/project/component/MyFile.java' * use substring(2) to ignore the 'a/' and 'b/' @@ -202,24 +222,24 @@ export class DiffModel { } return [] }) - - return this.changes + this.patchFileNodes.push(patchFileNode) + return patchFileNode } public getChanges() { - return this.changes + return this.patchFileNodes.flatMap((patchFileNode) => patchFileNode.children) } public getRoot() { - return this.changes[0] + return this.patchFileNodes.length > 0 ? this.patchFileNodes[0] : undefined } public saveChanges() { - this.changes.forEach((file) => { - file.saveChange() + this.patchFileNodes.forEach((patchFileNode) => { + patchFileNode.children.forEach((changeNode) => { + changeNode.saveChange() + }) }) - - this.clearChanges() } public rejectChanges() { @@ -227,11 +247,14 @@ export class DiffModel { } public clearChanges() { - this.changes = [] + this.patchFileNodes = [] + // transformByQState.setSummaryFilePath('') + // transformByQState.setProjectCopyFilePath('') + // transformByQState.setResultArchiveFilePath('') } } -export class TransformationResultsProvider implements vscode.TreeDataProvider { +export class TransformationResultsProvider implements vscode.TreeDataProvider { public static readonly viewType = 'aws.amazonq.transformationProposedChangesTree' private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() @@ -243,26 +266,49 @@ export class TransformationResultsProvider implements vscode.TreeDataProvider { - return element ? Promise.resolve([]) : this.model.getChanges() + /* + Here we check if the element is a PatchFileNode instance. If it is, we return its + children array, which contains ProposedChangeNode instances. This ensures that when the user expands a + PatchFileNode (representing a diff.patch file), its children (proposed change nodes) are displayed as indented nodes under it. + */ + public getChildren( + element?: ProposedChangeNode | PatchFileNode + ): (ProposedChangeNode | PatchFileNode)[] | Thenable<(ProposedChangeNode | PatchFileNode)[]> { + if (!element) { + return this.model.patchFileNodes + } else if (element instanceof PatchFileNode) { + return element.children + } else { + return Promise.resolve([]) + } } - public getParent(element: ProposedChangeNode): ProposedChangeNode | undefined { + public getParent(element: ProposedChangeNode | PatchFileNode): PatchFileNode | undefined { + if (element instanceof ProposedChangeNode) { + const patchFileNode = this.model.patchFileNodes.find((p) => p.children.includes(element)) + return patchFileNode + } return undefined } } export class ProposedTransformationExplorer { - private changeViewer: vscode.TreeView + private changeViewer: vscode.TreeView public static TmpDir = os.tmpdir() @@ -273,6 +319,27 @@ export class ProposedTransformationExplorer { treeDataProvider: transformDataProvider, }) + const pathContainingArchive = '/private/var/folders/mn/l6c4t6sd1jn7g4p4nb6wqhh80000gq/T/ExportResultArchive' + + const patchFiles: string[] = [] + fs.readdir(path.join(pathContainingArchive, 'patch'), (err, files) => { + if (err) { + getLogger().error(err) + } else { + files.forEach((file) => { + const filePath = path.join(path.join(pathContainingArchive, 'patch'), file) + if (file.endsWith('.patch')) { + patchFiles.push(filePath) + } + }) + } + }) + + const pathContainingPatchFileDescriptions = path.join(pathContainingArchive, 'patch', 'diff.json') + + const jsonData = fs.readFileSync(pathContainingPatchFileDescriptions, 'utf-8') + const patchFilesDescriptions: PatchDescription[] = JSON.parse(jsonData) + const reset = async () => { await setContext('gumby.transformationProposalReviewInProgress', false) await setContext('gumby.reviewState', TransformByQReviewStatus.NotStarted) @@ -321,15 +388,15 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.startReview', async () => { await setContext('gumby.reviewState', TransformByQReviewStatus.PreparingReview) - const pathToArchive = path.join( - ProposedTransformationExplorer.TmpDir, - transformByQState.getJobId(), - 'ExportResultsArchive.zip' - ) - let exportResultsArchiveSize = 0 + // const pathToArchive = path.join( + // ProposedTransformationExplorer.TmpDir, + // transformByQState.getJobId(), + // 'ExportResultsArchive.zip' + // ) + const exportResultsArchiveSize = 0 let downloadErrorMessage = undefined - const cwStreamingClient = await createCodeWhispererChatStreamingClient() + // const cwStreamingClient = await createCodeWhispererChatStreamingClient() try { await telemetry.codeTransform_downloadArtifact.run(async () => { telemetry.record({ @@ -338,17 +405,17 @@ export class ProposedTransformationExplorer { codeTransformJobId: transformByQState.getJobId(), }) - await downloadExportResultArchive( - cwStreamingClient, - { - exportId: transformByQState.getJobId(), - exportIntent: ExportIntent.TRANSFORMATION, - }, - pathToArchive - ) + // await downloadExportResultArchive( + // cwStreamingClient, + // { + // exportId: transformByQState.getJobId(), + // exportIntent: ExportIntent.TRANSFORMATION, + // }, + // pathToArchive + // ) // Update downloaded artifact size - exportResultsArchiveSize = (await fs.promises.stat(pathToArchive)).size + // exportResultsArchiveSize = (await fs.promises.stat(pathToArchive)).size telemetry.record({ codeTransformTotalByteSize: exportResultsArchiveSize }) }) @@ -369,20 +436,19 @@ export class ProposedTransformationExplorer { getLogger().error(`CodeTransformation: ExportResultArchive error = ${downloadErrorMessage}`) throw new Error('Error downloading diff') } finally { - cwStreamingClient.destroy() + // cwStreamingClient.destroy() } let deserializeErrorMessage = undefined - let pathContainingArchive = '' try { // Download and deserialize the zip - pathContainingArchive = path.dirname(pathToArchive) - const zip = new AdmZip(pathToArchive) - zip.extractAllTo(pathContainingArchive) - diffModel.parseDiff( - path.join(pathContainingArchive, ExportResultArchiveStructure.PathToDiffPatch), - transformByQState.getProjectPath() - ) + // pathContainingArchive = path.dirname(pathToArchive) + // const zip = new AdmZip(pathToArchive) + // zip.extractAllTo(pathContainingArchive) + + getLogger().info('descriptions', patchFilesDescriptions.toString()) + diffModel.parseDiff(patchFiles[0], transformByQState.getProjectPath(), patchFilesDescriptions[0]) + await setContext('gumby.reviewState', TransformByQReviewStatus.InReview) transformDataProvider.refresh() transformByQState.setSummaryFilePath( @@ -442,11 +508,24 @@ export class ProposedTransformationExplorer { diffModel.saveChanges() telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotification) - transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessage, - tabID: ChatSessionManager.Instance.getSession().tabID, - }) - await reset() + if (!diffModel.currentPatchIndex) { + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: CodeWhispererConstants.changesAppliedChatMessage, + tabID: ChatSessionManager.Instance.getSession().tabID, + }) + } + + // Load the next patch file + diffModel.currentPatchIndex++ + if (diffModel.currentPatchIndex < patchFiles.length) { + const nextPatchFile = patchFiles[diffModel.currentPatchIndex] + const nextPatchFileDescription = patchFilesDescriptions[diffModel.currentPatchIndex] + diffModel.parseDiff(nextPatchFile, transformByQState.getProjectPath(), nextPatchFileDescription) + transformDataProvider.refresh() + } else { + // All patches have been applied, reset the state + await reset() + } telemetry.codeTransform_viewArtifact.emit({ codeTransformArtifactType: 'ClientInstructions', @@ -461,6 +540,7 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.rejectChanges', async () => { diffModel.rejectChanges() + diffModel.currentPatchIndex = 0 await reset() telemetry.ui_click.emit({ elementId: 'transformationHub_rejectChanges' }) From 598ce081539959b5aa1a5a39d39e5ecb0dba0ee3 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 18 Oct 2024 15:31:11 -0700 Subject: [PATCH 02/22] addressing comments to add to models and minor edits --- .../core/src/codewhisperer/models/model.ts | 6 +++++ .../transformationResultsViewProvider.ts | 22 ++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index b066a78ba0e..ab25d6628ea 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -51,6 +51,12 @@ export type CrossFileStrategy = 'opentabs' | 'codemap' | 'bm25' | 'default' export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty' +export type PatchInfo = { + name: string + fileName: string + isSuccessful: boolean +} + export interface CodeWhispererSupplementalContext { isUtg: boolean isProcessTimeout: boolean diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index dc2340aa06f..e5188a66671 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -22,12 +22,6 @@ import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSess import { setContext } from '../../../shared/vscode/setContext' import * as codeWhisperer from '../../client/codewhisperer' -type PatchDescription = { - name: string - fileName: string - isSuccessful: boolean -} - export abstract class ProposedChangeNode { abstract readonly resourcePath: string @@ -161,7 +155,7 @@ export class DiffModel { * @param pathToWorkspace Path to the project that was transformed * @returns List of nodes containing the paths of files that were modified, added, or removed */ - public parseDiff(pathToDiff: string, pathToWorkspace: string, diffDescription: PatchDescription): PatchFileNode { + public parseDiff(pathToDiff: string, pathToWorkspace: string, diffDescription: PatchInfo): PatchFileNode { this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') @@ -248,9 +242,6 @@ export class DiffModel { public clearChanges() { this.patchFileNodes = [] - // transformByQState.setSummaryFilePath('') - // transformByQState.setProjectCopyFilePath('') - // transformByQState.setResultArchiveFilePath('') } } @@ -327,7 +318,7 @@ export class ProposedTransformationExplorer { getLogger().error(err) } else { files.forEach((file) => { - const filePath = path.join(path.join(pathContainingArchive, 'patch'), file) + const filePath = path.join(pathContainingArchive, 'patch', file) if (file.endsWith('.patch')) { patchFiles.push(filePath) } @@ -338,7 +329,7 @@ export class ProposedTransformationExplorer { const pathContainingPatchFileDescriptions = path.join(pathContainingArchive, 'patch', 'diff.json') const jsonData = fs.readFileSync(pathContainingPatchFileDescriptions, 'utf-8') - const patchFilesDescriptions: PatchDescription[] = JSON.parse(jsonData) + const patchFilesDescriptions: PatchInfo[] = JSON.parse(jsonData) const reset = async () => { await setContext('gumby.transformationProposalReviewInProgress', false) @@ -353,6 +344,7 @@ export class ProposedTransformationExplorer { } diffModel.clearChanges() + diffModel.currentPatchIndex = 0 transformByQState.setSummaryFilePath('') transformByQState.setProjectCopyFilePath('') transformByQState.setResultArchiveFilePath('') @@ -446,7 +438,7 @@ export class ProposedTransformationExplorer { // const zip = new AdmZip(pathToArchive) // zip.extractAllTo(pathContainingArchive) - getLogger().info('descriptions', patchFilesDescriptions.toString()) + //Because multiple patches are returned once the ZIP is downloaded, we want to show the first one to start diffModel.parseDiff(patchFiles[0], transformByQState.getProjectPath(), patchFilesDescriptions[0]) await setContext('gumby.reviewState', TransformByQReviewStatus.InReview) @@ -508,7 +500,8 @@ export class ProposedTransformationExplorer { diffModel.saveChanges() telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotification) - if (!diffModel.currentPatchIndex) { + //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch + if (diffModel.currentPatchIndex === 0) { transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.changesAppliedChatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, @@ -540,7 +533,6 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.rejectChanges', async () => { diffModel.rejectChanges() - diffModel.currentPatchIndex = 0 await reset() telemetry.ui_click.emit({ elementId: 'transformationHub_rejectChanges' }) From 6481abc88313da9cfd1cacda4ef9da428eba6735 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 18 Oct 2024 16:02:48 -0700 Subject: [PATCH 03/22] editing the clear changes and updating test file --- .../service/transformByQ/transformationResultsViewProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index e5188a66671..5fe7bc49b88 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -242,6 +242,7 @@ export class DiffModel { public clearChanges() { this.patchFileNodes = [] + this.currentPatchIndex = 0 } } @@ -344,7 +345,6 @@ export class ProposedTransformationExplorer { } diffModel.clearChanges() - diffModel.currentPatchIndex = 0 transformByQState.setSummaryFilePath('') transformByQState.setProjectCopyFilePath('') transformByQState.setResultArchiveFilePath('') From 631d3f75a33cfc6ac2158a69c3feae7d1cf3c498 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 18 Oct 2024 16:04:44 -0700 Subject: [PATCH 04/22] editing test file to use models patchinfo --- .../transformationResultsHandler.test.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index f0b0df1fda2..e17ce569a82 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -6,25 +6,23 @@ import assert from 'assert' import sinon from 'sinon' import os from 'os' import { DiffModel, AddedChangeNode, ModifiedChangeNode } from 'aws-core-vscode/codewhisperer/node' +import { PatchInfo } from 'aws-core-vscode/codewhisperer' import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' -type PatchDescription = { - name: string - fileName: string - isSuccessful: boolean -} - describe('DiffModel', function () { + let parsedTestDescription: PatchInfo + beforeEach(() => { + parsedTestDescription = JSON.parse( + '{"name": "Added file", "fileName": "resources/files/addedFile.diffs", "isSuccessful": true}' + ) + }) + afterEach(() => { sinon.restore() }) - const testDescription = - '{"name": "Added file", "fileName": "resources/files/addedFile.diffs", "isSuccessful": true}' - const parsedTestDescription: PatchDescription = JSON.parse(testDescription) - it('WHEN parsing a diff patch where a file was added THEN returns an array representing the added file', async function () { const testDiffModel = new DiffModel() From 5343877985de702a7452e93c890269ea6f66027b Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 18 Oct 2024 16:09:08 -0700 Subject: [PATCH 05/22] fixing package files that we don't want in commit --- package-lock.json | 2 +- packages/amazonq/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 583b51d9f08..9aeb32cddf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20139,7 +20139,7 @@ }, "engines": { "npm": "^10.1.0", - "vscode": "^1.68.0" + "vscode": "^1.83.0" } }, "packages/core/node_modules/@types/node": { diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index e63c0ac02b0..f0478d5f2e0 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -1095,6 +1095,6 @@ }, "engines": { "npm": "^10.1.0", - "vscode": "^1.83.0" + "vscode": "^1.68.0" } } From 6cd90c2b42d70e60a57a443398a8a14e923866b7 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Mon, 21 Oct 2024 14:27:06 -0700 Subject: [PATCH 06/22] added test cases for new parseDiff logic --- .../transformationResultsHandler.test.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index e17ce569a82..019c0b2d508 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -15,7 +15,7 @@ describe('DiffModel', function () { let parsedTestDescription: PatchInfo beforeEach(() => { parsedTestDescription = JSON.parse( - '{"name": "Added file", "fileName": "resources/files/addedFile.diffs", "isSuccessful": true}' + '{"name": "Added file", "fileName": "resources/files/addedFile.diff", "isSuccessful": true}' ) }) @@ -43,7 +43,37 @@ describe('DiffModel', function () { parsedTestDescription ) + assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) + assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescription.name) + const change = testDiffModel.patchFileNodes[0].children[0] + + assert.strictEqual(change instanceof AddedChangeNode, true) + }) + + it('WHEN parsing a diff patch with no diff.json where a file was added THEN returns an array representing the added file', async function () { + const testDiffModel = new DiffModel() + + const workspacePath = 'workspace' + + sinon.replace(fs, 'exists', async (path) => { + const pathStr = path.toString() + if (pathStr.includes(workspacePath)) { + return false + } + + return true + }) + + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/addedFile.diff'), + workspacePath, + parsedTestDescription + ) + + assert.strictEqual(testDiffModel.patchFileNodes.length, 1) + assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) + assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescription.name) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) @@ -67,7 +97,9 @@ describe('DiffModel', function () { parsedTestDescription ) + assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) + assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescription.name) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) From 693d4bef38d97d9bc8f4826557530d8958896e9d Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Mon, 21 Oct 2024 14:56:21 -0700 Subject: [PATCH 07/22] removing redundant unit test --- .../transformationResultsHandler.test.ts | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 019c0b2d508..fa5f505fedc 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -51,34 +51,6 @@ describe('DiffModel', function () { assert.strictEqual(change instanceof AddedChangeNode, true) }) - it('WHEN parsing a diff patch with no diff.json where a file was added THEN returns an array representing the added file', async function () { - const testDiffModel = new DiffModel() - - const workspacePath = 'workspace' - - sinon.replace(fs, 'exists', async (path) => { - const pathStr = path.toString() - if (pathStr.includes(workspacePath)) { - return false - } - - return true - }) - - testDiffModel.parseDiff( - getTestResourceFilePath('resources/files/addedFile.diff'), - workspacePath, - parsedTestDescription - ) - - assert.strictEqual(testDiffModel.patchFileNodes.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescription.name) - const change = testDiffModel.patchFileNodes[0].children[0] - - assert.strictEqual(change instanceof AddedChangeNode, true) - }) - it('WHEN parsing a diff patch where a file was modified THEN returns an array representing the modified file', async function () { const testDiffModel = new DiffModel() From f55cd3650c497170070a4454c4aaaecaad09fb31 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Wed, 23 Oct 2024 11:11:59 -0700 Subject: [PATCH 08/22] updated tests to account for no diff.json and performed e2e transformation assuming current download artifact workflow --- .../amazonqGumby/resources/files/diff.json | 7 ++ .../transformationResultsHandler.test.ts | 43 +++++++-- .../chat/controller/controller.ts | 4 +- .../commands/startTransformByQ.ts | 13 ++- .../transformationResultsViewProvider.ts | 95 ++++++++++--------- .../core/src/shared/utilities/download.ts | 1 + 6 files changed, 101 insertions(+), 62 deletions(-) create mode 100644 packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json diff --git a/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json b/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json new file mode 100644 index 00000000000..762e56409f7 --- /dev/null +++ b/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json @@ -0,0 +1,7 @@ +[ + { + "name": "Added file", + "fileName": "resources/files/addedFile.diff", + "isSuccessful": true + } +] diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index fa5f505fedc..8daeaf5c78d 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -12,10 +12,11 @@ import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' describe('DiffModel', function () { - let parsedTestDescription: PatchInfo + let parsedTestDescriptions: PatchInfo[] beforeEach(() => { - parsedTestDescription = JSON.parse( - '{"name": "Added file", "fileName": "resources/files/addedFile.diff", "isSuccessful": true}' + const fs = require('fs') + parsedTestDescriptions = JSON.parse( + fs.readFileSync(getTestResourceFilePath('resources/files/diff.json'), 'utf-8') ) }) @@ -36,16 +37,15 @@ describe('DiffModel', function () { return true }) - testDiffModel.parseDiff( getTestResourceFilePath('resources/files/addedFile.diff'), workspacePath, - parsedTestDescription + parsedTestDescriptions[0] ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescription.name) + assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescriptions[0].name) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) @@ -66,12 +66,39 @@ describe('DiffModel', function () { testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath, - parsedTestDescription + parsedTestDescriptions[0] ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescription.name) + assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescriptions[0].name) + const change = testDiffModel.patchFileNodes[0].children[0] + + assert.strictEqual(change instanceof ModifiedChangeNode, true) + + await fs.delete(path.join(workspacePath, 'README.md'), { recursive: true }) + }) + + it('WHEN parsing a diff patch where diff.json is not present and a file was modified THEN returns an array representing the modified file', async function () { + const testDiffModel = new DiffModel() + + const workspacePath = os.tmpdir() + + sinon.replace(fs, 'exists', async (path) => true) + + await fs.writeFile( + path.join(workspacePath, 'README.md'), + 'This guide walks you through using Gradle to build a simple Java project.' + ) + + testDiffModel.parseDiff(getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath, undefined) + + assert.strictEqual(testDiffModel.patchFileNodes.length, 1) + assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) + assert.strictEqual( + testDiffModel.patchFileNodes[0].patchFilePath, + getTestResourceFilePath('resources/files/modifiedFile.diff') + ) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 7249dd86aec..37d139f5f1d 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -17,7 +17,7 @@ import { featureName } from '../../models/constants' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { cleanupTransformationJob, - // compileProject, + compileProject, finishHumanInTheLoop, getValidLanguageUpgradeCandidateProjects, openBuildLogFile, @@ -499,7 +499,7 @@ export class GumbyController { try { this.sessionStorage.getSession().conversationState = ConversationState.COMPILING this.messenger.sendCompilationInProgress(message.tabID) - // await compileProject() + await compileProject() } catch (err: any) { this.messenger.sendUnrecoverableErrorResponse('could-not-compile-project', message.tabID) // reset state to allow "Start a new transformation" button to work diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index bed7bc336d1..089eadd5ba1 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -225,7 +225,7 @@ export function startInterval() { export async function startTransformByQ() { // Set the default state variables for our store and the UI - // const transformStartTime = globals.clock.Date.now() + const transformStartTime = globals.clock.Date.now() await setTransformationToRunningState() try { @@ -233,16 +233,16 @@ export async function startTransformByQ() { startInterval() // step 1: CreateUploadUrl and upload code - // const uploadId = await preTransformationUploadCode() + const uploadId = await preTransformationUploadCode() // step 2: StartJob and store the returned jobId in TransformByQState - // const jobId = await startTransformationJob(uploadId, transformStartTime) + const jobId = await startTransformationJob(uploadId, transformStartTime) // step 3 (intermediate step): show transformation-plan.md file - // await pollTransformationStatusUntilPlanReady(jobId) + await pollTransformationStatusUntilPlanReady(jobId) // step 4: poll until artifacts are ready to download - await humanInTheLoopRetryLogic('jobId') + await humanInTheLoopRetryLogic(jobId) } catch (error: any) { await transformationJobErrorHandler(error) } finally { @@ -261,8 +261,7 @@ export async function startTransformByQ() { export async function humanInTheLoopRetryLogic(jobId: string) { let status = '' try { - // status = await pollTransformationStatusUntilComplete(jobId) - status = 'COMPLETED' + status = await pollTransformationStatusUntilComplete(jobId) if (status === 'PAUSED') { const hilStatusFailure = await initiateHumanInTheLoopPrompt(jobId) if (hilStatusFailure) { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 5fe7bc49b88..5cda30966a5 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -// import AdmZip from 'adm-zip' +import AdmZip from 'adm-zip' import os from 'os' import fs from 'fs' // eslint-disable-line no-restricted-imports import { parsePatch, applyPatches, ParsedDiff } from 'diff' @@ -17,7 +17,7 @@ import { telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' import { MetadataResult } from '../../../shared/telemetry/telemetryClient' import * as CodeWhispererConstants from '../../models/constants' -// import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' +import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' import { setContext } from '../../../shared/vscode/setContext' import * as codeWhisperer from '../../client/codewhisperer' @@ -155,7 +155,11 @@ export class DiffModel { * @param pathToWorkspace Path to the project that was transformed * @returns List of nodes containing the paths of files that were modified, added, or removed */ - public parseDiff(pathToDiff: string, pathToWorkspace: string, diffDescription: PatchInfo): PatchFileNode { + public parseDiff( + pathToDiff: string, + pathToWorkspace: string, + diffDescription: PatchInfo | undefined + ): PatchFileNode { this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') @@ -197,7 +201,7 @@ export class DiffModel { } }, }) - const patchFileNode = new PatchFileNode(diffDescription.name) + const patchFileNode = new PatchFileNode(diffDescription ? diffDescription.name : pathToDiff) patchFileNode.children = changedFiles.flatMap((file) => { /* ex. file.oldFileName = 'a/src/java/com/project/component/MyFile.java' * ex. file.newFileName = 'b/src/java/com/project/component/MyFile.java' @@ -311,26 +315,9 @@ export class ProposedTransformationExplorer { treeDataProvider: transformDataProvider, }) - const pathContainingArchive = '/private/var/folders/mn/l6c4t6sd1jn7g4p4nb6wqhh80000gq/T/ExportResultArchive' - + // const pathContainingArchive = '/private/var/folders/mn/l6c4t6sd1jn7g4p4nb6wqhh80000gq/T/ExportResultArchive' const patchFiles: string[] = [] - fs.readdir(path.join(pathContainingArchive, 'patch'), (err, files) => { - if (err) { - getLogger().error(err) - } else { - files.forEach((file) => { - const filePath = path.join(pathContainingArchive, 'patch', file) - if (file.endsWith('.patch')) { - patchFiles.push(filePath) - } - }) - } - }) - - const pathContainingPatchFileDescriptions = path.join(pathContainingArchive, 'patch', 'diff.json') - - const jsonData = fs.readFileSync(pathContainingPatchFileDescriptions, 'utf-8') - const patchFilesDescriptions: PatchInfo[] = JSON.parse(jsonData) + let patchFilesDescriptions: PatchInfo[] | undefined = undefined const reset = async () => { await setContext('gumby.transformationProposalReviewInProgress', false) @@ -380,15 +367,15 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.startReview', async () => { await setContext('gumby.reviewState', TransformByQReviewStatus.PreparingReview) - // const pathToArchive = path.join( - // ProposedTransformationExplorer.TmpDir, - // transformByQState.getJobId(), - // 'ExportResultsArchive.zip' - // ) - const exportResultsArchiveSize = 0 + const pathToArchive = path.join( + ProposedTransformationExplorer.TmpDir, + transformByQState.getJobId(), + 'ExportResultsArchive.zip' + ) + let exportResultsArchiveSize = 0 let downloadErrorMessage = undefined - // const cwStreamingClient = await createCodeWhispererChatStreamingClient() + const cwStreamingClient = await createCodeWhispererChatStreamingClient() try { await telemetry.codeTransform_downloadArtifact.run(async () => { telemetry.record({ @@ -397,17 +384,17 @@ export class ProposedTransformationExplorer { codeTransformJobId: transformByQState.getJobId(), }) - // await downloadExportResultArchive( - // cwStreamingClient, - // { - // exportId: transformByQState.getJobId(), - // exportIntent: ExportIntent.TRANSFORMATION, - // }, - // pathToArchive - // ) + await downloadExportResultArchive( + cwStreamingClient, + { + exportId: transformByQState.getJobId(), + exportIntent: ExportIntent.TRANSFORMATION, + }, + pathToArchive + ) // Update downloaded artifact size - // exportResultsArchiveSize = (await fs.promises.stat(pathToArchive)).size + exportResultsArchiveSize = (await fs.promises.stat(pathToArchive)).size telemetry.record({ codeTransformTotalByteSize: exportResultsArchiveSize }) }) @@ -428,18 +415,34 @@ export class ProposedTransformationExplorer { getLogger().error(`CodeTransformation: ExportResultArchive error = ${downloadErrorMessage}`) throw new Error('Error downloading diff') } finally { - // cwStreamingClient.destroy() + cwStreamingClient.destroy() } let deserializeErrorMessage = undefined + let pathContainingArchive = '' try { // Download and deserialize the zip - // pathContainingArchive = path.dirname(pathToArchive) - // const zip = new AdmZip(pathToArchive) - // zip.extractAllTo(pathContainingArchive) + pathContainingArchive = path.dirname(pathToArchive) + const zip = new AdmZip(pathToArchive) + zip.extractAllTo(pathContainingArchive) + + const files = fs.readdirSync(path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch)) + files.forEach((file) => { + const filePath = path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch, file) + if (file.endsWith('.patch')) { + patchFiles.push(filePath) + } else if (file.endsWith('.json')) { + const jsonData = fs.readFileSync(filePath, 'utf-8') + patchFilesDescriptions = JSON.parse(jsonData) + } + }) //Because multiple patches are returned once the ZIP is downloaded, we want to show the first one to start - diffModel.parseDiff(patchFiles[0], transformByQState.getProjectPath(), patchFilesDescriptions[0]) + diffModel.parseDiff( + patchFiles[0], + transformByQState.getProjectPath(), + patchFilesDescriptions ? patchFilesDescriptions[0] : undefined + ) await setContext('gumby.reviewState', TransformByQReviewStatus.InReview) transformDataProvider.refresh() @@ -512,7 +515,9 @@ export class ProposedTransformationExplorer { diffModel.currentPatchIndex++ if (diffModel.currentPatchIndex < patchFiles.length) { const nextPatchFile = patchFiles[diffModel.currentPatchIndex] - const nextPatchFileDescription = patchFilesDescriptions[diffModel.currentPatchIndex] + const nextPatchFileDescription = patchFilesDescriptions + ? patchFilesDescriptions[diffModel.currentPatchIndex] + : undefined diffModel.parseDiff(nextPatchFile, transformByQState.getProjectPath(), nextPatchFileDescription) transformDataProvider.refresh() } else { diff --git a/packages/core/src/shared/utilities/download.ts b/packages/core/src/shared/utilities/download.ts index dfb4c551427..19c0adcae4f 100644 --- a/packages/core/src/shared/utilities/download.ts +++ b/packages/core/src/shared/utilities/download.ts @@ -13,6 +13,7 @@ import fs from '../fs/fs' export class ExportResultArchiveStructure { static readonly PathToSummary = 'summary/summary.md' static readonly PathToDiffPatch = 'patch/diff.patch' + static readonly PathToPatch = 'patch' static readonly PathToMetrics = 'metrics/metrics.json' static readonly PathToManifest = 'manifest.json' } From fb3436f8568118b302ce64152afb54f698b275bc Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Tue, 29 Oct 2024 09:02:34 -0700 Subject: [PATCH 09/22] added numbering to make it obvious to user which patch file they're viewing and updated chat messaging with start transformation prompt --- .../transformationResultsHandler.test.ts | 16 +++++-- .../chat/controller/controller.ts | 14 ++++-- .../chat/controller/messenger/messenger.ts | 26 +++++------ .../controller/messenger/messengerUtils.ts | 4 +- .../commands/startTransformByQ.ts | 10 ++--- .../src/codewhisperer/models/constants.ts | 9 ++-- .../transformByQ/transformApiHandler.ts | 1 + .../transformationResultsViewProvider.ts | 43 +++++++++++++++---- 8 files changed, 83 insertions(+), 40 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 8daeaf5c78d..6fa6fac3215 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -40,12 +40,14 @@ describe('DiffModel', function () { testDiffModel.parseDiff( getTestResourceFilePath('resources/files/addedFile.diff'), workspacePath, - parsedTestDescriptions[0] + parsedTestDescriptions[0], + 1 ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescriptions[0].name) + assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions[0].name)) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) @@ -66,12 +68,14 @@ describe('DiffModel', function () { testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath, - parsedTestDescriptions[0] + parsedTestDescriptions[0], + 1 ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescriptions[0].name) + assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions[0].name)) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) @@ -91,7 +95,12 @@ describe('DiffModel', function () { 'This guide walks you through using Gradle to build a simple Java project.' ) - testDiffModel.parseDiff(getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath, undefined) + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/modifiedFile.diff'), + workspacePath, + undefined, + 1 + ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) @@ -99,6 +108,7 @@ describe('DiffModel', function () { testDiffModel.patchFileNodes[0].patchFilePath, getTestResourceFilePath('resources/files/modifiedFile.diff') ) + assert(testDiffModel.patchFileNodes[0].label.endsWith('modifiedFile.diff')) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 37d139f5f1d..757ddfb2d14 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -591,11 +591,15 @@ export class GumbyController { ) } - private transformationFinished(data: { message: string | undefined; tabID: string }) { + private transformationFinished(data: { + message: string | undefined + tabID: string + includeStartNewTransformationButton: string + }) { this.resetTransformationChatFlow() // at this point job is either completed, partially_completed, cancelled, or failed if (data.message) { - this.messenger.sendJobFinishedMessage(data.tabID, data.message) + this.messenger.sendJobFinishedMessage(data.tabID, data.message, data.includeStartNewTransformationButton) } } @@ -701,7 +705,11 @@ export class GumbyController { try { await finishHumanInTheLoop() } catch (err: any) { - this.transformationFinished({ tabID: message.tabID, message: (err as Error).message }) + this.transformationFinished({ + tabID: message.tabID, + message: (err as Error).message, + includeStartNewTransformationButton: 'true', + }) } this.messenger.sendStaticTextResponse('end-HIL-early', message.tabID) diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 0acee2a3260..b4fdd967ca2 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -185,15 +185,11 @@ export class Messenger { options: [ { value: JDKVersion.JDK8, - label: JDKVersion.JDK8, + label: JDKVersion.JDK8.toString(), }, { value: JDKVersion.JDK11, - label: JDKVersion.JDK11, - }, - { - value: JDKVersion.JDK17, - label: JDKVersion.JDK17, + label: JDKVersion.JDK11.toString(), }, { value: JDKVersion.UNSUPPORTED, @@ -477,13 +473,19 @@ export class Messenger { this.dispatcher.sendCommandMessage(new SendCommandMessage(message.command, message.tabID, message.eventId)) } - public sendJobFinishedMessage(tabID: string, message: string) { + public sendJobFinishedMessage( + tabID: string, + message: string, + includeStartNewTransformationButton: string = 'true' + ) { const buttons: ChatItemButton[] = [] - buttons.push({ - keepCardAfterClick: false, - text: CodeWhispererConstants.startTransformationButtonText, - id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW, - }) + if (includeStartNewTransformationButton === 'true') { + buttons.push({ + keepCardAfterClick: false, + text: CodeWhispererConstants.startTransformationButtonText, + id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW, + }) + } this.dispatcher.sendChatMessage( new ChatMessage( diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index 43c833f5e86..fd9e8edc642 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -46,9 +46,7 @@ export default class MessengerUtils { if (jdkVersion === JDKVersion.JDK8) { javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(1.8)}` } else if (jdkVersion === JDKVersion.JDK11) { - javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(11)}` - } else if (jdkVersion === JDKVersion.JDK17) { - javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(17)}` + javaHomePrompt += ` ${CodeWhispererConstants.macJava11HomeHelpChatMessage}` } } else { javaHomePrompt += ` ${CodeWhispererConstants.linuxJavaHomeHelpChatMessage}` diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 089eadd5ba1..80806acf645 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -177,8 +177,6 @@ async function validateJavaHome(): Promise { javaVersionUsedByMaven = JDKVersion.JDK8 } else if (javaVersionUsedByMaven === '11.') { javaVersionUsedByMaven = JDKVersion.JDK11 - } else if (javaVersionUsedByMaven === '17.') { - javaVersionUsedByMaven = JDKVersion.JDK17 } } if (javaVersionUsedByMaven !== transformByQState.getSourceJDKVersion()) { @@ -817,9 +815,11 @@ export async function postTransformationJob() { chatMessage = CodeWhispererConstants.jobPartiallyCompletedChatMessage } - transformByQState - .getChatControllers() - ?.transformationFinished.fire({ message: chatMessage, tabID: ChatSessionManager.Instance.getSession().tabID }) + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: chatMessage, + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: 'true', + }) const durationInMs = calculateTotalLatency(CodeTransformTelemetryState.instance.getStartTime()) const resultStatusMessage = transformByQState.getStatus() diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index ebb2d75fd7f..44b91cff1f2 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -627,9 +627,11 @@ export const viewProposedChangesChatMessage = export const viewProposedChangesNotification = 'Download complete. You can view a summary of the transformation and accept or reject the proposed changes in the Transformation Hub.' -export const changesAppliedChatMessage = 'I applied the changes to your project.' +export const changesAppliedChatMessage = (currentPatchIndex: number, totalPatchFiles: number) => + `I applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` -export const changesAppliedNotification = 'Amazon Q applied the changes to your project.' +export const changesAppliedNotification = (currentPatchIndex: number, totalPatchFiles: number) => + `Amazon Q applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` export const noOpenProjectsFoundChatMessage = `I couldn\'t find a project that I can upgrade. Currently, I support Java 8, Java 11, and Java 17 projects built on Maven. Make sure your project is open in the IDE. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` @@ -661,9 +663,6 @@ export const windowsJavaHomeHelpChatMessage = export const macJavaVersionHomeHelpChatMessage = (version: number) => `To find the JDK path, run the following command in a new terminal: \`/usr/libexec/java_home -v ${version}\`` -export const macJava17HomeHelpChatMessage = - 'To find the JDK path, run the following command in a new terminal: `/usr/libexec/java_home -v 17`' - export const linuxJavaHomeHelpChatMessage = 'To find the JDK path, run the following command in a new terminal: `update-java-alternatives --list`' diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index dc599a1dcd0..c685929c5f2 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -405,6 +405,7 @@ export async function zipCode( transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.projectSizeTooLargeChatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: 'true', }) throw new ZipExceedsSizeLimitError() } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 5cda30966a5..99050840523 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -108,7 +108,7 @@ export class AddedChangeNode extends ProposedChangeNode { } export class PatchFileNode { - readonly label: string + label: string readonly patchFilePath: string children: ProposedChangeNode[] = [] @@ -158,7 +158,8 @@ export class DiffModel { public parseDiff( pathToDiff: string, pathToWorkspace: string, - diffDescription: PatchInfo | undefined + diffDescription: PatchInfo | undefined, + totalDiffPatches: number ): PatchFileNode { this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') @@ -202,6 +203,7 @@ export class DiffModel { }, }) const patchFileNode = new PatchFileNode(diffDescription ? diffDescription.name : pathToDiff) + patchFileNode.label = `Patch ${this.currentPatchIndex + 1} of ${totalDiffPatches}: ${patchFileNode.label}` patchFileNode.children = changedFiles.flatMap((file) => { /* ex. file.oldFileName = 'a/src/java/com/project/component/MyFile.java' * ex. file.newFileName = 'b/src/java/com/project/component/MyFile.java' @@ -266,7 +268,7 @@ export class TransformationResultsProvider implements vscode.TreeDataProvider { diffModel.saveChanges() telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) - void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotification) + void vscode.window.showInformationMessage( + CodeWhispererConstants.changesAppliedNotification(diffModel.currentPatchIndex, patchFiles.length) + ) //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch - if (diffModel.currentPatchIndex === 0) { + if (diffModel.currentPatchIndex === patchFiles.length - 1) { + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: CodeWhispererConstants.changesAppliedChatMessage( + diffModel.currentPatchIndex, + patchFiles.length + ), + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: 'true', + }) + } else { transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessage, + message: CodeWhispererConstants.changesAppliedChatMessage( + diffModel.currentPatchIndex, + patchFiles.length + ), tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: 'false', }) } @@ -518,7 +538,12 @@ export class ProposedTransformationExplorer { const nextPatchFileDescription = patchFilesDescriptions ? patchFilesDescriptions[diffModel.currentPatchIndex] : undefined - diffModel.parseDiff(nextPatchFile, transformByQState.getProjectPath(), nextPatchFileDescription) + diffModel.parseDiff( + nextPatchFile, + transformByQState.getProjectPath(), + nextPatchFileDescription, + patchFiles.length + ) transformDataProvider.refresh() } else { // All patches have been applied, reset the state From bd8f324cc84d9ecefa3aa69c1a0e09c7f342fb39 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 1 Nov 2024 16:53:32 -0700 Subject: [PATCH 10/22] updated json structure based on backend and now collecting patchfiles from json --- .../amazonqGumby/resources/files/diff.json | 16 +++---- .../transformationResultsHandler.test.ts | 22 ++++++---- .../chat/controller/controller.ts | 4 +- .../chat/controller/messenger/messenger.ts | 8 +--- .../commands/startTransformByQ.ts | 2 +- .../core/src/codewhisperer/models/model.ts | 6 ++- .../transformByQ/transformApiHandler.ts | 2 +- .../transformationResultsViewProvider.ts | 42 ++++++++++++------- 8 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json b/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json index 762e56409f7..5b73cdd201b 100644 --- a/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json +++ b/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json @@ -1,7 +1,9 @@ -[ - { - "name": "Added file", - "fileName": "resources/files/addedFile.diff", - "isSuccessful": true - } -] +{ + "content": [ + { + "name": "Added file", + "fileName": "resources/files/addedFile.diff", + "isSuccessful": true + } + ] +} diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 6fa6fac3215..7d63692fe46 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -6,13 +6,13 @@ import assert from 'assert' import sinon from 'sinon' import os from 'os' import { DiffModel, AddedChangeNode, ModifiedChangeNode } from 'aws-core-vscode/codewhisperer/node' -import { PatchInfo } from 'aws-core-vscode/codewhisperer' +import { DescriptionContent } from 'aws-core-vscode/codewhisperer' import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' describe('DiffModel', function () { - let parsedTestDescriptions: PatchInfo[] + let parsedTestDescriptions: DescriptionContent beforeEach(() => { const fs = require('fs') parsedTestDescriptions = JSON.parse( @@ -40,14 +40,17 @@ describe('DiffModel', function () { testDiffModel.parseDiff( getTestResourceFilePath('resources/files/addedFile.diff'), workspacePath, - parsedTestDescriptions[0], + parsedTestDescriptions.content[0], 1 ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescriptions[0].name) - assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions[0].name)) + assert.strictEqual( + testDiffModel.patchFileNodes[0].patchFilePath, + getTestResourceFilePath('resources/files/addedFile.diff') + ) + assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions.content[0].name)) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) @@ -68,14 +71,17 @@ describe('DiffModel', function () { testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath, - parsedTestDescriptions[0], + parsedTestDescriptions.content[0], 1 ) assert.strictEqual(testDiffModel.patchFileNodes.length, 1) assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].patchFilePath, parsedTestDescriptions[0].name) - assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions[0].name)) + assert.strictEqual( + testDiffModel.patchFileNodes[0].patchFilePath, + getTestResourceFilePath('resources/files/modifiedFile.diff') + ) + assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions.content[0].name)) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 757ddfb2d14..ba278c6ee47 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -594,7 +594,7 @@ export class GumbyController { private transformationFinished(data: { message: string | undefined tabID: string - includeStartNewTransformationButton: string + includeStartNewTransformationButton: boolean }) { this.resetTransformationChatFlow() // at this point job is either completed, partially_completed, cancelled, or failed @@ -708,7 +708,7 @@ export class GumbyController { this.transformationFinished({ tabID: message.tabID, message: (err as Error).message, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) } diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index b4fdd967ca2..38f7ae18ba9 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -473,13 +473,9 @@ export class Messenger { this.dispatcher.sendCommandMessage(new SendCommandMessage(message.command, message.tabID, message.eventId)) } - public sendJobFinishedMessage( - tabID: string, - message: string, - includeStartNewTransformationButton: string = 'true' - ) { + public sendJobFinishedMessage(tabID: string, message: string, includeStartNewTransformationButton: boolean = true) { const buttons: ChatItemButton[] = [] - if (includeStartNewTransformationButton === 'true') { + if (includeStartNewTransformationButton) { buttons.push({ keepCardAfterClick: false, text: CodeWhispererConstants.startTransformationButtonText, diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 80806acf645..24ffdadc4c1 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -818,7 +818,7 @@ export async function postTransformationJob() { transformByQState.getChatControllers()?.transformationFinished.fire({ message: chatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) const durationInMs = calculateTotalLatency(CodeTransformTelemetryState.instance.getStartTime()) const resultStatusMessage = transformByQState.getStatus() diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index ab25d6628ea..ec7515aa222 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -53,10 +53,14 @@ export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Emp export type PatchInfo = { name: string - fileName: string + filename: string isSuccessful: boolean } +export type DescriptionContent = { + content: PatchInfo[] +} + export interface CodeWhispererSupplementalContext { isUtg: boolean isProcessTimeout: boolean diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index c685929c5f2..2ff2f16f621 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -405,7 +405,7 @@ export async function zipCode( transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.projectSizeTooLargeChatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) throw new ZipExceedsSizeLimitError() } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 99050840523..8ac22f66560 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -10,7 +10,7 @@ import { parsePatch, applyPatches, ParsedDiff } from 'diff' import path from 'path' import vscode from 'vscode' import { ExportIntent } from '@amzn/codewhisperer-streaming' -import { TransformationType, TransformByQReviewStatus, transformByQState } from '../../models/model' +import { TransformByQReviewStatus, transformByQState, PatchInfo, DescriptionContent } from '../../models/model' import { ExportResultArchiveStructure, downloadExportResultArchive } from '../../../shared/utilities/download' import { getLogger } from '../../../shared/logger' import { telemetry } from '../../../shared/telemetry/telemetry' @@ -112,9 +112,9 @@ export class PatchFileNode { readonly patchFilePath: string children: ProposedChangeNode[] = [] - constructor(patchFilePath: string) { + constructor(description: PatchInfo | undefined = undefined, patchFilePath: string) { this.patchFilePath = patchFilePath - this.label = path.basename(patchFilePath) + this.label = description ? description.name : path.basename(patchFilePath) } } @@ -202,7 +202,7 @@ export class DiffModel { } }, }) - const patchFileNode = new PatchFileNode(diffDescription ? diffDescription.name : pathToDiff) + const patchFileNode = new PatchFileNode(diffDescription, pathToDiff) patchFileNode.label = `Patch ${this.currentPatchIndex + 1} of ${totalDiffPatches}: ${patchFileNode.label}` patchFileNode.children = changedFiles.flatMap((file) => { /* ex. file.oldFileName = 'a/src/java/com/project/component/MyFile.java' @@ -318,7 +318,8 @@ export class ProposedTransformationExplorer { }) const patchFiles: string[] = [] - let patchFilesDescriptions: PatchInfo[] | undefined = undefined + let singlePatchFile: string = '' + let patchFilesDescriptions: DescriptionContent | undefined = undefined const reset = async () => { await setContext('gumby.transformationProposalReviewInProgress', false) @@ -411,7 +412,7 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: `${CodeWhispererConstants.errorDownloadingDiffChatMessage} The download failed due to: ${downloadErrorMessage}`, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) await setContext('gumby.reviewState', TransformByQReviewStatus.NotStarted) getLogger().error(`CodeTransformation: ExportResultArchive error = ${downloadErrorMessage}`) @@ -431,19 +432,32 @@ export class ProposedTransformationExplorer { const files = fs.readdirSync(path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch)) files.forEach((file) => { const filePath = path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch, file) - if (file.endsWith('.patch')) { - patchFiles.push(filePath) + if (file === 'diff.patch') { + singlePatchFile = filePath } else if (file.endsWith('.json')) { const jsonData = fs.readFileSync(filePath, 'utf-8') patchFilesDescriptions = JSON.parse(jsonData) } }) + if (patchFilesDescriptions !== undefined) { + for (const patchInfo of patchFilesDescriptions.content) { + patchFiles.push( + path.join( + pathContainingArchive, + ExportResultArchiveStructure.PathToPatch, + patchInfo.filename + ) + ) + } + } else { + patchFiles.push(singlePatchFile) + } //Because multiple patches are returned once the ZIP is downloaded, we want to show the first one to start diffModel.parseDiff( patchFiles[0], transformByQState.getProjectPath(), - patchFilesDescriptions ? patchFilesDescriptions[0] : undefined, + patchFilesDescriptions ? patchFilesDescriptions.content[0] : undefined, patchFiles.length ) @@ -460,7 +474,7 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.viewProposedChangesChatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) await vscode.commands.executeCommand('aws.amazonq.transformationHub.summary.reveal') } catch (e: any) { @@ -469,7 +483,7 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: `${CodeWhispererConstants.errorDeserializingDiffChatMessage} ${deserializeErrorMessage}`, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) void vscode.window.showErrorMessage( `${CodeWhispererConstants.errorDeserializingDiffNotification} ${deserializeErrorMessage}` @@ -518,7 +532,7 @@ export class ProposedTransformationExplorer { patchFiles.length ), tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'true', + includeStartNewTransformationButton: true, }) } else { transformByQState.getChatControllers()?.transformationFinished.fire({ @@ -527,7 +541,7 @@ export class ProposedTransformationExplorer { patchFiles.length ), tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: 'false', + includeStartNewTransformationButton: false, }) } @@ -536,7 +550,7 @@ export class ProposedTransformationExplorer { if (diffModel.currentPatchIndex < patchFiles.length) { const nextPatchFile = patchFiles[diffModel.currentPatchIndex] const nextPatchFileDescription = patchFilesDescriptions - ? patchFilesDescriptions[diffModel.currentPatchIndex] + ? patchFilesDescriptions.content[diffModel.currentPatchIndex] : undefined diffModel.parseDiff( nextPatchFile, From 1f38262692107194c55f66fbb0440fe0ce32548a Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 8 Nov 2024 15:08:04 -0800 Subject: [PATCH 11/22] adding additional transformationFinished parameter --- packages/core/src/amazonqGumby/chat/controller/controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index ba278c6ee47..30b1c897c78 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -358,6 +358,7 @@ export class GumbyController { this.transformationFinished({ message: CodeWhispererConstants.jobCancelledChatMessage, tabID: message.tabID, + includeStartNewTransformationButton: true, }) break case ButtonActions.CONFIRM_SKIP_TESTS_FORM: @@ -563,6 +564,7 @@ export class GumbyController { this.transformationFinished({ message: CodeWhispererConstants.jobCancelledChatMessage, tabID: message.tabID, + includeStartNewTransformationButton: true, }) return } From d4ef6649bb34a4b88f51eaaefeb47f3335ef4eed Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Tue, 12 Nov 2024 09:06:33 -0800 Subject: [PATCH 12/22] added patch description chat message and working on user input for one or multiple diffs --- .../chat/controller/controller.ts | 62 ++++++++++++++- .../chat/controller/messenger/messenger.ts | 79 ++++++++++++++++++- .../controller/messenger/messengerUtils.ts | 2 + .../src/codewhisperer/models/constants.ts | 60 ++++++++++++-- .../transformationResultsViewProvider.ts | 20 ++++- 5 files changed, 211 insertions(+), 12 deletions(-) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 30b1c897c78..7acdfa956f4 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -217,6 +217,13 @@ export class GumbyController { // if previous transformation was already running, show correct message to user switch (this.sessionStorage.getSession().conversationState) { case ConversationState.JOB_SUBMITTED: + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) + this.messenger.sendPatchDescriptionMessage(message.tabID) this.messenger.sendAsyncEventProgress( message.tabID, true, @@ -367,6 +374,12 @@ export class GumbyController { case ButtonActions.CANCEL_SKIP_TESTS_FORM: this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) break + // case ButtonActions.CONFIRM_SELECTIVE_TRANSFORMATION_FORM: + // await this.handleSelectiveTransformationSelection(message) + // break + // case ButtonActions.CANCEL_SELECTIVE_TRANSFORMATION_FORM: + // this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) + // break case ButtonActions.CONFIRM_SQL_CONVERSION_TRANSFORMATION_FORM: await this.handleUserSQLConversionProjectSelection(message) break @@ -375,6 +388,13 @@ export class GumbyController { break case ButtonActions.VIEW_TRANSFORMATION_HUB: await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat) + this.messenger.sendPatchDescriptionMessage(message.tabID) + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) this.messenger.sendJobSubmittedMessage(message.tabID) break case ButtonActions.STOP_TRANSFORMATION_JOB: @@ -421,6 +441,16 @@ export class GumbyController { await this.validateBuildWithPromptOnError(message) } + // private async handleSelectiveTransformationSelection(message: any) { + // const selectiveTransformationSelection = message.formSelectedValues['GumbyTransformSelectiveTransformationForm'] + // if (selectiveTransformationSelection === CodeWhispererConstants.multipleDiffsMessage) { + // //Dynamically add to zipmanifest + // } + // this.messenger.sendSelectiveTransformationMessage(selectiveTransformationSelection, message.tabID) + // // perform local build + // // await this.validateBuildWithPromptOnError(message) + // } + private async handleUserLanguageUpgradeProjectChoice(message: any) { await telemetry.codeTransform_submitSelection.run(async () => { const pathToProject: string = message.formSelectedValues['GumbyTransformLanguageUpgradeProjectForm'] @@ -453,9 +483,9 @@ export class GumbyController { this.messenger.sendUnrecoverableErrorResponse('unsupported-source-jdk-version', message.tabID) return } - await processLanguageUpgradeTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion) await this.messenger.sendSkipTestsPrompt(message.tabID) + // await this.messenger.sendSelectiveTransformationPrompt(message.tabID) }) } @@ -476,6 +506,13 @@ export class GumbyController { await processSQLConversionTransformFormInput(pathToProject, schema) + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) + this.messenger.sendPatchDescriptionMessage(message.tabID) this.messenger.sendAsyncEventProgress( message.tabID, true, @@ -521,6 +558,13 @@ export class GumbyController { // give user a non-blocking warning if build file appears to contain absolute paths await parseBuildFile() + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) + this.messenger.sendPatchDescriptionMessage(message.tabID) this.messenger.sendAsyncEventProgress( message.tabID, true, @@ -618,8 +662,15 @@ export class GumbyController { this.messenger.sendDependencyVersionsFoundMessage(data.dependencies, data.tabID) } - private HILDependencySelectionUploaded(data: { tabID: string }) { + private async HILDependencySelectionUploaded(data: { tabID: string }) { this.sessionStorage.getSession().conversationState = ConversationState.JOB_SUBMITTED + this.messenger.sendPatchDescriptionMessage(data.tabID) + this.messenger.sendAsyncEventProgress( + data.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) this.messenger.sendHILResumeMessage(data.tabID) } @@ -685,6 +736,13 @@ export class GumbyController { } else if (message.error instanceof JobStartError) { this.resetTransformationChatFlow() } else if (message.error instanceof TransformationPreBuildError) { + this.messenger.sendPatchDescriptionMessage(message.tabID) + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_FAILED_IN_PRE_BUILD + ) this.messenger.sendJobSubmittedMessage(message.tabID, true) this.messenger.sendAsyncEventProgress( message.tabID, diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 38f7ae18ba9..951434c7928 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -116,6 +116,45 @@ export class Messenger { this.dispatcher.sendAuthenticationUpdate(new AuthenticationUpdateMessage(gumbyEnabled, authenticatingTabIDs)) } + public async sendSelectiveTransformationPrompt(tabID: string) { + const formItems: ChatItemFormItem[] = [] + formItems.push({ + id: 'GumbyTransformSelectiveTransformationForm', + type: 'select', + title: CodeWhispererConstants.selectiveTransformationFormTitle, + mandatory: true, + options: [ + { + value: CodeWhispererConstants.oneDiffMessage, + label: CodeWhispererConstants.oneDiffMessage, + }, + { + value: CodeWhispererConstants.multipleDiffsMessage, + label: CodeWhispererConstants.multipleDiffsMessage, + }, + ], + }) + + this.dispatcher.sendAsyncEventProgress( + new AsyncEventProgressMessage(tabID, { + inProgress: true, + message: CodeWhispererConstants.selectiveTransformationFormMessage, + }) + ) + + this.dispatcher.sendChatPrompt( + new ChatPrompt( + { + message: 'Proposed Changes Result', + formItems: formItems, + }, + 'TransformSelectiveTransformationForm', + tabID, + false + ) + ) + } + public async sendSkipTestsPrompt(tabID: string) { const formItems: ChatItemFormItem[] = [] formItems.push({ @@ -330,6 +369,33 @@ export class Messenger { ) } + public sendPatchDescriptionMessage( + tabID: string, + message: string = CodeWhispererConstants.userPatchDescriptionChatMessage + // messageID: string = GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) { + // const patchDescriptionChatMessage = new ChatMessage( + // { + // message, + // messageType: 'ai-prompt', + // messageId: messageID, + // }, + // tabID + // ) + this.dispatcher.sendAsyncEventProgress( + new AsyncEventProgressMessage(tabID, { + inProgress: true, + message, + }) + ) + this.dispatcher.sendAsyncEventProgress( + new AsyncEventProgressMessage(tabID, { + inProgress: false, + message: undefined, + }) + ) + } + public sendJobSubmittedMessage( tabID: string, disableJobActions: boolean = false, @@ -363,7 +429,6 @@ export class Messenger { }, tabID ) - this.dispatcher.sendChatMessage(jobSubmittedMessage) } @@ -560,6 +625,11 @@ export class Messenger { this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) } + public sendSelectiveTransformationMessage(selectiveTransformationSelection: string, tabID: string) { + const message = `Okay, I will create ${selectiveTransformationSelection.toLowerCase()} when building your project.` + this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) + } + public sendHumanInTheLoopInitialMessage(tabID: string, codeSnippet: string) { let message = `I was not able to upgrade all dependencies. To resolve it, I will try to find an updated depedency in your local Maven repository. I will need additional information from you to continue.` @@ -674,6 +744,13 @@ ${codeSnippet} undefined, GumbyNamedMessages.JOB_SUBMISSION_WITH_DEPENDENCY_STATUS_MESSAGE ) + this.sendPatchDescriptionMessage(tabID) + this.sendAsyncEventProgress( + tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_WITH_DEPENDENCY_STATUS_MESSAGE + ) this.sendJobSubmittedMessage( tabID, false, diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index fd9e8edc642..9925c049605 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -18,6 +18,8 @@ export enum ButtonActions { CANCEL_TRANSFORMATION_FORM = 'gumbyTransformFormCancel', // shared between Language Upgrade & SQL Conversion CONFIRM_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormConfirm', CANCEL_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormCancel', + CONFIRM_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformSelectiveTransformationFormConfirm', + CANCEL_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformSelectiveTransformationFormCancel', SELECT_SQL_CONVERSION_METADATA_FILE = 'gumbySQLConversionMetadataTransformFormConfirm', CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm', CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel', diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 44b91cff1f2..32cb2bba484 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { DescriptionContent } from '..' + /** * Automated and manual trigger */ @@ -22,6 +24,19 @@ export const AWSTemplateKeyWords = ['AWSTemplateFormatVersion', 'Resources', 'AW export const AWSTemplateCaseInsensitiveKeyWords = ['cloudformation', 'cfn', 'template', 'description'] +const patchDescriptions: { [key: string]: string } = { + 'Minimal Compatible Library Upgrade to Java 17': + 'This diff patch covers the set of upgrades for Springboot, JUnit, and PowerMockito frameworks.', + 'Popular Enterprise Specifications and Application Frameworks': + 'This diff patch covers the set of upgrades for Jakarta EE 10, Hibernate 6.2, and Micronaut 3.', + 'HTTP Client Utilities, Apache Commons Utilities, and Web Frameworks': + 'This diff patch covers the set of upgrades for Apache HTTP Client 5, Apache Commons utilities (Collections, IO, Lang, Math), Struts 6.0.', + 'Testing Tools and Frameworks': + 'This diff patch covers the set of upgrades for ArchUnit, Mockito, TestContainers, Cucumber, and additionally, Jenkins plugins and the Maven Wrapper.', + 'Miscellaneous Processing Documentation': + 'This diff patch covers a diverse set of upgrades spanning ORMs, XML processing, API documentation, and more.', +} + export const JsonConfigFileNamingConvention = new Set([ 'app.json', 'appsettings.json', @@ -454,6 +469,20 @@ export const chooseTransformationObjective = `I can help you with the following export const chooseTransformationObjectivePlaceholder = 'Enter "language upgrade" or "sql conversion"' +export const userPatchDescriptionChatMessage = ` +I will be dividing my proposed changes into smaller sections. Here is a description of what each section entails: + +• Minimal Compatible Library Upgrade to Java 17: This upgrades dependencies to the minimum compatible versions in Java 17. It also includes updated versions of Springboot as well as JUnit and PowerMockito frameworks. + +• Popular Enterprise Specifications Application Frameworks: This group aims to migrate to the latest versions of popular enterprise specifications and application frameworks like Jakarta EE 10 (the new javax namespace), Hibernate 6.2 (a widely used ORM), and Micronaut 3 (a modern, lightweight full-stack framework). + +• HTTP Client Utilities Web Frameworks: This section targets upgrades for HTTP client libraries (Apache HTTP Client 5), Apache Commons utilities (Collections, IO, Lang, Math), and web frameworks (Struts 6.0). The goal is to modernize these commonly used libraries and frameworks to their latest versions, ensuring compatibility with Java 17. + +• Testing Tools Frameworks: This set upgrades targets testing tools and frameworks like ArchUnit, Mockito, TestContainers, and Cucumber. Additionally, it updates build tools like Jenkins plugins and the Maven Wrapper. The goal is to bring the testing ecosystem and build tooling up-to-date with the latest versions and best practices. + +• Miscellaneous Processing Documentation: This group covers a diverse set of upgrades spanning ORMs (JpaRepository), XML processing (JAXB namespace), application servers (WebSphere to Liberty migration), API documentation (Swagger to SpringDoc/OpenAPI), and utilities (Okio, OkHttp, LaunchDarkly). +` + export const uploadingCodeStepMessage = 'Upload your code' export const buildCodeStepMessage = 'Build uploaded code in secure build environment' @@ -627,11 +656,23 @@ export const viewProposedChangesChatMessage = export const viewProposedChangesNotification = 'Download complete. You can view a summary of the transformation and accept or reject the proposed changes in the Transformation Hub.' -export const changesAppliedChatMessage = (currentPatchIndex: number, totalPatchFiles: number) => - `I applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` - -export const changesAppliedNotification = (currentPatchIndex: number, totalPatchFiles: number) => - `Amazon Q applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` +export const changesAppliedChatMessage = ( + currentPatchIndex: number, + totalPatchFiles: number, + description: string | undefined +) => + description + ? `I applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project. ${patchDescriptions[description]} You can make a commit if the diff shows success. If the diff shows partial success, apply and fix the errors, and start a new transformation.` + : 'I applied the changes to your project.' + +export const changesAppliedNotification = ( + currentPatchIndex: number, + totalPatchFiles: number, + patchFilesDescriptions: DescriptionContent | undefined +) => + patchFilesDescriptions + ? `Amazon Q applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` + : 'Amazon Q applied the changes to your project.' export const noOpenProjectsFoundChatMessage = `I couldn\'t find a project that I can upgrade. Currently, I support Java 8, Java 11, and Java 17 projects built on Maven. Make sure your project is open in the IDE. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` @@ -686,15 +727,24 @@ export const chooseProjectSchemaFormMessage = 'To continue, choose the project a export const skipUnitTestsFormTitle = 'Choose to skip unit tests' +export const selectiveTransformationFormTitle = 'Choose to receive multiple diffs' + export const skipUnitTestsFormMessage = 'I will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`.' +export const selectiveTransformationFormMessage = + 'Would you like me to produce one diff with all of my proposed changes or divide my proposed changes into smaller sections?' + export const runUnitTestsMessage = 'Run unit tests' +export const oneDiffMessage = 'One diff' + export const doNotSkipUnitTestsBuildCommand = 'clean test' export const skipUnitTestsMessage = 'Skip unit tests' +export const multipleDiffsMessage = 'Multiple diffs' + export const skipUnitTestsBuildCommand = 'clean test-compile' export const planTitle = 'Code Transformation plan by Amazon Q' diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 8ac22f66560..5e07a0e77ac 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -114,7 +114,9 @@ export class PatchFileNode { constructor(description: PatchInfo | undefined = undefined, patchFilePath: string) { this.patchFilePath = patchFilePath - this.label = description ? description.name : path.basename(patchFilePath) + this.label = description + ? `${description.name} (${description.isSuccessful ? 'Success' : 'Failure'})` + : path.basename(patchFilePath) } } @@ -522,14 +524,21 @@ export class ProposedTransformationExplorer { diffModel.saveChanges() telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) void vscode.window.showInformationMessage( - CodeWhispererConstants.changesAppliedNotification(diffModel.currentPatchIndex, patchFiles.length) + CodeWhispererConstants.changesAppliedNotification( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ) ) //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch if (diffModel.currentPatchIndex === patchFiles.length - 1) { transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.changesAppliedChatMessage( diffModel.currentPatchIndex, - patchFiles.length + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined ), tabID: ChatSessionManager.Instance.getSession().tabID, includeStartNewTransformationButton: true, @@ -538,7 +547,10 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.changesAppliedChatMessage( diffModel.currentPatchIndex, - patchFiles.length + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined ), tabID: ChatSessionManager.Instance.getSession().tabID, includeStartNewTransformationButton: false, From 88bba94eaf936bb7cbe145c84b00385ca1c0fa8d Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Thu, 14 Nov 2024 15:17:58 -0800 Subject: [PATCH 13/22] added form to select one or multiple diffs and dynamically add to manifest --- .../chat/controller/controller.ts | 74 ++++------------- .../chat/controller/messenger/messenger.ts | 76 +++++------------- .../controller/messenger/messengerUtils.ts | 4 +- .../src/codewhisperer/client/codewhisperer.ts | 2 +- .../commands/startTransformByQ.ts | 9 ++- .../src/codewhisperer/models/constants.ts | 58 +++++++++----- .../core/src/codewhisperer/models/model.ts | 10 +++ .../transformByQ/transformApiHandler.ts | 4 + .../transformationResultsViewProvider.ts | 79 ++++++++++++------- 9 files changed, 147 insertions(+), 169 deletions(-) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 7acdfa956f4..e2cc5d69cb4 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -217,13 +217,6 @@ export class GumbyController { // if previous transformation was already running, show correct message to user switch (this.sessionStorage.getSession().conversationState) { case ConversationState.JOB_SUBMITTED: - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) - this.messenger.sendPatchDescriptionMessage(message.tabID) this.messenger.sendAsyncEventProgress( message.tabID, true, @@ -374,12 +367,12 @@ export class GumbyController { case ButtonActions.CANCEL_SKIP_TESTS_FORM: this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) break - // case ButtonActions.CONFIRM_SELECTIVE_TRANSFORMATION_FORM: - // await this.handleSelectiveTransformationSelection(message) - // break - // case ButtonActions.CANCEL_SELECTIVE_TRANSFORMATION_FORM: - // this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) - // break + case ButtonActions.CONFIRM_SELECTIVE_TRANSFORMATION_FORM: + await this.handleOneOrMultipleDiffs(message) + break + case ButtonActions.CANCEL_SELECTIVE_TRANSFORMATION_FORM: + this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) + break case ButtonActions.CONFIRM_SQL_CONVERSION_TRANSFORMATION_FORM: await this.handleUserSQLConversionProjectSelection(message) break @@ -388,13 +381,6 @@ export class GumbyController { break case ButtonActions.VIEW_TRANSFORMATION_HUB: await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat) - this.messenger.sendPatchDescriptionMessage(message.tabID) - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) this.messenger.sendJobSubmittedMessage(message.tabID) break case ButtonActions.STOP_TRANSFORMATION_JOB: @@ -437,20 +423,19 @@ export class GumbyController { result: MetadataResult.Pass, }) this.messenger.sendSkipTestsSelectionMessage(skipTestsSelection, message.tabID) + await this.messenger.sendOneOrMultipleDiffsPrompt(message.tabID) + } + + private async handleOneOrMultipleDiffs(message: any) { + const oneOrMultipleDiffsSelection = message.formSelectedValues['GumbyTransformOneOrMultipleDiffsForm'] + if (oneOrMultipleDiffsSelection === CodeWhispererConstants.multipleDiffsMessage) { + transformByQState.setMultipleDiffs(true) + } + this.messenger.sendOneOrMultipleDiffsMessage(oneOrMultipleDiffsSelection, message.tabID) // perform local build await this.validateBuildWithPromptOnError(message) } - // private async handleSelectiveTransformationSelection(message: any) { - // const selectiveTransformationSelection = message.formSelectedValues['GumbyTransformSelectiveTransformationForm'] - // if (selectiveTransformationSelection === CodeWhispererConstants.multipleDiffsMessage) { - // //Dynamically add to zipmanifest - // } - // this.messenger.sendSelectiveTransformationMessage(selectiveTransformationSelection, message.tabID) - // // perform local build - // // await this.validateBuildWithPromptOnError(message) - // } - private async handleUserLanguageUpgradeProjectChoice(message: any) { await telemetry.codeTransform_submitSelection.run(async () => { const pathToProject: string = message.formSelectedValues['GumbyTransformLanguageUpgradeProjectForm'] @@ -485,7 +470,6 @@ export class GumbyController { } await processLanguageUpgradeTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion) await this.messenger.sendSkipTestsPrompt(message.tabID) - // await this.messenger.sendSelectiveTransformationPrompt(message.tabID) }) } @@ -506,13 +490,6 @@ export class GumbyController { await processSQLConversionTransformFormInput(pathToProject, schema) - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) - this.messenger.sendPatchDescriptionMessage(message.tabID) this.messenger.sendAsyncEventProgress( message.tabID, true, @@ -558,13 +535,6 @@ export class GumbyController { // give user a non-blocking warning if build file appears to contain absolute paths await parseBuildFile() - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) - this.messenger.sendPatchDescriptionMessage(message.tabID) this.messenger.sendAsyncEventProgress( message.tabID, true, @@ -664,13 +634,6 @@ export class GumbyController { private async HILDependencySelectionUploaded(data: { tabID: string }) { this.sessionStorage.getSession().conversationState = ConversationState.JOB_SUBMITTED - this.messenger.sendPatchDescriptionMessage(data.tabID) - this.messenger.sendAsyncEventProgress( - data.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) this.messenger.sendHILResumeMessage(data.tabID) } @@ -736,13 +699,6 @@ export class GumbyController { } else if (message.error instanceof JobStartError) { this.resetTransformationChatFlow() } else if (message.error instanceof TransformationPreBuildError) { - this.messenger.sendPatchDescriptionMessage(message.tabID) - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_FAILED_IN_PRE_BUILD - ) this.messenger.sendJobSubmittedMessage(message.tabID, true) this.messenger.sendAsyncEventProgress( message.tabID, diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 951434c7928..2dce5b39e83 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -116,21 +116,21 @@ export class Messenger { this.dispatcher.sendAuthenticationUpdate(new AuthenticationUpdateMessage(gumbyEnabled, authenticatingTabIDs)) } - public async sendSelectiveTransformationPrompt(tabID: string) { + public async sendSkipTestsPrompt(tabID: string) { const formItems: ChatItemFormItem[] = [] formItems.push({ - id: 'GumbyTransformSelectiveTransformationForm', + id: 'GumbyTransformSkipTestsForm', type: 'select', - title: CodeWhispererConstants.selectiveTransformationFormTitle, + title: CodeWhispererConstants.skipUnitTestsFormTitle, mandatory: true, options: [ { - value: CodeWhispererConstants.oneDiffMessage, - label: CodeWhispererConstants.oneDiffMessage, + value: CodeWhispererConstants.runUnitTestsMessage, + label: CodeWhispererConstants.runUnitTestsMessage, }, { - value: CodeWhispererConstants.multipleDiffsMessage, - label: CodeWhispererConstants.multipleDiffsMessage, + value: CodeWhispererConstants.skipUnitTestsMessage, + label: CodeWhispererConstants.skipUnitTestsMessage, }, ], }) @@ -138,38 +138,38 @@ export class Messenger { this.dispatcher.sendAsyncEventProgress( new AsyncEventProgressMessage(tabID, { inProgress: true, - message: CodeWhispererConstants.selectiveTransformationFormMessage, + message: CodeWhispererConstants.skipUnitTestsFormMessage, }) ) this.dispatcher.sendChatPrompt( new ChatPrompt( { - message: 'Proposed Changes Result', + message: 'Q Code Transformation', formItems: formItems, }, - 'TransformSelectiveTransformationForm', + 'TransformSkipTestsForm', tabID, false ) ) } - public async sendSkipTestsPrompt(tabID: string) { + public async sendOneOrMultipleDiffsPrompt(tabID: string) { const formItems: ChatItemFormItem[] = [] formItems.push({ - id: 'GumbyTransformSkipTestsForm', + id: 'GumbyTransformOneOrMultipleDiffsForm', type: 'select', - title: CodeWhispererConstants.skipUnitTestsFormTitle, + title: CodeWhispererConstants.selectiveTransformationFormTitle, mandatory: true, options: [ { - value: CodeWhispererConstants.runUnitTestsMessage, - label: CodeWhispererConstants.runUnitTestsMessage, + value: CodeWhispererConstants.oneDiffMessage, + label: CodeWhispererConstants.oneDiffMessage, }, { - value: CodeWhispererConstants.skipUnitTestsMessage, - label: CodeWhispererConstants.skipUnitTestsMessage, + value: CodeWhispererConstants.multipleDiffsMessage, + label: CodeWhispererConstants.multipleDiffsMessage, }, ], }) @@ -177,7 +177,7 @@ export class Messenger { this.dispatcher.sendAsyncEventProgress( new AsyncEventProgressMessage(tabID, { inProgress: true, - message: CodeWhispererConstants.skipUnitTestsFormMessage, + message: CodeWhispererConstants.userPatchDescriptionChatMessage, }) ) @@ -187,7 +187,7 @@ export class Messenger { message: 'Q Code Transformation', formItems: formItems, }, - 'TransformSkipTestsForm', + 'TransformOneOrMultipleDiffsForm', tabID, false ) @@ -369,33 +369,6 @@ export class Messenger { ) } - public sendPatchDescriptionMessage( - tabID: string, - message: string = CodeWhispererConstants.userPatchDescriptionChatMessage - // messageID: string = GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) { - // const patchDescriptionChatMessage = new ChatMessage( - // { - // message, - // messageType: 'ai-prompt', - // messageId: messageID, - // }, - // tabID - // ) - this.dispatcher.sendAsyncEventProgress( - new AsyncEventProgressMessage(tabID, { - inProgress: true, - message, - }) - ) - this.dispatcher.sendAsyncEventProgress( - new AsyncEventProgressMessage(tabID, { - inProgress: false, - message: undefined, - }) - ) - } - public sendJobSubmittedMessage( tabID: string, disableJobActions: boolean = false, @@ -625,8 +598,8 @@ export class Messenger { this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) } - public sendSelectiveTransformationMessage(selectiveTransformationSelection: string, tabID: string) { - const message = `Okay, I will create ${selectiveTransformationSelection.toLowerCase()} when building your project.` + public sendOneOrMultipleDiffsMessage(selectiveTransformationSelection: string, tabID: string) { + const message = `Okay, I will create ${selectiveTransformationSelection.toLowerCase()} when providing the proposed changes.` this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) } @@ -744,13 +717,6 @@ ${codeSnippet} undefined, GumbyNamedMessages.JOB_SUBMISSION_WITH_DEPENDENCY_STATUS_MESSAGE ) - this.sendPatchDescriptionMessage(tabID) - this.sendAsyncEventProgress( - tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_WITH_DEPENDENCY_STATUS_MESSAGE - ) this.sendJobSubmittedMessage( tabID, false, diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index 9925c049605..86ba350b2e0 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -18,8 +18,8 @@ export enum ButtonActions { CANCEL_TRANSFORMATION_FORM = 'gumbyTransformFormCancel', // shared between Language Upgrade & SQL Conversion CONFIRM_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormConfirm', CANCEL_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormCancel', - CONFIRM_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformSelectiveTransformationFormConfirm', - CANCEL_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformSelectiveTransformationFormCancel', + CONFIRM_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformOneOrMultipleDiffsFormConfirm', + CANCEL_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformOneOrMultipleDiffsFormCancel', SELECT_SQL_CONVERSION_METADATA_FILE = 'gumbySQLConversionMetadataTransformFormConfirm', CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm', CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel', diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 5104ef7ede0..5043ad159f3 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -32,7 +32,7 @@ export interface CodeWhispererConfig { export const defaultServiceConfig: CodeWhispererConfig = { region: 'us-east-1', - endpoint: 'https://codewhisperer.us-east-1.amazonaws.com/', + endpoint: 'https://rts.alpha-us-west-2.codewhisperer.ai.aws.dev/', } export function getCodewhispererConfig(): CodeWhispererConfig { diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 24ffdadc4c1..4813033b29f 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -809,10 +809,11 @@ export async function postTransformationJob() { } let chatMessage = transformByQState.getJobFailureErrorChatMessage() + const diffMessage = CodeWhispererConstants.diffMessage(transformByQState.getMultipleDiffs()) if (transformByQState.isSucceeded()) { - chatMessage = CodeWhispererConstants.jobCompletedChatMessage + chatMessage = CodeWhispererConstants.jobCompletedChatMessage(diffMessage) } else if (transformByQState.isPartiallySucceeded()) { - chatMessage = CodeWhispererConstants.jobPartiallyCompletedChatMessage + chatMessage = CodeWhispererConstants.jobPartiallyCompletedChatMessage(diffMessage) } transformByQState.getChatControllers()?.transformationFinished.fire({ @@ -842,11 +843,11 @@ export async function postTransformationJob() { } if (transformByQState.isSucceeded()) { - void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification) + void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification(diffMessage)) } else if (transformByQState.isPartiallySucceeded()) { void vscode.window .showInformationMessage( - CodeWhispererConstants.jobPartiallyCompletedNotification, + CodeWhispererConstants.jobPartiallyCompletedNotification(diffMessage), CodeWhispererConstants.amazonQFeedbackText ) .then((choice) => { diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 32cb2bba484..518e5d257c4 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -25,16 +25,17 @@ export const AWSTemplateKeyWords = ['AWSTemplateFormatVersion', 'Resources', 'AW export const AWSTemplateCaseInsensitiveKeyWords = ['cloudformation', 'cfn', 'template', 'description'] const patchDescriptions: { [key: string]: string } = { - 'Minimal Compatible Library Upgrade to Java 17': + 'Prepare minimal upgrade to Java 17': 'This diff patch covers the set of upgrades for Springboot, JUnit, and PowerMockito frameworks.', - 'Popular Enterprise Specifications and Application Frameworks': + 'Popular Enterprise Specifications and Application Frameworks upgrade': 'This diff patch covers the set of upgrades for Jakarta EE 10, Hibernate 6.2, and Micronaut 3.', 'HTTP Client Utilities, Apache Commons Utilities, and Web Frameworks': 'This diff patch covers the set of upgrades for Apache HTTP Client 5, Apache Commons utilities (Collections, IO, Lang, Math), Struts 6.0.', - 'Testing Tools and Frameworks': + 'Testing Tools and Frameworks upgrade': 'This diff patch covers the set of upgrades for ArchUnit, Mockito, TestContainers, Cucumber, and additionally, Jenkins plugins and the Maven Wrapper.', - 'Miscellaneous Processing Documentation': + 'Miscellaneous Processing Documentation upgrade': 'This diff patch covers a diverse set of upgrades spanning ORMs, XML processing, API documentation, and more.', + 'Upgrade Deprecated API': '', } export const JsonConfigFileNamingConvention = new Set([ @@ -470,17 +471,17 @@ export const chooseTransformationObjective = `I can help you with the following export const chooseTransformationObjectivePlaceholder = 'Enter "language upgrade" or "sql conversion"' export const userPatchDescriptionChatMessage = ` -I will be dividing my proposed changes into smaller sections. Here is a description of what each section entails: +I can now divide the transformation results into diff patches if you would like to review and accept each diff with fewer changes: -• Minimal Compatible Library Upgrade to Java 17: This upgrades dependencies to the minimum compatible versions in Java 17. It also includes updated versions of Springboot as well as JUnit and PowerMockito frameworks. +• Minimal Compatible Library Upgrade to Java 17: Dependencies to the minimum compatible versions in Java 17, including Springboot, JUnit, and PowerMockito. -• Popular Enterprise Specifications Application Frameworks: This group aims to migrate to the latest versions of popular enterprise specifications and application frameworks like Jakarta EE 10 (the new javax namespace), Hibernate 6.2 (a widely used ORM), and Micronaut 3 (a modern, lightweight full-stack framework). +• Popular Enterprise Specifications Application Frameworks: Popular enterprise and application frameworks like Jakarta EE, Hibernate, and Micronaut 3. -• HTTP Client Utilities Web Frameworks: This section targets upgrades for HTTP client libraries (Apache HTTP Client 5), Apache Commons utilities (Collections, IO, Lang, Math), and web frameworks (Struts 6.0). The goal is to modernize these commonly used libraries and frameworks to their latest versions, ensuring compatibility with Java 17. +• HTTP Client Utilities Web Frameworks: HTTP client libraries, Apache Commons utilities, and Struts frameworks. -• Testing Tools Frameworks: This set upgrades targets testing tools and frameworks like ArchUnit, Mockito, TestContainers, and Cucumber. Additionally, it updates build tools like Jenkins plugins and the Maven Wrapper. The goal is to bring the testing ecosystem and build tooling up-to-date with the latest versions and best practices. +• Testing Tools Frameworks: Testing tools like ArchUnit, Mockito, and TestContainers and build tools like Jenkins and Maven Wrapper. -• Miscellaneous Processing Documentation: This group covers a diverse set of upgrades spanning ORMs (JpaRepository), XML processing (JAXB namespace), application servers (WebSphere to Liberty migration), API documentation (Swagger to SpringDoc/OpenAPI), and utilities (Okio, OkHttp, LaunchDarkly). +• Miscellaneous Processing Documentation: Upgrades ORMs, XML processing, and Swagger to SpringDoc/OpenAPI. ` export const uploadingCodeStepMessage = 'Upload your code' @@ -612,13 +613,27 @@ export const jobCancelledChatMessage = export const jobCancelledNotification = 'You cancelled the transformation.' -export const jobCompletedChatMessage = `I transformed your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.` +export const diffMessage = (multipleDiffs: boolean) => { + return multipleDiffs + ? 'You can review the diff to see my proposed changes and accept or reject them. If you reject the diff, you will not be able to see the diffs later.' + : 'You can review the diff to see my proposed changes and accept or reject them.' +} -export const jobCompletedNotification = `Amazon Q transformed your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.` +export const jobCompletedChatMessage = (multipleDiffsString: string) => { + return `I upgraded your code. ${multipleDiffsString} The transformation summary has details about the files I updated.` +} -export const jobPartiallyCompletedChatMessage = `I transformed part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` +export const jobCompletedNotification = (multipleDiffsString: string) => { + return `Amazon Q upgraded your code. ${multipleDiffsString} The transformation summary has details about the files I updated.` +} -export const jobPartiallyCompletedNotification = `Amazon Q transformed part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` +export const jobPartiallyCompletedChatMessage = (multipleDiffsString: string) => { + return `I upgraded part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` +} + +export const jobPartiallyCompletedNotification = (multipleDiffsString: string) => { + return `Amazon Q upgraded part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` +} export const noPomXmlFoundChatMessage = `I couldn\'t find a project that I can upgrade. I couldn\'t find a pom.xml file in any of your open projects, nor could I find any embedded SQL statements. Currently, I can upgrade Java 8 or Java 11 projects built on Maven, or Oracle SQL to PostgreSQL statements in Java projects. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` @@ -656,16 +671,20 @@ export const viewProposedChangesChatMessage = export const viewProposedChangesNotification = 'Download complete. You can view a summary of the transformation and accept or reject the proposed changes in the Transformation Hub.' -export const changesAppliedChatMessage = ( +export const changesAppliedChatMessageOneDiff = 'I applied the changes to your project.' + +export const changesAppliedChatMessageMultipleDiffs = ( currentPatchIndex: number, totalPatchFiles: number, description: string | undefined ) => description - ? `I applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project. ${patchDescriptions[description]} You can make a commit if the diff shows success. If the diff shows partial success, apply and fix the errors, and start a new transformation.` + ? `I applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project. ${patchDescriptions[description]}` : 'I applied the changes to your project.' -export const changesAppliedNotification = ( +export const changesAppliedNotificationOneDiff = 'Amazon Q applied the changes to your project' + +export const changesAppliedNotificationMultipleDiffs = ( currentPatchIndex: number, totalPatchFiles: number, patchFilesDescriptions: DescriptionContent | undefined @@ -727,14 +746,11 @@ export const chooseProjectSchemaFormMessage = 'To continue, choose the project a export const skipUnitTestsFormTitle = 'Choose to skip unit tests' -export const selectiveTransformationFormTitle = 'Choose to receive multiple diffs' +export const selectiveTransformationFormTitle = 'Choose how to receive proposed changes' export const skipUnitTestsFormMessage = 'I will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`.' -export const selectiveTransformationFormMessage = - 'Would you like me to produce one diff with all of my proposed changes or divide my proposed changes into smaller sections?' - export const runUnitTestsMessage = 'Run unit tests' export const oneDiffMessage = 'One diff' diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index ec7515aa222..51a1d3b6a33 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -409,6 +409,8 @@ export class TransformByQState { private targetJDKVersion: JDKVersion = JDKVersion.JDK17 + private produceMultipleDiffs: boolean = false + private customBuildCommand: string = '' private sourceDB: DB | undefined = undefined @@ -501,6 +503,10 @@ export class TransformByQState { return this.linesOfCodeSubmitted } + public getMultipleDiffs() { + return this.produceMultipleDiffs + } + public getPreBuildLogFilePath() { return this.preBuildLogFilePath } @@ -665,6 +671,10 @@ export class TransformByQState { this.linesOfCodeSubmitted = lines } + public setMultipleDiffs(produceMultipleDiffs: boolean) { + this.produceMultipleDiffs = produceMultipleDiffs + } + public setStartTime(time: string) { this.startTime = time } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 2ff2f16f621..71d5364e1de 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -322,6 +322,10 @@ export async function zipCode( getLogger().info(`CodeTransformation: source code files size = ${sourceFilesSize}`) } + if (transformByQState.getMultipleDiffs() && zipManifest instanceof ZipManifest) { + zipManifest.transformCapabilities.push('SELECTIVE_TRANSFORMATION_V1') + } + if ( transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION && zipManifest instanceof ZipManifest diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 5e07a0e77ac..c3b84a97c65 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -163,6 +163,7 @@ export class DiffModel { diffDescription: PatchInfo | undefined, totalDiffPatches: number ): PatchFileNode { + console.log('parsing ', pathToDiff) this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') @@ -172,31 +173,37 @@ export class DiffModel { } const changedFiles = parsePatch(diffContents) + console.log('changed files: ', changedFiles) // path to the directory containing copy of the changed files in the transformed project const pathToTmpSrcDir = this.copyProject(pathToWorkspace, changedFiles) transformByQState.setProjectCopyFilePath(pathToTmpSrcDir) + console.log('path to tmp src dir: ', pathToTmpSrcDir) applyPatches(changedFiles, { loadFile: function (fileObj, callback) { // load original contents of file const filePath = path.join(pathToWorkspace, fileObj.oldFileName!.substring(2)) + console.log(`loading filePath ${filePath}, exists = ${fs.existsSync(filePath)}`) if (!fs.existsSync(filePath)) { // must be a new file (ex. summary.md), so pass empty string as original contents and do not pass error callback(undefined, '') } else { // must be a modified file (most common), so pass original contents const fileContents = fs.readFileSync(filePath, 'utf-8') + console.log('original contents = ', fileContents) callback(undefined, fileContents) } }, // by now, 'content' contains the changes from the patch patched: function (fileObj, content, callback) { const filePath = path.join(pathToTmpSrcDir, fileObj.newFileName!.substring(2)) + console.log(`about to write ${content} to ${filePath}`) // write changed contents to the copy of the original file (or create a new file) fs.writeFileSync(filePath, content) callback(undefined) }, complete: function (err) { + console.log(`error = ${err}`) if (err) { getLogger().error(`CodeTransformation: ${err} when applying patch`) } else { @@ -430,7 +437,7 @@ export class ProposedTransformationExplorer { pathContainingArchive = path.dirname(pathToArchive) const zip = new AdmZip(pathToArchive) zip.extractAllTo(pathContainingArchive) - + console.log('pathContainingArchive = ', pathContainingArchive) const files = fs.readdirSync(path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch)) files.forEach((file) => { const filePath = path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch, file) @@ -454,7 +461,6 @@ export class ProposedTransformationExplorer { } else { patchFiles.push(singlePatchFile) } - //Because multiple patches are returned once the ZIP is downloaded, we want to show the first one to start diffModel.parseDiff( patchFiles[0], @@ -481,6 +487,7 @@ export class ProposedTransformationExplorer { await vscode.commands.executeCommand('aws.amazonq.transformationHub.summary.reveal') } catch (e: any) { deserializeErrorMessage = (e as Error).message + console.log('error parsing diff ', deserializeErrorMessage) getLogger().error(`CodeTransformation: ParseDiff error = ${deserializeErrorMessage}`) transformByQState.getChatControllers()?.transformationFinished.fire({ message: `${CodeWhispererConstants.errorDeserializingDiffChatMessage} ${deserializeErrorMessage}`, @@ -523,37 +530,50 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.acceptChanges', async () => { diffModel.saveChanges() telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) - void vscode.window.showInformationMessage( - CodeWhispererConstants.changesAppliedNotification( - diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions - ) - ) - //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch - if (diffModel.currentPatchIndex === patchFiles.length - 1) { - transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessage( + if (transformByQState.getMultipleDiffs()) { + void vscode.window.showInformationMessage( + CodeWhispererConstants.changesAppliedNotificationMultipleDiffs( diffModel.currentPatchIndex, patchFiles.length, patchFilesDescriptions - ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name - : undefined - ), - tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, - }) + ) + ) + } else { + void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotificationOneDiff) + } + + //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch + if (transformByQState.getMultipleDiffs()) { + if (diffModel.currentPatchIndex === patchFiles.length - 1) { + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined + ), + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: true, + }) + } else { + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined + ), + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: false, + }) + } } else { transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessage( - diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions - ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name - : undefined - ), + message: CodeWhispererConstants.changesAppliedChatMessageOneDiff, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: false, + includeStartNewTransformationButton: true, }) } @@ -592,6 +612,11 @@ export class ProposedTransformationExplorer { await reset() telemetry.ui_click.emit({ elementId: 'transformationHub_rejectChanges' }) + transformByQState.getChatControllers()?.transformationFinished.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: true, + }) + telemetry.codeTransform_viewArtifact.emit({ codeTransformArtifactType: 'ClientInstructions', codeTransformVCSViewerSrcComponents: 'toastNotification', From 519226283a063f619eb64b13b82e61d2111bd514 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 15 Nov 2024 08:43:18 -0800 Subject: [PATCH 14/22] fixing java 11 messaging --- .../amazonqGumby/chat/controller/messenger/messengerUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index 86ba350b2e0..7be7100b2c0 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -48,7 +48,7 @@ export default class MessengerUtils { if (jdkVersion === JDKVersion.JDK8) { javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(1.8)}` } else if (jdkVersion === JDKVersion.JDK11) { - javaHomePrompt += ` ${CodeWhispererConstants.macJava11HomeHelpChatMessage}` + javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(11)}` } } else { javaHomePrompt += ` ${CodeWhispererConstants.linuxJavaHomeHelpChatMessage}` From d9ac6eb8f234aff6bc8bb39b2254299a2143a50e Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 15 Nov 2024 09:19:35 -0800 Subject: [PATCH 15/22] removed redudnant check for one or multiple diffs when accepting changes --- .../src/codewhisperer/models/constants.ts | 1 + .../transformationResultsViewProvider.ts | 56 +++++++++---------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 518e5d257c4..c5a43e31579 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -36,6 +36,7 @@ const patchDescriptions: { [key: string]: string } = { 'Miscellaneous Processing Documentation upgrade': 'This diff patch covers a diverse set of upgrades spanning ORMs, XML processing, API documentation, and more.', 'Upgrade Deprecated API': '', + 'Updated dependencies to latest version': '', } export const JsonConfigFileNamingConvention = new Set([ diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index c3b84a97c65..7851d61f801 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -10,7 +10,13 @@ import { parsePatch, applyPatches, ParsedDiff } from 'diff' import path from 'path' import vscode from 'vscode' import { ExportIntent } from '@amzn/codewhisperer-streaming' -import { TransformByQReviewStatus, transformByQState, PatchInfo, DescriptionContent } from '../../models/model' +import { + TransformByQReviewStatus, + transformByQState, + PatchInfo, + DescriptionContent, + TransformationType, +} from '../../models/model' import { ExportResultArchiveStructure, downloadExportResultArchive } from '../../../shared/utilities/download' import { getLogger } from '../../../shared/logger' import { telemetry } from '../../../shared/telemetry/telemetry' @@ -543,38 +549,30 @@ export class ProposedTransformationExplorer { } //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch - if (transformByQState.getMultipleDiffs()) { - if (diffModel.currentPatchIndex === patchFiles.length - 1) { - transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( - diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions - ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name - : undefined - ), - tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, - }) - } else { - transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( - diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions - ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name - : undefined - ), - tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: false, - }) - } - } else { + if (diffModel.currentPatchIndex === patchFiles.length - 1) { transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessageOneDiff, + message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined + ), tabID: ChatSessionManager.Instance.getSession().tabID, includeStartNewTransformationButton: true, }) + } else { + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined + ), + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: false, + }) } // Load the next patch file From c509bebaa421bf76ce2e7e3cddd8700868c86a21 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 15 Nov 2024 15:19:26 -0800 Subject: [PATCH 16/22] fixing tests and endpoint --- .../unit/amazonqGumby/transformationResultsHandler.test.ts | 4 ++-- packages/core/src/codewhisperer/client/codewhisperer.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 7d63692fe46..d533eac5420 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -50,7 +50,7 @@ describe('DiffModel', function () { testDiffModel.patchFileNodes[0].patchFilePath, getTestResourceFilePath('resources/files/addedFile.diff') ) - assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions.content[0].name)) + assert(testDiffModel.patchFileNodes[0].label.includes(parsedTestDescriptions.content[0].name)) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) @@ -81,7 +81,7 @@ describe('DiffModel', function () { testDiffModel.patchFileNodes[0].patchFilePath, getTestResourceFilePath('resources/files/modifiedFile.diff') ) - assert(testDiffModel.patchFileNodes[0].label.endsWith(parsedTestDescriptions.content[0].name)) + assert(testDiffModel.patchFileNodes[0].label.includes(parsedTestDescriptions.content[0].name)) const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 5043ad159f3..5104ef7ede0 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -32,7 +32,7 @@ export interface CodeWhispererConfig { export const defaultServiceConfig: CodeWhispererConfig = { region: 'us-east-1', - endpoint: 'https://rts.alpha-us-west-2.codewhisperer.ai.aws.dev/', + endpoint: 'https://codewhisperer.us-east-1.amazonaws.com/', } export function getCodewhispererConfig(): CodeWhispererConfig { From e63ab970b78ec36d7c996ab6f658f5ddfac1b53f Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Fri, 15 Nov 2024 17:13:22 -0800 Subject: [PATCH 17/22] addressing minor comments and fixing chat messaging --- packages/amazonq/package.json | 2 +- .../chat/controller/messenger/messenger.ts | 4 +-- .../src/codewhisperer/models/constants.ts | 2 +- .../transformationResultsViewProvider.ts | 35 ++++++++++--------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index f0478d5f2e0..e63c0ac02b0 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -1095,6 +1095,6 @@ }, "engines": { "npm": "^10.1.0", - "vscode": "^1.68.0" + "vscode": "^1.83.0" } } diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 2dce5b39e83..e532cc59c47 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -224,11 +224,11 @@ export class Messenger { options: [ { value: JDKVersion.JDK8, - label: JDKVersion.JDK8.toString(), + label: JDKVersion.JDK8, }, { value: JDKVersion.JDK11, - label: JDKVersion.JDK11.toString(), + label: JDKVersion.JDK11, }, { value: JDKVersion.UNSUPPORTED, diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index c5a43e31579..572e9c0d80f 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -472,7 +472,7 @@ export const chooseTransformationObjective = `I can help you with the following export const chooseTransformationObjectivePlaceholder = 'Enter "language upgrade" or "sql conversion"' export const userPatchDescriptionChatMessage = ` -I can now divide the transformation results into diff patches if you would like to review and accept each diff with fewer changes: +I can now divide the transformation results into diff patches (if applicable to the app) if you would like to review and accept each diff with fewer changes: • Minimal Compatible Library Upgrade to Java 17: Dependencies to the minimum compatible versions in Java 17, including Springboot, JUnit, and PowerMockito. diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 7851d61f801..cbd591328ff 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -169,7 +169,6 @@ export class DiffModel { diffDescription: PatchInfo | undefined, totalDiffPatches: number ): PatchFileNode { - console.log('parsing ', pathToDiff) this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') @@ -179,37 +178,31 @@ export class DiffModel { } const changedFiles = parsePatch(diffContents) - console.log('changed files: ', changedFiles) // path to the directory containing copy of the changed files in the transformed project const pathToTmpSrcDir = this.copyProject(pathToWorkspace, changedFiles) transformByQState.setProjectCopyFilePath(pathToTmpSrcDir) - console.log('path to tmp src dir: ', pathToTmpSrcDir) applyPatches(changedFiles, { loadFile: function (fileObj, callback) { // load original contents of file const filePath = path.join(pathToWorkspace, fileObj.oldFileName!.substring(2)) - console.log(`loading filePath ${filePath}, exists = ${fs.existsSync(filePath)}`) if (!fs.existsSync(filePath)) { // must be a new file (ex. summary.md), so pass empty string as original contents and do not pass error callback(undefined, '') } else { // must be a modified file (most common), so pass original contents const fileContents = fs.readFileSync(filePath, 'utf-8') - console.log('original contents = ', fileContents) callback(undefined, fileContents) } }, // by now, 'content' contains the changes from the patch patched: function (fileObj, content, callback) { const filePath = path.join(pathToTmpSrcDir, fileObj.newFileName!.substring(2)) - console.log(`about to write ${content} to ${filePath}`) // write changed contents to the copy of the original file (or create a new file) fs.writeFileSync(filePath, content) callback(undefined) }, complete: function (err) { - console.log(`error = ${err}`) if (err) { getLogger().error(`CodeTransformation: ${err} when applying patch`) } else { @@ -443,17 +436,26 @@ export class ProposedTransformationExplorer { pathContainingArchive = path.dirname(pathToArchive) const zip = new AdmZip(pathToArchive) zip.extractAllTo(pathContainingArchive) - console.log('pathContainingArchive = ', pathContainingArchive) const files = fs.readdirSync(path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch)) - files.forEach((file) => { - const filePath = path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch, file) - if (file === 'diff.patch') { - singlePatchFile = filePath - } else if (file.endsWith('.json')) { - const jsonData = fs.readFileSync(filePath, 'utf-8') - patchFilesDescriptions = JSON.parse(jsonData) + if (files.length === 1) { + singlePatchFile = path.join( + pathContainingArchive, + ExportResultArchiveStructure.PathToPatch, + files[0] + ) + } else { + const jsonFile = files.find((file) => file.endsWith('.json')) + if (!jsonFile) { + throw new Error('Expected JSON file not found') } - }) + const filePath = path.join( + pathContainingArchive, + ExportResultArchiveStructure.PathToPatch, + jsonFile + ) + const jsonData = fs.readFileSync(filePath, 'utf-8') + patchFilesDescriptions = JSON.parse(jsonData) + } if (patchFilesDescriptions !== undefined) { for (const patchInfo of patchFilesDescriptions.content) { patchFiles.push( @@ -493,7 +495,6 @@ export class ProposedTransformationExplorer { await vscode.commands.executeCommand('aws.amazonq.transformationHub.summary.reveal') } catch (e: any) { deserializeErrorMessage = (e as Error).message - console.log('error parsing diff ', deserializeErrorMessage) getLogger().error(`CodeTransformation: ParseDiff error = ${deserializeErrorMessage}`) transformByQState.getChatControllers()?.transformationFinished.fire({ message: `${CodeWhispererConstants.errorDeserializingDiffChatMessage} ${deserializeErrorMessage}`, From adda8afa6da9c146e7c0c7d539d8c64d96421c46 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Sat, 16 Nov 2024 17:46:54 -0800 Subject: [PATCH 18/22] fixing tests and addressing comments to remove redundancy --- .../transformationResultsHandler.test.ts | 29 ++++++-------- .../chat/controller/controller.ts | 2 +- .../chat/controller/messenger/messenger.ts | 4 ++ .../controller/messenger/messengerUtils.ts | 2 + .../commands/startTransformByQ.ts | 3 +- .../transformByQ/transformApiHandler.ts | 1 - .../transformationResultsViewProvider.ts | 40 +++++-------------- 7 files changed, 33 insertions(+), 48 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index d533eac5420..f9ea15b61c9 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -4,12 +4,13 @@ */ import assert from 'assert' import sinon from 'sinon' -import os from 'os' import { DiffModel, AddedChangeNode, ModifiedChangeNode } from 'aws-core-vscode/codewhisperer/node' import { DescriptionContent } from 'aws-core-vscode/codewhisperer' import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' +// eslint-disable-next-line no-restricted-imports +import { createTestWorkspace } from '../../../../core/dist/src/test/testUtil' describe('DiffModel', function () { let parsedTestDescriptions: DescriptionContent @@ -44,8 +45,6 @@ describe('DiffModel', function () { 1 ) - assert.strictEqual(testDiffModel.patchFileNodes.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) assert.strictEqual( testDiffModel.patchFileNodes[0].patchFilePath, getTestResourceFilePath('resources/files/addedFile.diff') @@ -59,24 +58,23 @@ describe('DiffModel', function () { it('WHEN parsing a diff patch where a file was modified THEN returns an array representing the modified file', async function () { const testDiffModel = new DiffModel() - const workspacePath = os.tmpdir() + const fileAmount = 1 + const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) sinon.replace(fs, 'exists', async (path) => true) await fs.writeFile( - path.join(workspacePath, 'README.md'), + path.join(workspaceFolder.uri.fsPath, 'README.md'), 'This guide walks you through using Gradle to build a simple Java project.' ) testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), - workspacePath, + workspaceFolder.uri.fsPath, parsedTestDescriptions.content[0], 1 ) - assert.strictEqual(testDiffModel.patchFileNodes.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) assert.strictEqual( testDiffModel.patchFileNodes[0].patchFilePath, getTestResourceFilePath('resources/files/modifiedFile.diff') @@ -86,30 +84,29 @@ describe('DiffModel', function () { assert.strictEqual(change instanceof ModifiedChangeNode, true) - await fs.delete(path.join(workspacePath, 'README.md'), { recursive: true }) + await fs.delete(path.join(workspaceFolder.uri.fsPath, 'README.md'), { recursive: true }) }) it('WHEN parsing a diff patch where diff.json is not present and a file was modified THEN returns an array representing the modified file', async function () { - const testDiffModel = new DiffModel() + const testDiffModel = new DiffModel() //try to see if you can reuse that test model from above - const workspacePath = os.tmpdir() + const fileAmount = 1 + const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) sinon.replace(fs, 'exists', async (path) => true) await fs.writeFile( - path.join(workspacePath, 'README.md'), + path.join(workspaceFolder.uri.fsPath, 'README.md'), 'This guide walks you through using Gradle to build a simple Java project.' ) testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), - workspacePath, + workspaceFolder.uri.fsPath, undefined, 1 ) - assert.strictEqual(testDiffModel.patchFileNodes.length, 1) - assert.strictEqual(testDiffModel.patchFileNodes[0].children.length, 1) assert.strictEqual( testDiffModel.patchFileNodes[0].patchFilePath, getTestResourceFilePath('resources/files/modifiedFile.diff') @@ -119,6 +116,6 @@ describe('DiffModel', function () { assert.strictEqual(change instanceof ModifiedChangeNode, true) - await fs.delete(path.join(workspacePath, 'README.md'), { recursive: true }) + await fs.delete(path.join(workspaceFolder.uri.fsPath, 'README.md'), { recursive: true }) }) }) diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index e2cc5d69cb4..07e946bcdae 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -632,7 +632,7 @@ export class GumbyController { this.messenger.sendDependencyVersionsFoundMessage(data.dependencies, data.tabID) } - private async HILDependencySelectionUploaded(data: { tabID: string }) { + private HILDependencySelectionUploaded(data: { tabID: string }) { this.sessionStorage.getSession().conversationState = ConversationState.JOB_SUBMITTED this.messenger.sendHILResumeMessage(data.tabID) } diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index e532cc59c47..a3f0f9741a0 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -230,6 +230,10 @@ export class Messenger { value: JDKVersion.JDK11, label: JDKVersion.JDK11, }, + { + value: JDKVersion.JDK17, + label: JDKVersion.JDK17, + }, { value: JDKVersion.UNSUPPORTED, label: 'Other', diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index 7be7100b2c0..a7b15810312 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -49,6 +49,8 @@ export default class MessengerUtils { javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(1.8)}` } else if (jdkVersion === JDKVersion.JDK11) { javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(11)}` + } else if (jdkVersion === JDKVersion.JDK17) { + javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(17)}` } } else { javaHomePrompt += ` ${CodeWhispererConstants.linuxJavaHomeHelpChatMessage}` diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 4813033b29f..76e51fa9a9b 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -177,6 +177,8 @@ async function validateJavaHome(): Promise { javaVersionUsedByMaven = JDKVersion.JDK8 } else if (javaVersionUsedByMaven === '11.') { javaVersionUsedByMaven = JDKVersion.JDK11 + } else if (javaVersionUsedByMaven === '17.') { + javaVersionUsedByMaven = JDKVersion.JDK17 } } if (javaVersionUsedByMaven !== transformByQState.getSourceJDKVersion()) { @@ -819,7 +821,6 @@ export async function postTransformationJob() { transformByQState.getChatControllers()?.transformationFinished.fire({ message: chatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, }) const durationInMs = calculateTotalLatency(CodeTransformTelemetryState.instance.getStartTime()) const resultStatusMessage = transformByQState.getStatus() diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 71d5364e1de..db9dca790db 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -409,7 +409,6 @@ export async function zipCode( transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.projectSizeTooLargeChatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, }) throw new ZipExceedsSizeLimitError() } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index cbd591328ff..11fd13304a0 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -420,7 +420,6 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: `${CodeWhispererConstants.errorDownloadingDiffChatMessage} The download failed due to: ${downloadErrorMessage}`, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, }) await setContext('gumby.reviewState', TransformByQReviewStatus.NotStarted) getLogger().error(`CodeTransformation: ExportResultArchive error = ${downloadErrorMessage}`) @@ -490,7 +489,6 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: CodeWhispererConstants.viewProposedChangesChatMessage, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, }) await vscode.commands.executeCommand('aws.amazonq.transformationHub.summary.reveal') } catch (e: any) { @@ -499,7 +497,6 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ message: `${CodeWhispererConstants.errorDeserializingDiffChatMessage} ${deserializeErrorMessage}`, tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, }) void vscode.window.showErrorMessage( `${CodeWhispererConstants.errorDeserializingDiffNotification} ${deserializeErrorMessage}` @@ -550,31 +547,17 @@ export class ProposedTransformationExplorer { } //We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch - if (diffModel.currentPatchIndex === patchFiles.length - 1) { - transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( - diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions - ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name - : undefined - ), - tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, - }) - } else { - transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( - diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions - ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name - : undefined - ), - tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: false, - }) - } + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined + ), + tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: diffModel.currentPatchIndex === patchFiles.length - 1, + }) // Load the next patch file diffModel.currentPatchIndex++ @@ -613,7 +596,6 @@ export class ProposedTransformationExplorer { transformByQState.getChatControllers()?.transformationFinished.fire({ tabID: ChatSessionManager.Instance.getSession().tabID, - includeStartNewTransformationButton: true, }) telemetry.codeTransform_viewArtifact.emit({ From 9342606f8f49f85f8d048747a8bd0c9e22d6eb48 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Sun, 17 Nov 2024 17:08:17 -0800 Subject: [PATCH 19/22] updating test import and adding changelog --- .../Feature-735529a9-8424-4f78-9989-58ec972af100.json | 4 ++++ .../unit/amazonqGumby/transformationResultsHandler.test.ts | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json diff --git a/packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json b/packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json new file mode 100644 index 00000000000..6792b4ae485 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Feature(Amazon Q Code Transformation): allow users to view results in 5 smaller diffs" +} diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index f9ea15b61c9..4873164bd50 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -9,8 +9,7 @@ import { DescriptionContent } from 'aws-core-vscode/codewhisperer' import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' -// eslint-disable-next-line no-restricted-imports -import { createTestWorkspace } from '../../../../core/dist/src/test/testUtil' +import { createTestWorkspace } from 'aws-core-vscode/test' describe('DiffModel', function () { let parsedTestDescriptions: DescriptionContent @@ -83,8 +82,6 @@ describe('DiffModel', function () { const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) - - await fs.delete(path.join(workspaceFolder.uri.fsPath, 'README.md'), { recursive: true }) }) it('WHEN parsing a diff patch where diff.json is not present and a file was modified THEN returns an array representing the modified file', async function () { @@ -115,7 +112,5 @@ describe('DiffModel', function () { const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) - - await fs.delete(path.join(workspaceFolder.uri.fsPath, 'README.md'), { recursive: true }) }) }) From 7b85bfbd3545a8e458d19941fccc1d73d9b49ba2 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Tue, 19 Nov 2024 13:48:49 -0800 Subject: [PATCH 20/22] addressing comments from PR and adding feature flag --- ...-735529a9-8424-4f78-9989-58ec972af100.json | 4 --- .../transformationResultsHandler.test.ts | 22 ++----------- .../chat/controller/controller.ts | 8 ++++- .../src/codewhisperer/models/constants.ts | 31 +++++++++---------- .../transformationResultsViewProvider.ts | 7 +++-- packages/core/src/dev/config.ts | 3 ++ 6 files changed, 32 insertions(+), 43 deletions(-) delete mode 100644 packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json diff --git a/packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json b/packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json deleted file mode 100644 index 6792b4ae485..00000000000 --- a/packages/amazonq/.changes/next-release/Feature-735529a9-8424-4f78-9989-58ec972af100.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "Feature(Amazon Q Code Transformation): allow users to view results in 5 smaller diffs" -} diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 4873164bd50..05e543ef5e3 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -6,18 +6,14 @@ import assert from 'assert' import sinon from 'sinon' import { DiffModel, AddedChangeNode, ModifiedChangeNode } from 'aws-core-vscode/codewhisperer/node' import { DescriptionContent } from 'aws-core-vscode/codewhisperer' -import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' import { createTestWorkspace } from 'aws-core-vscode/test' describe('DiffModel', function () { let parsedTestDescriptions: DescriptionContent - beforeEach(() => { - const fs = require('fs') - parsedTestDescriptions = JSON.parse( - fs.readFileSync(getTestResourceFilePath('resources/files/diff.json'), 'utf-8') - ) + beforeEach(async () => { + parsedTestDescriptions = JSON.parse(await fs.readFileText(getTestResourceFilePath('resources/files/diff.json'))) }) afterEach(() => { @@ -60,13 +56,6 @@ describe('DiffModel', function () { const fileAmount = 1 const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) - sinon.replace(fs, 'exists', async (path) => true) - - await fs.writeFile( - path.join(workspaceFolder.uri.fsPath, 'README.md'), - 'This guide walks you through using Gradle to build a simple Java project.' - ) - testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspaceFolder.uri.fsPath, @@ -90,13 +79,6 @@ describe('DiffModel', function () { const fileAmount = 1 const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) - sinon.replace(fs, 'exists', async (path) => true) - - await fs.writeFile( - path.join(workspaceFolder.uri.fsPath, 'README.md'), - 'This guide walks you through using Gradle to build a simple Java project.' - ) - testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspaceFolder.uri.fsPath, diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 07e946bcdae..a99631e29c1 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -62,6 +62,7 @@ import { getStringHash } from '../../../shared/utilities/textUtilities' import { getVersionData } from '../../../codewhisperer/service/transformByQ/transformMavenHandler' import AdmZip from 'adm-zip' import { AuthError } from '../../../auth/sso/server' +import { isSelectiveTransformationReady } from '../../../dev/config' // These events can be interactions within the chat, // or elsewhere in the IDE @@ -423,7 +424,12 @@ export class GumbyController { result: MetadataResult.Pass, }) this.messenger.sendSkipTestsSelectionMessage(skipTestsSelection, message.tabID) - await this.messenger.sendOneOrMultipleDiffsPrompt(message.tabID) + if (!isSelectiveTransformationReady) { + // perform local build + await this.validateBuildWithPromptOnError(message) + } else { + await this.messenger.sendOneOrMultipleDiffsPrompt(message.tabID) + } } private async handleOneOrMultipleDiffs(message: any) { diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 572e9c0d80f..055fd15923f 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { DescriptionContent } from '..' - /** * Automated and manual trigger */ @@ -35,8 +33,8 @@ const patchDescriptions: { [key: string]: string } = { 'This diff patch covers the set of upgrades for ArchUnit, Mockito, TestContainers, Cucumber, and additionally, Jenkins plugins and the Maven Wrapper.', 'Miscellaneous Processing Documentation upgrade': 'This diff patch covers a diverse set of upgrades spanning ORMs, XML processing, API documentation, and more.', - 'Upgrade Deprecated API': '', - 'Updated dependencies to latest version': '', + 'Deprecated API replacement and dependency upgrades': + 'This diff patch replaces deprecated APIs and makes additional dependency version upgrades.', } export const JsonConfigFileNamingConvention = new Set([ @@ -483,6 +481,8 @@ I can now divide the transformation results into diff patches (if applicable to • Testing Tools Frameworks: Testing tools like ArchUnit, Mockito, and TestContainers and build tools like Jenkins and Maven Wrapper. • Miscellaneous Processing Documentation: Upgrades ORMs, XML processing, and Swagger to SpringDoc/OpenAPI. + +• Deprecated API replacement and dependency upgrades: Replaces deprecated APIs and makes additional dependency version upgrades. ` export const uploadingCodeStepMessage = 'Upload your code' @@ -621,19 +621,19 @@ export const diffMessage = (multipleDiffs: boolean) => { } export const jobCompletedChatMessage = (multipleDiffsString: string) => { - return `I upgraded your code. ${multipleDiffsString} The transformation summary has details about the files I updated.` + return `I transformed your code. ${multipleDiffsString} The transformation summary has details about the files I updated.` } export const jobCompletedNotification = (multipleDiffsString: string) => { - return `Amazon Q upgraded your code. ${multipleDiffsString} The transformation summary has details about the files I updated.` + return `Amazon Q transformed your code. ${multipleDiffsString} The transformation summary has details about the files I updated.` } export const jobPartiallyCompletedChatMessage = (multipleDiffsString: string) => { - return `I upgraded part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` + return `I transformed part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` } export const jobPartiallyCompletedNotification = (multipleDiffsString: string) => { - return `Amazon Q upgraded part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` + return `Amazon Q transformed part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` } export const noPomXmlFoundChatMessage = `I couldn\'t find a project that I can upgrade. I couldn\'t find a pom.xml file in any of your open projects, nor could I find any embedded SQL statements. Currently, I can upgrade Java 8 or Java 11 projects built on Maven, or Oracle SQL to PostgreSQL statements in Java projects. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` @@ -685,14 +685,13 @@ export const changesAppliedChatMessageMultipleDiffs = ( export const changesAppliedNotificationOneDiff = 'Amazon Q applied the changes to your project' -export const changesAppliedNotificationMultipleDiffs = ( - currentPatchIndex: number, - totalPatchFiles: number, - patchFilesDescriptions: DescriptionContent | undefined -) => - patchFilesDescriptions - ? `Amazon Q applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` - : 'Amazon Q applied the changes to your project.' +export const changesAppliedNotificationMultipleDiffs = (currentPatchIndex: number, totalPatchFiles: number) => { + if (totalPatchFiles === 1) { + return 'Amazon Q applied the changes to your project.' + } else { + return `Amazon Q applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` + } +} export const noOpenProjectsFoundChatMessage = `I couldn\'t find a project that I can upgrade. Currently, I support Java 8, Java 11, and Java 17 projects built on Maven. Make sure your project is open in the IDE. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 11fd13304a0..3b851094aed 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -533,13 +533,16 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.acceptChanges', async () => { diffModel.saveChanges() + telemetry.codeTransform_submitSelection.emit({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + userChoice: `acceptChanges-${patchFilesDescriptions?.content[diffModel.currentPatchIndex].name}`, + }) telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) if (transformByQState.getMultipleDiffs()) { void vscode.window.showInformationMessage( CodeWhispererConstants.changesAppliedNotificationMultipleDiffs( diffModel.currentPatchIndex, - patchFiles.length, - patchFilesDescriptions + patchFiles.length ) ) } else { diff --git a/packages/core/src/dev/config.ts b/packages/core/src/dev/config.ts index d5fa49b2426..ece2f09fed7 100644 --- a/packages/core/src/dev/config.ts +++ b/packages/core/src/dev/config.ts @@ -10,3 +10,6 @@ export const betaUrl = { amazonq: '', toolkit: '', } + +//feature flag for Selective Transformation +export const isSelectiveTransformationReady = false From 7a0f743392bbe5dd9e50bf2231df6e8d0ce718d7 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Tue, 19 Nov 2024 14:49:54 -0800 Subject: [PATCH 21/22] fixing failed unit tests --- .../transformationResultsHandler.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 05e543ef5e3..4f2342caafa 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -6,6 +6,7 @@ import assert from 'assert' import sinon from 'sinon' import { DiffModel, AddedChangeNode, ModifiedChangeNode } from 'aws-core-vscode/codewhisperer/node' import { DescriptionContent } from 'aws-core-vscode/codewhisperer' +import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' import { fs } from 'aws-core-vscode/shared' import { createTestWorkspace } from 'aws-core-vscode/test' @@ -56,6 +57,13 @@ describe('DiffModel', function () { const fileAmount = 1 const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) + sinon.replace(fs, 'exists', async (path) => true) + + await fs.writeFile( + path.join(workspaceFolder.uri.fsPath, 'README.md'), + 'This guide walks you through using Gradle to build a simple Java project.' + ) + testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspaceFolder.uri.fsPath, @@ -79,6 +87,13 @@ describe('DiffModel', function () { const fileAmount = 1 const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) + sinon.replace(fs, 'exists', async (path) => true) + + await fs.writeFile( + path.join(workspaceFolder.uri.fsPath, 'README.md'), + 'This guide walks you through using Gradle to build a simple Java project.' + ) + testDiffModel.parseDiff( getTestResourceFilePath('resources/files/modifiedFile.diff'), workspaceFolder.uri.fsPath, From d6c2229f4fecaa4749dd6468f90832e3840a77a3 Mon Sep 17 00:00:00 2001 From: Neha Tarakad Date: Tue, 19 Nov 2024 19:58:18 -0800 Subject: [PATCH 22/22] updated test file --- .../unit/amazonqGumby/transformationResultsHandler.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 4f2342caafa..4e1ce627bd3 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -57,8 +57,6 @@ describe('DiffModel', function () { const fileAmount = 1 const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) - sinon.replace(fs, 'exists', async (path) => true) - await fs.writeFile( path.join(workspaceFolder.uri.fsPath, 'README.md'), 'This guide walks you through using Gradle to build a simple Java project.' @@ -82,13 +80,11 @@ describe('DiffModel', function () { }) it('WHEN parsing a diff patch where diff.json is not present and a file was modified THEN returns an array representing the modified file', async function () { - const testDiffModel = new DiffModel() //try to see if you can reuse that test model from above + const testDiffModel = new DiffModel() const fileAmount = 1 const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) - sinon.replace(fs, 'exists', async (path) => true) - await fs.writeFile( path.join(workspaceFolder.uri.fsPath, 'README.md'), 'This guide walks you through using Gradle to build a simple Java project.'