Have you been in a situation where you initiated a process (API call, running a script, etc) and wondered., man I wish ServiceNow did not just freeze like that! Well, guess what, I got good news for you, my friend. It’s called the GlideScriptedHierarchicalWorker. This lesser-known API can enable you to display a progress bar creating an impression that a process is underway and your instance., technically did not just hang.
Looks so pretty innit 🙂 Alright, enough of the boring bit, you’re here to see how it’s implemented, I know. Now let’s get down to business,
The Use Case
What’s a tutorial without a use case! So for the purposes of this blog post, I have a REST API call to another instance that creates an incident every time the state of the incident switches to ‘Closed’. Instead of making the user stare at a frozen ServiceNow screen, I’m going to display a pretty little progress bar (it’s gonna be beautiful I swear!).
Incident form changes
I am going to create a new field that holds the incident number of the partner incident that gets created in instance 2. Nothing too fancy here, just head over to form layout and add a string field that does this. In my case, I have gone about creating a field called ‘Partner Incident’.
Business Rule
Nothing too complicated, just the basics here, a before BR that gets triggered when the state changes to ‘Closed’.
and in the script section of the BR, I am invoking a DTO class that initiates the rest message,
(function executeRule(current, previous /*null when async*/) {
// Add your code here
var createIncidentDTO = new global.createIncidentDTO();
createIncidentDTO.initiateIncidentCreationProcess(current);
})(current, previous);
Let’s paint the UI, shall we?
The client script is rather straightforward, I am going to trigger it every time the incident state changes to ‘closed’.
function onSubmit() {
//Type appropriate comment here, and begin script below
if(g_form.getValue('state') == 7) {
var incidentNumber = g_form.getControl('number').value;
var progressViewerDlg =
new GlideDialogWindow('hierarchical_progress_viewer');
//define the AJAX script include to call
progressViewerDlg.setPreference('sysparm_incident_number', incidentNumber);
progressViewerDlg.setPreference('sysparm_ajax_processor',
'CreateIncidentRunner');
progressViewerDlg.setPreference("sysparm_button_close", 'Close');
progressViewerDlg.setSize(600,500);
progressViewerDlg.setTitle(
'<h4 style="text-align: center;
vertical-align: middle;
border-bottom: 1px solid #bdbdbd;
padding-bottom: 0.5em;
font-weight: bolder;">Incident creation in progress...</h4>');
progressViewerDlg.on("executionComplete", function(trackerObj) {
var progressBarDom = document
.querySelector('#body_hierarchical_progress_viewer #container');
var successMsgBox = document.createElement('div');
successMsgBox.style.cssText = "width: 90%; margin: auto; margin-bottom: 1em;";
var closeButton = document.querySelector('#sysparm_button_close');
successMsgBox.className = "alert alert-success msg-feedback";
for(var msg in trackerObj.result) {
var msgDom = document.createElement('p');
msgDom.innerHTML = trackerObj.result[msg];
successMsgBox.appendChild(msgDom);
}
progressBarDom.insertAdjacentElement('afterEnd',successMsgBox);
closeButton.addEventListener('click', function(event) {
progressViewerDlg.destroy();
location.reload(true);
});
});
//render the viewer
progressViewerDlg.render();
}
}
The GlideDialog window takes in hierarchical_progress_viewer as the parameter, if you’re wondering where you can find this UI page, you can’t…it’s internal to servicenow. The parameters that this object takes are similar to a Glide Ajax script, let me break this down a little,
sysparm_ajax_processor –> client callable script include name
sysparm_incident_number –> parameter that I’m passing to the script include
So what exactly happens in this client script?! Well to put it in simple words, every time the incident form state is flipped to state ‘closed‘ a GlideDialog window with a progress viewer (progress bar) is invoked, which therein invokes a progress worker in the background which is defined in the script include (we’ll get into it soon).
Upon execution complete, a callback function is invoked to perform some post-execution activities which in this case I’ll be using to print the result of my outcome.
NOTE: you may have noticed the use of DOM manipulation in the callback function, in order for this to work you need to uncheck the ‘isolate script’ flag in your client script.
The Script Includes – This is where the magic really happens
I’m creating a client callable script include CreateIncidentRunner which will be invoked from the client script previously defined. Make sure you check the Client Callable flag on this one.
var CreateIncidentRunner = Class.create();
CreateIncidentRunner.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
start: function() {
//instantiate worker
var worker = new GlideScriptedHierarchicalWorker();
var currentIncident = new GlideRecord('incident');
currentIncident.get(this.getParameter('sysparm_incident_number'));
worker.setProgressName("Incident creation worker");
worker.setBackground(true);
//call the script include worker and method
worker.setScriptIncludeName("createIncident");
worker.putConstructorArg('incidentObj', currentIncident);
worker.setScriptIncludeMethod("execute");
worker.setBackground(true);
worker.start();
//get the worker id so we can track progress from the client
var progressID = worker.getProgressID();
return progressID;
},
type: 'CreateIncidentRunner'
});
The worker object is an instance of the GlideScriptedHierarchicalWorker, this is where the magic truly lies. This object can help create a process, run it in the background, set a name for the process, etc. In this example, I am running a script include ‘createIncident’ that needs to be run in the background. The GlideScriptedHierarchicalWorker is an undocumented API but here’s a list of available methods:
initProgressFields setCannotCancel setProgressState toString wait cancel registerChild getClass setProgressStateCode hashCode stage fail notify setProgressName setParentController setProgressErrorState getParentController isCancelled getOutputSummary updateDetailMessage run getProgressMessage start isUncancelable setProgressTable equals getProgressState runScript updateMessage setProgressError addNonEscapedParameter isFailed addMessage setProgressMessage addParameter setBackground success setName notifyAll isBackground isPending getWorkerThreadID isError getProgressID setOutputSummary isStarting loadProgressWorker getProgressTable
Credit goes to a user post on SNC community
The script includes that follow, is the script that runs in the background defined in the previous step.
var createIncident = Class.create();
createIncident.prototype = {
initialize: function(incidentObj) {
this.incidentObj = incidentObj;
},
execute: function() {
var createIncidentDTO = new createIncidentDTO();
createIncidentDTO.initiateIncidentCreationProcess(this.incidentObj);
},
type: 'createIncident'
};
The block of code below is the DTO class
var createIncidentDTO = Class.create();
createIncidentDTO.prototype = {
initialize: function() {
this.drm = new IncidentCreationRestMessage();
},
initiateIncidentCreationProcess: function(current) {
var decomResponse = null;
decomResponse = this.drm.createIncident();
if (decomResponse != null) {
if (decomResponse.getStatusCode() == 201) {
var data = JSON.parse(decomResponse.getBody());
current.u_partner_incident = data.result.number;
current.update();
gs.addInfoMessage('Incident Created');
} else {
gs.addErrorMessage('Sorry your Incident could not be created');
}
} else {
gs.addErrorMessage('Sorry your Incident could not be created');
}
},
type: 'createIncidentDTO'
};
The following snippet is code for initiating the rest calls to the second instance,
var IncidentCreationRestMessage = Class.create();
IncidentCreationRestMessage.prototype = {
initialize: function() {
},
createIncident: function() {
var request = new sn_ws.RESTMessageV2();
request.setEndpoint('https://devxxxxx.service-now.com/api/now/table/incident');
request.setHttpMethod('POST');
//Eg. UserName="admin", Password="admin" for this code sample.
var user = 'admin';
var password = 'xxxxxxxxxx';
request.setBasicAuth(user,password);
request.setRequestHeader("Accept","application/json");
var response = request.execute();
return response;
},
type: 'IncidentCreationRestMessage'
};
The Results…Drum roll please 🥁
Alright alright! The moment we’ve been waiting for, how does the output look like, here it goes,
Amazing work!!