diff --git a/packages/core/src/amazonq/webview/ui/tabs/constants.ts b/packages/core/src/amazonq/webview/ui/tabs/constants.ts index 8696274c692..1eb2783a4e5 100644 --- a/packages/core/src/amazonq/webview/ui/tabs/constants.ts +++ b/packages/core/src/amazonq/webview/ui/tabs/constants.ts @@ -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 = { @@ -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!', }, } diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index cbde4728f98..20fa66295b5 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -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, @@ -62,8 +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' -import { spawnSync } from 'child_process' // These events can be interactions within the chat, // or elsewhere in the IDE @@ -190,35 +188,16 @@ export class GumbyController { this.messenger.sendCommandMessage(data) } - // silently check if user has open Java projects using embedded SQL - private async anyProjectContainsEmbeddedOracleSQL() { + private async transformInitiated(message: any) { + // silently check for projects eligible for SQL conversion + let embeddedSQLProjects: TransformationCandidateProject[] = [] try { - // gets just open Java projects - const projects = await getValidSQLConversionCandidateProjects() - for (const project of projects) { - // case-insensitive, recursive search, display only count of matching lines - const args = ['-i', '-r', '-c', 'oracle.jdbc.OracleDriver'] - // TO-DO: handle Windows - const spawnResult = spawnSync('grep', args, { - cwd: project.path, - shell: true, - encoding: 'utf-8', - }) - if (spawnResult.status !== 0) { - return false - } - // TO-DO: parse stdout for the count of matching lines - } + embeddedSQLProjects = await getValidSQLConversionCandidateProjects() } catch (err) { - return false + getLogger().error(`Error validating SQL conversion projects: ${err}`) } - return true - } - private async transformInitiated(message: any) { - // feature flag for SQL transformations - const containsOracleSQL = await this.anyProjectContainsEmbeddedOracleSQL() - if (!isSQLTransformReady && !containsOracleSQL) { + if (embeddedSQLProjects.length === 0) { await this.handleLanguageUpgrade(message) return } diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 7acbf04f32e..6c228a094f3 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -20,6 +20,7 @@ import { TransformByQStatus, DB, TransformationType, + TransformationCandidateProject, } from '../models/model' import { convertDateToTimestamp } from '../../shared/utilities/textUtilities' import { @@ -80,6 +81,7 @@ import { HumanInTheLoopManager } from '../service/transformByQ/humanInTheLoopMan import { setContext } from '../../shared/vscode/setContext' import { makeTemporaryToolkitFolder } from '../../shared' import globals from '../../shared/extensionGlobals' +import { spawnSync } from 'child_process' function getFeedbackCommentData() { const jobId = transformByQState.getJobId() @@ -734,7 +736,28 @@ export async function getValidLanguageUpgradeCandidateProjects() { export async function getValidSQLConversionCandidateProjects() { const openProjects = await getOpenProjects() const javaProjects = await getJavaProjects(openProjects) - return javaProjects + const embeddedSQLProjects: TransformationCandidateProject[] = [] + 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) { + const command = process.platform === 'win32' ? 'findstr' : 'grep' + // case-insensitive, recursive search + const args = command === 'findstr' ? ['/i', '/s', str] : ['-i', '-r', str] + const spawnResult = spawnSync(command, args, { + cwd: project.path, + shell: true, // TO-DO: better for this to be false? Test on project with a space in the name + encoding: 'utf-8', + }) + // in case our search unexpectedly fails, still allow user to transform that project + // also, anything in stdout here means search string was detected + if (spawnResult.status !== 0 || spawnResult.error || spawnResult.stdout.trim()) { + embeddedSQLProjects.push(project) + break + } + } + } + return embeddedSQLProjects } export async function setTransformationToRunningState() { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index e7736dc05e0..d397ee3dc3a 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -251,7 +251,7 @@ function isExcludedSourceFile(path: string): boolean { } // zip all dependency files and all source files excluding "target" (contains large JARs) plus ".git" and ".idea" (may appear in diff.patch) -function getFilesRecursively(dir: string, isDependenciesFolder: boolean): string[] { +export function getFilesRecursively(dir: string, isDependenciesFolder: boolean): string[] { const entries = nodefs.readdirSync(dir, { withFileTypes: true }) const files = entries.flatMap((entry) => { const res = path.resolve(dir, entry.name) diff --git a/packages/core/src/dev/config.ts b/packages/core/src/dev/config.ts index 6c630aaf287..d5fa49b2426 100644 --- a/packages/core/src/dev/config.ts +++ b/packages/core/src/dev/config.ts @@ -10,6 +10,3 @@ export const betaUrl = { amazonq: '', toolkit: '', } - -// feature flag for SQL transformations -export const isSQLTransformReady = true diff --git a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts index 20a5da3275a..9cf23632260 100644 --- a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts +++ b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts @@ -33,6 +33,7 @@ import { updateJobHistory, zipCode, getTableMapping, + getFilesRecursively, } from '../../../codewhisperer/service/transformByQ/transformApiHandler' import { validateOpenProjects, @@ -288,6 +289,19 @@ describe('transformByQ', function () { }) }) + it(`WHEN getFilesRecursively on source code THEN ignores excluded directories`, async function () { + const sourceFolder = path.join(tempDir, 'src') + await fs.mkdir(sourceFolder) + await fs.writeFile(path.join(sourceFolder, 'HelloWorld.java'), 'sample content for the test file') + + const gitFolder = path.join(tempDir, '.git') + await fs.mkdir(gitFolder) + await fs.writeFile(path.join(gitFolder, 'config'), 'sample content for the test file') + + const zippedFiles = getFilesRecursively(tempDir, false) + assert.strictEqual(zippedFiles.length, 1) + }) + it(`WHEN getTableMapping on complete step 0 progressUpdates THEN map IDs to tables`, async function () { const stepZeroProgressUpdates = [ {