constructor()

in packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts [325:613]


    constructor(context: vscode.ExtensionContext) {
        const diffModel = new DiffModel()
        const transformDataProvider = new TransformationResultsProvider(diffModel)
        this.changeViewer = vscode.window.createTreeView(TransformationResultsProvider.viewType, {
            treeDataProvider: transformDataProvider,
        })

        let patchFiles: string[] = []
        let singlePatchFile: string = ''
        let patchFilesDescriptions: DescriptionContent | undefined = undefined

        const reset = async () => {
            await setContext('gumby.transformationProposalReviewInProgress', false)
            await setContext('gumby.reviewState', TransformByQReviewStatus.NotStarted)

            // delete result archive after changes cleared; summary is under ResultArchiveFilePath
            if (fs.existsSync(transformByQState.getResultArchiveFilePath())) {
                fs.rmSync(transformByQState.getResultArchiveFilePath(), { recursive: true, force: true })
            }
            if (fs.existsSync(transformByQState.getProjectCopyFilePath())) {
                fs.rmSync(transformByQState.getProjectCopyFilePath(), { recursive: true, force: true })
            }

            diffModel.clearChanges()
            // update summary path to where it is locally after user accepts changes, so that View Summary button works
            transformByQState.setSummaryFilePath(
                path.join(transformByQState.getProjectPath(), ExportResultArchiveStructure.PathToSummary)
            )
            transformByQState.setProjectCopyFilePath('')
            transformByQState.setResultArchiveFilePath('')
            transformDataProvider.refresh()
        }

        vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.refresh', () =>
            transformDataProvider.refresh()
        )

        vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.reset', async () => await reset())

        vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.reveal', async () => {
            await setContext('gumby.transformationProposalReviewInProgress', true)
            const root = diffModel.getRoot()
            if (root) {
                await this.changeViewer.reveal(root, {
                    expand: true,
                })
            }
        })

        vscode.commands.registerCommand('aws.amazonq.transformationHub.summary.reveal', async () => {
            if (fs.existsSync(transformByQState.getSummaryFilePath())) {
                await vscode.commands.executeCommand(
                    'markdown.showPreview',
                    vscode.Uri.file(transformByQState.getSummaryFilePath())
                )
            }
        })

        vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.startReview', async () => {
            await setContext('gumby.reviewState', TransformByQReviewStatus.PreparingReview)

            const pathToArchive = path.join(
                ProposedTransformationExplorer.TmpDir,
                transformByQState.getJobId(),
                'ExportResultsArchive.zip'
            )
            let exportResultsArchiveSize = 0
            let downloadErrorMessage = undefined

            const cwStreamingClient = await createCodeWhispererChatStreamingClient()
            try {
                await telemetry.codeTransform_downloadArtifact.run(async () => {
                    telemetry.record({
                        codeTransformArtifactType: 'ClientInstructions',
                        codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
                        codeTransformJobId: transformByQState.getJobId(),
                    })

                    await downloadExportResultArchive(
                        cwStreamingClient,
                        {
                            exportId: transformByQState.getJobId(),
                            exportIntent: ExportIntent.TRANSFORMATION,
                        },
                        pathToArchive,
                        AuthUtil.instance.regionProfileManager.activeRegionProfile
                    )

                    getLogger().info('CodeTransformation: downloaded results successfully')
                    // Update downloaded artifact size
                    exportResultsArchiveSize = (await fs.promises.stat(pathToArchive)).size

                    telemetry.record({ codeTransformTotalByteSize: exportResultsArchiveSize })
                })
            } catch (e: any) {
                // user can retry the download
                downloadErrorMessage = (e as Error).message
                if (downloadErrorMessage.includes('Encountered an unexpected error when processing the request')) {
                    downloadErrorMessage = CodeWhispererConstants.errorDownloadingExpiredDiff
                }
                void vscode.window.showErrorMessage(
                    `${CodeWhispererConstants.errorDownloadingDiffNotification} The download failed due to: ${downloadErrorMessage}`
                )
                transformByQState.getChatControllers()?.transformationFinished.fire({
                    message: `${CodeWhispererConstants.errorDownloadingDiffChatMessage} The download failed due to: ${downloadErrorMessage}`,
                    tabID: ChatSessionManager.Instance.getSession().tabID,
                })
                await setContext('gumby.reviewState', TransformByQReviewStatus.NotStarted)
                getLogger().error(`CodeTransformation: ExportResultArchive error = ${downloadErrorMessage}`)
                throw new Error('Error downloading diff')
            } finally {
                cwStreamingClient.destroy()
                UserWrittenCodeTracker.instance.onQFeatureInvoked()
            }

            let deserializeErrorMessage = undefined
            let pathContainingArchive = ''
            patchFiles = [] // reset patchFiles if there was a previous transformation
            try {
                // Download and deserialize the zip
                pathContainingArchive = path.dirname(pathToArchive)
                const zip = new AdmZip(pathToArchive)
                zip.extractAllTo(pathContainingArchive)
                const files = fs.readdirSync(path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch))
                if (files.length === 1) {
                    singlePatchFile = path.join(
                        pathContainingArchive,
                        ExportResultArchiveStructure.PathToPatch,
                        files[0]
                    )
                } else {
                    const jsonFile = files.find((file) => file.endsWith('.json'))
                    if (!jsonFile) {
                        throw new Error('Expected JSON file not found')
                    }
                    const filePath = path.join(
                        pathContainingArchive,
                        ExportResultArchiveStructure.PathToPatch,
                        jsonFile
                    )
                    const jsonData = fs.readFileSync(filePath, 'utf-8')
                    patchFilesDescriptions = JSON.parse(jsonData)
                }
                if (patchFilesDescriptions !== undefined) {
                    for (const patchInfo of patchFilesDescriptions.content) {
                        patchFiles.push(
                            path.join(
                                pathContainingArchive,
                                ExportResultArchiveStructure.PathToPatch,
                                patchInfo.filename
                            )
                        )
                    }
                } else {
                    patchFiles.push(singlePatchFile)
                }
                // Because multiple patches are returned once the ZIP is downloaded, we want to show the first one to start
                diffModel.parseDiff(
                    patchFiles[0],
                    transformByQState.getProjectPath(),
                    patchFilesDescriptions ? patchFilesDescriptions.content[0] : undefined,
                    patchFiles.length
                )

                await setContext('gumby.reviewState', TransformByQReviewStatus.InReview)
                transformDataProvider.refresh()
                transformByQState.setSummaryFilePath(
                    path.join(pathContainingArchive, ExportResultArchiveStructure.PathToSummary)
                )
                transformByQState.setResultArchiveFilePath(pathContainingArchive)
                await setContext('gumby.isSummaryAvailable', true)

                // Do not await this so that the summary reveals without user needing to close this notification
                void vscode.window.showInformationMessage(CodeWhispererConstants.viewProposedChangesNotification)
                transformByQState.getChatControllers()?.transformationFinished.fire({
                    message: CodeWhispererConstants.viewProposedChangesChatMessage,
                    tabID: ChatSessionManager.Instance.getSession().tabID,
                })
                await vscode.commands.executeCommand('aws.amazonq.transformationHub.summary.reveal')
            } catch (e: any) {
                deserializeErrorMessage = (e as Error).message
                getLogger().error(`CodeTransformation: ParseDiff error = ${deserializeErrorMessage}`)
                transformByQState.getChatControllers()?.transformationFinished.fire({
                    message: `${CodeWhispererConstants.errorDeserializingDiffChatMessage} ${deserializeErrorMessage}`,
                    tabID: ChatSessionManager.Instance.getSession().tabID,
                })
                void vscode.window.showErrorMessage(
                    `${CodeWhispererConstants.errorDeserializingDiffNotification} ${deserializeErrorMessage}`
                )
            }

            try {
                const metricsPath = path.join(pathContainingArchive, ExportResultArchiveStructure.PathToMetrics)
                const metricsData = JSON.parse(fs.readFileSync(metricsPath, 'utf8'))

                await codeWhisperer.codeWhispererClient.sendTelemetryEvent({
                    telemetryEvent: {
                        transformEvent: {
                            jobId: transformByQState.getJobId(),
                            timestamp: new Date(),
                            ideCategory: 'VSCODE',
                            programmingLanguage: {
                                languageName:
                                    transformByQState.getTransformationType() === TransformationType.LANGUAGE_UPGRADE
                                        ? 'java'
                                        : 'sql',
                            },
                            linesOfCodeChanged: metricsData.linesOfCodeChanged,
                            charsOfCodeChanged: metricsData.charactersOfCodeChanged,
                            linesOfCodeSubmitted: transformByQState.getLinesOfCodeSubmitted(), // currently unavailable for SQL conversions
                        },
                    },
                })
            } catch (err: any) {
                // log error, but continue to show user diff.patch with results
                getLogger().error(`CodeTransformation: SendTelemetryEvent error = ${err.message}`)
            }
        })

        vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.acceptChanges', async () => {
            telemetry.codeTransform_submitSelection.run(() => {
                getLogger().info('CodeTransformation: accepted changes')
                diffModel.saveChanges()
                telemetry.record({
                    codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
                    codeTransformJobId: transformByQState.getJobId(),
                    userChoice: `acceptChanges-${patchFilesDescriptions?.content[diffModel.currentPatchIndex].name}`,
                })
            })
            if (transformByQState.getMultipleDiffs()) {
                void vscode.window.showInformationMessage(
                    CodeWhispererConstants.changesAppliedNotificationMultipleDiffs(
                        diffModel.currentPatchIndex,
                        patchFiles.length
                    )
                )
            } else {
                void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotificationOneDiff)
            }

            // We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch
            transformByQState.getChatControllers()?.transformationFinished.fire({
                message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs(
                    diffModel.currentPatchIndex,
                    patchFiles.length,
                    patchFilesDescriptions
                        ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name
                        : undefined
                ),
                tabID: ChatSessionManager.Instance.getSession().tabID,
                includeStartNewTransformationButton: diffModel.currentPatchIndex === patchFiles.length - 1,
            })

            // Load the next patch file
            diffModel.currentPatchIndex++
            if (diffModel.currentPatchIndex < patchFiles.length) {
                const nextPatchFile = patchFiles[diffModel.currentPatchIndex]
                const nextPatchFileDescription = patchFilesDescriptions
                    ? patchFilesDescriptions.content[diffModel.currentPatchIndex]
                    : undefined
                diffModel.parseDiff(
                    nextPatchFile,
                    transformByQState.getProjectPath(),
                    nextPatchFileDescription,
                    patchFiles.length
                )
                transformDataProvider.refresh()
            } else {
                // All patches have been applied, reset the state
                await reset()
            }
        })

        vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.rejectChanges', async () => {
            await telemetry.codeTransform_submitSelection.run(async () => {
                getLogger().info('CodeTransformation: rejected changes')
                diffModel.rejectChanges()
                await reset()
                telemetry.record({
                    codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
                    codeTransformJobId: transformByQState.getJobId(),
                    userChoice: 'rejectChanges',
                })
            })
            transformByQState.getChatControllers()?.transformationFinished.fire({
                tabID: ChatSessionManager.Instance.getSession().tabID,
            })
        })
    }