Chuck Tomasi

4 minute read

20190517_085941.jpg

Thanks to Dhruv Gupta and Phil Swann for pointing me to a scripting API called HistoryWalker. In short, it allows you to move through the version history of a record. It does this by using the audit/history tables to generate a historical record.

Let’s say your record has been saved seven times (see the field sys_mod_count); you can use this API to get a copy of the GlideRecord from any version of the updates along the way.

Just start by instantiating the class with the table name and sys_id of the record. For example:

var hw = new sn_hw.HistoryWalker(gr.getTableName(), gr.getUniqueValue());

Want to rewind the clock back to the original record? Just say walkTo(0). Want to move forward one version? walkForward(). It’s pretty easy to figure out.

Given this information, I thought it might be fun to create an “Undo” button (UI action) to allow one to revert changes. While this sounds simple enough, there were some challenges including ‘current’ in a UI action doesn’t have any changed records, so step one is to get a HistoryWalker version of the current record to know what changed. The second challenge was how to determine which fields changed.

In the HistoryWalker examples, there is an undocumented API called GlideScriptRecordUtil which has a method to get the changed field names; however, this class is not available to scoped apps. Thanks to Ishaan Shoor, there’s a way to do that using GlideRecord’s getElements method. And in a classic case of “over engineering”, I wanted a function that could retrieve various versions of the record (first, last, prev, or specific number). With those pieces in place, I present the script to make your very own Undo UI action below.

This code is provided without any warranty or support. You are free to copy it, modify it, share it, and make it your own.

// Need to initially call this because 'current'
// doesn't have any changed fields in a UI action
// For example: getChangedFieldNames(current) returns an empty list.
var lastGr = getHistoryGr(current, 'last');

// Determine what fields changed on this latest update...
var fieldsChanged = getChangedFieldNames(lastGr);

// Now get the previous version of the record
var prevGr = getHistoryGr(current, 'prev');

// Now copy the indicated fields from the previous record to the current record
copyFields(prevGr, current, fieldsChanged);

/*
 * copy the field values from a source GlideRecord to destination
 *
 * @param srcGr - GlideRecord
 * @param destGr - GlideRecord
 * @param fieldList - array of strings, names of fields to copy from src to target
 * 
 */
function copyFields(srcGr, destGr, fieldList) {

    gs.info('srcGr.from=' + srcGr.getValue('from') + ' destGr.from=' + destGr.getValue('from'));

    for (var i = 0; i < fieldList.length; i++) {
        var f = fieldList[i];
        gs.info(f + ' = ' + srcGr.getValue(f));
        destGr.setValue(f, srcGr.getValue(f));
    }
    var id = destGr.update();
    gs.info('id=' + id);
    
}

/*
 * get a specific position in the history record
 * 
 * @param gr - GlideRecord
 * @param goTo - integer | "first" | "last" | "prev" (default)
 * @return GlideRecord
 *
 */

function getHistoryGr(gr, goTo) {

    var hw = new sn_hw.HistoryWalker(gr.getTableName(), gr.getUniqueValue());
    var mod_count = parseInt(gr.getValue('sys_mod_count'), 10);
    var position = mod_count - 1;

    // If a second parameter has been provided
    if (goTo) {
        // Determine if it's a string
        if (typeof goTo == 'string') {
            // Based on the string, determine position
            switch (goTo) {
                case 'first':
                    position = 0;
                    break;
                case 'last':
                    position = mod_count;
                    break;
                default:
                    /* prev = last - 1*/
                    break;

            }
            // Otherwise, check if it's a number
        } else if (typeof goTo == 'number') {
            position = goTo;
        }
    }

    // Now move the pointer to the proper version
    hw.walkTo(position);

    // And return the GlideRecord representing that point in time
    return hw.getWalkedRecordCopy();

}

/*
 * Determine the fields changed in this GlideRecord
 *        Thanks Ishaan S!
 *
 * @param gr - GlideRecord
 * @return result - array of strings (minus sys_ fields)
 * 
 */
function getChangedFieldNames(gr) {
 
    var result = [];
    var elements = gr.getElements();
    var size = elements.length;

    for (var i = 0; i  < size; i++) {
        
        var ge = elements[i];
        
        if (ge.changes()) {
            var name = ge.getName();
            if (!name.startsWith('sys_')) {
                result.push(ge.getName());
            }
        }
    }   

    return result;

}

Comments