Skip to content

Commit

Permalink
adding logic to handle jobs requiring approvals (#718)
Browse files Browse the repository at this point in the history
* adding logic to handle jobs requiring approvals
* typo
* Update run_job.js
---------
Co-authored-by: Ken Celenza <[email protected]>
  • Loading branch information
whitej6 authored Feb 24, 2024
1 parent 8bd121d commit 4de2396
Showing 1 changed file with 144 additions and 127 deletions.
271 changes: 144 additions & 127 deletions nautobot_golden_config/static/run_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,66 @@
* @param {string} callBack - The promise function to return the success or failure message, leave `undefined` no callback is required.
*/
function startJob(jobClass, data, redirectUrlTemplate, callBack) {
var jobApi = `/api/extras/jobs/${jobClass}/run/`;
var jobApi = `/api/extras/jobs/${jobClass}/run/`;

if (typeof callBack === "undefined") {
var callBack = getMessage;
}
if (typeof callBack === "undefined") {
var callBack = getMessage;
}

$.ajax({
type: 'POST',
url: jobApi,
contentType: "application/json",
data: JSON.stringify(data),
dataType: 'json',
headers: {
'X-CSRFToken': nautobot_csrf_token
},
beforeSend: function() {
// Normalize to base as much as you can.
$('#jobStatus').html("Pending").show();
$('#loaderImg').show();
$('#jobResults').hide();
$('#redirectLink').hide();
$('#detailMessages').hide();
},
success: function(jobData) {
$('#jobStatus').html("Started").show();
var jobResultUrl = "/extras/job-results/" + jobData.job_result.id + "/";
$('#jobResults').html(iconLink(jobResultUrl, "mdi-open-in-new", "Job Details")).show();
pollJobStatus(jobData.job_result.url, callBack);
if (typeof redirectUrlTemplate !== "undefined") {
var redirectUrl = _renderTemplate(redirectUrlTemplate, jobData);
$('#redirectLink').html(iconLink(redirectUrl, "mdi-open-in-new", "Info"));
}
},
error: function(e) {
$("#loaderImg").hide();
console.log("There was an error with your request...");
console.log("error: " + JSON.stringify(e));
$('#jobStatus').html("Failed").show();
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-danger text-center');
$('#detailMessages').html("<b>Error: </b>" + e.responseText);
}
});
$.ajax({
type: 'POST',
url: jobApi,
contentType: "application/json",
data: JSON.stringify(data),
dataType: 'json',
headers: {
'X-CSRFToken': nautobot_csrf_token
},
beforeSend: function () {
// Normalize to base as much as you can.
$('#jobStatus').html("Pending").show();
$('#loaderImg').show();
$('#jobResults').hide();
$('#redirectLink').hide();
$('#detailMessages').hide();
},
success: function (jobData) {
if (jobData.job_result) {
// if you get a 200 response and job_result data is there, you know it was a good run
$('#jobStatus').html("Started").show();
var jobResultUrl = "/extras/job-results/" + jobData.job_result.id + "/";
$('#jobResults').html(iconLink(jobResultUrl, "mdi-open-in-new", "Job Details")).show();
pollJobStatus(jobData.job_result.url, callBack);
if (typeof redirectUrlTemplate !== "undefined") {
var redirectUrl = _renderTemplate(redirectUrlTemplate, jobData);
$('#redirectLink').html(iconLink(redirectUrl, "mdi-open-in-new", "Info"));
}
} else if (jobData.scheduled_job && jobData.scheduled_job.approval_required) {
// Job approvals have this relevant data structure { "scheduled_job": {"approval_required": true }}
$("#loaderImg").hide();
$('#jobStatus').html("Pending Approval").show();
$('#detailMessages').show();
$('#detailMessages').html(`<p>Job is pending approval, please review <a href="/extras/jobs/scheduled-jobs/approval-queue/${jobData.scheduled_job.id}/">Job Approval</a></p>`);
} else {
// Response was 200 but did not return a job_result OR a scheduled_job pending approval, this condition should not be possible at the time of development but was added to prevent the modal going into a hung state
$("#loaderImg").hide();
$('#jobStatus').html("Unknown").show();
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-danger text-center');
$('#detailMessages').html("<p>The call to launch the job was successful but an unknown error has occurred, response has been logged to the browser console.</p>");
console.log("error: " + JSON.stringify(jobData));
}
},
error: function (e) {
$("#loaderImg").hide();
console.log("There was an error with your request...");
console.log("error: " + JSON.stringify(e));
$('#jobStatus').html("Failed").show();
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-danger text-center');
$('#detailMessages').html("<b>Error: </b>" + e.responseText);
}
});
}

/**
Expand All @@ -70,50 +87,50 @@ function startJob(jobClass, data, redirectUrlTemplate, callBack) {
* @returns {void}
*/
function pollJobStatus(jobId, callBack) {
$.ajax({
url: jobId,
type: "GET",
async: false,
dataType: "json",
headers: {
'X-CSRFToken': nautobot_csrf_token
},
success: function(data) {
$('#jobStatus').html(data.status.value.charAt(0).toUpperCase() + data.status.value.slice(1)).show();
if (["FAILURE", "REVOKED"].includes(data.status.value)) {
$("#loaderImg").hide();
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-warning text-center');
$('#detailMessages').html("Job started but failed during the Job run. This job may have partially completed. See Job Results for more details on the errors.");
} else if (["PENDING", "RECEIVED", "RETRY", "STARTED"].includes(data.status.value)) {
// Job is still processing, continue polling
setTimeout(function() {
pollJobStatus(jobId, callBack);
}, 1000); // Poll every 1 seconds
} else if (["SUCCESS"].includes(data.status.value)) {
$("#loaderImg").hide();
$('#detailMessages').show();
callBack(data.id)
.then((message) => {
$('#redirectLink').show();
$('#detailMessages').attr('class', 'alert alert-success text-center');
$('#detailMessages').html(message)
})
.catch((message) => {
$('#detailMessages').attr('class', 'alert alert-warning text-center');
$('#detailMessages').html(message)
})
}
},
error: function(e) {
$("#loaderImg").hide();
console.log("There was an error with your request...");
console.log("error: " + JSON.stringify(e));
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-danger text-center');
$('#detailMessages').html("<b>Error: </b>" + e.responseText);
}
})
$.ajax({
url: jobId,
type: "GET",
async: false,
dataType: "json",
headers: {
'X-CSRFToken': nautobot_csrf_token
},
success: function (data) {
$('#jobStatus').html(data.status.value.charAt(0).toUpperCase() + data.status.value.slice(1)).show();
if (["FAILURE", "REVOKED"].includes(data.status.value)) {
$("#loaderImg").hide();
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-warning text-center');
$('#detailMessages').html("Job started but failed during the Job run. This job may have partially completed. See Job Results for more details on the errors.");
} else if (["PENDING", "RECEIVED", "RETRY", "STARTED"].includes(data.status.value)) {
// Job is still processing, continue polling
setTimeout(function () {
pollJobStatus(jobId, callBack);
}, 1000); // Poll every 1 seconds
} else if (["SUCCESS"].includes(data.status.value)) {
$("#loaderImg").hide();
$('#detailMessages').show();
callBack(data.id)
.then((message) => {
$('#redirectLink').show();
$('#detailMessages').attr('class', 'alert alert-success text-center');
$('#detailMessages').html(message)
})
.catch((message) => {
$('#detailMessages').attr('class', 'alert alert-warning text-center');
$('#detailMessages').html(message)
})
}
},
error: function (e) {
$("#loaderImg").hide();
console.log("There was an error with your request...");
console.log("error: " + JSON.stringify(e));
$('#detailMessages').show();
$('#detailMessages').attr('class', 'alert alert-danger text-center');
$('#detailMessages').html("<b>Error: </b>" + e.responseText);
}
})
};
/**
* Converts a list of form data objects to a dictionary.
Expand All @@ -123,25 +140,25 @@ function pollJobStatus(jobId, callBack) {
* @returns {Object} - The dictionary representation of the form data.
*/
function formDataToDictionary(formData, listKeys) {
const dict = {};
const dict = {};

formData.forEach(item => {
const {
name,
value
} = item;
if (listKeys.includes(name)) {
if (!dict[name]) {
dict[name] = [value];
} else {
dict[name].push(value);
}
} else {
dict[name] = value;
}
});
formData.forEach(item => {
const {
name,
value
} = item;
if (listKeys.includes(name)) {
if (!dict[name]) {
dict[name] = [value];
} else {
dict[name].push(value);
}
} else {
dict[name] = value;
}
});

return dict;
return dict;
}

/**
Expand All @@ -154,12 +171,12 @@ function formDataToDictionary(formData, listKeys) {
*/
function iconLink(url, icon, title) {

const linkUrl = `<a href="${url}" target="_blank">` +
` <span class="text-primary">` +
` <i class="mdi ${icon}" title="${title}"></i>` +
` </span>` +
`</a>`
return linkUrl
const linkUrl = `<a href="${url}" target="_blank">` +
` <span class="text-primary">` +
` <i class="mdi ${icon}" title="${title}"></i>` +
` </span>` +
`</a>`
return linkUrl
}

/**
Expand All @@ -170,28 +187,28 @@ function iconLink(url, icon, title) {
* @returns {string} - The rendered string with placeholders replaced by actual values from jobData.
*/
function _renderTemplate(templateString, data) {
// Create a regular expression to match placeholders in the template
const placeholderRegex = /\{jobData\.([^\}]+)\}/g;
// Create a regular expression to match placeholders in the template
const placeholderRegex = /\{jobData\.([^\}]+)\}/g;

// Replace placeholders with corresponding values from jobData
const renderedString = templateString.replace(placeholderRegex, (match, key) => {
const keys = key.split(".");
let value = data;
for (const k of keys) {
if (value.hasOwnProperty(k)) {
value = value[k];
} else {
return match; // If the key is not found, keep the original placeholder
}
}
return value;
});
// Replace placeholders with corresponding values from jobData
const renderedString = templateString.replace(placeholderRegex, (match, key) => {
const keys = key.split(".");
let value = data;
for (const k of keys) {
if (value.hasOwnProperty(k)) {
value = value[k];
} else {
return match; // If the key is not found, keep the original placeholder
}
}
return value;
});

return renderedString;
return renderedString;
}

function getMessage(jobResultId) {
return new Promise((resolve) => {
resolve("Job Completed Successfully.");
});
return new Promise((resolve) => {
resolve("Job Completed Successfully.");
});
}

0 comments on commit 4de2396

Please sign in to comment.