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(amazonq): enable SQL conversions feature #5925

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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,4 @@
{
"type": "Feature",
"description": "Feature(Amazon Q Code Transformation): support conversions of embedded SQL from Oracle to PostgreSQL"
}
13 changes: 1 addition & 12 deletions packages/core/src/amazonq/webview/ui/tabs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { isSQLTransformReady } from '../../../../dev/config'
import { TabType } from '../storages/tabsStorage'

export type TabTypeData = {
Expand Down Expand Up @@ -34,16 +33,6 @@ What would you like to work on?`,
gumby: {
title: 'Q - Code Transformation',
placeholder: 'Open a new tab to chat with Q',
welcome: isSQLTransformReady
? `Welcome to code transformation!

I can help you with the following tasks:
- Upgrade your Java 8 and Java 11 codebases to Java 17
- Convert embedded SQL from Oracle databases to PostgreSQL

What would you like to do? You can enter 'language upgrade' or 'SQL conversion'.`
: `Welcome to code transformation!

I can help you upgrade your Java 8 and 11 codebases to Java 17.`,
welcome: 'Welcome to Code Transformation!',
},
}
47 changes: 35 additions & 12 deletions packages/core/src/amazonqGumby/chat/controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
getValidSQLConversionCandidateProjects,
validateSQLMetadataFile,
} from '../../../codewhisperer/commands/startTransformByQ'
import { JDKVersion, transformByQState } from '../../../codewhisperer/models/model'
import { JDKVersion, TransformationCandidateProject, transformByQState } from '../../../codewhisperer/models/model'
import {
AbsolutePathDetectedError,
AlternateDependencyVersionsNotFoundError,
Expand Down Expand Up @@ -62,7 +62,6 @@ 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 { isSQLTransformReady } from '../../../dev/config'

// These events can be interactions within the chat,
// or elsewhere in the IDE
Expand Down Expand Up @@ -190,12 +189,31 @@ export class GumbyController {
}

private async transformInitiated(message: any) {
// feature flag for SQL transformations
if (!isSQLTransformReady) {
// silently check for projects eligible for SQL conversion
let embeddedSQLProjects: TransformationCandidateProject[] = []
try {
embeddedSQLProjects = await getValidSQLConversionCandidateProjects()
} catch (err) {
getLogger().error(`CodeTransformation: error validating SQL conversion projects: ${err}`)
}

if (embeddedSQLProjects.length === 0) {
await this.handleLanguageUpgrade(message)
return
}

let javaUpgradeProjects: TransformationCandidateProject[] = []
try {
javaUpgradeProjects = await getValidLanguageUpgradeCandidateProjects()
} catch (err) {
getLogger().error(`CodeTransformation: error validating Java upgrade projects: ${err}`)
}

if (javaUpgradeProjects.length === 0) {
await this.handleSQLConversion(message)
return
}

// if previous transformation was already running, show correct message to user
switch (this.sessionStorage.getSession().conversationState) {
case ConversationState.JOB_SUBMITTED:
Expand Down Expand Up @@ -224,7 +242,10 @@ export class GumbyController {
this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE
this.messenger.sendStaticTextResponse('choose-transformation-objective', message.tabID)
this.messenger.sendChatInputEnabled(message.tabID, true)
this.messenger.sendUpdatePlaceholder(message.tabID, "Enter 'language upgrade' or 'SQL conversion'")
this.messenger.sendUpdatePlaceholder(
message.tabID,
CodeWhispererConstants.chooseTransformationObjectivePlaceholder
)
}

private async beginTransformation(message: any) {
Expand Down Expand Up @@ -310,13 +331,7 @@ export class GumbyController {

private async validateSQLConversionProjects(message: any) {
try {
const validProjects = await telemetry.codeTransform_validateProject.run(async () => {
telemetry.record({
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
})
const validProjects = await getValidSQLConversionCandidateProjects()
return validProjects
})
const validProjects = await getValidSQLConversionCandidateProjects()
return validProjects
} catch (e: any) {
if (e instanceof NoJavaProjectsFoundError) {
Expand Down Expand Up @@ -624,6 +639,14 @@ export class GumbyController {

case ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE: {
const objective = data.message.trim().toLowerCase()
// since we're prompting the user, their project(s) must be eligible for both types of transformations, so track how often this happens here
if (objective === 'language upgrade' || objective === 'sql conversion') {
telemetry.codeTransform_submitSelection.emit({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Best practice to use a run block, but I don't have anything to do here, just want to emit a metric to track how often the user gets to this point because it's meaningful for us. Furthermore, handleLanguageUpgrade and handleSQLConversion below already emit a validateProject metric.

codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
userChoice: objective,
result: 'Succeeded',
})
}
if (objective === 'language upgrade') {
await this.handleLanguageUpgrade(data)
} else if (objective === 'sql conversion') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,15 @@ export class Messenger {
formItems.push({
id: 'GumbyTransformSQLConversionProjectForm',
type: 'select',
title: 'Choose a project to transform',
title: CodeWhispererConstants.chooseProjectFormTitle,
mandatory: true,
options: projectFormOptions,
})

formItems.push({
id: 'GumbyTransformSQLSchemaForm',
type: 'select',
title: 'Choose the schema of the database',
title: CodeWhispererConstants.chooseSchemaFormTitle,
mandatory: true,
options: Array.from(transformByQState.getSchemaOptions()).map((schema) => ({
value: schema,
Expand All @@ -271,7 +271,7 @@ export class Messenger {
this.dispatcher.sendAsyncEventProgress(
new AsyncEventProgressMessage(tabID, {
inProgress: true,
message: 'I can convert your embedded SQL, but I need some more info from you first.',
message: CodeWhispererConstants.chooseProjectSchemaFormMessage,
})
)

Expand Down Expand Up @@ -390,7 +390,7 @@ export class Messenger {
message = 'I will continue transforming your code without upgrading this dependency.'
break
case 'choose-transformation-objective':
message = 'Choose your transformation objective.'
message = CodeWhispererConstants.chooseTransformationObjective
break
}

Expand Down Expand Up @@ -422,6 +422,7 @@ export class Messenger {
message = CodeWhispererConstants.noJavaProjectsFoundChatMessage
break
case 'no-maven-java-project-found':
// shown when user has no pom.xml, but at this point also means they have no eligible SQL conversion projects
message = CodeWhispererConstants.noPomXmlFoundChatMessage
break
case 'could-not-compile-project':
Expand All @@ -447,23 +448,7 @@ export class Messenger {
break
}

const buttons: ChatItemButton[] = []
buttons.push({
keepCardAfterClick: false,
text: CodeWhispererConstants.startTransformationButtonText,
id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW,
})

this.dispatcher.sendChatMessage(
new ChatMessage(
{
message,
messageType: 'ai-prompt',
buttons,
},
tabID
)
)
this.sendJobFinishedMessage(tabID, message)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/amazonqGumby/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
export const gumbyChat = 'gumbyChat'

// This sets the tab name
export const featureName = 'Q - Code Transform'
export const featureName = 'Q - Code Transformation'

export const dependencyNoAvailableVersions = 'no available versions'
55 changes: 51 additions & 4 deletions packages/core/src/codewhisperer/commands/startTransformByQ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
TransformByQStatus,
DB,
TransformationType,
TransformationCandidateProject,
} from '../models/model'
import {
createZipManifest,
Expand Down Expand Up @@ -80,6 +81,8 @@ import { setContext } from '../../shared/vscode/setContext'
import { makeTemporaryToolkitFolder } from '../../shared'
import globals from '../../shared/extensionGlobals'
import { convertDateToTimestamp } from '../../shared/datetime'
import { isWin } from '../../shared/vscode/env'
import { ChildProcess } from '../../shared/utilities/processUtils'

function getFeedbackCommentData() {
const jobId = transformByQState.getJobId()
Expand Down Expand Up @@ -150,7 +153,7 @@ export async function validateSQLMetadataFile(fileContents: string, message: any
}

export async function setMaven() {
let mavenWrapperExecutableName = os.platform() === 'win32' ? 'mvnw.cmd' : 'mvnw'
let mavenWrapperExecutableName = isWin() ? 'mvnw.cmd' : 'mvnw'
const mavenWrapperExecutablePath = path.join(transformByQState.getProjectPath(), mavenWrapperExecutableName)
if (fs.existsSync(mavenWrapperExecutablePath)) {
if (mavenWrapperExecutableName === 'mvnw') {
Expand Down Expand Up @@ -728,13 +731,57 @@ export async function finalizeTransformationJob(status: string) {
export async function getValidLanguageUpgradeCandidateProjects() {
const openProjects = await getOpenProjects()
const javaMavenProjects = await validateOpenProjects(openProjects)
getLogger().info(`CodeTransformation: found ${javaMavenProjects.length} projects eligible for language upgrade`)
return javaMavenProjects
}

export async function getValidSQLConversionCandidateProjects() {
const openProjects = await getOpenProjects()
const javaProjects = await getJavaProjects(openProjects)
return javaProjects
const embeddedSQLProjects: TransformationCandidateProject[] = []
await telemetry.codeTransform_validateProject.run(async () => {
telemetry.record({
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
})
const openProjects = await getOpenProjects()
const javaProjects = await getJavaProjects(openProjects)
let errorLog = ''
const isWindows = isWin()
const command = isWindows ? 'findstr' : 'grep'
for (const project of javaProjects) {
// as long as at least one of these strings is found, project contains embedded SQL statements
const searchStrings = ['oracle.jdbc.OracleDriver', 'jdbc:oracle:thin:@//', 'jdbc:oracle:oci:@//']
for (const str of searchStrings) {
// case-insensitive, recursive search
const args = isWindows ? ['/i', '/s', str, '*.*'] : ['-i', '-r', str]
const spawnResult = await new ChildProcess(command, args).run({
spawnOptions: { cwd: project.path, shell: false },
})
// just for telemetry purposes
if (spawnResult.error || spawnResult.stderr) {
errorLog += `${JSON.stringify(spawnResult)}---`
} else {
errorLog += `${command} succeeded: ${spawnResult.exitCode}`
}
/*
note:
error is set when command fails to spawn; stderr is set when command itself fails.
status of 0 means search string was detected (will be printed to stdout).
status will be non-zero and stdout / stderr / error will be empty when search string is not detected.
*/
getLogger().info(`CodeTransformation: searching for ${str} in ${project.path}, result = ${errorLog}`)
if (spawnResult.exitCode === 0) {
embeddedSQLProjects.push(project)
break
}
}
}
getLogger().info(
`CodeTransformation: found ${embeddedSQLProjects.length} projects with embedded SQL statements`
)
telemetry.record({
codeTransformMetadata: errorLog,
})
})
return embeddedSQLProjects
}

export async function setTransformationToRunningState() {
Expand Down
Loading
Loading