@@ -16,6 +16,7 @@ import com.intellij.openapi.vfs.LocalFileSystem
16
16
import com.intellij.openapi.vfs.VirtualFile
17
17
import kotlinx.coroutines.launch
18
18
import kotlinx.coroutines.withContext
19
+ import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType
19
20
import software.aws.toolkits.core.utils.error
20
21
import software.aws.toolkits.core.utils.exists
21
22
import software.aws.toolkits.core.utils.getLogger
@@ -26,6 +27,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.CODE_TRANSFORM_TROUBLESH
26
27
import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient
27
28
import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformMessageListener
28
29
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
29
32
import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact
30
33
import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason
31
34
import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
@@ -40,36 +43,31 @@ import java.nio.file.Path
40
43
import java.time.Instant
41
44
import java.util.concurrent.atomic.AtomicBoolean
42
45
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 = " " )
44
47
45
48
const val DOWNLOAD_PROXY_WILDCARD_ERROR : String = " Dangling meta character '*' near index 0"
46
49
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"
47
51
48
52
class ArtifactHandler (private val project : Project , private val clientAdaptor : GumbyClient ) {
49
53
private val telemetry = CodeTransformTelemetryManager .getInstance(project)
50
54
private val downloadedArtifacts = mutableMapOf<JobId , Path >()
51
55
private val downloadedSummaries = mutableMapOf<JobId , TransformationSummary >()
52
-
56
+ private val downloadedBuildLogPath = mutableMapOf< JobId , Path >()
53
57
private var isCurrentlyDownloading = AtomicBoolean (false )
58
+
54
59
internal suspend fun displayDiff (job : JobId ) {
55
60
if (isCurrentlyDownloading.get()) return
56
- val result = downloadArtifact(job)
61
+ val result = downloadArtifact(job, TransformationDownloadArtifactType . CLIENT_INSTRUCTIONS )
57
62
if (result.artifact == null ) {
58
63
notifyUnableToApplyPatch(result.zipPath, result.errorMessage)
59
64
} else {
65
+ result.artifact as CodeModernizerArtifact
60
66
displayDiffUsingPatch(result.artifact.patch, job)
61
67
}
62
68
}
63
69
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 > {
73
71
val zipFilePath = withContext(getCoroutineBgContext()) {
74
72
if (outputDirPath == null ) {
75
73
Files .createTempFile(null , " .zip" )
@@ -106,28 +104,47 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
106
104
}
107
105
}
108
106
109
- suspend fun downloadArtifact (job : JobId ): DownloadArtifactResult {
107
+ suspend fun downloadArtifact (
108
+ job : JobId ,
109
+ artifactType : TransformationDownloadArtifactType ,
110
+ isPreFetch : Boolean = false
111
+ ): DownloadArtifactResult {
110
112
isCurrentlyDownloading.set(true )
111
113
val downloadStartTime = Instant .now()
112
114
try {
113
115
// 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
+ }
115
121
if (previousArtifact != null && previousArtifact.exists()) {
116
122
val zipPath = previousArtifact.toAbsolutePath().toString()
117
123
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
+ }
121
131
} catch (e: RuntimeException ) {
122
132
LOG .error { e.message.toString() }
123
133
DownloadArtifactResult (null , zipPath, e.message.orEmpty())
124
134
}
125
135
}
126
136
127
137
// 2. Download the data
128
- notifyDownloadStart()
129
138
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
+ }
131
148
132
149
// 3. Convert to zip
133
150
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
136
153
val zipPath = path.toAbsolutePath().toString()
137
154
LOG .info { " Successfully converted the download to a zip at $zipPath ." }
138
155
139
- // 4. Deserialize zip to CodeModernizerArtifact
156
+ // 4. Deserialize zip
140
157
var telemetryErrorMessage: String? = null
141
158
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
+ }
144
169
output
145
170
} catch (e: RuntimeException ) {
146
171
LOG .error { e.message.toString() }
147
172
telemetryErrorMessage = " Unexpected error when downloading result ${e.localizedMessage} "
148
173
DownloadArtifactResult (null , zipPath, e.message.orEmpty())
149
174
} finally {
175
+ // TODO: add artifact type to telemetry to differentiate downloads for client instructions vs logs
150
176
telemetry.jobArtifactDownloadAndDeserializeTime(
151
177
downloadStartTime,
152
178
job,
@@ -157,14 +183,19 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
157
183
} catch (e: Exception ) {
158
184
var errorMessage: String = e.message.orEmpty()
159
185
// 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
+ }
168
199
}
169
200
return DownloadArtifactResult (null , " " , errorMessage)
170
201
} finally {
@@ -202,6 +233,14 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
202
233
}
203
234
}
204
235
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
+
205
244
fun notifyUnableToApplyPatch (patchPath : String , errorMessage : String ) {
206
245
LOG .error { " Unable to find patch for file: $patchPath " }
207
246
notifyStickyWarn(
@@ -230,6 +269,20 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
230
269
)
231
270
}
232
271
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
+
233
286
fun displayDiffAction (jobId : JobId ) = runReadAction {
234
287
telemetry.vcsViewerClicked(jobId)
235
288
projectCoroutineScope(project).launch {
@@ -243,8 +296,9 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
243
296
if (isCurrentlyDownloading.get()) return
244
297
runReadAction {
245
298
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
248
302
val summaryMarkdownVirtualFile = LocalFileSystem .getInstance().refreshAndFindFileByIoFile(summary)
249
303
if (summaryMarkdownVirtualFile != null ) {
250
304
runInEdt {
@@ -255,6 +309,23 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
255
309
}
256
310
}
257
311
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
+
258
329
companion object {
259
330
val LOG = getLogger<ArtifactHandler >()
260
331
}
0 commit comments