Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(amazon q): display multiple diff patches #5812

Merged
merged 22 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f04eb9c
adding functionality to allow jdk17 as source and display multiple gi…
ntarakad-aws Oct 18, 2024
598ce08
addressing comments to add to models and minor edits
ntarakad-aws Oct 18, 2024
6481abc
editing the clear changes and updating test file
ntarakad-aws Oct 18, 2024
631d3f7
editing test file to use models patchinfo
ntarakad-aws Oct 18, 2024
5343877
fixing package files that we don't want in commit
ntarakad-aws Oct 18, 2024
6cd90c2
added test cases for new parseDiff logic
ntarakad-aws Oct 21, 2024
693d4be
removing redundant unit test
ntarakad-aws Oct 21, 2024
f55cd36
updated tests to account for no diff.json and performed e2e transform…
ntarakad-aws Oct 23, 2024
fb3436f
added numbering to make it obvious to user which patch file they're v…
ntarakad-aws Oct 29, 2024
bd8f324
updated json structure based on backend and now collecting patchfiles…
ntarakad-aws Nov 1, 2024
1f38262
adding additional transformationFinished parameter
ntarakad-aws Nov 8, 2024
d4ef664
added patch description chat message and working on user input for on…
ntarakad-aws Nov 12, 2024
88bba94
added form to select one or multiple diffs and dynamically add to man…
ntarakad-aws Nov 14, 2024
5192262
fixing java 11 messaging
ntarakad-aws Nov 15, 2024
d9ac6eb
removed redudnant check for one or multiple diffs when accepting changes
ntarakad-aws Nov 15, 2024
c509beb
fixing tests and endpoint
ntarakad-aws Nov 15, 2024
e63ab97
addressing minor comments and fixing chat messaging
ntarakad-aws Nov 16, 2024
adda8af
fixing tests and addressing comments to remove redundancy
ntarakad-aws Nov 17, 2024
9342606
updating test import and adding changelog
ntarakad-aws Nov 18, 2024
7b85bfb
addressing comments from PR and adding feature flag
ntarakad-aws Nov 19, 2024
7a0f743
fixing failed unit tests
ntarakad-aws Nov 19, 2024
d6c2229
updated test file
ntarakad-aws Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"content": [
{
"name": "Added file",
"fileName": "resources/files/addedFile.diff",
"isSuccessful": true
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
*/
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'
import { createTestWorkspace } from 'aws-core-vscode/test'

describe('DiffModel', function () {
let parsedTestDescriptions: DescriptionContent
beforeEach(async () => {
parsedTestDescriptions = JSON.parse(await fs.readFileText(getTestResourceFilePath('resources/files/diff.json')))
})

afterEach(() => {
sinon.restore()
})
Expand All @@ -28,34 +34,76 @@ describe('DiffModel', function () {

return true
})
testDiffModel.parseDiff(
getTestResourceFilePath('resources/files/addedFile.diff'),
workspacePath,
parsedTestDescriptions.content[0],
1
)

testDiffModel.parseDiff(getTestResourceFilePath('resources/files/addedFile.diff'), workspacePath)

assert.strictEqual(testDiffModel.changes.length, 1)
const change = testDiffModel.changes[0]
assert.strictEqual(
testDiffModel.patchFileNodes[0].patchFilePath,
getTestResourceFilePath('resources/files/addedFile.diff')
)
assert(testDiffModel.patchFileNodes[0].label.includes(parsedTestDescriptions.content[0].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()

const workspacePath = os.tmpdir()

sinon.replace(fs, 'exists', async (path) => true)
const fileAmount = 1
const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' })

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)
testDiffModel.parseDiff(
getTestResourceFilePath('resources/files/modifiedFile.diff'),
workspaceFolder.uri.fsPath,
parsedTestDescriptions.content[0],
1
)

assert.strictEqual(testDiffModel.changes.length, 1)
const change = testDiffModel.changes[0]
assert.strictEqual(
testDiffModel.patchFileNodes[0].patchFilePath,
getTestResourceFilePath('resources/files/modifiedFile.diff')
)
assert(testDiffModel.patchFileNodes[0].label.includes(parsedTestDescriptions.content[0].name))
const change = testDiffModel.patchFileNodes[0].children[0]

assert.strictEqual(change instanceof ModifiedChangeNode, 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()

await fs.delete(path.join(workspacePath, 'README.md'), { recursive: true })
const fileAmount = 1
const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' })

await fs.writeFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our CI caught this block being duplicated, consider making a function like makeReadme(). This will not block the PR

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,
undefined,
1
)

assert.strictEqual(
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)
})
})
38 changes: 34 additions & 4 deletions packages/core/src/amazonqGumby/chat/controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -358,6 +359,7 @@ export class GumbyController {
this.transformationFinished({
message: CodeWhispererConstants.jobCancelledChatMessage,
tabID: message.tabID,
includeStartNewTransformationButton: true,
})
break
case ButtonActions.CONFIRM_SKIP_TESTS_FORM:
Expand All @@ -366,6 +368,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.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
Expand Down Expand Up @@ -416,6 +424,20 @@ export class GumbyController {
result: MetadataResult.Pass,
})
this.messenger.sendSkipTestsSelectionMessage(skipTestsSelection, message.tabID)
if (!isSelectiveTransformationReady) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this is just a feature flag, waiting for our backend work to be ready, will merge with this flag until then

// perform local build
await this.validateBuildWithPromptOnError(message)
} else {
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)
}
Expand Down Expand Up @@ -452,7 +474,6 @@ export class GumbyController {
this.messenger.sendUnrecoverableErrorResponse('unsupported-source-jdk-version', message.tabID)
return
}

await processLanguageUpgradeTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion)
await this.messenger.sendSkipTestsPrompt(message.tabID)
})
Expand Down Expand Up @@ -563,6 +584,7 @@ export class GumbyController {
this.transformationFinished({
message: CodeWhispererConstants.jobCancelledChatMessage,
tabID: message.tabID,
includeStartNewTransformationButton: true,
})
return
}
Expand Down Expand Up @@ -591,11 +613,15 @@ export class GumbyController {
)
}

private transformationFinished(data: { message: string | undefined; tabID: string }) {
private transformationFinished(data: {
message: string | undefined
tabID: string
includeStartNewTransformationButton: boolean
}) {
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)
}
}

Expand Down Expand Up @@ -701,7 +727,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,45 @@ export class Messenger {
)
}

public async sendOneOrMultipleDiffsPrompt(tabID: string) {
const formItems: ChatItemFormItem[] = []
formItems.push({
id: 'GumbyTransformOneOrMultipleDiffsForm',
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.userPatchDescriptionChatMessage,
})
)

this.dispatcher.sendChatPrompt(
new ChatPrompt(
{
message: 'Q Code Transformation',
formItems: formItems,
},
'TransformOneOrMultipleDiffsForm',
tabID,
false
)
)
}

public async sendLanguageUpgradeProjectPrompt(projects: TransformationCandidateProject[], tabID: string) {
const projectFormOptions: { value: any; label: string }[] = []
const detectedJavaVersions = new Array<JDKVersion | undefined>()
Expand Down Expand Up @@ -367,7 +406,6 @@ export class Messenger {
},
tabID
)

this.dispatcher.sendChatMessage(jobSubmittedMessage)
}

Expand Down Expand Up @@ -477,13 +515,15 @@ 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: boolean = true) {
const buttons: ChatItemButton[] = []
buttons.push({
keepCardAfterClick: false,
text: CodeWhispererConstants.startTransformationButtonText,
id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW,
})
if (includeStartNewTransformationButton) {
buttons.push({
keepCardAfterClick: false,
text: CodeWhispererConstants.startTransformationButtonText,
id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW,
})
}

this.dispatcher.sendChatMessage(
new ChatMessage(
Expand Down Expand Up @@ -562,6 +602,11 @@ export class Messenger {
this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID))
}

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))
}

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.`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 'gumbyTransformOneOrMultipleDiffsFormConfirm',
CANCEL_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformOneOrMultipleDiffsFormCancel',
SELECT_SQL_CONVERSION_METADATA_FILE = 'gumbySQLConversionMetadataTransformFormConfirm',
CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm',
CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel',
Expand Down
16 changes: 9 additions & 7 deletions packages/core/src/codewhisperer/commands/startTransformByQ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,15 +811,17 @@ 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({ message: chatMessage, tabID: ChatSessionManager.Instance.getSession().tabID })
transformByQState.getChatControllers()?.transformationFinished.fire({
message: chatMessage,
tabID: ChatSessionManager.Instance.getSession().tabID,
})
const durationInMs = calculateTotalLatency(CodeTransformTelemetryState.instance.getStartTime())
const resultStatusMessage = transformByQState.getStatus()

Expand All @@ -842,11 +844,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) => {
Expand Down
Loading
Loading