FujiForty - HierarchicalWorker and Progress Viewer

You might have noticed that Fuji has fancy new progress bars for a variety of activities. You might like them so much that you want to make your own. Well here's how to pull it off.

 

We'll need a few different components:

  • Client side script (UI Action) to start a worker and display the progress
  • Client-callable AJAX script include (The Runner) that the client action will call and report progress back to the client
  • Script Include (The Worker) that will do the work on the server

Let's start with the UI Action. It's important that this be a client-side action since the progress display will be rendered on the client. Comments are inline, but basically we are calling page "hierarchical_progress_viewer" in a dialog window and defining the runner client script include to call. The "on executionComplete" callback will fire when the worker is complete. Here we'll access the trackerObj that was passed back so we know what happened on the server.

// UI Action: Show Some Progress
// Client: true
// Onclick: displayProgressWorker()

function displayProgressWorker() {
    //instantiate new progress viewer
    var progressViewerDlg = 
        new GlideDialogWindow('hierarchical_progress_viewer');
    //define the AJAX script include to call
    progressViewerDlg.setPreference('sysparm_ajax_processor',
                                    'x_cavu_progress.CAVURunner');
    //give the window a title
    progressViewerDlg.setTitle("CAVU Worker Progress");
    
    progressViewerDlg.on("executionComplete", function(trackerObj) {
        //optional callback on completion
        //see what's in trackerObj
        console.log(trackerObj);
        //use some data we passed back from the worker 
        //from 'this.tracker.updateResult({incidentsCreated: totalIncidents});'
        alert("All done creating " + 
              trackerObj.result.incidentsCreated + " incidents");
        //close the viewer
        progressViewerDlg.destroy();
    });
    
    //render the viewer
    progressViewerDlg.render();
}

 

Now for the runner, a client-callable AJAX script include. The GlideScriptedHierarchicalWorker will handle the worker process. In this case it's set to run the worker in a background process. The worker is called in the "setScriptIncludeName" and "setScriptIncludeMethod" methods. Finally we need to return the worker's ID to the client so the client can keep track of the worker progress.

var CAVURunner = Class.create();
CAVURunner.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
    start: function() {
        //instantiate worker
        var worker = new GlideScriptedHierarchicalWorker();
        //give it a name
        worker.setProgressName("CACU Worker");
        //run in the background so user can close progress if they get bored watching
        worker.setBackground(true);
        //call the script include worker and method
        worker.setScriptIncludeName("x_cavu_progress.CAVUWorker");
        worker.setScriptIncludeMethod("runMe");
        worker.start();
        //get the worker id so we can track progress from the client
        var progressID = worker.getProgressID();
        return progressID;
    },
    
    type: 'CAVURunner'
});

 

The last piece is the worker script include. During the initialization we need to get a GlideExecutionTracker (this.tracker) to manage the progress data. This is how the worker passes progress and completion information to the client progress viewer.

To buy us some time to actually see the progress bar we'll generate a bunch of incidents.

So what can we do with the tracker? If you're running in an app scope it's somewhat limited compared to what you can do in the global scope (no surprise there), but there's enough to do what you would need for most cases. We will have to do a bit more work to calculate our own progress intervals which isn't required if you have the full global API.

While we're creating incidents we want to increase the progress bar at a proper rate so it completes around the same time as the work. To increment the progress bar you call the incrementPercentComplete(amount_to_increase). The "amount_to_increase" value must be an integer so 1 is the smallest increment. In the case where we have more than 100 events we'll need to determine the interval that makes up one percent of progress and increment by one at each chunk. There's also the code to handle count of things less than 100, where we need to increment the progress more than one percent for each thing.

Once the work is complete we'll send some JSON data back to the client by calling tracker.updateResult(data) and either call tracker.success or fail.

var CAVUWorker = Class.create();
CAVUWorker.prototype = {
    
    initialize: function() {
        //get the tracker so we can send progress updates
        this.tracker = GlideExecutionTracker.getLastRunning();
    },
    
    runMe: function() {
        //let's take our time doing something like create 200 incidents
        var totalThings = 200;
        var incCounter = 1;
        for (var i = 1; i <= totalThings; i++) {
            if (totalThings < 100) {
                //determine how many percent to increment for each interval
                var intervalPercent = Math.floor(100 / totalThings);
                this.tracker.incrementPercentComplete(intervalPercent);
            }
            else {
                //determine number of things for one percent
                var onePercentInterval = Math.floor(totalThings / 100);
                if (i % onePercentInterval == 0) {
                    //increment one percent more
                    this.tracker.incrementPercentComplete(1);
                }
            }
            
            this._createIncident(i);
        }
        
        this.tracker.success("Finished creating " + totalIncidents + " incidents");
        //otherwise this.tracker.fail(msg);
        this.tracker.updateResult({incidentsCreated: totalIncidents});
    },
    
    _createIncident: function(num) {
        var gr = new GlideRecord("incident");
        gr.short_description = "testing " + num;
        gr.insert();
    },
    
    type: 'CAVUWorker'
};

 

Running the client action displays the progress.

Once complete we get the alert in the callback function that displays some data from the tracker.updateResult data.

If you're interested in what else the tracker is returning to the client you can check the browser console log since we are writing out the trackerObj in the callback. 

blog_progress.png