Skip to content

Commit 7010c57

Browse files
authored
Amazon Q Code Transform: Failure Build Log (#4570)
1 parent 4e5b072 commit 7010c57

File tree

22 files changed

+321
-62
lines changed

22 files changed

+321
-62
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Amazon Q Code Transform: Allow user to view transformation build log"
4+
}

plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt

Lines changed: 103 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.intellij.openapi.vfs.LocalFileSystem
1616
import com.intellij.openapi.vfs.VirtualFile
1717
import kotlinx.coroutines.launch
1818
import kotlinx.coroutines.withContext
19+
import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType
1920
import software.aws.toolkits.core.utils.error
2021
import software.aws.toolkits.core.utils.exists
2122
import software.aws.toolkits.core.utils.getLogger
@@ -26,6 +27,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESH
2627
import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient
2728
import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
2829
import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact
30+
import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformDownloadArtifact
31+
import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformFailureBuildLog
2932
import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact
3033
import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason
3134
import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
@@ -40,36 +43,31 @@ import java.nio.file.Path
4043
import java.time.Instant
4144
import java.util.concurrent.atomic.AtomicBoolean
4245

43-
data class DownloadArtifactResult(val artifact: CodeModernizerArtifact?, val zipPath: String, val errorMessage: String = "")
46+
data class DownloadArtifactResult(val artifact: CodeTransformDownloadArtifact?, val zipPath: String, val errorMessage: String = "")
4447

4548
const val DOWNLOAD_PROXY_WILDCARD_ERROR: String = "Dangling meta character '*' near index 0"
4649
const val DOWNLOAD_SSL_HANDSHAKE_ERROR: String = "Unable to execute HTTP request: javax.net.ssl.SSLHandshakeException"
50+
const val INVALID_ARTIFACT_ERROR: String = "Invalid artifact"
4751

4852
class ArtifactHandler(private val project: Project, private val clientAdaptor: GumbyClient) {
4953
private val telemetry = CodeTransformTelemetryManager.getInstance(project)
5054
private val downloadedArtifacts = mutableMapOf<JobId, Path>()
5155
private val downloadedSummaries = mutableMapOf<JobId, TransformationSummary>()
52-
56+
private val downloadedBuildLogPath = mutableMapOf<JobId, Path>()
5357
private var isCurrentlyDownloading = AtomicBoolean(false)
58+
5459
internal suspend fun displayDiff(job: JobId) {
5560
if (isCurrentlyDownloading.get()) return
56-
val result = downloadArtifact(job)
61+
val result = downloadArtifact(job, TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS)
5762
if (result.artifact == null) {
5863
notifyUnableToApplyPatch(result.zipPath, result.errorMessage)
5964
} else {
65+
result.artifact as CodeModernizerArtifact
6066
displayDiffUsingPatch(result.artifact.patch, job)
6167
}
6268
}
6369

64-
private fun notifyDownloadStart() {
65-
notifyStickyInfo(
66-
message("codemodernizer.notification.info.download.started.title"),
67-
message("codemodernizer.notification.info.download.started.content"),
68-
project,
69-
)
70-
}
71-
72-
private suspend fun unzipToPath(byteArrayList: List<ByteArray>, outputDirPath: Path? = null): Pair<Path, Int> {
70+
suspend fun unzipToPath(byteArrayList: List<ByteArray>, outputDirPath: Path? = null): Pair<Path, Int> {
7371
val zipFilePath = withContext(getCoroutineBgContext()) {
7472
if (outputDirPath == null) {
7573
Files.createTempFile(null, ".zip")
@@ -106,28 +104,47 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
106104
}
107105
}
108106

109-
suspend fun downloadArtifact(job: JobId): DownloadArtifactResult {
107+
suspend fun downloadArtifact(
108+
job: JobId,
109+
artifactType: TransformationDownloadArtifactType,
110+
isPreFetch: Boolean = false
111+
): DownloadArtifactResult {
110112
isCurrentlyDownloading.set(true)
111113
val downloadStartTime = Instant.now()
112114
try {
113115
// 1. Attempt reusing previously downloaded artifact for job
114-
val previousArtifact = downloadedArtifacts.getOrDefault(job, null)
116+
val previousArtifact = if (artifactType == TransformationDownloadArtifactType.LOGS) {
117+
downloadedBuildLogPath.getOrDefault(job, null)
118+
} else {
119+
downloadedArtifacts.getOrDefault(job, null)
120+
}
115121
if (previousArtifact != null && previousArtifact.exists()) {
116122
val zipPath = previousArtifact.toAbsolutePath().toString()
117123
return try {
118-
val artifact = CodeModernizerArtifact.create(zipPath)
119-
downloadedSummaries[job] = artifact.summary
120-
DownloadArtifactResult(artifact, zipPath)
124+
if (artifactType == TransformationDownloadArtifactType.LOGS) {
125+
DownloadArtifactResult(CodeTransformFailureBuildLog.create(zipPath), zipPath)
126+
} else {
127+
val artifact = CodeModernizerArtifact.create(zipPath)
128+
downloadedSummaries[job] = artifact.summary
129+
DownloadArtifactResult(artifact, zipPath)
130+
}
121131
} catch (e: RuntimeException) {
122132
LOG.error { e.message.toString() }
123133
DownloadArtifactResult(null, zipPath, e.message.orEmpty())
124134
}
125135
}
126136

127137
// 2. Download the data
128-
notifyDownloadStart()
129138
LOG.info { "About to download the export result archive" }
130-
val downloadResultsResponse = clientAdaptor.downloadExportResultArchive(job)
139+
// only notify if downloading client instructions (upgraded code)
140+
if (artifactType == TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS) {
141+
notifyDownloadStart()
142+
}
143+
val downloadResultsResponse = if (artifactType == TransformationDownloadArtifactType.LOGS) {
144+
clientAdaptor.downloadExportResultArchive(job, null, TransformationDownloadArtifactType.LOGS)
145+
} else {
146+
clientAdaptor.downloadExportResultArchive(job)
147+
}
131148

132149
// 3. Convert to zip
133150
LOG.info { "Downloaded the export result archive, about to transform to zip" }
@@ -136,17 +153,26 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
136153
val zipPath = path.toAbsolutePath().toString()
137154
LOG.info { "Successfully converted the download to a zip at $zipPath." }
138155

139-
// 4. Deserialize zip to CodeModernizerArtifact
156+
// 4. Deserialize zip
140157
var telemetryErrorMessage: String? = null
141158
return try {
142-
val output = DownloadArtifactResult(CodeModernizerArtifact.create(zipPath), zipPath)
143-
downloadedArtifacts[job] = path
159+
val output = if (artifactType == TransformationDownloadArtifactType.LOGS) {
160+
DownloadArtifactResult(CodeTransformFailureBuildLog.create(zipPath), zipPath)
161+
} else {
162+
DownloadArtifactResult(CodeModernizerArtifact.create(zipPath), zipPath)
163+
}
164+
if (artifactType == TransformationDownloadArtifactType.LOGS) {
165+
downloadedBuildLogPath[job] = path
166+
} else {
167+
downloadedArtifacts[job] = path
168+
}
144169
output
145170
} catch (e: RuntimeException) {
146171
LOG.error { e.message.toString() }
147172
telemetryErrorMessage = "Unexpected error when downloading result ${e.localizedMessage}"
148173
DownloadArtifactResult(null, zipPath, e.message.orEmpty())
149174
} finally {
175+
// TODO: add artifact type to telemetry to differentiate downloads for client instructions vs logs
150176
telemetry.jobArtifactDownloadAndDeserializeTime(
151177
downloadStartTime,
152178
job,
@@ -157,14 +183,19 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
157183
} catch (e: Exception) {
158184
var errorMessage: String = e.message.orEmpty()
159185
// SdkClientException will be thrown, masking actual issues like SSLHandshakeException underneath
160-
if (e.message.toString().contains(DOWNLOAD_PROXY_WILDCARD_ERROR)) {
161-
errorMessage = message("codemodernizer.notification.warn.download_failed_wildcard.content")
162-
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.PROXY_WILDCARD_ERROR)
163-
} else if (e.message.toString().contains(DOWNLOAD_SSL_HANDSHAKE_ERROR)) {
164-
errorMessage = message("codemodernizer.notification.warn.download_failed_ssl.content")
165-
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.SSL_HANDSHAKE_ERROR)
166-
} else {
167-
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.OTHER(e.message.toString()))
186+
// TODO: remove this check once we are no longer pre-fetching for build log, as the check will no longer be needed
187+
if (!isPreFetch) {
188+
if (e.message.toString().contains(DOWNLOAD_PROXY_WILDCARD_ERROR)) {
189+
errorMessage = message("codemodernizer.notification.warn.download_failed_wildcard.content")
190+
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.PROXY_WILDCARD_ERROR(artifactType))
191+
} else if (e.message.toString().contains(DOWNLOAD_SSL_HANDSHAKE_ERROR)) {
192+
errorMessage = message("codemodernizer.notification.warn.download_failed_ssl.content")
193+
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.SSL_HANDSHAKE_ERROR(artifactType))
194+
} else if (e.message.toString().contains(INVALID_ARTIFACT_ERROR)) {
195+
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.INVALID_ARTIFACT(artifactType))
196+
} else {
197+
CodeTransformMessageListener.instance.onDownloadFailure(DownloadFailureReason.OTHER(artifactType, e.message.toString()))
198+
}
168199
}
169200
return DownloadArtifactResult(null, "", errorMessage)
170201
} finally {
@@ -202,6 +233,14 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
202233
}
203234
}
204235

236+
private fun notifyDownloadStart() {
237+
notifyStickyInfo(
238+
message("codemodernizer.notification.info.download.started.title"),
239+
message("codemodernizer.notification.info.download.started.content"),
240+
project,
241+
)
242+
}
243+
205244
fun notifyUnableToApplyPatch(patchPath: String, errorMessage: String) {
206245
LOG.error { "Unable to find patch for file: $patchPath" }
207246
notifyStickyWarn(
@@ -230,6 +269,20 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
230269
)
231270
}
232271

272+
fun notifyUnableToShowBuildLog() {
273+
LOG.error { "Unable to display build log" }
274+
notifyStickyWarn(
275+
message("codemodernizer.notification.warn.view_build_log_failed.title"),
276+
message("codemodernizer.notification.warn.view_build_log_failed.content"),
277+
project,
278+
listOf(
279+
openTroubleshootingGuideNotificationAction(
280+
CODE_TRANSFORM_TROUBLESHOOT_DOC_ARTIFACT
281+
)
282+
),
283+
)
284+
}
285+
233286
fun displayDiffAction(jobId: JobId) = runReadAction {
234287
telemetry.vcsViewerClicked(jobId)
235288
projectCoroutineScope(project).launch {
@@ -243,8 +296,9 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
243296
if (isCurrentlyDownloading.get()) return
244297
runReadAction {
245298
projectCoroutineScope(project).launch {
246-
val result = downloadArtifact(job)
247-
val summary = result.artifact?.summaryMarkdownFile ?: return@launch notifyUnableToShowSummary()
299+
val result = downloadArtifact(job, TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS)
300+
val artifact = result.artifact as? CodeModernizerArtifact ?: return@launch notifyUnableToShowSummary()
301+
val summary = artifact.summaryMarkdownFile
248302
val summaryMarkdownVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(summary)
249303
if (summaryMarkdownVirtualFile != null) {
250304
runInEdt {
@@ -255,6 +309,23 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
255309
}
256310
}
257311

312+
fun showBuildLog(job: JobId) {
313+
if (isCurrentlyDownloading.get()) return
314+
runReadAction {
315+
projectCoroutineScope(project).launch {
316+
val result = downloadArtifact(job, TransformationDownloadArtifactType.LOGS)
317+
val artifact = result.artifact as? CodeTransformFailureBuildLog ?: return@launch notifyUnableToShowBuildLog()
318+
val buildLog = artifact.logFile
319+
val buildLogVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(buildLog)
320+
if (buildLogVirtualFile != null) {
321+
runInEdt {
322+
FileEditorManager.getInstance(project).openFile(buildLogVirtualFile, true)
323+
}
324+
}
325+
}
326+
}
327+
}
328+
258329
companion object {
259330
val LOG = getLogger<ArtifactHandler>()
260331
}

plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation
1818
import software.amazon.awssdk.services.codewhispererruntime.model.TransformationProgressUpdateStatus
1919
import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus
2020
import software.amazon.awssdk.services.codewhispererruntime.model.TransformationUserActionStatus
21+
import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType
2122
import software.aws.toolkits.core.utils.error
2223
import software.aws.toolkits.core.utils.exists
2324
import software.aws.toolkits.core.utils.getLogger
@@ -78,6 +79,7 @@ class CodeModernizerSession(
7879
private val isDisposed = AtomicBoolean(false)
7980
private val shouldStop = AtomicBoolean(false)
8081
private val telemetry = CodeTransformTelemetryManager.getInstance(sessionContext.project)
82+
private val artifactHandler = ArtifactHandler(sessionContext.project, GumbyClient.getInstance(sessionContext.project))
8183

8284
private var mvnBuildResult: MavenCopyCommandsResult? = null
8385
private var transformResult: CodeModernizerJobCompletedResult? = null
@@ -481,8 +483,16 @@ class CodeModernizerSession(
481483
val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.unknown_start_failure")
482484
return CodeModernizerJobCompletedResult.JobFailed(jobId, failureReason)
483485
} else if (!passedBuild) {
484-
val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.maven_failed.content")
485-
return CodeModernizerJobCompletedResult.JobFailedInitialBuild(jobId, failureReason)
486+
// This is a short term solution to check if build log is available by attempting to download it.
487+
// In the long term, we should check if build log is available from transformation metadata.
488+
val downloadArtifactResult = artifactHandler.downloadArtifact(jobId, TransformationDownloadArtifactType.LOGS, true)
489+
if (downloadArtifactResult.artifact != null) {
490+
val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.maven_failed.content")
491+
return CodeModernizerJobCompletedResult.JobFailedInitialBuild(jobId, failureReason, true)
492+
} else {
493+
val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.maven_failed.content")
494+
return CodeModernizerJobCompletedResult.JobFailedInitialBuild(jobId, failureReason, false)
495+
}
486496
} else {
487497
val failureReason = result.jobDetails?.reason() ?: message("codemodernizer.notification.warn.unknown_status_response")
488498
return CodeModernizerJobCompletedResult.JobFailed(jobId, failureReason)

plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeTransformChatApp.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private enum class CodeTransformMessageTypes(val type: String) {
3636
CodeTransformOpenMvnBuild("codetransform-open-mvn-build"),
3737
ViewDiff("codetransform-view-diff"),
3838
ViewSummary("codetransform-view-summary"),
39+
ViewBuildLog("codetransform-view-build-log"),
3940
AuthFollowUpWasClicked("auth-follow-up-was-clicked"),
4041
BodyLinkClicked("response-body-link-click"),
4142
ConfirmHilSelection("codetransform-confirm-hil-selection"),
@@ -64,6 +65,7 @@ class CodeTransformChatApp : AmazonQApp {
6465
CodeTransformMessageTypes.CodeTransformOpenMvnBuild.type to IncomingCodeTransformMessage.CodeTransformOpenMvnBuild::class,
6566
CodeTransformMessageTypes.ViewDiff.type to IncomingCodeTransformMessage.CodeTransformViewDiff::class,
6667
CodeTransformMessageTypes.ViewSummary.type to IncomingCodeTransformMessage.CodeTransformViewSummary::class,
68+
CodeTransformMessageTypes.ViewBuildLog.type to IncomingCodeTransformMessage.CodeTransformViewBuildLog::class,
6769
CodeTransformMessageTypes.AuthFollowUpWasClicked.type to IncomingCodeTransformMessage.AuthFollowUpWasClicked::class,
6870
CodeTransformMessageTypes.BodyLinkClicked.type to IncomingCodeTransformMessage.BodyLinkClicked::class,
6971
CodeTransformMessageTypes.ConfirmHilSelection.type to IncomingCodeTransformMessage.ConfirmHilSelection::class,
@@ -132,6 +134,7 @@ class CodeTransformChatApp : AmazonQApp {
132134
is IncomingCodeTransformMessage.CodeTransformOpenMvnBuild -> inboundAppMessagesHandler.processCodeTransformOpenMvnBuild(message)
133135
is IncomingCodeTransformMessage.CodeTransformViewDiff -> inboundAppMessagesHandler.processCodeTransformViewDiff(message)
134136
is IncomingCodeTransformMessage.CodeTransformViewSummary -> inboundAppMessagesHandler.processCodeTransformViewSummary(message)
137+
is IncomingCodeTransformMessage.CodeTransformViewBuildLog -> inboundAppMessagesHandler.processCodeTransformViewBuildLog(message)
135138
is IncomingCodeTransformMessage.TabCreated -> inboundAppMessagesHandler.processTabCreated(message)
136139
is IncomingCodeTransformMessage.TabRemoved -> inboundAppMessagesHandler.processTabRemoved(message)
137140
is IncomingCodeTransformMessage.AuthFollowUpWasClicked -> inboundAppMessagesHandler.processAuthFollowUpClick(message)

plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/InboundAppMessagesHandler.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ interface InboundAppMessagesHandler {
2323

2424
suspend fun processCodeTransformViewSummary(message: IncomingCodeTransformMessage.CodeTransformViewSummary)
2525

26+
suspend fun processCodeTransformViewBuildLog(message: IncomingCodeTransformMessage.CodeTransformViewBuildLog)
27+
2628
suspend fun processCodeTransformNewAction(message: IncomingCodeTransformMessage.CodeTransformNew)
2729

2830
suspend fun processCodeTransformCommand(message: CodeTransformActionMessage)

0 commit comments

Comments
 (0)