Detect/Prevent Update Set Conflicts Before They Happen

Do you have multiple developers working in the same instance? If so, there's a good chance that on at least several occasions, one of them has "stolen" an update/record from another. I'll explain what I mean by way of an example:

  • Developer A is working on a project that involves changing a script include. 
  • Developer B, working in parallel on a separate task, also changes the script include.
  • The update sets are pushed. Depending on the order, at least one developer is likely to see results in production that they do not expect based on their development. 

So, how can we prevent these kinds of conflicts/confusion? 

What if we could alert a developer whenever they're viewing a record that is captured in another active update set, that does not belong to them--

And this is pretty darn easy to implement. I've even provided a completed update set that you can import to implement this for you automatically! Check out the details after the jump, or click here to jump straight to the update set...

Pro Tip: Be aware that the following development should take place in your Dev environment. It won't hurt anything to move it to prod, but since this is geared toward assisting future development, it should run primarily in your dev environment. 

For starters, we need to create a script include that we can call on any record to figure out whether it belongs to someone else's update set or not. 
So, start by going to System Definition -> Script Includes, and clicking on New to create a new Script Include record. 

Set the name to checkUpdateSetConflict, check the Client callable check-box, give it a good description, and leave everything else default. 

In the Script box, copy and paste the code below:

//Takes one input: 'inpt', a GlideRecord containing the current record. 
function checkUpdateSetConflict(inpt) {
    var answer = {
        conflict:false,
        updateSetName:'',
        updateSetOwner:'',
        daysAgo:0,
        noConflictReason:''
    }; 
    var sid = inpt.getValue('sys_id');
    var table = inpt.getValue('sys_class_name');
    var updateName = table + '_' + sid;
    var update = new GlideRecord('sys_update_version');
    update.addQuery('name', updateName);
    update.addQuery('state', 'current');
    update.query();
    //get the update record. We're expecting only a single record here. 
    //If no update record exists, then do pretty much nothing. 
    if (update.next()) { 
        var updateSetSID;
        var updateSetState;
        var updateSetName;
        var updateSetOwner;
        var createdDate;
        var secondsAgo;
        var createdDaysAgo;
        var msg;
        var today = new GlideDateTime();
        today = gs.nowDateTime();
        var updateSet = new GlideRecord('sys_update_set');
        updateSetSID = update.getValue('source');
        updateSet.get(updateSetSID);  //Get the update set that the current record is in. 
        updateSetName = updateSet.getValue('name');
        updateSetOwner = updateSet.getValue('sys_created_by');
        //Figure out how long ago this update set was created.
        createdDate = new GlideDateTime(updateSet.getValue('sys_created_on'));
        secondsAgo = gs.dateDiff(createdDate.getDisplayValue(), today.getDisplayValue(), true); 
        createdDaysAgo = Math.round(((secondsAgo/60)/60)/24); //Convert seconds to days
        if (secondsAgo < 86400) {
            createdDaysAgo = 0;
        }
        //Check if default update set. If so, no conflict. 
        if (updateSetName.toLowerCase().indexOf('default') !== 0) {  
            updateSetState = updateSet.getValue('state');
            //Check if update set is in progress. If not, no conflict. 
            if (updateSetState == 'in progress') {
                var currentUser = gs.getUser().getName();
                //Check if current user created update set. If so, no conflict. 
                if (updateSetOwner != currentUser) {
                    answer.conflict = true;
                    msg = 'WARNING: This record is part of an active update set. \nName: \'' + updateSetName + '\', created by: ' + updateSetOwner + ', created: ';
                    //add 'X days ago' to string.
                    if (createdDaysAgo <= 0) {
                        msg +=  'less than a day ago.';
                    }
                    else if (createdDaysAgo == 1) {
                        msg += '1 day ago.';
                    }
                    else {
                        msg +=  createdDaysAgo + ' days ago.';
                    }
                    gs.addErrorMessage(msg);
                }
                else {
                    answer.noConflictReason = 'Current user is the same account that created the update set.';
                }
            }
            else {
                answer.noConflictReason = 'Current update set not in progress.';
            }
        }
        else {
            //This is the default update set
            answer.noConflictReason = 'Current update set is Default.';
        }
        answer.updateSetName = updateSetName;
        answer.updateSetOwner = updateSetOwner;
        answer.daysAgo = createdDaysAgo;
        
        return answer;
    }
    else {
        return answer;
    }
}  
function onDisplay(current, g_scratchpad) {
    var conflict = checkUpdateSetConflict(current);
} 
Pro tip: When you have a super long drop-down list like the "Table" field, you can - if you type quickly - just start typing the name of the option you want. So for example, you could open up this drop down, and type "script incl"-- and it would highlight the script include option for you. Then just press tab, enter, or click on it. 

Right-click the header, and click on Save. This will save the business rule, but without bouncing you back into the list view. 

Now that your first business rule is created, it will monitor the script include table and make sure that people are aware of any conflicts before updating script includes to be included in their own update sets. Whenever a developer or other user is viewing a script include record, if it's in another developer's active update set, they'll see that warning from the top of this article.
Pretty cool, right?

But you probably want to do this on far more than just the script include table, so let's do this for Business Rules and Client Scripts as well.

From the business rule record you just saved, change the Table drop-down to Business Rule [sys_script], then right-click the header, and click Insert and Stay. This inserts a new Business Rule record, then displays it. This does NOT overwrite the previous entry, so you now have two business rules called Check for update set conflict, which is totally fine because they're on different tables.

Now, you can just repeat the process, but this time select the Client Script [sys_script_client] table before hitting Insert and Stay.

Now you've got 3 tables that will have this alerting functionality.

Want more tables to have this functionality? No problem. Add as many tables as you like by repeating the last couple steps above to create new business rules for each.

Here are the tables I recommend creating business rules for:

  1. Scheduled jobs [sysauto]
  2. Scheduled scripts [sysauto_script]
  3. Fix Script [sys_script_fix]
  4. Table [sys_db_object]
  5. Choice [sys_choice]
  6. Catalog UI Policy [catalog_ui_policy]
  7. Catalog Client Scripts [catalog_script_client]
  8. UI Policy [sys_ui_policy]
  9. UI Action [sys_ui_action]
  10. Business Rule [sys_script]
  11. Client Script [sys_script_client]
  12. Script Includes [sys_script_include]
  13. Dictionary [sys_dictionary] 

Don't have time to create these scripts on your own? No problem. We've created a remote update set that you can load into your instance in a matter of minutes to enable this functionality on all of the tables listed above! Click here to download the update set XML, and click here for a guide on how to load a remote update set into your instance from an XML file (follow the instructions there, then click Preview Update Set, then Commit Update Set).


UPDATE: Since writing this article, I've enhanced the functionality of this script so that it also alerts Admins when they're on the Default update set, if the record would be saved to that update set. This is to prevent Admins from accidentally doing a bunch of work and having it get lost in a Default update set.
If you want the updated XML for that, it can be downloaded at this link


UPDATE 2: Here is yet another update (Version 3) of the update set conflict checker.  It adds business rules to support several additional tables, including all of the dictionary and dictionary-related tables.

Here's how to install this functionality in your instance:

  1. Download the XML file above, and save it to your computer.
  2. Elevate privileges to the security_admin role.
  3. Navigate to System Update Sets -> Retrieved Update Sets.
  4. Click the Import Update Set from XML link.
  5. Click Choose File and select the XML file you downloaded, then click Upload
  6. Commit the update set. 
    1. Open the update set you just uploaded
    2. Click Commit Update Set

That's all there is to it!
Thanks for reading, don't forget to subscribe, and happy developing!