Files
UnrealEngine/Engine/Extras/RoboMerge/v3/public/js/stomp.js
2025-05-18 13:04:45 +08:00

345 lines
14 KiB
JavaScript

// Copyright Epic Games, Inc. All Rights Reserved.
function goHome() {
location.href = '/' + location.hash
}
function stompVerify() {
let queryParams = processQueryParameters(['bot', 'branch', 'cl', 'target'])
if (!queryParams) return;
let requestedBotName = queryParams["bot"]
let requestedBranchName = queryParams["branch"]
let targetBranchName = queryParams["target"]
let requestedBranchCl = parseInt(queryParams["cl"], 10)
if (isNaN(requestedBranchCl)) {
stompFailure(`CL ${queryParams["cl"]} not a number.`)
return
}
// Filled in if/when user clicks the stomp button
let stompOperation = $.Deferred()
// Get requested branch infomation and setup the page data
getBranch(requestedBotName, requestedBranchName, function(data) {
try {
// Ensure we have data
if (!data) {
stompFailure(`Couldn't retrieve data for ${requestedBotName}:${requestedBranchName}`)
return
}
let requestedNode = data.branch
// Verify we got a node
if (!requestedNode) {
let errText = `Could not find matching branch for ${requestedBotName}:${requestedBranchName}.`
if (data.message) {
errText += `\n${data.message}`
}
stompFailure(errText)
return
}
let requestedEdge = requestedNode.edges[targetBranchName.toUpperCase()]
// Ensure this node actually has this requested edge
if (!requestedEdge) {
stompFailure(`${requestedBotName}:${requestedNodeName} has no edge for "${targetBranchName}".`)
$('#result').append(renderSingleBranchTable(requestedNode))
return
}
// Verify the request branch is paused
if (!requestedEdge.blockage) {
stompFailure(`${requestedEdge.display_name} not currently blocked, no need to stomp.`)
$('#result').append(renderSingleBranchTable(requestedNode))
return
}
// Ensure the current pause is applicable to the CL
if (requestedEdge.blockage.change !== requestedBranchCl) {
displayWarningMessage(`${requestedEdge.display_name} currently blocked, but not at requested CL ${requestedBranchCl}. Performing no action.`, false)
$('#singleBranchDisplay').append(renderSingleBranchTable(requestedNode)).fadeIn("fast", "swing")
return
}
// Passed all safety checks. Display branch info and stomp warning, and begin verifying the request
$('#branchPreviewBeforeVerify').append(renderSingleBranchTable(requestedNode, requestedEdge))
$('.sourceName').each(function() {
$(this).text(requestedBranchName)
})
$('.targetName').each(function() {
$(this).text(targetBranchName)
})
$('.changelist').each(function() {
$(this).text(requestedBranchCl)
})
// Data all verified, time to perform the node operation.
let stompQueryData = {
cl: requestedEdge.blockage.change,
target: targetBranchName
}
let verifyOperation = nodeAPIOp(requestedNode.bot, requestedNode.def.name, "/verifystomp?" + toQuery(stompQueryData))
// If verification succeeded:
verifyOperation.done(function(success) {
// Stomp returns a JSON payload on success
const stompJson = JSON.parse(success)
console.log(`Stomp Verification message: ${stompJson.message}`)
// Visualize the json
visualizeStompVerification(requestedBranchCl, stompJson)
if (stompJson.validRequest) {
$('#afterVerificationResultText').html('Stomp Verification Complete')
$('#afterVerificationResultText').append(
$('<h4 style="text-align: center">').text("Use the button below to proceed with the stomp operation")
)
} else {
$('#afterVerificationResultText').html('<span><i class="fas fa-exclamation-triangle"></i></span> Stomp Verification Returned Issues.')
$('#afterVerificationResultText').css('color', 'red');
}
transitionPostVerification()
// If we're a valid request, add form buttons to formally request a stomp
if (stompJson.validRequest) {
const formButtonDiv = $('#formButtons')
// Return to Robomerge homepage
let cancelButton = $('<button type="button" class="btn btn-lg btn-info" style="margin:.3em;">').text(`Cancel`).appendTo(formButtonDiv)
cancelButton.click(function() {
goHome()
})
// Perform stomp
let stompButton = $('<button type="button" class="btn btn-lg btn-primary" style="margin:.3em;">').text(`Perform Stomp`).appendTo(formButtonDiv)
stompButton.click(function() {
stompButton.attr('disabled', true)
stompButton.text(`Requesting stomp with CL ${requestedBranchCl}...`)
transitionStompChanges(requestedBranchCl)
nodeAPIOp(requestedNode.bot, requestedNode.def.name, "/stompchanges?" + toQuery(stompQueryData))
.done(function (success) {
stompOperation.resolve(success)
})
.fail(function(jqXHR, textStatus, errMsg) {
stompOperation.reject(jqXHR, textStatus, errMsg)
})
})
} else {
$('#returnbutton').removeClass("initiallyHidden")
}
})
// If verification failed:
verifyOperation.fail(function(jqXHR, _textStatus, errMsg) {
transitionPostVerification()
$('#returnbutton').removeClass("initiallyHidden")
const errText = `${jqXHR && jqXHR.status ? jqXHR.status + ": " : ""}${jqXHR && jqXHR.responseText ? jqXHR.responseText : errMsg}`
stompFailure(`Encountered issues verifying CL ${requestedEdge.blockage.change} as a candidate to stomp changes in ${targetBranchName}: "${errText}" If you think this is an error, please contact Robomerge support.`)
// Call getBranchList a second time to display most up-to-date data
getBranch(requestedNode.bot, requestedNode.def.name, function(data) {
const upToDateEdgeData = data.edges && data.edges[targetBranchName.toUpperCase()] ? data.edges[targetBranchName.toUpperCase()] : null
$('#afterVerification').append(renderSingleBranchTable(data.branch, upToDateEdgeData))
})
})
}
catch (err) {
stompFailure(`Error encountered displaying stomp verification results: ${err}`)
}
})
// Logic controlling stomp operation
$.when(stompOperation).done(function(success) {
try {
transitionDisplayStompResults()
stompSuccess(success)
}
catch (err) {
stompFailure(`Error encountered displaying stomp changes results: ${err}`)
}
}).fail(function(jqXHR, textStatus, errMsg) {
try {
transitionDisplayStompResults()
stompFailure(`${textStatus}: ${errMsg}`)
}
catch (err) {
stompFailure(`Error encountered displaying stomp changes failure: ${err}`)
}
})
}
function transitionPostVerification() {
// Remove before verify animation, fade in new visualization
$('#beforeVerification').fadeOut("fast", "swing", function() { $('#afterVerification').fadeIn("fast") })
$('#loadingDiv').fadeOut("fast", "swing")
}
function transitionStompChanges(changelist) {
$('#loadingText').text(`Performing stomp with CL ${changelist}...`)
$('#afterVerification').fadeOut("fast", "swing", function() { $('#loadingDiv').fadeIn("fast") })
}
function transitionDisplayStompResults() {
$('#loadingDiv').fadeOut("fast", "swing", function() { $('#afterStomp').fadeIn("fast") })
$('#returnbutton').removeClass("initiallyHidden")
}
// Create visualization of the changelist, with links to swarm, a display of the description and a list of relevant files to this request
function visualizeChangelist(changelist, changelistData, swarmURL) {
const changelistDiv = $(`<div id="${changelist}" class="stomp-visual">`)
if (robomergeUser && changelistData.author.toLowerCase() === robomergeUser.userName.toLowerCase()) {
changelistDiv.append($('<h3>').html(`${makeClLink(changelist, swarmURL)} by <strong>you</strong>`))
} else {
changelistDiv.append($('<h3>').html(`${makeClLink(changelist, swarmURL)} by ${changelistData.author}`))
}
changelistDiv.append($('<hr style="margin-top:0em; border-width: 2px;">'))
changelistDiv.append($('<pre>').text(changelistData.description))
// Begin compiling the list of relevant files in this changelist
let fileList = $('<ul>')
let unresolvedFiles = false
for (const fileObject of changelistData.filesStomped.sort()) {
let itemHtml = fileObject.filename
// If the file isn't resolved, it will be stomped (or in the case of text files, block the stomp)
if (!fileObject.resolved) {
itemHtml = `<strong>${fileObject.filename}</strong>`
unresolvedFiles = true
}
fileList.append($('<li>').html(itemHtml))
}
// Append list
if (unresolvedFiles) {
changelistDiv.append($('<h5>').html('Relevant files in changelist (unresolved files in <strong>bold</strong>):'))
} else {
changelistDiv.append($('<h5>').html('Relevant files in changelist:'))
}
fileList.appendTo(changelistDiv)
return changelistDiv
}
function visualizeStompVerification(requestedBranchCl, stompJson) {
// For visualization, sort data by stomped revision
// (JSON data is sorted by file)
let clDict = {}
let encounteredStompedRevisionsCalculationIssues = false
for (const file of stompJson.files) {
const swarmFileLink = makeSwarmFileLink(file.targetFileName, stompJson.swarmURL)
// Process resolved files
if (file.resolved) {
// Add text files to the warning list
if (file.filetype.startsWith('text')) {
$('#mixedMergeWarningList').append(
$('<li>').html(swarmFileLink)
)
}
}
// Add unresolved text files to the error list
else if (file.filetype.startsWith('text')) {
$('#unresolvedTextFilesList').append(
$('<li>').html(swarmFileLink)
)
}
if (file.stompedRevisionsSkipped) {
$('#stompedRevisionsSkippedDiv').removeClass('initiallyHidden')
$('#stompedRevisionsSkippedList').append(
$('<li>').html(swarmFileLink)
)
// If this file skipped stomped revisions, don't bother attempting to add to clDict
continue
}
if (file.stompedRevisionsCalculationIssues) {
encounteredStompedRevisionsCalculationIssues = true
$('#stompedRevisionsIssuesList').append(
$('<li>').html(swarmFileLink)
)
// If this file couldn't calculate stomped revisions, don't bother attempting to add to clDict
continue
}
const fileObj = {
filename: file.targetFileName,
resolved: file.resolved
}
// Process unresolved files
for (const stompedRev of file.stompedRevisions) {
// If we don't track this changelist, add a new entry to the dictionary
if (!clDict[stompedRev.changelist]) {
clDict[stompedRev.changelist] = {
author: stompedRev.author,
description: stompedRev.description,
filesStomped: [ fileObj ]
}
}
// Otherwise, add another file stomped to the existing entry
else {
clDict[stompedRev.changelist].filesStomped.push(fileObj)
}
}
}
const linkToSwarm = makeClLink(requestedBranchCl, stompJson.swarmURL, `CL ${requestedBranchCl}`)
if (encounteredStompedRevisionsCalculationIssues) {
$('#stompedRevisionsIssuesDiv').removeClass('initiallyHidden')
}
// Display error if non-binary files remain after the merge
if (!stompJson.remainingAllBinary) {
$('#verificationNotAllBinary').removeClass('initiallyHidden')
$('#resultVisualization').append(
$('<h5 class="centered-text">').html(`Robomerge cannot proceed with the stomp request using ${linkToSwarm}. The following changelists contain unstompable changes due to the presence of text files:`)
)
}
if (stompJson.validRequest) {
if (stompJson.nonBinaryFilesResolved) {
// Enable warning if we're doing a mixed merge of binary and non-binary files
$('#mixedMergeWarning').removeClass('initiallyHidden')
}
// Display valid request
$('#resultVisualization').append(
$('<h5 class="centered-text">').html(
`The following changelists will be affected after stomping changes with files from ${linkToSwarm}:`
)
)
}
// Now display each changelist
const sortedKeys = Object.keys(clDict).sort()
sortedKeys.forEach(function(key) {
$('#resultVisualization').append(visualizeChangelist(key, clDict[key], stompJson.swarmURL))
})
}
function stompFailure(message) {
displayErrorMessage(message, false)
$('#loadingDiv').fadeOut("fast", "swing")
$('#returnbutton').removeClass("initiallyHidden")
}
function stompSuccess(message) {
displaySuccessfulMessage(message, false)
$('#loadingDiv').fadeOut("fast", "swing")
$('#returnbutton').removeClass("initiallyHidden")
}