public async showPlanProgress()

in packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts [314:532]


    public async showPlanProgress(startTime: number): Promise<string> {
        const styleSheet = this._view?.webview.asWebviewUri(
            vscode.Uri.joinPath(this._extensionUri, 'resources', 'css', 'amazonqTransformationHub.css')
        )
        const simpleStep = (icon: string, text: string, isActive: boolean) => {
            return isActive
                ? `<p class="simple-step active">${icon} ${text}</p>`
                : `<p class="simple-step">${icon} ${text}</p>`
        }

        let planSteps = transformByQState.getPlanSteps()
        // no plan for SQL conversions
        if (
            transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION &&
            jobPlanProgress['generatePlan'] === StepProgress.Succeeded &&
            transformByQState.isRunning()
        ) {
            try {
                planSteps = await getTransformationSteps(
                    transformByQState.getJobId(),
                    AuthUtil.instance.regionProfileManager.activeRegionProfile
                )
                transformByQState.setPlanSteps(planSteps)
            } catch (e: any) {
                // no-op; re-use current plan steps and try again in next polling cycle
                getLogger().error(
                    `CodeTransformation: failed to get plan steps to show updates in transformation hub, continuing transformation; error = %O`,
                    e
                )
            }
        }
        let progressHtml
        // for each step that has succeeded, increment activeStepId by 1
        let activeStepId = [
            jobPlanProgress.uploadCode,
            jobPlanProgress.buildCode,
            jobPlanProgress.generatePlan,
            jobPlanProgress.transformCode,
        ]
            .map((it) => (it === StepProgress.Succeeded ? 1 : 0) as number)
            .reduce((prev, current) => prev + current)
        // When we receive plan step details, we want those to be active -> increment activeStepId
        activeStepId += planSteps === undefined || planSteps.length === 0 ? 0 : 1

        if (jobPlanProgress['transformCode'] !== StepProgress.NotStarted) {
            const waitingMarkup = simpleStep(
                this.getProgressIconMarkup(jobPlanProgress['uploadCode']),
                CodeWhispererConstants.uploadingCodeStepMessage,
                activeStepId === 0
            )
            const buildMarkup =
                activeStepId >= 1 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show buildCode step
                    ? simpleStep(
                          this.getProgressIconMarkup(jobPlanProgress['buildCode']),
                          CodeWhispererConstants.buildCodeStepMessage,
                          activeStepId === 1
                      )
                    : ''
            const planMarkup =
                activeStepId >= 2 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show generatePlan step
                    ? simpleStep(
                          this.getProgressIconMarkup(jobPlanProgress['generatePlan']),
                          CodeWhispererConstants.generatePlanStepMessage,
                          activeStepId === 2
                      )
                    : ''
            const transformMarkup =
                activeStepId >= 3
                    ? simpleStep(
                          this.getProgressIconMarkup(jobPlanProgress['transformCode']),
                          CodeWhispererConstants.transformStepMessage,
                          activeStepId === 3
                      )
                    : ''

            const isTransformFailed = jobPlanProgress['transformCode'] === StepProgress.Failed
            const progress = this.getTransformationStepProgressMarkup(planSteps, isTransformFailed)
            const latestGenericStepDetails = this.getLatestGenericStepDetails(transformByQState.getPolledJobStatus())
            const jobId = transformByQState.getJobId()
            progressHtml = `
            <div id="progress" class="column">
                <p><b>Transformation Progress</b> <span id="runningTime"></span></p>
                <p>${jobId ? `Job ID: ${jobId}` : ''}</p>
                ${waitingMarkup}
                ${buildMarkup}
                ${planMarkup}
                ${transformMarkup}
                ${progress[0]}
            </div>
            <div id="stepdetails" class="column">
                <div class="substep center ${
                    transformByQState.isNotStarted() ? 'blocked' : ''
                }" id="generic-step-details">
                    <div class="column--container">
                        <div class="center-flex substep-icon"><p class="center-flex"><span class="spinner status-PENDING"> ↻ </span></p></div>
                        <div><p>${latestGenericStepDetails}</p></div>
                    </div>
                </div>
                ${progress[1]}
            </div>
            `
        } else {
            progressHtml = `
            <div id="progress" class="column">
                <p><b>Transformation Progress</b></p>
                <p>No job ongoing</p>
            </div>`
        }
        return `<!DOCTYPE html>
            <html lang="en">
            
            <head>
                <title>Transformation Hub</title>
                <link href="${styleSheet}" rel="stylesheet">
            </head>
            <body>
            <div class="wrapper">
                <div style="flex:1; overflow: auto;">
                    <div class="column--container">
                        ${progressHtml}
                    </div>
                </div>
            </div>
            <script>
                let intervalId = undefined;
                let runningTime = "";

                function updateTimer() {
                    if (${transformByQState.isRunning()}) {
                        runningTime = convertToTimeString(Date.now() - ${startTime});
                        document.getElementById("runningTime").textContent = "Time elapsed: " + runningTime;
                    } else {
                        clearInterval(intervalId);
                    }
                }

                // copied from textUtilities.ts
                function convertToTimeString(durationInMs) {
                    const time = new Date(durationInMs);
                    const hours = time.getUTCHours();
                    const minutes = time.getUTCMinutes();
                    const seconds = time.getUTCSeconds();
                    let timeString = seconds + " sec"
                    if (minutes > 0) {
                        timeString = minutes + " min " + timeString
                    }
                    if (hours > 0) {
                        timeString = hours + " hr " + timeString
                    }
                    return timeString
                }

                function clearActiveSteps(){
                    const activeSteps = document.querySelectorAll(".active")
                    for(const step of activeSteps){
                        step.classList.remove("active")
                    }
                }

                function showStepDetails(item) {
                    const visibleSubSteps = document.querySelectorAll(".visible");
                    const substep = document.getElementById(item.id.replace("step-", "substep-"))
                    clearActiveSteps()
                    for(const visibleSubStep of visibleSubSteps){                
                        visibleSubStep.classList.remove("visible")
                        document.getElementById(visibleSubStep.id.replace("substep-", "step-")).classList.remove("active")
                    }
                    
                    substep.classList.add("visible")
                    item.classList.add("active")
                    document.getElementById("generic-step-details").classList.add("blocked")
                }

                function handleSimpleStepClicked(item) {  
                    clearActiveSteps()
                    item.classList.add("active")
                    if(document.getElementById("generic-step-details").classList.contains("blocked")){
                        return
                    }
                    document.getElementById("generic-step-details").classList.add("visible")
                }


                function addShowSubstepEventListeners() {
                    const steps = document.getElementsByClassName("step");
                    for(const item of steps) {
                        item.addEventListener("click", (event) => {
                            showStepDetails(item)
                        })
                    }
                }

                function addHighlightStepWithoutSubstepListeners() {
                    const steps = document.getElementsByClassName("simple-step");
                    for(const item of steps) {
                        item.addEventListener("click", (event) => {
                            handleSimpleStepClicked(item)
                        })
                    }
                }

                function showCurrentActiveSubstep() {
                    const activeStep = document.getElementsByClassName("active")[0]
                    if(activeStep && activeStep.classList.contains("step")){
                        showStepDetails(activeStep)
                    } else if(activeStep && activeStep.classList.contains("simple-step")){
                        handleSimpleStepClicked(activeStep)
                    }
                }

                intervalId = setInterval(updateTimer, 1000);
                addShowSubstepEventListeners();
                addHighlightStepWithoutSubstepListeners();
                showCurrentActiveSubstep();
                updateTimer()
            </script>
            </body>
            </html>`
    }