diff --git a/.changes/next-release/bugfix-29d1b3f1-b7e6-489d-b614-49f7d908f221.json b/.changes/next-release/bugfix-29d1b3f1-b7e6-489d-b614-49f7d908f221.json new file mode 100644 index 00000000000..bbc84bbe256 --- /dev/null +++ b/.changes/next-release/bugfix-29d1b3f1-b7e6-489d-b614-49f7d908f221.json @@ -0,0 +1,4 @@ +{ + "type" : "bugfix", + "description" : "/transform: prompt user to re-authenticate if credentials expire during transformation" +} \ No newline at end of file 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 4d73038a303..0d51765749d 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 @@ -8,6 +8,7 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionToolbar import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runInEdt import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkVersion @@ -38,7 +39,7 @@ import javax.swing.BorderFactory import javax.swing.JPanel class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPanel(BorderLayout()) { - private var lastShownProgressPanel: Component? = null + private var progressPanel: Component? = null val toolbar = createToolbar().apply { targetComponent = this@CodeModernizerBottomWindowPanelManager component.border = BorderFactory.createCompoundBorder( @@ -62,14 +63,16 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa } private fun setUI(function: () -> Unit) { - lastShownProgressPanel = this.components.firstOrNull { it == fullSizeLoadingPanel || it == buildProgressSplitterPanelManager } ?: lastShownProgressPanel - removeAll() - add(BorderLayout.WEST, toolbar.component) - add(BorderLayout.NORTH, banner) - function.invoke() - updateRunTime() - revalidate() - repaint() + runInEdt { + progressPanel = this.components.firstOrNull { it == fullSizeLoadingPanel || it == buildProgressSplitterPanelManager } ?: progressPanel + removeAll() + add(BorderLayout.WEST, toolbar.component) + add(BorderLayout.NORTH, banner) + function.invoke() + updateRunTime() + revalidate() + repaint() + } } private fun updateRunTime(now: Instant? = null) { @@ -173,8 +176,8 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa } fun showUnalteredJobUI() = setUI { - if (lastShownProgressPanel != null) { - add(BorderLayout.CENTER, lastShownProgressPanel) + if (progressPanel != null) { + add(BorderLayout.CENTER, progressPanel) } else { add(BorderLayout.CENTER, fullSizeLoadingPanel) fullSizeLoadingPanel.progressIndicatorLabel.text = "No jobs active" 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 54e99d9dffb..9ddc086d819 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 @@ -4,6 +4,7 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.utils import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.notification.NotificationAction import com.intellij.openapi.project.Project import com.intellij.serviceContainer.AlreadyDisposedException import kotlinx.coroutines.delay @@ -23,12 +24,14 @@ import software.aws.toolkits.core.utils.WaiterUnrecoverableException import software.aws.toolkits.core.utils.Waiters.waitUntil import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient +import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener 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.jetbrains.utils.notifyStickyWarn import software.aws.toolkits.resources.message import java.time.Duration import java.util.Locale @@ -64,8 +67,7 @@ suspend fun JobId.pollTransformationStatusAndPlan( val maxRefreshes = 10 var numRefreshes = 0 - // We refresh token at the start of polling, but for some long jobs that runs for 30 minutes plus, tokens may need to be - // refreshed again when AccessDeniedException or InvalidGrantException are caught. + // refresh token at start of polling since local build just prior can take a long time refreshToken(project) try { @@ -113,8 +115,17 @@ suspend fun JobId.pollTransformationStatusAndPlan( refreshToken(project) return@waitUntil state } catch (e: InvalidGrantException) { - if (numRefreshes++ > maxRefreshes) throw e - refreshToken(project) + CodeTransformMessageListener.instance.onCheckAuth() + notifyStickyWarn( + message("codemodernizer.notification.warn.expired_credentials.title"), + message("codemodernizer.notification.warn.expired_credentials.content"), + project, + listOf( + NotificationAction.createSimpleExpiring(message("codemodernizer.notification.warn.action.reauthenticate")) { + CodeTransformMessageListener.instance.onReauthStarted() + } + ) + ) return@waitUntil state } finally { delay(sleepDurationMillis) 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 c71d1019690..948024cc99d 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 @@ -6,6 +6,7 @@ import io.mockk.every import io.mockk.just import io.mockk.mockkStatic import io.mockk.runs +import io.mockk.verify import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -26,7 +27,9 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseBuildF 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.notifyStickyWarn import software.aws.toolkits.jetbrains.utils.rules.addFileToModule +import software.aws.toolkits.resources.message import java.util.concurrent.atomic.AtomicBoolean import kotlin.io.path.createTempFile @@ -114,18 +117,18 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase } @Test - fun `refresh on invalid grant`() { + fun `show re-auth notification on invalid grant exception`() { val mockInvalidGrantException = Mockito.mock(InvalidGrantException::class.java) - mockkStatic(::refreshToken) - every { refreshToken(any()) } just runs + mockkStatic(::notifyStickyWarn) + every { notifyStickyWarn(any(), any(), any(), any(), any()) } just runs Mockito.doThrow( mockInvalidGrantException ).doReturn( exampleGetCodeMigrationResponse, exampleGetCodeMigrationResponse.replace(TransformationStatus.STARTED), - exampleGetCodeMigrationResponse.replace(TransformationStatus.COMPLETED), // Should stop before this point + exampleGetCodeMigrationResponse.replace(TransformationStatus.COMPLETED), ).whenever(clientAdaptorSpy).getCodeModernizationJob(any()) Mockito.doReturn(exampleGetCodeMigrationPlanResponse) @@ -152,7 +155,7 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase TransformationStatus.STARTED, ) assertThat(expected).isEqualTo(mutableList) - io.mockk.verify { refreshToken(any()) } + verify { notifyStickyWarn(message("codemodernizer.notification.warn.expired_credentials.title"), any(), any(), any(), any()) } } @Test 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 2ce5422fc3e..e90647fa030 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -779,6 +779,7 @@ codemodernizer.notification.warn.download_failed_invalid_artifact=Amazon Q was u codemodernizer.notification.warn.download_failed_other.content=Amazon Q ran into an issue while trying to download your {0}. Please try again. {1} codemodernizer.notification.warn.download_failed_ssl.content=Please make sure all your certificates for your proxy client have been set up correctly for your IDE. 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.content=Unable to check transformation status as your credentials expired. Try signing out of Amazon Q and signing back in again if 'Reauthenticate' below does not work. codemodernizer.notification.warn.expired_credentials.title=Your connection to Q has expired 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, Java 17, and Java 21 projects built on Maven, with content roots configured. 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).