yjaques
Tera Contributor

One thing that I've always found problematic with Change Requests in Service Now is that there is no easy way to analyze the actual impact on users and groups. The Affected and Impacted Services/CIs tabs are great, but any relationship to users is sadly lacking. To remedy this we've added a field or two and done a bit of scripting so that now Change Requests display affected user groups. What are the steps?

1. We added a column to the Applications table called "User Group" that references the User Group table. We have import sets that pull users from application databases and maintain the user groups for each application so we can easily associate Applications and the set of users that use them.

2. We added a string field to the Change Request table called "Affected Users" and we display it in the Change Request View.

3. We have a Business Rule that is fired when a Change Request is inserted in the table (i.e. when someone raises a change request). The Business Rule grabs the CI from the Change Request and iterates through the Upstream relationships looking for User Groups. Whenever it finds one it adds it to a key value array and counts the members. When it's done iterating it writes the array as a small ASCII text table to the Affected Users field of the Change Request. This way even before the request gets sent to our CAB the person raising the request can get an idea of how many groups and users will be impacted. Here's the script that goes into the business rule:

// Speed up calls to hasOwnProperty

var hasOwnProperty = Object.prototype.hasOwnProperty;

if (current != null) {

        var thisRequest = current.sys_id;

        var rootStr = '';

        var depthLim = 5;

        var req = new GlideRecord('change_request');        

        req.get(thisRequest);

        logFields(req, 0);

         

        //get the CI associated to this change request and use it to find other CIs that will be affected

        var rootCI = getCI(req.cmdb_ci);

        rootStr = rootCI.getDisplayValue() + ' (' + rootCI.category + '/' + rootCI.subcategory + ') Planned period: ' + req.start_date + ' - ' + req.end_date;

       

        //this object holds affected user group keys with user member counts that are then added to the change request comments to help approvers understand the situation.

        var affectedGroups = {};

        //process the root CI

        affectedGroups = processCI(rootCI, 0,affectedGroups);        

        //start iterating the tree of relationships

        affectedGroups = findUpstream(req.cmdb_ci, 1, affectedGroups);

       

        //count affected users and groups along the way and write that back into the request

        if (! isEmpty(affectedGroups)) {

                  gs.log("Preparing to update Change request with affected user info.........................................................");

                  var userTotal = 0;

                  req.u_affected_users+='The following user groups will be affected by this change request:\n\nGroup\t\tUserCount\n====================\n';

                  for (var key in affectedGroups) {

                            gs.log(key+'='+affectedGroups[key]);

                            req.u_affected_users += key+'\t\t'+affectedGroups[key]+"\n";

                            userTotal += affectedGroups[key];

                  }

                  req.u_affected_users += "----------------------------------------\nTotal\t\t\t"+userTotal;

                  req.update();

        }                            

}

//ServiceNow doesn't support ECMA5 so need this function to be sure the object is not empty

function isEmpty(obj) {

        // null and undefined are "empty"

        if (obj == null) return true;

        // Assume if it has a length property with a non-zero value

        // that that property is correct.

        if (obj.length > 0)       return false;

        if (obj.length === 0)   return true;

        // Otherwise, does it have any properties of its own?

        // Note that this doesn't handle

        // toString and valueOf enumeration bugs in IE < 9

        for (var key in obj) {

                  if (hasOwnProperty.call(obj, key)) return false;

        }

        return true;

}

// find the related items of the affected item

function findUpstream(itemSysID, depth, affectedGroups) {

        //only go to a certain depth      

        if (depth < depthLim) {

                  //get all the glide records of the related items

                  var rel = new GlideRecord('cmdb_rel_ci');

                  rel.addQuery('child', itemSysID);

                  rel.query();

                  while (rel.next()) {

                            //the parent value of the glide record is the actual related CI sys_id so use that to get the actual CI object

                            affectedGroups = processCI(getCI(rel.parent), depth, affectedGroups);

                            //iterate the tree

                            affectedGroups = findUpstream(rel.parent, depth + 1, affectedGroups);

                  }                                      

        }

        return affectedGroups;

}

//gets the ci record from a sys_id

function getCI(itemSysID) {

        var ci = new GlideRecord('cmdb_ci');

        ci.get(itemSysID);

        return ci;

}

function processCI(ci, depth, affectedGroups) {

        logFields(ci, depth);

        //check for a u_user_group. Note the ref_ notation that allows to get the field from the extended table that is not present in the base table

        var userGroup = ci.ref_cmdb_ci_appl.u_user_group;

        if (userGroup) {

                  var userGroupName = userGroup.getDisplayValue();

                  gs.log('Found user group: ' + userGroupName);

                  //add it to the affected groups with count as long as not exists already

                  if(affectedGroups[userGroupName]   === undefined) {

                            var members = new GlideRecord('sys_user_grmember');

                            members.addQuery('group', userGroup);

                            members.query();

                            affectedGroups[userGroupName] = members.getRowCount();

                  }

        }

        return affectedGroups;

}

function logFields(ci, depth) {

        var tabs = '       ';

        for (i = 0; i < depth; i++) {

                  tabs += tabs;

        }

        if(gs) {

                  gs.addInfoMessage('==========================================');

                  gs.addInfoMessage(rootStr);

                  gs.addInfoMessage(tabs + 'Configuration Item: ' + ci.getDisplayValue());

        } else {

                  log.info( 'Configuration Item: ' + ci.getDisplayValue());

        }

        //get all the fields and print them

        var fields = ci.getFields();

        if(gs) {

                  gs.addInfoMessage(tabs + 'Field values:');

        }

        for (var i = 0; i < fields.size(); i++) {

                  var glideElement = fields.get(i);

                  if (glideElement.hasValue()) {

                            if(gs) {

                                      gs.addInfoMessage(tabs + glideElement.getName() + ':               ' + glideElement);

                            } else {

                                      log.info(glideElement.getName() + ':               ' + glideElement);

                            }

                  }

        }

       

}