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>`
}