Asynchronous ServiceNow Progress Worker
August 18, 2015
Tags: ServiceNow ProgressWorker Async
This post will explain how you can show the progress of a time consuming process in ServiceNow. We’ll achieve this via a Progress Worker.
A progress worker is a async task that runs on a separate thread so you can continue your other tasks while the task executes. An example is you may continue working while a plugin is being installed.
As you can see in the image below, the progress is shown with a live status of the plugin installation.The following components are required for the progress worker:GlideModal of “simple_progress_viewer” to show the progress
A Script Include that does the work and updates the progress
#Example
In this post I will create a progress viewer which shows the progress of creating incidents in bulk.
Code: https://github.com/aqibmushtaq/snc-progress-worker
#1) Input fields
Let’s start of by creating the a UI Page which sets up the page with 2 input fields and a button:
Fields:
- Name: create_incidents
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<label for="total" class="col-sm-3 control-label">Number of incidents to create</label>
<input type="text" id="total" />
<label for="prefix" class="col-sm-3 control-label">Short description prefix</label>
<input type="text" id="prefix" />
<button id="create_incidents" class="btn btn-primary">Create incidents</button>
var gr = new GlideRecord(''sys_ui_script'');
gr.orderByDesc(''sys_updated_on'');
gr.query();
if(gr.next())
gr.getValue(''sys_updated_on'');
</g2:evaluate>
</j:jelly>
In the above code you’ll notice the following:
- Input field for the number of incidents to create
- A button to trigger the creation
#2) UI Script to Create GlideModal
The aim of the JS UI Script is to initiate the GlideModal and call the Ajax Processor with the value of the two fields above. It will also create the required buttons on completion.
Create a UI Script with the following values:
- Name: create_incident_progress
- Global: false
addLoadEvent(function() {
event.stop();
// (1) A GlideModal is created referring to simple_progress_viewer
var dd = new GlideModal("simple_progress_viewer", false, "40em", "10.5em");
var map = getMessages(["Creating incidents", "Close", "List new incidents"]);
dd.setTitle(map["Creating incidents"]);
var totalIncidents = $("total").value;
var prefix = $("prefix").value;
// (2) We set the ajax script with sysparm_ajax_processor and the parameters
// all start with sysparm_ajax_processor following their name
dd.setPreference("sysparm_ajax_processor_total_incidents", totalIncidents);
dd.setPreference("sysparm_ajax_processor_prefix", prefix);
dd.setPreference("sysparm_ajax_processor", ''CreateIncidentAJAXProcessor'');
dd.setPreference("sysparm_renderer_progress_title", map["Creating incidents"]);
// (3) Specify the buttons to display on completion by, all buttons are prefixed
// with sysparm_button
dd.setPreference("sysparm_button_new_incidents", map["List new incidents"]);
dd.setPreference("sysparm_button_close", map["Close"]);
// (4) An event listener is added on executionComplete which is called
// when the progress worker is completed. A tracker object is
// passed to the event listener which contains a map of values
// which you will see in the CreateIncidentWorker Script Include below.
dd.on("executionComplete", function(trackerObj) {
// (5) Event listeners are attached to the buttons
var newIncidentsBtn = $("sysparm_button_new_incidents");
if (newIncidentsBtn) {
newIncidentsBtn.on(''click'', function() {
location.href = trackerObj.result.url_new_incidents;
});
}
var closeBtn = $("sysparm_button_close");
if (closeBtn) {
closeBtn.on(''click'', function() {
dd.destroy();
});
}
});
dd.render();
});
In the above code you’ll notice the following:
- A GlideModal is created referring to simple_progress_viewer
- We set the ajax script with sysparm_ajax_processor and the parameters all start with sysparm_ajax_processor following their name
- Specify the buttons to display on completion by, all buttons are prefixed with sysparm_button
- An event listener is added on executionComplete which is called when the progress worker is completed. A tracker object is passed to the event listener which contains a map of values which you will see in the CreateIncidentWorker Script Include below.
- Event listeners are attached to the buttons
#3) Create an Ajax Processor
We now need to create an ajax processor which will start the worker code when called.
Create a Script Include with the following values:
- Name: CreateIncidentAJAXProcessor
- Client callable: true
var CreateIncidentAJAXProcessor = Class.create();
CreateIncidentAJAXProcessor.prototype = Object.extendsObject(AbstractAjaxProcessor, {
check: function() {
var trackerGr = new GlideRecord("sys_execution_tracker");
trackerGr.addQuery("source_table", "incident");
trackerGr.addQuery("state", "IN", "0,1");
trackerGr.query();
if (trackerGr.next())
return trackerGr.getUniqueValue();
return "";
},
/**
* Start the Scripted Hierarchical Worker if one does not already exist
*/
start: function() {
var trackerId = this.check();
if (trackerId)
return trackerId;
var totalIncidents = this.getParameter("sysparm_ajax_processor_total_incidents");
var prefix = this.getParameter("sysparm_ajax_processor_prefix");
var worker = new GlideScriptedHierarchicalWorker();
worker.setProgressName("Creating incidents");
worker.setScriptIncludeName("CreateIncidentWorker");
worker.setScriptIncludeMethod("start");
worker.putMethodArg("totalIncidents", totalIncidents);
worker.putMethodArg("prefix", prefix);
worker.setBackground(true);
worker.start();
return worker.getProgressID();
},
type: 'CreateIncidentAJAXProcessor'
});
In the above code you’ll notice the following:
- The start method is the entry point into the processor
- A check is made to ensure a worker isn’t already running for the job
- Create a GlideScriptedHierarchicalWorker with the CreateIncidentWorker Script Include that we’ll create below in step 4
#4) Create the Worker
Finally we need to create the worker Script Include which will do the nessacary work to complete the task. In this case it will create the specified number of incidents while updating the progress with a messageCreate a Script Include with the following values:
- Name: CreateIncidentAJAXProcessor
- Client callable: true
var CreateIncidentWorker = Class.create();
CreateIncidentWorker.prototype = {
},
start: function(totalIncidents, prefix) {
// (1) Get the execution tracker and start it with the correct max progress
var tracker = SNC.GlideExecutionTracker.getLastRunning();
tracker.setSourceTable("incident");
tracker.run();
tracker.setMaxProgressValue(totalIncidents);
// (2) Create the incidents and update status
var ids = [];
for (var i = 0; i < totalIncidents; i++) {
var gr = new GlideRecord("incident");
var incidentNumber = i + 1;
var shortDescription = prefix + " " + incidentNumber;
gr.setValue("short_description", shortDescription);
var id = gr.insert();
if (id)
ids.push(id);
tracker.incrementProgressValue();
var progressMsg = gs.getMessage("Created incident {0} of {1}", [incidentNumber+"", totalIncidents]);
tracker.updateDetailMessage(progressMsg);
}
// (3) Update execution tracker result
var completeMsg = gs.getMessage("Completed incident creation, {0} incidents created out of {1}", [ids.length+"", totalIncidents]);
tracker.updateDetailMessage(completeMsg);
if (ids.length == totalIncidents)
tracker.success(gs.getMessage("The incident creation is complete"));
else
tracker.fail(gs.getMessage("The incident creation is complete with failures"));
// (4) Generate a URL to take the user to the newly created incidents
var gu = new GlideURL("incident_list.do");
gu.set(''sysparm_query'', ''sys_idIN'' + ids);
// Post URL in tracker object which is passed to the client
tracker.updateResult(result);
},
type: 'CreateIncidentWorker'
};
In the above code you’ll notice the following:
- We get the last running tracker and set the appropriate details on the tracker, such as source table and maximum progress
- Start the actual work by creating incidents and updating the progress on the tracker object
- Update the final result on the tracker
- Create a result object (aka tracker object) which will be passed to the client in step 2 in the executionComplete event handler.