diff --git a/.changes/next-release/feature-ca9e6d61-dd42-4165-a860-093207e64c5e.json b/.changes/next-release/feature-ca9e6d61-dd42-4165-a860-093207e64c5e.json new file mode 100644 index 00000000000..fae2b2c3439 --- /dev/null +++ b/.changes/next-release/feature-ca9e6d61-dd42-4165-a860-093207e64c5e.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Feature(Amazon Q Code Transformation): support conversions of embedded SQL from Oracle to PostgreSQL" +} \ No newline at end of file diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt index 16d799c2783..5fe7d6fc192 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerManager.kt @@ -41,6 +41,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerStartJobResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.CustomerSelection import software.aws.toolkits.jetbrains.services.codemodernizer.model.Dependency import software.aws.toolkits.jetbrains.services.codemodernizer.model.InvalidTelemetryReason @@ -59,6 +60,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.toolwindow.CodeMo import software.aws.toolkits.jetbrains.services.codemodernizer.utils.STATES_WHERE_PLAN_EXIST import software.aws.toolkits.jetbrains.services.codemodernizer.utils.createFileCopy import software.aws.toolkits.jetbrains.services.codemodernizer.utils.findLineNumberByString +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getJavaModulesWithSQL import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getMavenVersion import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getModuleOrProjectNameForFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilArtifactPomFile @@ -125,27 +127,26 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo JavaSdkVersion.JDK_11 to setOf(JavaSdkVersion.JDK_17), JavaSdkVersion.JDK_17 to setOf(JavaSdkVersion.JDK_17), ) + init { CodeModernizerSessionState.getInstance(project).setDefaults() } - fun validate(project: Project): ValidationResult { + fun validate(project: Project, transformationType: CodeTransformType): ValidationResult { fun validateCore(project: Project): ValidationResult { if (isRunningOnRemoteBackend()) { return ValidationResult( false, - message("codemodernizer.notification.warn.invalid_project.description.reason.remote_backend"), InvalidTelemetryReason( - CodeTransformPreValidationError.RemoteRunProject + CodeTransformPreValidationError.RemoteRunProject, ) ) } if (!isCodeTransformAvailable(project)) { return ValidationResult( false, - message("codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in"), InvalidTelemetryReason( - CodeTransformPreValidationError.NonSsoLogin + CodeTransformPreValidationError.NonSsoLogin, ) ) } @@ -153,25 +154,37 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo if (ProjectRootManager.getInstance(project).contentRoots.isEmpty()) { return ValidationResult( false, - message("codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots"), InvalidTelemetryReason( - CodeTransformPreValidationError.NoPom + CodeTransformPreValidationError.EmptyProject, ) ) } + + if (transformationType == CodeTransformType.SQL_CONVERSION) { + val javaModules = project.getJavaModulesWithSQL() + return if (javaModules.isNotEmpty()) { + ValidationResult( + true, + ) + } else { + ValidationResult( + false, + InvalidTelemetryReason( + CodeTransformPreValidationError.NoJavaProject, + ) + ) + } + } + val supportedModules = project.getSupportedModules(supportedJavaMappings).toSet() val validProjectJdk = project.getSupportedJavaMappings(supportedJavaMappings).isNotEmpty() val projectJdk = project.tryGetJdk() if (supportedModules.isEmpty() && !validProjectJdk) { return ValidationResult( false, - message( - "codemodernizer.notification.warn.invalid_project.description.reason.invalid_jdk_versions", - supportedJavaMappings.keys.joinToString() - ), InvalidTelemetryReason( CodeTransformPreValidationError.UnsupportedJavaVersion, - projectJdk?.toString().orEmpty() + projectJdk.toString() ) ) } @@ -180,17 +193,12 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo ValidationResult( true, validatedBuildFiles = validatedBuildFiles, - validatedProjectJdkName = projectJdk?.description.orEmpty(), buildSystem = CodeTransformBuildSystem.Maven, buildSystemVersion = getMavenVersion(project) ) } else { ValidationResult( false, - invalidReason = message( - "codemodernizer.notification.warn.invalid_project.description.reason.no_valid_files", - supportedBuildFileNames.joinToString() - ), invalidTelemetryReason = InvalidTelemetryReason( CodeTransformPreValidationError.UnsupportedBuildSystem, if (isGradleProject(project)) "Gradle build" else "other build" @@ -248,7 +256,7 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo } } - fun runModernize(copyResult: MavenCopyCommandsResult) { + fun runModernize(copyResult: MavenCopyCommandsResult? = null) { initStopParameters() val session = codeTransformationSession as CodeModernizerSession initModernizationJobUI(true, project.getModuleOrProjectNameForFile(session.sessionContext.configurationFile)) @@ -256,7 +264,9 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo } suspend fun resumePollingFromHil() { - val result = handleJobResumedFromHil(managerState.getLatestJobId(), codeTransformationSession as CodeModernizerSession) + val transformationType = + if (codeTransformationSession?.sessionContext?.sqlMetadataZip != null) CodeTransformType.SQL_CONVERSION else CodeTransformType.LANGUAGE_UPGRADE + val result = handleJobResumedFromHil(managerState.getLatestJobId(), codeTransformationSession as CodeModernizerSession, transformationType) postModernizationJob(result) } @@ -312,7 +322,7 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo ) } - fun launchModernizationJob(session: CodeModernizerSession, copyResult: MavenCopyCommandsResult) = projectCoroutineScope(project).launch { + fun launchModernizationJob(session: CodeModernizerSession, copyResult: MavenCopyCommandsResult?) = projectCoroutineScope(project).launch { val result = initModernizationJob(session, copyResult) postModernizationJob(result) @@ -339,7 +349,8 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo CodeModernizerSessionState.getInstance(project).currentJobCreationTime = currentJobResult.creationTime() codeTransformationSession = session initModernizationJobUI(false, project.getModuleOrProjectNameForFile(session.sessionContext.configurationFile)) - codeModernizerBottomWindowPanelManager.setResumeJobUI(currentJobResult, plan, session.sessionContext.sourceJavaVersion) + val transformationType = if (session.sessionContext.sqlMetadataZip != null) CodeTransformType.SQL_CONVERSION else CodeTransformType.LANGUAGE_UPGRADE + codeModernizerBottomWindowPanelManager.setResumeJobUI(currentJobResult, plan, session.sessionContext.sourceJavaVersion, transformationType) session.resumeJob(currentJobResult.creationTime(), lastJobId) val result = handleJobStarted(lastJobId, session) postModernizationJob(result) @@ -395,7 +406,7 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo fun parseBuildFile(): String? = parseBuildFile(codeTransformationSession?.sessionContext?.configurationFile) - internal suspend fun initModernizationJob(session: CodeModernizerSession, copyResult: MavenCopyCommandsResult): CodeModernizerJobCompletedResult = + private suspend fun initModernizationJob(session: CodeModernizerSession, copyResult: MavenCopyCommandsResult?): CodeModernizerJobCompletedResult = when (val result = session.createModernizationJob(copyResult)) { is CodeModernizerStartJobResult.ZipCreationFailed -> { CodeModernizerJobCompletedResult.UnableToCreateJob( @@ -441,10 +452,12 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo private suspend fun handleJobResumedFromHil( jobId: JobId, session: CodeModernizerSession, + transformType: CodeTransformType, ): CodeModernizerJobCompletedResult = session.pollUntilJobCompletion( + transformType, jobId ) { new, plan -> - codeModernizerBottomWindowPanelManager.handleJobTransition(new, plan, session.sessionContext.sourceJavaVersion) + codeModernizerBottomWindowPanelManager.handleJobTransition(new, plan, session.sessionContext.sourceJavaVersion, transformType) } private suspend fun handleJobStarted(jobId: JobId, session: CodeModernizerSession): CodeModernizerJobCompletedResult { @@ -455,8 +468,10 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo codeModernizerBottomWindowPanelManager.setJobRunningUI() } - return session.pollUntilJobCompletion(jobId) { new, plan -> - codeModernizerBottomWindowPanelManager.handleJobTransition(new, plan, session.sessionContext.sourceJavaVersion) + val transformType = if (session.sessionContext.sqlMetadataZip != null) CodeTransformType.SQL_CONVERSION else CodeTransformType.LANGUAGE_UPGRADE + + return session.pollUntilJobCompletion(transformType, jobId) { new, plan -> + codeModernizerBottomWindowPanelManager.handleJobTransition(new, plan, session.sessionContext.sourceJavaVersion, transformType) } } @@ -667,10 +682,14 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo fun createCodeModernizerSession(customerSelection: CustomerSelection, project: Project) { codeTransformationSession = CodeModernizerSession( CodeModernizerSessionContext( - project, - customerSelection.configurationFile, - customerSelection.sourceJavaVersion, - customerSelection.targetJavaVersion, + project = project, + configurationFile = customerSelection.configurationFile, + sourceJavaVersion = customerSelection.sourceJavaVersion, + targetJavaVersion = customerSelection.targetJavaVersion, + sourceVendor = customerSelection.sourceVendor, + targetVendor = customerSelection.targetVendor, + sourceServerName = customerSelection.sourceServerName, + sqlMetadataZip = customerSelection.sqlMetadataZip, ), ) } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt index 69cff581e04..26ffb69b188 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt @@ -32,6 +32,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerStartJobResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadArtifactResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult @@ -61,12 +62,7 @@ import java.util.concurrent.CancellationException import java.util.concurrent.atomic.AtomicBoolean import javax.net.ssl.SSLHandshakeException -const val ZIP_SOURCES_PATH = "sources" -const val BUILD_LOG_PATH = "build-logs.txt" -const val UPLOAD_ZIP_MANIFEST_VERSION = 1.0F const val MAX_ZIP_SIZE = 2000000000 // 2GB -const val HIL_1P_UPGRADE_CAPABILITY = "HIL_1pDependency_VersionUpgrade" -const val EXPLAINABILITY_V1 = "EXPLAINABILITY_V1" // constants for handling SDKClientException const val CONNECTION_REFUSED_ERROR: String = "Connection refused" @@ -144,7 +140,7 @@ class CodeModernizerSession( * * Based on [CodeWhispererCodeScanSession] */ - fun createModernizationJob(copyResult: MavenCopyCommandsResult): CodeModernizerStartJobResult { + fun createModernizationJob(copyResult: MavenCopyCommandsResult?): CodeModernizerStartJobResult { LOG.info { "Compressing local project" } val payload: File? var payloadSize = 0 @@ -164,6 +160,7 @@ class CodeModernizerSession( telemetryErrorMessage = "Disposed when about to create zip" return CodeModernizerStartJobResult.Disposed } + // for language upgrades, copyResult should always be Successful here, failure cases already handled val result = sessionContext.createZipWithModuleFiles(copyResult) if (result is ZipCreationResult.Missing1P) { @@ -324,11 +321,6 @@ class CodeModernizerSession( return clientAdaptor.getCodeModernizationJob(jobId.id).transformationJob() } - fun getTransformPlanDetails(jobId: JobId): TransformationPlan { - LOG.info { "Getting transform plan details." } - return clientAdaptor.getCodeModernizationPlan(jobId).transformationPlan() - } - /** * This will resume the job, i.e. it will resume the main job loop kicked of by [createModernizationJob] */ @@ -418,6 +410,7 @@ class CodeModernizerSession( } suspend fun pollUntilJobCompletion( + transformType: CodeTransformType, jobId: JobId, jobTransitionHandler: (currentStatus: TransformationStatus, migrationPlan: TransformationPlan?) -> Unit, ): CodeModernizerJobCompletedResult { @@ -432,6 +425,7 @@ class CodeModernizerSession( var passedStart = false val result = jobId.pollTransformationStatusAndPlan( + transformType, succeedOn = setOf( TransformationStatus.COMPLETED, TransformationStatus.PAUSED, @@ -463,8 +457,8 @@ class CodeModernizerSession( } } - // Open the transformation plan detail panel once transformation plan is available - if (state.transformationPlan != null && !isTransformationPlanEditorOpened) { + // Open the transformation plan detail panel once transformation plan is available (no plan for SQL conversions) + if (transformType != CodeTransformType.SQL_CONVERSION && state.transformationPlan != null && !isTransformationPlanEditorOpened) { tryOpenTransformationPlanEditor() isTransformationPlanEditorOpened = true } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt index 4077362f11c..e39c12c908b 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt @@ -32,7 +32,10 @@ private enum class CodeTransformMessageTypes(val type: String) { TabCreated("new-tab-was-created"), TabRemoved("tab-was-removed"), Transform("transform"), + ChatPrompt("chat-prompt"), // for getting the transformation objective CodeTransformStart("codetransform-start"), + CodeTransformSelectSQLMetadata("codetransform-select-sql-metadata"), + CodeTransformSelectSQLModuleSchema("codetransform-select-sql-module-schema"), CodeTransformStop("codetransform-stop"), CodeTransformCancel("codetransform-cancel"), CodeTransformConfirmSkipTests("codetransform-confirm-skip-tests"), @@ -63,8 +66,11 @@ class CodeTransformChatApp : AmazonQApp { CodeTransformMessageTypes.TabRemoved.type to IncomingCodeTransformMessage.TabRemoved::class, CodeTransformMessageTypes.Transform.type to IncomingCodeTransformMessage.Transform::class, CodeTransformMessageTypes.CodeTransformStart.type to IncomingCodeTransformMessage.CodeTransformStart::class, + CodeTransformMessageTypes.CodeTransformSelectSQLMetadata.type to IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata::class, + CodeTransformMessageTypes.CodeTransformSelectSQLModuleSchema.type to IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema::class, CodeTransformMessageTypes.CodeTransformStop.type to IncomingCodeTransformMessage.CodeTransformStop::class, CodeTransformMessageTypes.CodeTransformCancel.type to IncomingCodeTransformMessage.CodeTransformCancel::class, + CodeTransformMessageTypes.ChatPrompt.type to IncomingCodeTransformMessage.ChatPrompt::class, CodeTransformMessageTypes.CodeTransformConfirmSkipTests.type to IncomingCodeTransformMessage.CodeTransformConfirmSkipTests::class, CodeTransformMessageTypes.CodeTransformNew.type to IncomingCodeTransformMessage.CodeTransformNew::class, CodeTransformMessageTypes.CodeTransformOpenTransformHub.type to IncomingCodeTransformMessage.CodeTransformOpenTransformHub::class, @@ -153,8 +159,12 @@ class CodeTransformChatApp : AmazonQApp { when (message) { is IncomingCodeTransformMessage.Transform -> inboundAppMessagesHandler.processTransformQuickAction(message) is IncomingCodeTransformMessage.CodeTransformStart -> inboundAppMessagesHandler.processCodeTransformStartAction(message) + is IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata -> inboundAppMessagesHandler.processCodeTransformSelectSQLMetadataAction(message) + is IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema -> + inboundAppMessagesHandler.processCodeTransformSelectSQLModuleSchemaAction(message) is IncomingCodeTransformMessage.CodeTransformCancel -> inboundAppMessagesHandler.processCodeTransformCancelAction(message) is IncomingCodeTransformMessage.CodeTransformStop -> inboundAppMessagesHandler.processCodeTransformStopAction(message.tabId) + is IncomingCodeTransformMessage.ChatPrompt -> inboundAppMessagesHandler.processChatPromptMessage(message) is IncomingCodeTransformMessage.CodeTransformConfirmSkipTests -> inboundAppMessagesHandler.processCodeTransformConfirmSkipTests(message) is IncomingCodeTransformMessage.CodeTransformNew -> inboundAppMessagesHandler.processCodeTransformNewAction(message) is IncomingCodeTransformMessage.CodeTransformOpenTransformHub -> inboundAppMessagesHandler.processCodeTransformOpenTransformHub(message) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt index a8a217b0d33..3f2d7a6d31d 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformTelemetryManager.kt @@ -61,16 +61,19 @@ class CodeTransformTelemetryManager(private val project: Project) { codeTransformBuildSystem = validationResult.buildSystem, codeTransformSessionId = sessionId, result = if (validationResult.valid) Result.Succeeded else Result.Failed, - reason = if (validationResult.valid) null else validationResult.invalidTelemetryReason.additonalInfo, + reason = if (validationResult.valid) null else validationResult.invalidTelemetryReason.additionalInfo, ) } fun submitSelection(userChoice: String, customerSelection: CustomerSelection? = null, telemetryErrorMessage: String? = null) { CodetransformTelemetry.submitSelection( + // TODO: remove below 2 lines (JavaSource / JavaTarget) once BI is updated to use source / target codeTransformJavaSourceVersionsAllowed = CodeTransformJavaSourceVersionsAllowed.from(customerSelection?.sourceJavaVersion?.name.orEmpty()), codeTransformJavaTargetVersionsAllowed = CodeTransformJavaTargetVersionsAllowed.from(customerSelection?.targetJavaVersion?.name.orEmpty()), codeTransformSessionId = sessionId, codeTransformProjectId = customerSelection?.let { getProjectHash(it) }, + source = if (userChoice == "Confirm-Java") customerSelection?.sourceJavaVersion?.name.orEmpty() else customerSelection?.sourceVendor.orEmpty(), + target = if (userChoice == "Confirm-Java") customerSelection?.targetJavaVersion?.name.orEmpty() else customerSelection?.targetVendor.orEmpty(), userChoice = userChoice, result = if (telemetryErrorMessage.isNullOrEmpty()) Result.Succeeded else Result.Failed, reason = telemetryErrorMessage, @@ -145,7 +148,7 @@ class CodeTransformTelemetryManager(private val project: Project) { } fun getProjectHash(customerSelection: CustomerSelection) = Base64.getEncoder().encodeToString( - DigestUtils.sha256(customerSelection.configurationFile.toNioPath().toAbsolutePath().toString()) + DigestUtils.sha256(customerSelection.configurationFile?.toNioPath()?.toAbsolutePath().toString()) ) /** diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt index 284a1008017..0b65db328c2 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt @@ -13,8 +13,14 @@ interface InboundAppMessagesHandler { suspend fun processCodeTransformStartAction(message: IncomingCodeTransformMessage.CodeTransformStart) + suspend fun processCodeTransformSelectSQLMetadataAction(message: IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata) + + suspend fun processCodeTransformSelectSQLModuleSchemaAction(message: IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema) + suspend fun processCodeTransformStopAction(tabId: String) + suspend fun processChatPromptMessage(message: IncomingCodeTransformMessage.ChatPrompt) + suspend fun processCodeTransformConfirmSkipTests(message: IncomingCodeTransformMessage.CodeTransformConfirmSkipTests) suspend fun processCodeTransformOpenTransformHub(message: IncomingCodeTransformMessage.CodeTransformOpenTransformHub) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt index 86f704caa8c..ac2d4d8bfc3 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt @@ -26,6 +26,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact import software.aws.toolkits.jetbrains.services.codemodernizer.model.Dependency import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason +import software.aws.toolkits.jetbrains.services.codemodernizer.model.SqlMetadataValidationResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getModuleOrProjectNameForFile @@ -34,6 +35,7 @@ import software.aws.toolkits.jetbrains.services.cwc.messages.FollowUp import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformPreValidationError +// shared Cancel button private val cancelUserSelectionButton = Button( keepCardAfterClick = false, waitMandatoryFormItems = false, @@ -41,13 +43,27 @@ private val cancelUserSelectionButton = Button( id = CodeTransformButtonId.CancelTransformation.id, ) -private val confirmUserSelectionButton = Button( +private val confirmUserSelectionLanguageUpgradeButton = Button( keepCardAfterClick = false, waitMandatoryFormItems = true, text = message("codemodernizer.chat.message.button.confirm"), id = CodeTransformButtonId.StartTransformation.id, ) +private val confirmUserSelectionSQLConversionModuleSchemaButton = Button( + keepCardAfterClick = false, + waitMandatoryFormItems = true, + text = message("codemodernizer.chat.message.button.confirm"), + id = CodeTransformButtonId.SelectSQLModuleSchema.id, +) + +private val confirmUserSelectionSQLConversionMetadataButton = Button( + keepCardAfterClick = true, + waitMandatoryFormItems = true, + text = message("codemodernizer.chat.message.button.select_sql_metadata"), + id = CodeTransformButtonId.SelectSQLMetadata.id, +) + private val confirmSkipTestsSelectionButton = Button( keepCardAfterClick = false, waitMandatoryFormItems = true, @@ -129,6 +145,18 @@ private fun getSelectModuleFormItem(project: Project, moduleBuildFiles: List) = FormItem( + id = CodeTransformFormItemId.SelectModule.id, + title = message("codemodernizer.chat.form.user_selection.item.choose_module"), + mandatory = true, + options = javaModules.map { + FormItemOption( + label = project.getModuleOrProjectNameForFile(it), + value = it.path, + ) + } +) + private val selectTargetVersionFormItem = FormItem( id = CodeTransformFormItemId.SelectTargetVersion.id, title = message("codemodernizer.chat.form.user_selection.item.choose_target_version"), @@ -141,6 +169,18 @@ private val selectTargetVersionFormItem = FormItem( ) ) +private fun getSelectSQLSchemaFormItem(schemaOptions: Set) = FormItem( + id = CodeTransformFormItemId.SelectSQLSchema.id, + title = "Choose the schema", + mandatory = true, + options = schemaOptions.map { + FormItemOption( + label = it, + value = it, + ) + } +) + private val selectSkipTestsFlagFormItem = FormItem( id = CodeTransformFormItemId.SelectSkipTestsFlag.id, title = message("codemodernizer.chat.form.user_selection.item.choose_skip_tests_option"), @@ -157,7 +197,7 @@ private val selectSkipTestsFlagFormItem = FormItem( ) ) -private fun getUserSelectionFormattedMarkdown(moduleName: String): String = """ +private fun getUserLanguageUpgradeSelectionFormattedMarkdown(moduleName: String): String = """ ### ${message("codemodernizer.chat.prompt.title.details")} ------------- @@ -167,6 +207,16 @@ private fun getUserSelectionFormattedMarkdown(moduleName: String): String = """ | **${message("codemodernizer.chat.prompt.label.target_version")}** | JDK17 | """.trimIndent() +private fun getUserSQLConversionSelectionFormattedMarkdown(moduleName: String, schema: String) = """ + ### ${message("codemodernizer.chat.prompt.title.details")} + ------------- + + | | | + | :------------------- | -------: | + | **${message("codemodernizer.chat.prompt.label.module")}** | $moduleName | + | **Schema** | $schema | +""".trimIndent() + private fun getUserHilSelectionMarkdown(dependencyName: String, currentVersion: String, selectedVersion: String): String = """ ### ${message("codemodernizer.chat.prompt.title.dependency_details")} ------------- @@ -178,19 +228,35 @@ private fun getUserHilSelectionMarkdown(dependencyName: String, currentVersion: | **${message("codemodernizer.chat.prompt.label.dependency_selected_version")}** | $selectedVersion | """.trimIndent() +fun buildChooseTransformationObjectiveChatContent() = CodeTransformChatMessageContent( + message = message("codemodernizer.chat.message.choose_objective"), + type = CodeTransformChatMessageType.FinalizedAnswer, +) + +fun buildObjectiveChosenChatContent(objective: String) = CodeTransformChatMessageContent( + message = objective, + type = CodeTransformChatMessageType.Prompt, +) + fun buildCheckingValidProjectChatContent() = CodeTransformChatMessageContent( - message = message("codemodernizer.chat.message.validation.check_eligible_projects"), + message = message("codemodernizer.chat.message.validation.check_eligible_modules"), type = CodeTransformChatMessageType.PendingAnswer, ) -fun buildProjectValidChatContent() = CodeTransformChatMessageContent( +fun buildLanguageUpgradeProjectValidChatContent() = CodeTransformChatMessageContent( message = message("codemodernizer.chat.message.validation.check_passed"), type = CodeTransformChatMessageType.FinalizedAnswer, ) + fun buildProjectInvalidChatContent(validationResult: ValidationResult): CodeTransformChatMessageContent { val errorMessage = when (validationResult.invalidTelemetryReason.category) { CodeTransformPreValidationError.NoPom -> message("codemodernizer.chat.message.validation.error.no_pom", CODE_TRANSFORM_PREREQUISITES) CodeTransformPreValidationError.UnsupportedJavaVersion -> message("codemodernizer.chat.message.validation.error.unsupported_java_version") + CodeTransformPreValidationError.RemoteRunProject -> message("codemodernizer.notification.warn.invalid_project.description.reason.remote_backend") + CodeTransformPreValidationError.NonSsoLogin -> message("codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in") + CodeTransformPreValidationError.EmptyProject -> message("codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots") + CodeTransformPreValidationError.UnsupportedBuildSystem -> message("codemodernizer.chat.message.validation.error.no_pom") + CodeTransformPreValidationError.NoJavaProject -> message("codemodernizer.chat.message.validation.error.no_java_project") else -> message("codemodernizer.chat.message.validation.error.other") } @@ -229,13 +295,13 @@ fun buildUserSkipTestsFlagSelectionChatContent(skipTestsSelection: String) = Cod message = message("codemodernizer.chat.message.skip_tests_form.response", skipTestsSelection.lowercase()) ) -fun buildUserInputChatContent(project: Project, validationResult: ValidationResult): CodeTransformChatMessageContent { +fun buildUserInputLanguageUpgradeChatContent(project: Project, validationResult: ValidationResult): CodeTransformChatMessageContent { val moduleBuildFiles = validationResult.validatedBuildFiles return CodeTransformChatMessageContent( message = message("codemodernizer.chat.form.user_selection.title"), buttons = listOf( - confirmUserSelectionButton, + confirmUserSelectionLanguageUpgradeButton, cancelUserSelectionButton, ), formItems = listOf( @@ -246,6 +312,56 @@ fun buildUserInputChatContent(project: Project, validationResult: ValidationResu ) } +fun buildUserInputSQLConversionMetadataChatContent() = CodeTransformChatMessageContent( + message = message("codemodernizer.chat.form.user_selection.item.choose_sql_metadata_file"), + buttons = listOf( + confirmUserSelectionSQLConversionMetadataButton, + cancelUserSelectionButton, + ), + type = CodeTransformChatMessageType.FinalizedAnswer, +) + +fun buildModuleSchemaFormChatContent(project: Project, javaModules: List, schemaOptions: Set) = CodeTransformChatMessageContent( + type = CodeTransformChatMessageType.FinalizedAnswer, + buttons = listOf( + confirmUserSelectionSQLConversionModuleSchemaButton, + cancelUserSelectionButton, + ), + formItems = listOf( + getSelectSQLModuleFormItem(project, javaModules), + getSelectSQLSchemaFormItem(schemaOptions), + ), +) + +fun buildModuleSchemaFormIntroChatContent() = CodeTransformChatMessageContent( + type = CodeTransformChatMessageType.FinalizedAnswer, + message = message("codemodernizer.chat.message.sql_module_schema_prompt"), +) + +fun buildSQLMetadataValidationSuccessIntroChatContent() = CodeTransformChatMessageContent( + type = CodeTransformChatMessageType.FinalizedAnswer, + message = message("codemodernizer.chat.message.sql_metadata_success"), +) + +fun buildSQLMetadataValidationSuccessDetailsChatContent(validationResult: SqlMetadataValidationResult) = CodeTransformChatMessageContent( + type = CodeTransformChatMessageType.FinalizedAnswer, + message = """ + ### ${message("codemodernizer.chat.prompt.title.details")} + ------------- + + | | | + | :------------------- | -------: | + | **Source DB** | ${validationResult.sourceVendor} | + | **Target DB** | ${validationResult.targetVendor} | + | **Host** | ${validationResult.sourceServerName} | + """.trimIndent(), +) + +fun buildSQLMetadataValidationErrorChatContent(errorReason: String) = CodeTransformChatMessageContent( + type = CodeTransformChatMessageType.FinalizedAnswer, + message = errorReason, +) + fun buildUserCancelledChatContent() = CodeTransformChatMessageContent( type = CodeTransformChatMessageType.FinalizedAnswer, message = message("codemodernizer.chat.message.transform_cancelled_by_user"), @@ -266,9 +382,14 @@ fun buildTransformStoppedChatContent() = CodeTransformChatMessageContent( type = CodeTransformChatMessageType.FinalizedAnswer, ) -fun buildUserSelectionSummaryChatContent(moduleName: String) = CodeTransformChatMessageContent( +fun buildUserSQLConversionSelectionSummaryChatContent(moduleName: String, schema: String) = CodeTransformChatMessageContent( + type = CodeTransformChatMessageType.Prompt, + message = getUserSQLConversionSelectionFormattedMarkdown(moduleName, schema) +) + +fun buildUserLanguageUpgradeSelectionSummaryChatContent(moduleName: String) = CodeTransformChatMessageContent( type = CodeTransformChatMessageType.Prompt, - message = getUserSelectionFormattedMarkdown(moduleName) + message = getUserLanguageUpgradeSelectionFormattedMarkdown(moduleName) ) fun buildCompileLocalInProgressChatContent() = CodeTransformChatMessageContent( diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt index 7f9570ee413..b3905c14f3c 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt @@ -5,15 +5,20 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.controller import com.intellij.ide.BrowserUtil import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.fileChooser.FileChooser +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.projectRoots.JavaSdkVersion +import com.intellij.openapi.util.io.FileUtil.createTempDirectory import com.intellij.openapi.vfs.VirtualFile import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info +import software.aws.toolkits.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthFollowUpType @@ -28,6 +33,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTran import software.aws.toolkits.jetbrains.services.codemodernizer.constants.FEATURE_NAME import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildAbsolutePathWarning import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildCheckingValidProjectChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildChooseTransformationObjectiveChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildCompileHilAlternativeVersionContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildCompileLocalFailedChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildCompileLocalFailedNoJdkChatContent @@ -40,8 +46,14 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildHi import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildHilRejectContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildHilResumeWithErrorContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildHilResumedContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildLanguageUpgradeProjectValidChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildModuleSchemaFormChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildModuleSchemaFormIntroChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildObjectiveChosenChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildProjectInvalidChatContent -import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildProjectValidChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildSQLMetadataValidationErrorChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildSQLMetadataValidationSuccessDetailsChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildSQLMetadataValidationSuccessIntroChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildStartNewTransformFollowup import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTransformAwaitUserInputChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTransformBeginChatContent @@ -54,10 +66,12 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTr import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTransformStoppingChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserCancelledChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserHilSelection -import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputLanguageUpgradeChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputSQLConversionMetadataChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputSkipTestsFlagChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputSkipTestsFlagChatIntroContent -import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserSelectionSummaryChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserLanguageUpgradeSelectionSummaryChatContent +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserSQLConversionSelectionSummaryChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserSkipTestsFlagSelectionChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserStopTransformChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.messages.AuthenticationNeededExceptionMessage @@ -66,6 +80,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTran import software.aws.toolkits.jetbrains.services.codemodernizer.messages.IncomingCodeTransformMessage import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.CustomerSelection import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId @@ -74,14 +89,18 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_BUILD import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenDependencyReportCommandsResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason +import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager import software.aws.toolkits.jetbrains.services.codemodernizer.session.ChatSessionStorage import software.aws.toolkits.jetbrains.services.codemodernizer.session.Session import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getJavaModulesWithSQL import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getModuleOrProjectNameForFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable import software.aws.toolkits.jetbrains.services.codemodernizer.utils.toVirtualFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.tryGetJdk +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.unzipFile +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.validateSctMetadata import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformVCSViewerSrcComponents @@ -97,6 +116,25 @@ class CodeTransformChatController( private val artifactHandler = ArtifactHandler(context.project, GumbyClient.getInstance(context.project)) private val telemetry = CodeTransformTelemetryManager.getInstance(context.project) + override suspend fun processChatPromptMessage(message: IncomingCodeTransformMessage.ChatPrompt) { + val objective = message.message.trim().lowercase() + + codeTransformChatHelper.addNewMessage(buildObjectiveChosenChatContent(objective)) + codeTransformChatHelper.sendChatInputEnabledMessage(message.tabId, false) + codeTransformChatHelper.sendUpdatePlaceholderMessage(message.tabId, "Open a new tab to chat with Q") + + // since we're prompting the user, their module(s) must be eligible for both types of transformations, so track how often this happens here + if (objective == "language upgrade" || objective == "sql conversion") { + telemetry.submitSelection(objective) + } + + when (objective) { + "language upgrade" -> this.handleLanguageUpgrade() + "sql conversion" -> this.handleSQLConversion() + else -> this.getUserObjective(message.tabId) // ask user again for objective + } + } + override suspend fun processTransformQuickAction(message: IncomingCodeTransformMessage.Transform) { telemetry.prepareForNewJobSubmission() @@ -116,13 +154,38 @@ class CodeTransformChatController( // Publish a metric when transform is first initiated from chat prompt. telemetry.initiateTransform() + val anyModuleContainsOracleSQL = codeModernizerManager.validate(context.project, CodeTransformType.SQL_CONVERSION).valid + + if (!anyModuleContainsOracleSQL) { + this.handleLanguageUpgrade() + return + } + + val eligibleForLanguageUpgrade = codeModernizerManager.validate(context.project, CodeTransformType.LANGUAGE_UPGRADE).valid + + if (!eligibleForLanguageUpgrade) { + this.handleSQLConversion() + return + } + + // eligible for both language upgrade and sql conversion, so ask user what they want to do + this.getUserObjective(message.tabId) + } + + private suspend fun getUserObjective(tabId: String) { + codeTransformChatHelper.addNewMessage(buildChooseTransformationObjectiveChatContent()) + codeTransformChatHelper.sendChatInputEnabledMessage(tabId, true) + codeTransformChatHelper.sendUpdatePlaceholderMessage(tabId, message("codemodernizer.chat.message.choose_objective")) + } + + private suspend fun validateAndReplyOnError(transformationType: CodeTransformType): ValidationResult? { codeTransformChatHelper.addNewMessage( buildCheckingValidProjectChatContent() ) - codeTransformChatHelper.chatDelayLong() + codeTransformChatHelper.chatDelayShort() - val validationResult = codeModernizerManager.validate(context.project) + val validationResult = codeModernizerManager.validate(context.project, transformationType) if (!validationResult.valid) { codeTransformChatHelper.updateLastPendingMessage( @@ -131,21 +194,30 @@ class CodeTransformChatController( codeTransformChatHelper.addNewMessage( buildStartNewTransformFollowup() ) - return + return null } + return validationResult + } - codeTransformChatHelper.updateLastPendingMessage( - buildProjectValidChatContent() + private suspend fun handleSQLConversion() { + this.validateAndReplyOnError(CodeTransformType.SQL_CONVERSION) ?: return + codeTransformChatHelper.addNewMessage( + buildUserInputSQLConversionMetadataChatContent() ) + } + private suspend fun handleLanguageUpgrade() { + val validationResult = this.validateAndReplyOnError(CodeTransformType.LANGUAGE_UPGRADE) ?: return + codeTransformChatHelper.updateLastPendingMessage( + buildLanguageUpgradeProjectValidChatContent() + ) codeTransformChatHelper.chatDelayShort() - codeTransformChatHelper.addNewMessage( - buildUserInputChatContent(context.project, validationResult) + buildUserInputLanguageUpgradeChatContent(context.project, validationResult) ) } - suspend fun tryRestoreChatProgress(): Boolean { + private suspend fun tryRestoreChatProgress(): Boolean { val isTransformOngoing = codeModernizerManager.isModernizationJobActive() val isMvnRunning = codeModernizerManager.isRunningMvn() @@ -213,21 +285,21 @@ class CodeTransformChatController( val moduleVirtualFile: VirtualFile = modulePath.toVirtualFile() as VirtualFile val moduleName = context.project.getModuleOrProjectNameForFile(moduleVirtualFile) - codeTransformChatHelper.addNewMessage(buildUserSelectionSummaryChatContent(moduleName)) + codeTransformChatHelper.addNewMessage(buildUserLanguageUpgradeSelectionSummaryChatContent(moduleName)) val sourceJdk = getSourceJdk(moduleVirtualFile) val selection = CustomerSelection( - moduleVirtualFile, - sourceJdk, - JavaSdkVersion.JDK_17, + configurationFile = moduleVirtualFile, + sourceJavaVersion = sourceJdk, + targetJavaVersion = JavaSdkVersion.JDK_17, ) // Create and set a session codeModernizerManager.createCodeModernizerSession(selection, context.project) // Publish metric to capture user selection before local build starts - telemetry.submitSelection("Confirm", selection) + telemetry.submitSelection("Confirm-Java", selection) codeTransformChatHelper.run { addNewMessage(buildUserInputSkipTestsFlagChatIntroContent()) @@ -235,6 +307,64 @@ class CodeTransformChatController( } } + override suspend fun processCodeTransformSelectSQLModuleSchemaAction(message: IncomingCodeTransformMessage.CodeTransformSelectSQLModuleSchema) { + val moduleName = context.project.getModuleOrProjectNameForFile(message.modulePath.toVirtualFile()) + codeTransformChatHelper.addNewMessage(buildUserSQLConversionSelectionSummaryChatContent(moduleName, message.schema)) + codeModernizerManager.codeTransformationSession?.let { + it.sessionContext.configurationFile = message.modulePath.toVirtualFile() + it.sessionContext.schema = message.schema + } + // start the SQL conversion + runInEdt { + codeModernizerManager.runModernize() + } + } + + override suspend fun processCodeTransformSelectSQLMetadataAction(message: IncomingCodeTransformMessage.CodeTransformSelectSQLMetadata) { + withContext(EDT) { + val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor() + .withDescription("Select metadata file") + .withFileFilter { it.extension == "zip" } + + val selectedZipFile = FileChooser.chooseFile(descriptor, null, null) ?: return@withContext + val extractedZip = createTempDirectory("codeTransformSQLMetadata", null) + + unzipFile(selectedZipFile.toNioPath(), extractedZip.toPath(), true) + + val sctFile = extractedZip.listFiles { file -> file.name.endsWith(".sct") }.firstOrNull() + + val metadataValidationResult = validateSctMetadata(sctFile) + + if (!metadataValidationResult.valid) { + codeTransformChatHelper.run { + addNewMessage(buildSQLMetadataValidationErrorChatContent(metadataValidationResult.errorReason)) + addNewMessage(buildStartNewTransformFollowup()) + } + return@withContext + } + + codeTransformChatHelper.run { + addNewMessage(buildSQLMetadataValidationSuccessIntroChatContent()) + addNewMessage(buildSQLMetadataValidationSuccessDetailsChatContent(metadataValidationResult)) + addNewMessage(buildModuleSchemaFormIntroChatContent()) + addNewMessage( + buildModuleSchemaFormChatContent(context.project, context.project.getJavaModulesWithSQL(), metadataValidationResult.schemaOptions) + ) + } + val selection = CustomerSelection( + // for SQL conversions (no sourceJavaVersion), use dummy value of Java 8 so that startJob API can be called + sourceJavaVersion = JavaSdkVersion.JDK_1_8, + targetJavaVersion = JavaSdkVersion.JDK_17, + sourceVendor = metadataValidationResult.sourceVendor, + targetVendor = metadataValidationResult.targetVendor, + sourceServerName = metadataValidationResult.sourceServerName, + sqlMetadataZip = extractedZip, + ) + codeModernizerManager.createCodeModernizerSession(selection, context.project) + telemetry.submitSelection("Confirm-SQL", selection) + } + } + override suspend fun processCodeTransformConfirmSkipTests(message: IncomingCodeTransformMessage.CodeTransformConfirmSkipTests) { val customBuildCommand = when (message.skipTestsSelection) { message("codemodernizer.chat.message.skip_tests_form.skip") -> MAVEN_BUILD_SKIP_UNIT_TESTS diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatHelper.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatHelper.kt index e1e34fbf557..0b13c37a0b4 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatHelper.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatHelper.kt @@ -5,12 +5,13 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.controller import kotlinx.coroutines.delay import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher +import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatInputEnabledMessage import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatMessage import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatMessageContent import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatMessageType import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatUpdateMessage import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformCreateTab -import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformNotificationMessage +import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformUpdatePlaceholderMessage import software.aws.toolkits.jetbrains.services.codemodernizer.session.ChatSessionStorage import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType import java.util.UUID @@ -36,14 +37,10 @@ class CodeTransformChatHelper( fun getHilPomItemId(): String? = hilPomItemId - suspend fun showChatNotification(title: String, content: String) { - messagePublisher.publish( - CodeTransformNotificationMessage( - title = title, - content = content, - ) - ) - } + suspend fun sendChatInputEnabledMessage(tabId: String, enabled: Boolean) = messagePublisher.publish(CodeTransformChatInputEnabledMessage(tabId, enabled)) + + suspend fun sendUpdatePlaceholderMessage(tabId: String, newPlaceholder: String) = + messagePublisher.publish(CodeTransformUpdatePlaceholderMessage(tabId, newPlaceholder)) suspend fun addNewMessage( content: CodeTransformChatMessageContent, diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/messages/CodeTransformMessage.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/messages/CodeTransformMessage.kt index 0fab137e222..14cc6dc8014 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/messages/CodeTransformMessage.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/messages/CodeTransformMessage.kt @@ -17,6 +17,8 @@ sealed interface CodeTransformBaseMessage : AmazonQMessage enum class CodeTransformButtonId(val id: String) { StartTransformation("codetransform-input-confirm"), + SelectSQLMetadata("codetransform-input-select-sql-metadata"), + SelectSQLModuleSchema("codetransform-input-select-sql-module-schema"), CancelTransformation("codetransform-input-cancel"), ConfirmSkipTests("codetransform-input-confirm-skip-tests"), StopTransformation("stop_transform"), @@ -32,6 +34,7 @@ enum class CodeTransformButtonId(val id: String) { enum class CodeTransformFormItemId(val id: String) { SelectModule("module"), + SelectSQLSchema("sqlSchema"), SelectTargetVersion("targetVersion"), SelectSkipTestsFlag("skipTestsSelection"), DependencyVersion("dependencyVersion"), @@ -75,6 +78,21 @@ sealed interface IncomingCodeTransformMessage : CodeTransformBaseMessage { val targetVersion: String, ) : IncomingCodeTransformMessage + data class CodeTransformSelectSQLMetadata( + @JsonProperty("tabID") val tabId: String, + ) : IncomingCodeTransformMessage + + data class CodeTransformSelectSQLModuleSchema( + @JsonProperty("tabID") val tabId: String, + val modulePath: String, + val schema: String, + ) : IncomingCodeTransformMessage + + data class ChatPrompt( + @JsonProperty("tabID") val tabId: String, + val message: String, + ) : IncomingCodeTransformMessage + data class CodeTransformStop( @JsonProperty("tabID") val tabId: String, ) : IncomingCodeTransformMessage @@ -194,12 +212,20 @@ data class CodeTransformCommandMessage( type = "codeTransformCommandMessage", ) -data class CodeTransformNotificationMessage( - val title: String, - val content: String, +data class CodeTransformUpdatePlaceholderMessage( + @JsonProperty("tabID") override val tabId: String, + val newPlaceholder: String, ) : CodeTransformUiMessage( - tabId = null, - type = "codeTransformNotificationMessage", + tabId = tabId, + type = "updatePlaceholderMessage" +) + +data class CodeTransformChatInputEnabledMessage( + @JsonProperty("tabID") override val tabId: String, + val enabled: Boolean, +) : CodeTransformUiMessage( + tabId = tabId, + type = "chatInputEnabledMessage" ) data class CodeTransformChatUpdateMessage( diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt index ed3c0723d2c..8065baa06f4 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerArtifact.kt @@ -4,8 +4,11 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.dataformat.xml.XmlMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule import com.intellij.openapi.util.io.FileUtil.createTempDirectory import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile @@ -43,6 +46,7 @@ open class CodeModernizerArtifact( private const val METRICS_FILE_NAME = "metrics.json" val LOG = getLogger() val MAPPER = jacksonObjectMapper() + val XML_MAPPER = XmlMapper().registerKotlinModule().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) /** * Extracts the file at [zipPath] and uses its contents to produce a [CodeModernizerArtifact]. diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt index 22e0774c728..24d940d525a 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt @@ -43,43 +43,55 @@ const val MANIFEST_PATH = "manifest.json" const val ZIP_SOURCES_PATH = "sources" const val ZIP_DEPENDENCIES_PATH = "dependencies" const val BUILD_LOG_PATH = "build-logs.txt" +const val UPLOAD_ZIP_MANIFEST_VERSION = "1.0" +const val HIL_1P_UPGRADE_CAPABILITY = "HIL_1pDependency_VersionUpgrade" +const val EXPLAINABILITY_V1 = "EXPLAINABILITY_V1" const val MAVEN_CONFIGURATION_FILE_NAME = "pom.xml" const val MAVEN_BUILD_RUN_UNIT_TESTS = "clean test" const val MAVEN_BUILD_SKIP_UNIT_TESTS = "clean test-compile" const val MAVEN_DEFAULT_BUILD_DIRECTORY_NAME = "target" const val IDEA_DIRECTORY_NAME = ".idea" +const val GIT_DIRECTORY_NAME = ".git" +const val DS_STORE_FILE_NAME = ".DS_Store" const val INVALID_SUFFIX_SHA = "sha1" const val INVALID_SUFFIX_REPOSITORIES = "repositories" +const val ORACLE_DB = "ORACLE" +const val AURORA_DB = "AURORA_POSTGRESQL" +const val RDS_DB = "RDS_POSTGRESQL" data class CodeModernizerSessionContext( val project: Project, - val configurationFile: VirtualFile, - val sourceJavaVersion: JavaSdkVersion, - val targetJavaVersion: JavaSdkVersion, + var configurationFile: VirtualFile? = null, // used to ZIP module + val sourceJavaVersion: JavaSdkVersion, // always needed for startJob API + val targetJavaVersion: JavaSdkVersion = JavaSdkVersion.JDK_17, // only one supported var customBuildCommand: String = MAVEN_BUILD_RUN_UNIT_TESTS, // run unit tests by default + val sourceVendor: String = ORACLE_DB, // only one supported + val targetVendor: String? = null, + val sourceServerName: String? = null, + var schema: String? = null, + val sqlMetadataZip: File? = null, ) { private val mapper = jacksonObjectMapper() private val ignoredDependencyFileExtensions = setOf(INVALID_SUFFIX_SHA, INVALID_SUFFIX_REPOSITORIES) - fun File.isMavenTargetFolder(): Boolean { + private fun File.isMavenTargetFolder(): Boolean { val hasPomSibling = this.resolveSibling(MAVEN_CONFIGURATION_FILE_NAME).exists() val isMavenTargetDirName = this.isDirectory && this.name == MAVEN_DEFAULT_BUILD_DIRECTORY_NAME return isMavenTargetDirName && hasPomSibling } - fun File.isIdeaFolder(): Boolean { - val isIdea = this.isDirectory && this.name == IDEA_DIRECTORY_NAME - return isIdea - } + private fun File.isIdeaFolder(): Boolean = this.isDirectory && this.name == IDEA_DIRECTORY_NAME + + private fun File.isGitFolder(): Boolean = this.isDirectory && this.name == GIT_DIRECTORY_NAME private fun findDirectoriesToExclude(sourceFolder: File): List { val excluded = mutableListOf() sourceFolder.walkTopDown().onEnter { - if (it.isMavenTargetFolder() || it.isIdeaFolder()) { + if (it.isMavenTargetFolder() || it.isIdeaFolder() || it.isGitFolder()) { excluded.add(it) return@onEnter false } return@onEnter true - }.forEach { + }.forEach { _ -> // noop, collects the sequence } return excluded @@ -104,8 +116,8 @@ data class CodeModernizerSessionContext( } fun getDependenciesUsingMaven(): MavenCopyCommandsResult { - val root = configurationFile.parent - val sourceFolder = File(root.path) + val root = configurationFile?.parent + val sourceFolder = File(root?.path) val buildLogBuilder = StringBuilder("Starting Build Log...\n") return executeMavenCopyCommands(sourceFolder, buildLogBuilder) } @@ -174,23 +186,27 @@ data class CodeModernizerSessionContext( } } - fun createZipWithModuleFiles(copyResult: MavenCopyCommandsResult): ZipCreationResult { - val root = configurationFile.parent - val sourceFolder = File(root.path) + fun createZipWithModuleFiles(copyResult: MavenCopyCommandsResult?): ZipCreationResult { + val root = configurationFile?.parent + val sourceFolder = File(root?.path) val buildLogBuilder = StringBuilder("Starting Build Log...\n") val depDirectory = if (copyResult is MavenCopyCommandsResult.Success) { showTransformationHub() copyResult.dependencyDirectory - } else { + } else if (copyResult != null) { // failure cases already handled by now, but to be safe set depDir to null if copyResult failed null + } else { + sqlMetadataZip // null copyResult means doing a SQL conversion } return runReadAction { try { - val directoriesToExclude = findDirectoriesToExclude(sourceFolder) - val files = VfsUtil.collectChildrenRecursively(root).filter { child -> - val childPath = Path(child.path) - !child.isDirectory && directoriesToExclude.none { childPath.startsWith(it.toPath()) } + val dirsToExclude = findDirectoriesToExclude(sourceFolder) + val files = root?.let { + VfsUtil.collectChildrenRecursively(it).filter { child -> + val childPath = Path(child.path) + !child.isDirectory && !child.name.endsWith(DS_STORE_FILE_NAME) && dirsToExclude.none { dir -> childPath.startsWith(dir.toPath()) } + } } val dependencyFiles = if (depDirectory != null) { iterateThroughDependencies(depDirectory) @@ -202,17 +218,26 @@ data class CodeModernizerSessionContext( val depSources = File(ZIP_DEPENDENCIES_PATH) val outputFile = createTemporaryZipFile { zip -> // 1) Manifest file - val dependenciesRoot = if (depDirectory != null) "$ZIP_DEPENDENCIES_PATH/${depDirectory.name}" else null - mapper.writeValueAsString(ZipManifest(dependenciesRoot = dependenciesRoot, customBuildCommand = customBuildCommand)) + var manifest = ZipManifest(customBuildCommand = customBuildCommand) + if (sqlMetadataZip != null) { + // doing a SQL conversion, not language upgrade + val sctFileName = sqlMetadataZip.listFiles { file -> file.name.endsWith(".sct") }.first().name + manifest = ZipManifest( + requestedConversions = RequestedConversions( + SQLConversion(sourceVendor, targetVendor, schema, sourceServerName, sctFileName) + ) + ) + } + mapper.writeValueAsString(manifest) .byteInputStream() .use { zip.putNextEntry(Path(MANIFEST_PATH).toString(), it) } - // 2) Dependencies + // 2) Dependencies / SQL conversion metadata if (depDirectory != null) { dependencyFiles.forEach { depFile -> - val relativePath = File(depFile.path).relativeTo(depDirectory.parentFile) + val relativePath = File(depFile.path).relativeTo(depDirectory) val paddedPath = depSources.resolve(relativePath) var paddedPathString = paddedPath.toPath().toString() // Convert Windows file path to work on Linux @@ -228,7 +253,7 @@ data class CodeModernizerSessionContext( LOG.info { "Dependency files size = ${dependencyFiles.sumOf { it.length().toInt() }}" } // 3) Sources - files.forEach { file -> + files?.forEach { file -> val relativePath = File(file.path).relativeTo(sourceFolder) val paddedPath = zipSources.resolve(relativePath) var paddedPathString = paddedPath.toPath().toString() @@ -246,13 +271,14 @@ data class CodeModernizerSessionContext( } } - LOG.info { "Source code files size = ${files.sumOf { it.length.toInt() }}" } + LOG.info { "Source code files size = ${files?.sumOf { it.length.toInt() }}" } // 4) Build Log buildLogBuilder.toString().byteInputStream().use { zip.putNextEntry(Path(BUILD_LOG_PATH).toString(), it) } }.toFile() + // depDirectory should never be null if (depDirectory != null) ZipCreationResult.Succeeded(outputFile) else ZipCreationResult.Missing1P(outputFile) } catch (e: NoSuchFileException) { throw CodeModernizerException("Source folder not found") diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformType.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformType.kt new file mode 100644 index 00000000000..8c0d685c678 --- /dev/null +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeTransformType.kt @@ -0,0 +1,9 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codemodernizer.model + +enum class CodeTransformType { + LANGUAGE_UPGRADE, + SQL_CONVERSION, +} diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CustomerSelection.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CustomerSelection.kt index 809e15af29c..9e08c7d91f1 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CustomerSelection.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CustomerSelection.kt @@ -5,9 +5,15 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.vfs.VirtualFile +import java.io.File data class CustomerSelection( - val configurationFile: VirtualFile, - val sourceJavaVersion: JavaSdkVersion, - val targetJavaVersion: JavaSdkVersion, + val configurationFile: VirtualFile? = null, // used to ZIP module + val sourceJavaVersion: JavaSdkVersion, // always needed, use default of JDK_8 for SQL conversions for startJob API call + val targetJavaVersion: JavaSdkVersion = JavaSdkVersion.JDK_17, + val sourceVendor: String = ORACLE_DB, // only one supported + val targetVendor: String? = null, + val sourceServerName: String? = null, + val sqlMetadataZip: File? = null, + // note: schema and customBuildCommand are passed in to CodeModernizerSessionContext separately, *after* CodeModernizerSession is created ) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/InvalidTelemetryReason.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/InvalidTelemetryReason.kt index 9f1fbd02c0c..99bc1b8d6a4 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/InvalidTelemetryReason.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/InvalidTelemetryReason.kt @@ -5,4 +5,4 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model import software.aws.toolkits.telemetry.CodeTransformPreValidationError -data class InvalidTelemetryReason(val category: CodeTransformPreValidationError? = CodeTransformPreValidationError.Unknown, val additonalInfo: String = "") +data class InvalidTelemetryReason(val category: CodeTransformPreValidationError? = CodeTransformPreValidationError.Unknown, val additionalInfo: String = "") diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobHistoryItem.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobHistoryItem.kt index 356fc81ad0d..4483f9fc5b7 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobHistoryItem.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/JobHistoryItem.kt @@ -7,9 +7,9 @@ import java.time.Instant import kotlin.time.Duration.Companion.seconds import kotlin.time.toKotlinDuration -data class JobHistoryItem(val moduleName: String, val status: String, val startTime: Instant, val runTime: java.time.Duration, val jobId: String) { +data class JobHistoryItem(val moduleName: String?, val status: String, val startTime: Instant, val runTime: java.time.Duration, val jobId: String) { operator fun get(col: Int): Any = when (col) { - 0 -> moduleName + 0 -> moduleName.orEmpty() 1 -> status 2 -> startTime 3 -> runTime.toKotlinDuration().inWholeSeconds.seconds.toString() diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SctMetadata.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SctMetadata.kt new file mode 100644 index 00000000000..85d1d44d665 --- /dev/null +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SctMetadata.kt @@ -0,0 +1,79 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codemodernizer.model + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement + +@JacksonXmlRootElement(localName = "tree") +data class SctMetadata( + @JsonProperty("instances") + val instances: Instances, +) + +data class Instances( + @JsonProperty("ProjectModel") + val projectModel: ProjectModel, +) + +data class ProjectModel( + @JsonProperty("entities") + val entities: Entities, + @JsonProperty("relations") + val relations: Relations, +) + +data class Entities( + @JsonProperty("sources") + val sources: Sources, + @JsonProperty("targets") + val targets: Targets, +) + +data class Sources( + @JsonProperty("DbServer") + val dbServer: DbServer, +) + +data class Targets( + @JsonProperty("DbServer") + val dbServer: DbServer, +) + +data class DbServer( + @JsonProperty("vendor") + val vendor: String, + @JsonProperty("name") + val name: String, +) + +data class Relations( + @JsonProperty("server-node-location") + @JacksonXmlElementWrapper(useWrapping = false) + val serverNodeLocation: List, +) + +data class ServerNodeLocation( + @JsonProperty("FullNameNodeInfoList") + val fullNameNodeInfoList: FullNameNodeInfoList, +) + +data class FullNameNodeInfoList( + @JsonProperty("nameParts") + val nameParts: NameParts, +) + +data class NameParts( + @JsonProperty("FullNameNodeInfo") + @JacksonXmlElementWrapper(useWrapping = false) + val fullNameNodeInfo: List, +) + +data class FullNameNodeInfo( + @JsonProperty("typeNode") + val typeNode: String, + @JsonProperty("nameNode") + val nameNode: String, +) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SqlMetadataValidationResult.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SqlMetadataValidationResult.kt new file mode 100644 index 00000000000..b9e7fc2b8a1 --- /dev/null +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/SqlMetadataValidationResult.kt @@ -0,0 +1,13 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codemodernizer.model + +data class SqlMetadataValidationResult( + val valid: Boolean, + val errorReason: String = "", + val sourceVendor: String = "", + val targetVendor: String = "", + val sourceServerName: String = "", + val schemaOptions: Set = emptySet(), +) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ValidationResult.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ValidationResult.kt index 1db3c1fe57b..e4dd4a4bae4 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ValidationResult.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ValidationResult.kt @@ -8,10 +8,8 @@ import software.aws.toolkits.telemetry.CodeTransformBuildSystem data class ValidationResult( val valid: Boolean, - val invalidReason: String? = null, val invalidTelemetryReason: InvalidTelemetryReason = InvalidTelemetryReason(), val validatedBuildFiles: List = emptyList(), - val validatedProjectJdkName: String = "", val buildSystem: CodeTransformBuildSystem = CodeTransformBuildSystem.Unknown, val buildSystemVersion: String = "", ) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt index 05d8791fd44..9267de9ddb9 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt @@ -3,18 +3,25 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model -import software.aws.toolkits.jetbrains.services.codemodernizer.BUILD_LOG_PATH -import software.aws.toolkits.jetbrains.services.codemodernizer.EXPLAINABILITY_V1 -import software.aws.toolkits.jetbrains.services.codemodernizer.HIL_1P_UPGRADE_CAPABILITY -import software.aws.toolkits.jetbrains.services.codemodernizer.UPLOAD_ZIP_MANIFEST_VERSION -import software.aws.toolkits.jetbrains.services.codemodernizer.ZIP_SOURCES_PATH - data class ZipManifest( val sourcesRoot: String = ZIP_SOURCES_PATH, - val dependenciesRoot: String? = null, + val dependenciesRoot: String = ZIP_DEPENDENCIES_PATH, val buildLogs: String = BUILD_LOG_PATH, - val version: String = UPLOAD_ZIP_MANIFEST_VERSION.toString(), + val version: String = UPLOAD_ZIP_MANIFEST_VERSION, val hilCapabilities: List = listOf(HIL_1P_UPGRADE_CAPABILITY), val transformCapabilities: List = listOf(EXPLAINABILITY_V1), val customBuildCommand: String = MAVEN_BUILD_RUN_UNIT_TESTS, + val requestedConversions: RequestedConversions? = null, // only used for SQL conversions for now +) + +data class RequestedConversions( + val sqlConversion: SQLConversion? = null, +) + +data class SQLConversion( + val source: String? = null, + val target: String? = null, + val schema: String? = null, + val host: String? = null, + val sctFileName: String? = null, ) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/BuildProgressSplitterPanelManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/BuildProgressSplitterPanelManager.kt index c9b65c90357..44638a5443c 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/BuildProgressSplitterPanelManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/BuildProgressSplitterPanelManager.kt @@ -13,6 +13,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.aws.toolkits.jetbrains.services.codemodernizer.model.BuildProgressStepTreeItem import software.aws.toolkits.jetbrains.services.codemodernizer.model.BuildStepStatus import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerException +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.ProgressStepId import software.aws.toolkits.jetbrains.services.codemodernizer.panels.BuildProgressStepDetailsPanel import software.aws.toolkits.jetbrains.services.codemodernizer.panels.BuildProgressTreePanel @@ -42,12 +43,9 @@ class BuildProgressSplitterPanelManager(private val project: Project) : val userObject = selectedNode.userObject if (userObject is BuildProgressStepTreeItem) { if (isValidStepClick(userObject.id) && userObject.transformationStepId != null) { - System.out.println("Is valid stepID") buildProgressStepDetailsPanel.updateListData(userObject.transformationStepId) revalidate() repaint() - } else { - System.out.println("Is NOT valid stepID") } } } @@ -114,18 +112,26 @@ class BuildProgressSplitterPanelManager(private val project: Project) : } } - fun handleProgressStateChanged(newState: TransformationStatus, transformationPlan: TransformationPlan?, jdkVersion: JavaSdkVersion) { + fun handleProgressStateChanged(newState: TransformationStatus, plan: TransformationPlan?, jdk: JavaSdkVersion, transformType: CodeTransformType) { val currentState = statusTreePanel.getCurrentElements() val loadingPanelText: String // show the details panel when there are progress updates // otherwise it would show an empty panel val backendProgressStepsAvailable = ( - transformationPlan != null && - transformationPlan.hasTransformationSteps() && - haveProgressUpdates(transformationPlan) + plan != null && + plan.hasTransformationSteps() && + haveProgressUpdates(plan) ) - fun maybeAdd(stepId: ProgressStepId, string: String) { + fun maybeAddTransformationStep(stepId: ProgressStepId, string: String) { + // don't show building or generate plan message for SQL conversions since we don't build or generate plan + if (transformType == CodeTransformType.SQL_CONVERSION && ( + string == message("codemodernizer.toolwindow.progress.building") || + string == message("codemodernizer.toolwindow.progress.planning") + ) + ) { + return + } if (currentState.none { it.id == stepId }) { currentState.add(BuildProgressStepTreeItem(string, BuildStepStatus.WORKING, stepId)) } @@ -137,41 +143,37 @@ class BuildProgressSplitterPanelManager(private val project: Project) : TransformationStatus.PREPARING, ) ) { - maybeAdd(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) - maybeAdd(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) + maybeAddTransformationStep(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) + maybeAddTransformationStep(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) } - if (newState in setOf( - TransformationStatus.PREPARED, - TransformationStatus.PLANNING, - ) - ) { - maybeAdd(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) - maybeAdd(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) - maybeAdd(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) + if (newState in setOf(TransformationStatus.PREPARED, newState == TransformationStatus.PLANNING)) { + maybeAddTransformationStep(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) + maybeAddTransformationStep(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) + maybeAddTransformationStep(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) } if (newState in setOf( TransformationStatus.PLANNED, TransformationStatus.TRANSFORMING, ) ) { - maybeAdd(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) - maybeAdd(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) - maybeAdd(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) - maybeAdd(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) + maybeAddTransformationStep(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) + maybeAddTransformationStep(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) + maybeAddTransformationStep(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) + maybeAddTransformationStep(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) } if (newState == TransformationStatus.PAUSED) { - maybeAdd(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) - maybeAdd(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) - maybeAdd(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) - maybeAdd(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) + maybeAddTransformationStep(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) + maybeAddTransformationStep(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) + maybeAddTransformationStep(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) + maybeAddTransformationStep(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) } if (newState == TransformationStatus.RESUMED) { - maybeAdd(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) - maybeAdd(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) - maybeAdd(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) - maybeAdd(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) + maybeAddTransformationStep(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) + maybeAddTransformationStep(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) + maybeAddTransformationStep(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) + maybeAddTransformationStep(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) } if (newState in setOf( @@ -179,21 +181,21 @@ class BuildProgressSplitterPanelManager(private val project: Project) : TransformationStatus.PARTIALLY_COMPLETED, ) ) { - maybeAdd(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) - maybeAdd(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) - maybeAdd(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) - maybeAdd(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) + maybeAddTransformationStep(ProgressStepId.UPLOADING, message("codemodernizer.toolwindow.progress.uploading")) + maybeAddTransformationStep(ProgressStepId.BUILDING, message("codemodernizer.toolwindow.progress.building")) + maybeAddTransformationStep(ProgressStepId.PLANNING, message("codemodernizer.toolwindow.progress.planning")) + maybeAddTransformationStep(ProgressStepId.TRANSFORMING, message("codemodernizer.toolwindow.progress.transforming")) } // Figure out if we should add plan steps val statuses: List = if (currentState.isEmpty()) { defaultProgressData().toList() } else { - if (backendProgressStepsAvailable && transformationPlan != null) { + if (backendProgressStepsAvailable && plan != null) { currentBuildingTransformationStep = newBuildingTransformationStep // skip step 0 (contains supplemental info) - newBuildingTransformationStep = transformationPlan.transformationSteps().size - 1 - val transformationPlanSteps = transformationPlan.transformationSteps()?.drop(1)?.map { + newBuildingTransformationStep = plan.transformationSteps().size - 1 + val transformationPlanSteps = plan.transformationSteps()?.drop(1)?.map { getUpdatedBuildProgressStepTreeItem(it) } transformationPlanSteps?.sortedBy { it.transformationStepId } @@ -215,22 +217,38 @@ class BuildProgressSplitterPanelManager(private val project: Project) : } TransformationStatus.PREPARING -> { - loadingPanelText = message("codemodernizer.toolwindow.scan_in_progress.building", jdkVersion.description) + loadingPanelText = if (transformType == CodeTransformType.SQL_CONVERSION) { + message("codemodernizer.toolwindow.scan_in_progress.transforming") + } else { + message("codemodernizer.toolwindow.scan_in_progress.building", jdk.description) + } statuses.update(BuildStepStatus.DONE, ProgressStepId.UPLOADING) } TransformationStatus.PREPARED -> { - loadingPanelText = message("codemodernizer.toolwindow.scan_in_progress.building", jdkVersion.description) + loadingPanelText = if (transformType == CodeTransformType.SQL_CONVERSION) { + message("codemodernizer.toolwindow.scan_in_progress.transforming") + } else { + message("codemodernizer.toolwindow.scan_in_progress.building", jdk.description) + } statuses.update(BuildStepStatus.DONE, ProgressStepId.BUILDING) } TransformationStatus.PLANNING -> { - loadingPanelText = message("codemodernizer.toolwindow.scan_in_progress.planning") + loadingPanelText = if (transformType == CodeTransformType.SQL_CONVERSION) { + message("codemodernizer.toolwindow.scan_in_progress.transforming") + } else { + message("codemodernizer.toolwindow.scan_in_progress.planning") + } statuses.update(BuildStepStatus.DONE, ProgressStepId.BUILDING) } TransformationStatus.PLANNED -> { - loadingPanelText = message("codemodernizer.toolwindow.scan_in_progress.planning") + loadingPanelText = if (transformType == CodeTransformType.SQL_CONVERSION) { + message("codemodernizer.toolwindow.scan_in_progress.transforming") + } else { + message("codemodernizer.toolwindow.scan_in_progress.planning") + } statuses.update(BuildStepStatus.DONE, ProgressStepId.PLANNING) } @@ -289,8 +307,8 @@ class BuildProgressSplitterPanelManager(private val project: Project) : this.remove(loadingPanel) setProgressStepsDefaultUI() } - if (transformationPlan != null) { - buildProgressStepDetailsPanel.setTransformationPlan(transformationPlan) + if (plan != null) { + buildProgressStepDetailsPanel.setTransformationPlan(plan) // automatically jump to the next step's right details' panel only when the current step is finished and next step starts if (newBuildingTransformationStep == 1 || currentBuildingTransformationStep != newBuildingTransformationStep) { buildProgressStepDetailsPanel.updateListData(newBuildingTransformationStep) @@ -348,6 +366,7 @@ class BuildProgressSplitterPanelManager(private val project: Project) : .toKotlinDuration().inWholeSeconds.seconds.toString() to formatter.format(Date.from(endTime)) } + // will be False for SQL conversions, which is what we want so that the (nonexistent) progressUpdates do not render private fun haveProgressUpdates(plan: TransformationPlan): Boolean = plan.transformationSteps().any { it.progressUpdates().size > 0 } private fun isValidStepClick(stepId: ProgressStepId): Boolean = when (stepId) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt index fe26278aedf..b07e00987bf 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt @@ -21,6 +21,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.panels.CodeModernizerBanner import software.aws.toolkits.jetbrains.services.codemodernizer.panels.CodeModernizerJobHistoryTablePanel import software.aws.toolkits.jetbrains.services.codemodernizer.panels.LoadingPanel @@ -216,15 +217,6 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa } } - fun setProjectInvalidUI(reason: String) = setUI { - banner.updateContent(reason, AllIcons.General.Error) - fullSizeLoadingPanel.apply { - fullSizeLoadingPanel.showFailureUI() - revalidate() - repaint() - } - } - fun userInitiatedStopCodeModernizationUI() = setUI { stopTimer() add(BorderLayout.CENTER, buildProgressSplitterPanelManager) @@ -238,7 +230,7 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa return actionManager.createActionToolbar(ACTION_PLACE, group, false) } - fun handleJobTransition(new: TransformationStatus, plan: TransformationPlan?, sourceJavaVersion: JavaSdkVersion) = invokeLater { + fun handleJobTransition(new: TransformationStatus, plan: TransformationPlan?, sourceJdk: JavaSdkVersion, transformType: CodeTransformType) = invokeLater { if (new in listOf( TransformationStatus.PLANNED, TransformationStatus.TRANSFORMING, @@ -246,12 +238,12 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa TransformationStatus.PAUSED, TransformationStatus.COMPLETED, TransformationStatus.PARTIALLY_COMPLETED - ) + ) && transformType != CodeTransformType.SQL_CONVERSION // no plan for SQL conversions ) { addPlanToBanner() } buildProgressSplitterPanelManager.apply { - handleProgressStateChanged(new, plan, sourceJavaVersion) + handleProgressStateChanged(new, plan, sourceJdk, transformType) if (timer == null) { timer = Timer() timer?.scheduleAtFixedRate( @@ -300,11 +292,11 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa } } - fun setResumeJobUI(currentJobResult: TransformationJob, plan: TransformationPlan?, sourceJavaVersion: JavaSdkVersion) { + fun setResumeJobUI(currentJobResult: TransformationJob, plan: TransformationPlan?, sourceJdk: JavaSdkVersion, transformationType: CodeTransformType) { setJobRunningUI() buildProgressSplitterPanelManager.apply { reset() - handleProgressStateChanged(currentJobResult.status(), plan, sourceJavaVersion) + handleProgressStateChanged(currentJobResult.status(), plan, sourceJdk, transformationType) } } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerSessionState.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerSessionState.kt index 7725a2c74da..3f0acd86b5b 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerSessionState.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerSessionState.kt @@ -21,7 +21,7 @@ import kotlin.io.path.Path class CodeModernizerSessionState { var currentJobStatus: TransformationStatus = TransformationStatus.UNKNOWN_TO_SDK_VERSION - private val previousJobHistory = mutableMapOf() + private val previousJobHistory = mutableMapOf() var currentJobCreationTime: Instant = Instant.MIN var currentJobStopTime: Instant = Instant.MIN var transformationPlan: TransformationPlan? = null @@ -37,7 +37,9 @@ class CodeModernizerSessionState { currentJobStatus = TransformationStatus.UNKNOWN_TO_SDK_VERSION } - private fun getJobModuleName(sessionContext: CodeModernizerSessionContext) = Path(sessionContext.configurationFile.path).toAbsolutePath().toString() + private fun getJobModuleName(sessionContext: CodeModernizerSessionContext) = + sessionContext.configurationFile?.let { Path(it.path).toAbsolutePath().toString() } + fun putJobHistory(sessionContext: CodeModernizerSessionContext, status: TransformationStatus, jobId: String = "", startedAt: Instant = Instant.now()) { val moduleName = getJobModuleName(sessionContext) val jobHistoryItem = JobHistoryItem( diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt index 3eedffc1234..9a109308684 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt @@ -28,7 +28,7 @@ fun buildState(context: CodeModernizerSessionContext, isJobOngoing: Boolean, job lastJobContext.putAll( setOf( JobDetails.LAST_JOB_ID to jobId.id, - JobDetails.CONFIGURATION_FILE_PATH to context.configurationFile.path, + JobDetails.CONFIGURATION_FILE_PATH to (context.configurationFile?.path ?: error("No configuration file store in the state")), JobDetails.TARGET_JAVA_VERSION to context.targetJavaVersion.description, JobDetails.SOURCE_JAVA_VERSION to context.sourceJavaVersion.description, JobDetails.CUSTOM_BUILD_COMMAND to context.customBuildCommand diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 5e0d43d9b45..54e99d9dffb 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -26,6 +26,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClien import software.aws.toolkits.jetbrains.services.codemodernizer.constants.BILLING_RATE import software.aws.toolkits.jetbrains.services.codemodernizer.constants.JOB_STATISTICS_TABLE_KEY import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact.Companion.MAPPER +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId import software.aws.toolkits.jetbrains.services.codemodernizer.model.PlanTable import software.aws.toolkits.resources.message @@ -44,6 +45,7 @@ data class PollingResult( * Wrapper around [waitUntil] that polls the API DescribeMigrationJob to check the migration job status. */ suspend fun JobId.pollTransformationStatusAndPlan( + transformType: CodeTransformType, succeedOn: Set, failOn: Set, clientAdaptor: GumbyClient, @@ -89,7 +91,7 @@ suspend fun JobId.pollTransformationStatusAndPlan( transformationResponse = clientAdaptor.getCodeModernizationJob(this.id) val newStatus = transformationResponse?.transformationJob()?.status() ?: throw RuntimeException("Unable to get job status") var newPlan: TransformationPlan? = null - if (newStatus in STATES_WHERE_PLAN_EXIST) { + if (newStatus in STATES_WHERE_PLAN_EXIST && transformType != CodeTransformType.SQL_CONVERSION) { // no plan for SQL conversions delay(sleepDurationMillis) newPlan = clientAdaptor.getCodeModernizationPlan(this).transformationPlan() } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt index e5f5f627e61..bba84915c2b 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt @@ -3,13 +3,15 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.utils -import com.fasterxml.jackson.dataformat.xml.XmlMapper +import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.openapi.application.runReadAction import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFile import software.aws.toolkits.core.utils.createParentDirectories +import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.exists +import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager.Companion.LOG import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_ARTIFACT_DIR_NAME @@ -20,8 +22,15 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_DEP import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_POM_FILE_NAME import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_POM_VERSION_PLACEHOLDER import software.aws.toolkits.jetbrains.services.codemodernizer.constants.HIL_UPLOAD_ZIP_NAME +import software.aws.toolkits.jetbrains.services.codemodernizer.controller.CodeTransformChatController +import software.aws.toolkits.jetbrains.services.codemodernizer.model.AURORA_DB +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact.Companion.XML_MAPPER import software.aws.toolkits.jetbrains.services.codemodernizer.model.DependencyUpdatesReport import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_CONFIGURATION_FILE_NAME +import software.aws.toolkits.jetbrains.services.codemodernizer.model.ORACLE_DB +import software.aws.toolkits.jetbrains.services.codemodernizer.model.RDS_DB +import software.aws.toolkits.jetbrains.services.codemodernizer.model.SctMetadata +import software.aws.toolkits.jetbrains.services.codemodernizer.model.SqlMetadataValidationResult import software.aws.toolkits.jetbrains.services.codewhisperer.util.content import software.aws.toolkits.resources.message import java.io.File @@ -104,14 +113,20 @@ fun parseBuildFile(buildFile: VirtualFile?): String? { /** * Unzips a zip into a dir. Returns the true when successfully unzips the file pointed to by [zipFilePath] to [destDir] */ -fun unzipFile(zipFilePath: Path, destDir: Path): Boolean { +fun unzipFile(zipFilePath: Path, destDir: Path, isSqlMetadata: Boolean = false): Boolean { if (!zipFilePath.exists()) return false val zipFile = ZipFile(zipFilePath.toFile()) zipFile.use { file -> file.entries().asSequence() .filterNot { it.isDirectory } .map { zipEntry -> - val destPath = destDir.resolve(zipEntry.name) + var fileName = zipEntry.name + if (isSqlMetadata) { + // when manually compressing ZIP files, the files get unzipped under a subdirectory where we extract them to, + // this change puts the files directly under the root of the target directory, which is what we want + fileName = zipEntry.name.substringAfterLast(File.separatorChar) + } + val destPath = destDir.resolve(fileName) destPath.createParentDirectories() FileOutputStream(destPath.toFile()).use { targetFile -> zipFile.getInputStream(zipEntry).copyTo(targetFile) @@ -123,11 +138,55 @@ fun unzipFile(zipFilePath: Path, destDir: Path): Boolean { fun parseXmlDependenciesReport(pathToXmlDependency: Path): DependencyUpdatesReport { val reportFile = pathToXmlDependency.toFile() - val xmlMapper = XmlMapper() - val report = xmlMapper.readValue(reportFile, DependencyUpdatesReport::class.java) + val report = XML_MAPPER.readValue(reportFile, DependencyUpdatesReport::class.java) return report } +fun validateSctMetadata(sctFile: File?): SqlMetadataValidationResult { + if (sctFile == null) { + return SqlMetadataValidationResult(false, message("codemodernizer.chat.message.validation.error.missing_sct_file")) + } + + val sctMetadata: SctMetadata + try { + sctMetadata = XML_MAPPER.readValue(sctFile) + } catch (e: Exception) { + getLogger().error { "Error parsing .sct metadata file; invalid XML encountered: ${e.localizedMessage}" } + return SqlMetadataValidationResult(false, message("codemodernizer.chat.message.validation.error.invalid_sct")) + } + + try { + val projectModel = sctMetadata.instances.projectModel + val sourceDbServer = projectModel.entities.sources.dbServer + val sourceVendor = sourceDbServer.vendor.trim().uppercase() + if (sourceVendor != ORACLE_DB) { + return SqlMetadataValidationResult(false, message("codemodernizer.chat.message.validation.error.invalid_source_db")) + } + + val sourceServerName = sourceDbServer.name.trim() + + val targetDbServer = projectModel.entities.targets.dbServer + val targetVendor = targetDbServer.vendor.trim().uppercase() + if (targetVendor != AURORA_DB && targetVendor != RDS_DB) { + return SqlMetadataValidationResult(false, message("codemodernizer.chat.message.validation.error.invalid_target_db")) + } + + val schemaNames = mutableSetOf() + projectModel.relations.serverNodeLocation.forEach { serverNodeLocation -> + val fullNameNodeInfoList = serverNodeLocation.fullNameNodeInfoList.nameParts.fullNameNodeInfo + fullNameNodeInfoList.forEach { node -> + if (node.typeNode.lowercase() == "schema") { + schemaNames.add(node.nameNode.uppercase()) + } + } + } + return SqlMetadataValidationResult(true, "", sourceVendor, targetVendor, sourceServerName, schemaNames) + } catch (e: Exception) { + getLogger().error { "Error parsing .sct metadata file: ${e.localizedMessage}" } + return SqlMetadataValidationResult(false, message("codemodernizer.chat.message.validation.error.invalid_sct")) + } +} + fun createFileCopy(originalFile: File, outputPath: Path): File { val outputFile = outputPath.toFile() diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtils.kt index bdddf7605a4..fef9674314a 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformModuleUtils.kt @@ -10,6 +10,9 @@ import com.intellij.openapi.projectRoots.impl.JavaSdkImpl import com.intellij.openapi.roots.LanguageLevelModuleExtensionImpl import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.text.StringSearcher /** * @description Try to get the module SDK version and/or Language level from the project settings > modules > source field. @@ -35,5 +38,27 @@ fun Module.tryGetJdkLanguageLevelJdk(): JavaSdkVersion? { val moduleRootManager = ModuleRootManager.getInstance(this) val languageLevelModuleExtension = moduleRootManager.getModuleExtension(LanguageLevelModuleExtensionImpl::class.java) val languageLevel = languageLevelModuleExtension?.languageLevel - return languageLevel?.let { JavaSdkVersion.fromLanguageLevel(it) } ?: null + return languageLevel?.let { JavaSdkVersion.fromLanguageLevel(it) } +} + +// search for Strings that indicate embedded Oracle SQL statements are present +fun containsSQL(contentRoot: VirtualFile): Boolean { + val patterns = listOf( + "oracle.jdbc.OracleDriver", + "jdbc:oracle:thin:@", + "jdbc:oracle:oci:@", + "jdbc:odbc:", + ) + + val searchers = patterns.map { StringSearcher(it, false, true) } + + return VfsUtilCore.iterateChildrenRecursively(contentRoot, null) { file -> + if (!file.isDirectory && !file.fileType.isBinary) { + val content = file.contentsToByteArray().toString(Charsets.UTF_8) + if (searchers.any { it.scan(content) != -1 }) { + return@iterateChildrenRecursively false // found a match; stop searching + } + } + true // no match found; continue searching + }.not() // invert result because iterateChildrenRecursively returns false when match found } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformProjectUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformProjectUtils.kt index 8fd40f30e45..2c0f75f27b6 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformProjectUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformProjectUtils.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.project.modules import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.projectRoots.impl.JavaSdkImpl import com.intellij.openapi.roots.LanguageLevelProjectExtension +import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.VirtualFile @@ -78,4 +79,25 @@ fun Project.getSupportedModules(supportedJavaMappings: Map + val rootManager = ModuleRootManager.getInstance(module) + if (rootManager.sdk?.sdkType?.name?.lowercase()?.contains("java") == true) { + val contentRoots = rootManager.contentRoots + if (contentRoots.isNotEmpty()) { + val contentRoot = contentRoots.first() + val children = contentRoot.children + if (children.isNotEmpty() && containsSQL(contentRoot)) { + listOf(children.first()) + } else { + emptyList() + } + } else { + emptyList() + } + } else { + emptyList() + } +} + +fun Project.getModuleOrProjectNameForFile(file: VirtualFile?) = file?.let { ModuleUtil.findModuleForFile(it, this)?.name } ?: this.name diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt index 0d921f8b323..50f165c9745 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt @@ -57,6 +57,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerStartJobResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_BUILD_SKIP_UNIT_TESTS import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason @@ -502,7 +503,7 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa doReturn(exampleStartCodeMigrationResponse).whenever(clientAdaptorSpy).startCodeModernization(any(), any(), any()) doNothing().whenever(testSessionStateSpy).updateJobHistory(any(), any(), any()) - val result = testSessionSpy.pollUntilJobCompletion(jobId) { _, _ -> } + val result = testSessionSpy.pollUntilJobCompletion(CodeTransformType.LANGUAGE_UPGRADE, jobId) { _, _ -> } assertEquals(CodeModernizerJobCompletedResult.JobCompletedSuccessfully(jobId), result) // two polls to check status as we 1. check for plan existing and 2. check if job completed @@ -523,7 +524,7 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa doReturn(exampleStartCodeMigrationResponse).whenever(clientAdaptorSpy).startCodeModernization(any(), any(), any()) doNothing().whenever(testSessionStateSpy).updateJobHistory(any(), any(), any()) - val result = testSessionSpy.pollUntilJobCompletion(jobId) { _, _ -> } + val result = testSessionSpy.pollUntilJobCompletion(CodeTransformType.LANGUAGE_UPGRADE, jobId) { _, _ -> } assertEquals(CodeModernizerJobCompletedResult.JobPartiallySucceeded(jobId, testSessionContextSpy.targetJavaVersion), result) verify(clientAdaptorSpy, times(4)).getCodeModernizationJob(any()) verify(clientAdaptorSpy, atLeastOnce()).getCodeModernizationPlan(any()) diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt index f2c4f993b1f..39bb0bc5cb6 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerTest.kt @@ -23,6 +23,7 @@ import software.amazon.awssdk.services.codewhispererstreaming.model.Transformati import software.amazon.awssdk.services.ssooidc.model.SsoOidcException import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadArtifactResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.InvalidTelemetryReason @@ -30,7 +31,6 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.ParseZipFai import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult import software.aws.toolkits.jetbrains.services.codemodernizer.utils.filterOnlyParentFiles import software.aws.toolkits.jetbrains.services.codemodernizer.utils.unzipFile -import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.CodeTransformPreValidationError import software.aws.toolkits.telemetry.CodeTransformVCSViewerSrcComponents import kotlin.io.path.Path @@ -207,10 +207,9 @@ class CodeWhispererCodeModernizerTest : CodeWhispererCodeModernizerTestBase() { @Test fun `start transformation without IdC connection`() { - val result = codeModernizerManagerSpy.validate(project) + val result = codeModernizerManagerSpy.validate(project, CodeTransformType.LANGUAGE_UPGRADE) val expectedResult = ValidationResult( false, - message("codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in"), InvalidTelemetryReason( CodeTransformPreValidationError.NonSsoLogin ) diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt index 6dab0303971..c71d1019690 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt @@ -19,13 +19,16 @@ import software.amazon.awssdk.services.codewhispererruntime.model.AccessDeniedEx import software.amazon.awssdk.services.codewhispererruntime.model.TransformationProgressUpdate import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getBillingText import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getTableMapping import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseBuildFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.pollTransformationStatusAndPlan import software.aws.toolkits.jetbrains.services.codemodernizer.utils.refreshToken +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.validateSctMetadata import software.aws.toolkits.jetbrains.utils.rules.addFileToModule import java.util.concurrent.atomic.AtomicBoolean +import kotlin.io.path.createTempFile class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase() { @Before @@ -47,6 +50,7 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase val mutableList = mutableListOf() runBlocking { jobId.pollTransformationStatusAndPlan( + CodeTransformType.LANGUAGE_UPGRADE, setOf(TransformationStatus.STARTED), setOf(TransformationStatus.FAILED), clientAdaptorSpy, @@ -88,6 +92,7 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase val mutableList = mutableListOf() runBlocking { jobId.pollTransformationStatusAndPlan( + CodeTransformType.LANGUAGE_UPGRADE, setOf(TransformationStatus.STARTED), setOf(TransformationStatus.FAILED), clientAdaptorSpy, @@ -129,6 +134,7 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase val mutableList = mutableListOf() runBlocking { jobId.pollTransformationStatusAndPlan( + CodeTransformType.LANGUAGE_UPGRADE, setOf(TransformationStatus.STARTED), setOf(TransformationStatus.FAILED), clientAdaptorSpy, @@ -161,6 +167,7 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase val result = runBlocking { jobId.pollTransformationStatusAndPlan( + CodeTransformType.LANGUAGE_UPGRADE, setOf(TransformationStatus.COMPLETED), setOf(TransformationStatus.FAILED), clientAdaptorSpy, @@ -229,4 +236,175 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase val actual = getBillingText(376) assertThat(expected).isEqualTo(actual) } + + @Test + fun `WHEN validateMetadataFile on fully valid sct file THEN passes validation`() { + val sampleFileContents = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.trimIndent() + + val tempFile = createTempFile("valid-sctFile", ".xml").toFile() + tempFile.writeText(sampleFileContents) + + val isValidMetadata = validateSctMetadata(tempFile) + assertThat(isValidMetadata.valid).isTrue() + assertThat(isValidMetadata.errorReason).isEmpty() + assertThat(isValidMetadata.sourceVendor).isEqualTo("ORACLE") + assertThat(isValidMetadata.targetVendor).isEqualTo("AURORA_POSTGRESQL") + assertThat(isValidMetadata.sourceServerName).isEqualTo("sample.rds.amazonaws.com") + assertThat(isValidMetadata.schemaOptions.containsAll(setOf("SCHEMA1", "SCHEMA2", "SCHEMA3"))).isTrue() + } + + @Test + fun `WHEN validateMetadataFile on sct file with invalid source DB THEN fails validation`() { + val sampleFileContents = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.trimIndent() + + val tempFile = createTempFile("invalid-sctFile1", ".xml").toFile() + tempFile.writeText(sampleFileContents) + + val isValidMetadata = validateSctMetadata(tempFile) + assertThat(isValidMetadata.valid).isFalse() + assertThat(isValidMetadata.errorReason.contains("I can only convert SQL for migrations from an Oracle source database")).isTrue() + } + + @Test + fun `WHEN validateMetadataFile on sct file with invalid target DB THEN fails validation`() { + val sampleFileContents = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.trimIndent() + + val tempFile = createTempFile("invalid-sctFile2", ".xml").toFile() + tempFile.writeText(sampleFileContents) + + val isValidMetadata = validateSctMetadata(tempFile) + assertThat(isValidMetadata.valid).isFalse() + assertThat( + isValidMetadata.errorReason.contains("I can only convert SQL for migrations to Aurora PostgreSQL or Amazon RDS for PostgreSQL target databases") + ).isTrue() + } } diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeTransformChatConnector.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeTransformChatConnector.ts index 70382918819..35609731fac 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeTransformChatConnector.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeTransformChatConnector.ts @@ -8,6 +8,7 @@ import {ExtensionMessage} from '../commands' import {TabsStorage, TabType} from '../storages/tabsStorage' import {FollowUpGenerator} from '../followUps/generator' import {FormButtonIds} from '../forms/constants' +import {ChatPayload} from "../connector"; export interface ICodeTransformChatConnectorProps { sendMessageToExtension: (message: ExtensionMessage) => void @@ -15,6 +16,8 @@ export interface ICodeTransformChatConnectorProps { onCodeTransformMessageReceived: (tabID: string, message: ChatItem, isLoading: boolean, clearPreviousItemButtons?: boolean) => void onCodeTransformMessageUpdate: (tabID: string, messageId: string, chatItem: Partial) => void onCodeTransformCommandMessageReceived: (message: ChatItem, command?: string) => void + onChatInputEnabled: (tabID: string, enabled: boolean) => void + onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void onNotification: (props: {content: string; title?: string; type: NotificationType}) => void onStartNewTransform: (tabID: string) => void onUpdateAuthentication: (featureDevEnabled: boolean, codeTransformEnabled: boolean, authenticatingTabIDs: string[]) => void @@ -29,6 +32,8 @@ export class CodeTransformChatConnector { private readonly onCodeTransformMessageUpdated private readonly onCodeTransformCommandMessageReceived private readonly onNotification + private readonly onUpdatePlaceholder + private readonly onChatInputEnabled private readonly onStartNewTransform private readonly onUpdateAuthentication private readonly onNewTab @@ -42,6 +47,8 @@ export class CodeTransformChatConnector { this.onCodeTransformMessageReceived = props.onCodeTransformMessageReceived this.onCodeTransformMessageUpdated = props.onCodeTransformMessageUpdate this.onCodeTransformCommandMessageReceived = props.onCodeTransformCommandMessageReceived + this.onChatInputEnabled = props.onChatInputEnabled + this.onUpdatePlaceholder = props.onUpdatePlaceholder this.onNotification = props.onNotification this.onUpdateAuthentication = props.onUpdateAuthentication this.tabsStorage = props.tabsStorage @@ -68,6 +75,16 @@ export class CodeTransformChatConnector { } } + requestAnswer = (tabID: string, payload: ChatPayload) => { + this.tabsStorage.updateTabStatus(tabID, 'busy') + this.sendMessageToExtension({ + tabID: tabID, + command: 'chat-prompt', + tabType: 'codetransform', + message: payload.chatMessage + }) + } + private processCodeTransformCommandMessage = (messageData: any): void => { this.onCodeTransformCommandMessageReceived(messageData, messageData.command) } @@ -168,6 +185,16 @@ export class CodeTransformChatConnector { return } + if (messageData.type === 'updatePlaceholderMessage') { + this.onUpdatePlaceholder(messageData.tabID, messageData.newPlaceholder) + return + } + + if (messageData.type === 'chatInputEnabledMessage') { + this.onChatInputEnabled(messageData.tabID, messageData.enabled) + return + } + if (messageData.type === 'authNeededException') { await this.processAuthNeededException(messageData) return @@ -205,6 +232,20 @@ export class CodeTransformChatConnector { modulePath: action.formItemValues?.module, targetVersion: 'Java 17', }) + } else if (action.id === FormButtonIds.CodeTransformInputSQLMetadata) { + this.sendMessageToExtension({ + command: 'codetransform-select-sql-metadata', + tabID, + tabType: 'codetransform', + }) + } else if (action.id === FormButtonIds.CodeTransformInputSQLModuleSchema) { + this.sendMessageToExtension({ + command: 'codetransform-select-sql-module-schema', + tabID, + tabType: 'codetransform', + modulePath: action.formItemValues?.module, + schema: action.formItemValues?.sqlSchema, + }) } else if (action.id === FormButtonIds.CodeTransformInputCancel) { this.sendMessageToExtension({ command: 'codetransform-cancel', diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/commands.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/commands.ts index 0503e044b7b..d69c36d1410 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/commands.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/commands.ts @@ -31,6 +31,8 @@ type MessageCommand = | 'transform' | 'footer-info-link-click' | 'codetransform-start' + | 'codetransform-select-sql-metadata' + | 'codetransform-select-sql-module-schema' | 'codetransform-cancel' | 'codetransform-stop' | 'codetransform-confirm-skip-tests' diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts index ab7fc6ac20b..e51291b0c88 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts @@ -130,6 +130,13 @@ export class Connector { } } + requestAnswer = (tabID: string, payload: ChatPayload) => { + switch (this.tabsStorage.getTab(tabID)?.type) { + case 'codetransform': + return this.codeTransformChatConnector.requestAnswer(tabID, payload) + } + } + requestGenerativeAIAnswer = (tabID: string, payload: ChatPayload): Promise => new Promise((resolve, reject) => { if (this.isUIReady) { diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/forms/constants.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/forms/constants.ts index c81ed0a36e5..32312888bd8 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/forms/constants.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/forms/constants.ts @@ -5,6 +5,8 @@ export const enum FormButtonIds { CodeTransformInputConfirm = 'codetransform-input-confirm', + CodeTransformInputSQLMetadata = 'codetransform-input-select-sql-metadata', + CodeTransformInputSQLModuleSchema = 'codetransform-input-select-sql-module-schema', CodeTransformInputCancel = 'codetransform-input-cancel', CodeTransformInputSkipTests = 'codetransform-input-confirm-skip-tests', OpenMvnBuild = 'open_mvn_build', @@ -22,6 +24,8 @@ export const isFormButtonCodeTransform = (id: string): boolean => { return ( id === FormButtonIds.CodeTransformInputConfirm || id === FormButtonIds.CodeTransformInputCancel || + id === FormButtonIds.CodeTransformInputSQLMetadata || + id === FormButtonIds.CodeTransformInputSQLModuleSchema || id === FormButtonIds.CodeTransformInputSkipTests || id === FormButtonIds.CodeTransformViewDiff || id === FormButtonIds.CodeTransformViewSummary || diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts index 126d10f446d..f7d1cae7193 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts @@ -436,6 +436,11 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT mynahUI.addChatItem(tabID, { type: ChatItemType.ANSWER_STREAM, }) + } else if (tabsStorage.getTab(tabID)?.type === 'codetransform') { + connector.requestAnswer(tabID, { + chatMessage: prompt.prompt ?? '' + }) + return } if (prompt.command !== undefined && prompt.command.trim() !== '') { diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index c4cbf4cd2f2..512fa934d86 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -564,6 +564,7 @@ code.aws.workspaces.short=Dev Environments codemodernizer.builderrordialog.description.title=Error occurred when building your project codemodernizer.chat.form.user_selection.item.choose_module=Choose a module to transform codemodernizer.chat.form.user_selection.item.choose_skip_tests_option=Choose to skip unit tests +codemodernizer.chat.form.user_selection.item.choose_sql_metadata_file=Okay, I can convert the embedded SQL code for your Oracle to PostgreSQL transformation. To get started, upload the zipped metadata file from your schema conversion in AWS Data Migration Service (DMS). To retrieve the metadata file:\n1. Open your database migration project in the AWS DMS console.\n2. Open the schema conversion and choose **Convert the embedded SQL in your application**.\n3. Once you complete the conversion, close the project and go to the S3 bucket where your project is stored.\n4. Open the folder and find the project folder ("sct-project").\n5. Download the object inside the project folder. This will be a zip file.\n\nFor more info, refer to the [documentation](https://docs.aws.amazon.com/dms/latest/userguide/schema-conversion-save-apply.html#schema-conversion-save). codemodernizer.chat.form.user_selection.item.choose_target_version=Choose the target code version codemodernizer.chat.form.user_selection.title=Q - Code transformation codemodernizer.chat.message.absolute_path_detected=I detected {0} potential absolute file path(s) in your {1} file: **{2}**. Absolute file paths might cause issues when I build your code. Any errors will show up in the build log. @@ -574,11 +575,13 @@ codemodernizer.chat.message.button.hil_cancel=Cancel codemodernizer.chat.message.button.hil_submit=Submit codemodernizer.chat.message.button.open_file=Open file codemodernizer.chat.message.button.open_transform_hub=Open Transformation Hub +codemodernizer.chat.message.button.select_sql_metadata=Select metadata file codemodernizer.chat.message.button.stop_transform=Stop transformation codemodernizer.chat.message.button.view_build=View build progress codemodernizer.chat.message.button.view_diff=View diff codemodernizer.chat.message.button.view_failure_build_log=View Failure Build Log codemodernizer.chat.message.button.view_summary=View summary +codemodernizer.chat.message.choose_objective=Enter "language upgrade" or "SQL conversion" codemodernizer.chat.message.download_failed_client_instructions_expired=Your transformation is not available anymore. Your code and transformation summary are deleted 24 hours after the transformation completes. Please try starting the transformation again. codemodernizer.chat.message.download_failed_invalid_artifact=Sorry, I was unable to find your {0}. Artifacts are deleted after 24 hours. Please try starting the transformation again. codemodernizer.chat.message.download_failed_other=Sorry, I ran into an issue while trying to download your {0}. Please try again. {1} @@ -611,14 +614,16 @@ codemodernizer.chat.message.result.fail=Sorry, I ran into an issue during the tr codemodernizer.chat.message.result.fail_initial_build=I am having trouble building your project in the secure build environment and couldn't complete the transformation. codemodernizer.chat.message.result.fail_initial_build_no_build_log=I am having trouble building your project in the secure build environment: {0}. codemodernizer.chat.message.result.fail_with_known_reason=Sorry, I couldn''t complete the transformation. {0} -codemodernizer.chat.message.result.partially_success=I upgraded 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. -codemodernizer.chat.message.result.success=I successfully finished the transformation. 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. +codemodernizer.chat.message.result.partially_success=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. +codemodernizer.chat.message.result.success=I successfully 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. codemodernizer.chat.message.result.zip_too_large=Sorry, your project size exceeds the Amazon Q Code Transformation upload limit of 2GB. codemodernizer.chat.message.resume_ongoing=I'm still transforming your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your module. To monitor progress, go to the Transformation Hub. codemodernizer.chat.message.skip_tests=I will build your module using `mvn test` by default. If you would like me to build your module without running unit tests, I will use `mvn test-compile`. codemodernizer.chat.message.skip_tests_form.response=Okay, I will {0} when building your module. codemodernizer.chat.message.skip_tests_form.run_tests=Run unit tests codemodernizer.chat.message.skip_tests_form.skip=Skip unit tests +codemodernizer.chat.message.sql_metadata_success=I found the following source database, target database, and host based on the schema conversion metadata you provided: +codemodernizer.chat.message.sql_module_schema_prompt=To continue, choose the module and schema for this transformation. codemodernizer.chat.message.transform_begin=I'm starting to transform your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your module. To monitor progress, go to the Transformation Hub. codemodernizer.chat.message.transform_cancelled_by_user=I cancelled your transformation. If you want to start another transformation, choose **Start a new transformation**. codemodernizer.chat.message.transform_in_progress=If I run into any issues, I might pause the transformation to get input from you on how to proceed. @@ -629,9 +634,14 @@ codemodernizer.chat.message.upload_failed_http_error=Sorry, I couldn''t upload y codemodernizer.chat.message.upload_failed_other=Sorry, I was unable to upload your project due to an unexpected failure. {0} codemodernizer.chat.message.upload_failed_ssl_error=Sorry, I was unable to upload your project. This might have been caused by your IDE not trusting the certificate of your HTTP proxy. Ensure all certificates for your proxy client have been configured in your IDE, and then retry transformation. codemodernizer.chat.message.upload_failed_url_expired=Sorry, I couldn't upload your project to begin the transformation. The Amazon S3 pre-signed URL used to upload your code expired after 30 minutes. This might have been caused by delays introduced by intermediate services in your network infrastructure.\n\nCheck your network configuration for services that might be causing delays. If the issue persists, you might need to allow list the following Amazon S3 bucket: 'amazonq-code-transformation-us-east-1-c6160f047e0.s3.amazonaws.com'. -codemodernizer.chat.message.validation.check_eligible_projects=I'm checking for open projects that are eligible for Code Transformation. +codemodernizer.chat.message.validation.check_eligible_modules=Checking for eligible modules... codemodernizer.chat.message.validation.check_passed=I can upgrade your Java module. To start the transformation, I need some information from you. Choose the module you want to upgrade and the target code version to upgrade to. Then, choose **Confirm**.\n\nIf you do not see the module you want to transform, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and language level. In the Modules tab, set the correct module JDK and language level. +codemodernizer.chat.message.validation.error.invalid_sct=It looks like the .sct file you provided isn't valid. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS. +codemodernizer.chat.message.validation.error.invalid_source_db=I can only convert SQL for migrations from an Oracle source database. The provided .sct file indicates another source database for this migration. +codemodernizer.chat.message.validation.error.invalid_target_db=I can only convert SQL for migrations to Aurora PostgreSQL or Amazon RDS for PostgreSQL target databases. The provided .sct file indicates another target database for this migration. +codemodernizer.chat.message.validation.error.missing_sct_file=An .sct file is required for transformation. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS. codemodernizer.chat.message.validation.error.more_info=For more information, see the [Amazon Q documentation]({0}). +codemodernizer.chat.message.validation.error.no_java_project=Sorry, I could not find an open Java module with embedded Oracle SQL statements. Make sure you have a Java module open that has at least 1 content root. codemodernizer.chat.message.validation.error.no_pom=I couldn't find a module that I can upgrade. Your Java project must be built on Maven and contain a pom.xml file. For more information, see the [Amazon Q documentation]({0}). codemodernizer.chat.message.validation.error.other=I couldn't find a module 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. If you have a Java 8, Java 11, or Java 17 module in your workspace, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and the correct language level. In the Modules tab, set the correct module JDK and language level. codemodernizer.chat.message.validation.error.unsupported_java_version=I couldn't find a module 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. If you have a Java 8, Java 11, or Java 17 in your workspace, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and the correct language level. In the Modules tab, set the correct module JDK and language level. @@ -689,7 +699,7 @@ codemodernizer.notification.info.modernize_failed.connection_failed=Amazon Q cou codemodernizer.notification.info.modernize_failed.title=Transformation failed codemodernizer.notification.info.modernize_failed.unknown_failure_reason=Unknown failure reason codemodernizer.notification.info.modernize_ongoing.view_status=View status -codemodernizer.notification.info.modernize_partial_complete.content=Amazon Q upgraded 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. +codemodernizer.notification.info.modernize_partial_complete.content=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. codemodernizer.notification.info.modernize_partial_complete.title=Transformation partially successful! codemodernizer.notification.info.transformation_resume.content=Amazon Q was unable to resume polling for the job you started before closing the project. codemodernizer.notification.info.transformation_resume.title=Unable to resume polling for job updates. @@ -710,7 +720,7 @@ codemodernizer.notification.warn.download_failed_ssl.content=Please make sure al codemodernizer.notification.warn.download_failed_wildcard.content=Check your IDE proxy settings and remove any wildcard (*) references, and then try viewing the diff again. codemodernizer.notification.warn.expired_credentials.title=Your connection to Q has expired codemodernizer.notification.warn.invalid_project.description.reason.invalid_jdk_versions=None of your open modules are supported for code transformation with Amazon Q. Amazon Q can upgrade Java 8, Java 11, and Java 17 projects built on Maven. -codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots=None of your open modules are supported for code transformation with Amazon Q. Amazon Q can upgrade Java 8, Java 11, and Java 17 projects built on Maven. +codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots=None of your open modules are supported for code transformation with Amazon Q. Amazon Q can upgrade Java 8, Java 11, and Java 17 projects built on Maven, with content roots configured. codemodernizer.notification.warn.invalid_project.description.reason.no_valid_files=None of your open modules are supported for code transformation with Amazon Q. A pom.xml is required for transformation. Amazon Q can upgrade Java 8, Java 11, and Java 17 projects built on Maven. codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in=Amazon Q cannot start the transformation as you are not logged in with Identity Center or Builder ID. Also ensure that you are not using IntelliJ version 232.8660.185 and that you are not developing on a remote host (uncommon). codemodernizer.notification.warn.invalid_project.description.reason.remote_backend=None of your open modules are supported for code transformation with Amazon Q. Amazon Q cannot transform projects running on a remote host. @@ -756,7 +766,7 @@ codemodernizer.toolwindow.problems_window_not_found=Unable to display Code Trans codemodernizer.toolwindow.progress.building=Build uploaded code in secure build environment codemodernizer.toolwindow.progress.parent=Transformation progress codemodernizer.toolwindow.progress.planning=Generate transformation plan -codemodernizer.toolwindow.progress.transforming=Transform your code to Java 17 using transformation plan +codemodernizer.toolwindow.progress.transforming=Transform your code codemodernizer.toolwindow.progress.uploading=Uploading your code codemodernizer.toolwindow.scan_display=Transformation details codemodernizer.toolwindow.scan_in_progress=Amazon Q is scanning the project files and getting ready to start the job. To start the job, Amazon Q needs to upload the project artifacts. Once that's done, Q can start the transformation job. The estimated time for this operation ranges from a few seconds to several minutes. @@ -765,7 +775,7 @@ codemodernizer.toolwindow.scan_in_progress.building=Amazon Q is building your co codemodernizer.toolwindow.scan_in_progress.failed=The step failed, fetching additional details... codemodernizer.toolwindow.scan_in_progress.planning=Amazon Q is analyzing your code in order to generate a transformation plan. codemodernizer.toolwindow.scan_in_progress.stopping=Stopping the job... -codemodernizer.toolwindow.scan_in_progress.transforming=Amazon Q is transforming your code. Details will appear soon. +codemodernizer.toolwindow.scan_in_progress.transforming=Amazon Q is transforming your code. codemodernizer.toolwindow.stop_scan=Stop job codemodernizer.toolwindow.table.header.date=Date codemodernizer.toolwindow.table.header.job_id=Job Id