Bi‐Directional Comments and Work Notes Synchronization between RITMs and SCTASKs - ben-vargas/servicenow-wiki GitHub Wiki

This page details the implementation of a robust solution to synchronize comments and work notes bi-directionally between Request Items (RITMs - sc_req_item table) and Service Catalog Tasks (SCTASKs - sc_task table) in ServiceNow. This solution ensures that comments/work notes made on either a RITM or an associated SCTASK will automatically appear on the other related records. The architecture prevents infinite loops and clearly identifies the source of each update.

Solution Architecture

The synchronization is achieved using ServiceNow Business Rules. Four Business Rules are configured, two for comments and two for work notes. Here's a breakdown:

  1. RITM to SCTASK (Comments): When a comment is added to a RITM, propagate it to all associated SCTASK records.
  2. SCTASK to RITM (Comments): When a comment is added to a SCTASK, propagate it to its parent RITM record.
  3. RITM to SCTASK (Work Notes): When a work note is added to a RITM, propagate it to all associated SCTASK records.
  4. SCTASK to RITM (Work Notes): When a work note is added to a SCTASK, propagate it to its parent RITM record.

To prevent infinite looping and to help identify the source of each update, the system will embed hidden HTML tags with identifiers into every comment and work note made through the syncing process. The Business Rules are configured to detect the presence of such identifiers before proceeding with the synching logic to avoid any unintentional loops.

Implementation

Business Rules

1. RITM to SCTASK (Comments)

  • Table: sc_req_item

  • When: after

  • Insert: true

  • Update: true

  • Filter Conditions: Comments changes

  • Script:

    (function() {
        if(current.sys_id.nil())
    		return;
        if (current.comments.changes()) {
        var lastComment = current.comments.getJournalEntry(1);
           if(lastComment.indexOf('data-sync-type="task_to_ritm"') == -1){
                var taskGR = new GlideRecord('sc_task');
                taskGR.addQuery('request_item', current.sys_id);
                taskGR.query();
                while (taskGR.next()) {
                   var commentText = '<span style="display:none;" data-sync-type="ritm_to_task"></span>From RITM: ' + '<a href="' + gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=sc_req_item.do?sys_id=' + current.sys_id + '">' + current.number + '</a><br/>' + current.comments.getJournalEntry(1);
                    taskGR.comments = commentText;
                    taskGR.update();
                }
            }
        }
    
    })();

2. SCTASK to RITM (Comments)

  • Table: sc_task

  • When: after

  • Insert: true

  • Update: true

  • Filter Conditions: Comments changes

  • Script:

    (function() {
        if(current.sys_id.nil())
    		return;
    
        if (current.comments.changes()) {
           var lastComment = current.comments.getJournalEntry(1);
            if(lastComment.indexOf('data-sync-type="ritm_to_task"') == -1){
                var ritmGR = new GlideRecord('sc_req_item');
                if(ritmGR.get(current.request_item)){
                  var commentText = '<span style="display:none;" data-sync-type="task_to_ritm"></span>From SCTASK: ' + '<a href="' + gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=sc_task.do?sys_id=' + current.sys_id + '">' + current.number + '</a><br/>' + current.comments.getJournalEntry(1);
                   ritmGR.comments = commentText;
                    ritmGR.update();
                }
             }
        }
    
    })();

3. RITM to SCTASK (Work Notes)

  • Table: sc_req_item

  • When: after

  • Insert: true

  • Update: true

  • Filter Conditions: Work notes changes

  • Script:

    (function() {
    if(current.sys_id.nil())
    	return;
        if (current.work_notes.changes()) {
             var lastWorkNote = current.work_notes.getJournalEntry(1);
          if(lastWorkNote.indexOf('data-sync-type="task_to_ritm_work"') == -1){
               var taskGR = new GlideRecord('sc_task');
                taskGR.addQuery('request_item', current.sys_id);
                taskGR.query();
                while (taskGR.next()) {
                   var workNoteText = '<span style="display:none;" data-sync-type="ritm_to_task_work"></span>From RITM: ' + '<a href="' + gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=sc_req_item.do?sys_id=' + current.sys_id + '">' + current.number + '</a><br/>' + current.work_notes.getJournalEntry(1);
                    taskGR.work_notes = workNoteText;
                    taskGR.update();
                }
           }
        }
    })();

4. SCTASK to RITM (Work Notes)

  • Table: sc_task

  • When: after

  • Insert: true

  • Update: true

  • Filter Conditions: Work notes changes

  • Script:

    (function() {
        if(current.sys_id.nil())
    		return;
        if (current.work_notes.changes()) {
             var lastWorkNote = current.work_notes.getJournalEntry(1);
           if(lastWorkNote.indexOf('data-sync-type="ritm_to_task_work"') == -1){
                var ritmGR = new GlideRecord('sc_req_item');
                if(ritmGR.get(current.request_item)){
                    var workNoteText = '<span style="display:none;" data-sync-type="task_to_ritm_work"></span>From SCTASK: ' + '<a href="' + gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=sc_task.do?sys_id=' + current.sys_id + '">' + current.number + '</a><br/>' + current.work_notes.getJournalEntry(1);
                    ritmGR.work_notes = workNoteText;
                    ritmGR.update();
                }
            }
        }
    })();

Code Explanation

  • if(current.sys_id.nil()) return;: This line prevents the rules from trying to run when a record does not exist.
  • current.comments.changes() / current.work_notes.changes(): Checks if the comments or work notes fields were modified.
  • GlideRecord:
    • new GlideRecord('sc_task') / new GlideRecord('sc_req_item'): Creates a GlideRecord object to interact with the database.
    • addQuery('request_item', current.sys_id): In the RITM to SCTASK rules, this finds all sc_task records related to the RITM.
    • ritmGR.get(current.request_item): In the SCTASK to RITM rules, this retrieves the specific parent RITM.
    • taskGR.query() / ritmGR.query(): Executes the database query.
    • while(taskGR.next()) / if (ritmGR.next()): Iterates through the query results.
  • Hidden Tag Implementation:
    • '<span style="display:none;" data-sync-type="[type]"></span>' is embedded into the comment or work note and is not visible on the user interface. The data-sync-type attribute allows the rules to know where the comment/work note originated from (e.g. ritm_to_task, task_to_ritm, etc.) to prevent recursion.
  • lastComment = current.comments.getJournalEntry(1); / lastWorkNote = current.work_notes.getJournalEntry(1);: Gets the latest journal entry from the corresponding field.
    • if(lastComment.indexOf('data-sync-type="task_to_ritm"') == -1) / if(lastWorkNote.indexOf('data-sync-type="task_to_ritm_work"') == -1): These lines prevent the infinite looping. If the data-sync-type tag is not present, then we are free to proceed with the sync logic. Otherwise we just exit the function without making further updates.
  • Hyperlink Creation:
    • '<a href="' + gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=sc_req_item.do?sys_id=' + current.sys_id + '">' + current.number + '</a>': Creates a fully-qualified hyperlink to navigate directly to the origin record.
  • current.comments = commentText / current.work_notes = workNoteText: The generated comment or work note along with the source and hyperlink are added to the related record.
  • current.update(): Saves the changes to the record in the related table.

Implementation Instructions

  1. Create Business Rules: Navigate to "System Definition" -> "Business Rules".
  2. Create New Business Rules: For each of the four business rules, click "New" and fill the following:
    • Name: (e.g. "RITM to SCTASK (Comments)")
    • Table: (as specified in each rule above)
    • When: after
    • Insert: true
    • Update: true
    • Filter conditions: (as specified in each rule above)
  3. Copy Script: Copy the corresponding script into the "Script" section.
  4. Save: Save each business rule.

Testing

  1. RITM to SCTASK: Add a comment or work note to a RITM. Verify that the comment/work note appears on all related SCTASKs and that the prefix (with hyperlink) is present.
  2. SCTASK to RITM: Add a comment or work note to a SCTASK. Verify it appears on the parent RITM, again with the prefix and hyperlink.
  3. Loop Test: After the comments/work notes sync across records, try adding more comments/work notes to the synchronized record. Verify that it does not cause any looping behavior.

Conclusion

This solution provides a robust, bi-directional synchronization of comments and work notes between RITMs and SCTASKs. The use of embedded HTML tags with source identifiers prevents infinite loops and provides clear insight into the origin of each comment/work note. Be sure to rigorously test all functionality before putting it into production.


⚠️ **GitHub.com Fallback** ⚠️