In the Crossfuze Knowledge Turnkey, we’ve got a lot of great functionality. There are hundreds of updates that take the Knowledge Base to a whole new level. This includes full version tracking and the ability to revert to past versions for knowledge articles. When you’re looking at the various versions of a long article, however, it can be like trying to find a needle in a haystack to identify what’s actually changed between the versions.
After a bit of digging I was able to figure out how ServiceNow was doing those types of comparisons for update sets and versions of Business Rules, UI Pages, etc. They’ve got a number of different script includes that take the different XML updates and then compare the various parts of them. Down under the hood a little ways there is a class that they have to compare between actual text values. Finding this was the jackpot since it allowed comparing just those things that I specifically wanted to compare. Using this class ended up being a relatively straight forward process, but did require some scripting (including JellyScript) so it may not be for the faint of heart.
Here’s what it will look like in the end:
The biggest part of the challenge is handled by ServiceNow’s “Differ” script include. This class has one main function that is used to do the magic: diff.
The diff function takes in four parameters:
- text1 – The first string that will be used in the comparison
- text2 – The second string that will be used in the comparison
- name – The label for what you’re comparing. Most likely going to be the field label for the field that you’re comparing. This shows up in a column on the left of the comparison
- change – An optional parameter that indicates whether to return a result if the strings match
The end result of this is a string with part of a table that contains the strings compared line by line just like you see when comparing differences in an update set preview. It is assumed that the results of this function will be included as part of a table given a specific class so that the rows are formatted correctly. There is an XML header that needs to be removed from the result but essentially this is the code needed to use the result of the function:
<thead>
<tr>
<th class="top_left_field"></th>
<th class="texttitle" colspan="2">Left Label</th>
<th class="texttitle" colspan="2">Right Label</th>
</tr>
</thead>
diff_output
</table>
The left and right labels identify what it is that you’re comparing and diff_output is the output from the function.
There are a few different ways this functionality can be used; after looking at it I opted to use a GlideDialog window and UI Page to provide a similar experience to what is already used in other places. To do this a UI Action needs to be set up to open the dialog window and a UI Page needs to be set up to do the comparison and show the results.
Here’s what’s needed in the UI Action. It’s relatively generic other than the UI Page that is referenced. For the fields that I haven’t included you can use the default or adjust it as you see fit:
Name: Compare to Current
Table: table that has the version info you want to compare to the current record
Client: true
Form link: true
List context menu: true
Onclick: showTheComparison()
Script:
//Get the values to pass into the dialog
var sysId;
// Check if this is called from a List, if so, rowSysId will have the Sys ID of the record
if (typeof rowSysId == 'undefined'){
// If it's called from a form, get the Sys ID of the record being shown
sysId = gel('sys_uniqueValue').value;
} else {
sysId = rowSysId;
}
//Initialize and open the dialog
//Instantiate the comparison dialog, the parameter is the name of your UI Page
var dialog = new GlideDialogWindow("compare_kb_to_version");
//Set the dialog title
dialog.setTitle("Version Comparison");
// Pass in the Sys ID of the record to compare
dialog.setPreference("version_sys_id", sysId);
// Set the size
dialog.setSize('95%', '95%');
//Open the dialog
dialog.render();
}
The UI Page has a little more to it since it needs to reference the specific tables and fields where the two versions are stored.
Name: compare_kb_to_version
HTML:
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate>
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Diff magic using the SN Differ class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
// variable to aggregate the differences for the fields into
var diff = "";
// get the SN object that does the comparing
var differ = new Differ();
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Values needed for the diff
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
var left_label = "Published Article";
var right_label = "";
// Get the version info from the dialog property
var version_sys_id = RP.getWindowProperties().get('version_sys_id');
var version = new GlideRecord("kb_submission");
if (version.get(version_sys_id)) {
// Set the label for the right side of the difference
right_label = "Version: " + version.number;
/*~~~~~ Short Description field ~~~~~*/
// Pass in the strings that need comparing
// u_kb_article is the reference field that refers to the KB article that the version is for
var kb_short_description = differ.diff(version.u_kb_article.short_description, version.short_description, version.short_description.getLabel(), true);
// Strip off the XML header
var starter = kb_short_description.indexOf("?>");
kb_short_description = kb_short_description.substring(starter + 2);
diff += kb_short_description;
/*~~~~~ Text field ~~~~~*/
// Pass in the strings that need comparing
var kb_text = differ.diff(version.u_kb_article.text, version.text, version.text.getLabel(), true);
// Strip off the XML header
var starter = kb_text.indexOf("?>");
kb_text = kb_text.substring(starter + 2);
diff += kb_text;
}
</g:evaluate>
<table class="diff">
<thead>
<tr>
<th class="top_left_field"></th>
<th class="texttitle" colspan="2">${left_label}</th>
<th class="texttitle" colspan="2">${right_label}</th>
</tr>
</thead>
<g:no_escape>${diff}</g:no_escape>
</table>
</j:jelly>
The first section of code in the g:evaluate tag sets up the Differ class. Then we get the labels for each side of the comparison. Then we get the comparisons for each of the fields that we’re trying to compare. In this case we’re comparing the Short Description and the Text fields. The comparison results are cleaned up and concatenated in a string to be used inside the wrapper HTML code that sets up the header and styles.
Once you’ve got all of that in place, seeing the differences is just a click or two away.
I have also tried using the same Diff script to compare the scripts in different instance much like the version comparison we have in service-now. But this is a awesome way of using the same script for a entirely different purpose 🙂 Thanks for sharing this.
Have you had any success defining the widths of the $[diff] element? It seems that no matter what I try, it overrides my width definitions and ends up having the “old value” column way too small.