logo

NJP

Automatically convert ticket numbers in work notes to hyperlinks via Business Rule

Import · Aug 04, 2022 · article

The on-before business rule script is at the bottom of this article, but it is worth reading through to understand what it is achieving and how it affects you.

Introduction

I have been asked (and pondered the same myself) whether we could automatically swap out record (ticket) numbers with a hyperlink to the record itself, when entered into the work notes of an incident (or any ticket with work notes, e.g., requested item, etc.).

First of all, in order to insert a hyperlink into a journal field, you need to use a code block:

[code]<a href="___URL_HERE___">___LABEL_HERE___</a>[/code]

NOTE: This method requires the ability to render HTML in a journal field using code blocks. If you have security hardening enabled to prevent this, it will not work, see more info here:

However, simply pasting in a ticket number is the easiest method to refer to another ticket in a work note, and having to convert that to a link in a code block each time can get tedious very quickly.

An on-before business rule

I've seen several questions around the community forum attempting to cover this topic, and I've researched the same myself, but I haven't yet found a suitable solution for this specific query, but after some digging and tinkering, I've come up with a solution.

Targeting the [task] table and running when Work notes changes:

image

The early stages of the script

The script for this is fairly straightforward in terms of the overall process:

  • Get the list of ticket number prefixes and the count of digits that make up the number (eg. the [incident] table has a prefix of "INC" and uses 7 digits)
  • Build a regex string to find and replace these in the work_notes string
  • Add this formatted string to the work_notes of the ticket

There was also the issue of potentially having more than one table using the same number prefix. This isn't an OOB practice, but can happen in some environments. This was an easy fix by ensuring the link uses "text_search_exact_match.do?sysparm_search=TICKET_NUMBER".

// Grab raw input for processing
var currentNote = current.work_notes.toString();

// Get all tables with a number prefix
var table = new GlideRecord("sys_db_object");
table.addEncodedQuery("sys_update_nameISNOTEMPTY^number_refISNOTEMPTY");
table.query();

// Build regex string
var regex = "";
while (table.next()) {
    if (table.number_ref.maximum_digits > 0) {
        if (regex != "") {
            regex += "|";
        }
        regex += table.number_ref.getDisplayValue() + "\\d{" + table.number_ref.maximum_digits.getDisplayValue() + "}";
    }
}

if (regex != "(") {
    // Finalise regex string (add negative look ahead)
    var regX = new RegExp("(" + regex + ")(?!\\w)", "gi");

    // Replace ticket numbers with exact match search hyperlink
    var formattedNote = currentNote.replace(regX, "[code]<a href=\"text_search_exact_match.do?sysparm_search=$&\">$&</a>[/code]");

    // Apply formatted string
    current.work_notes = formattedNote;
}

The problem

This does the job, but has several issues that need to be addressed.

For example, if I was to add a work note of "INC0009009 is similar to RITM0000001", the following would be the result:

image

The reason for this is the existing string has already been added to a Journal Entry, and assigning a new string to the work_notes property of the ticket simply adds a new journal entry, so you get the original raw input with the formatted string above it.

I looked at working around this by aborting the update, and then setting the work_notes and triggering an update afterwards, but this simply cancels out the abort, and gives the same result as above.

The workaround

There is a work around, however, and it's pretty easy to use:

// Disable processing and flush raw input
current.setWorkflow(false);
current.update();

// Enable processing again and apply formatted string
current.setWorkflow(true);
current.work_notes = formattedNote;

// NOTE: no need to run current.update() here

Basically, this disables further processing if an update was to occur on this ticket, followed by triggering an update. This causes the journal entry of the raw input to be saved in the [sys_journal_field] table, but not applied to the ticket. Effectively, this "flushes" the raw input.

If you combine this with your replace, then make sure to re-enable the processing before you apply your own string, you will get something like this:

image

Perfect!

Code block conflicts

Except there is one more issue I was experiencing, and that is any manually entered code blocks that contained a ticket number were broken by this business rule.

This was a tricky one to decipher, but I was able to find that the replace() function for strings can take in a function as the second parameter. This allowed me to make use of capture groups in regex, not only allowing me to ignore code blocks, but also capitalise ticket numbers for consistency. All leading me to...

The final script

Bear in mind this is aimed at remaining generic and usable in most environment setups, but you can tailor it to your own environment and needs.

(function executeRule(current, previous /*null when async*/) {

    // Grab raw input for processing
    var currentNote = current.work_notes.toString();

    // Get all tables with a number prefix
    var table = new GlideRecord("sys_db_object");
    table.addEncodedQuery("sys_update_nameISNOTEMPTY^number_refISNOTEMPTY");
    table.query();

    // Build regex string
    var regex = "";
    while (table.next()) {
        if (table.number_ref.maximum_digits > 0) {
            if (regex != "") {
                regex += "|";
            }
            regex += table.number_ref.getDisplayValue() + "\\d{" + table.number_ref.maximum_digits.getDisplayValue() + "}";
        }
    }

    // Define replacer function to only replace matches that are caught in capture a group (non-code blocks)
    function replacer(match, p1, offset, string) {
        if (p1 != null) {
            return "[code]<a href=\"text_search_exact_match.do?sysparm_search=" + p1.toUpperCase() + "\">" + p1.toUpperCase() + "</a>[/code]";
        } else {
            return match;
        }
    }

    if (regex != "(") {
        // Finalise regex string (add negative look ahead)
        // This ensures any code blocks are not matched inside a group
        var regX = new RegExp("\\[code][^\\[]+\\[\\/code]|(" + regex + ")(?!\\w)", "gi");

        // Replace ticket numbers with exact match search hyperlink
        currentNote = currentNote.replace(regX, replacer);

        // Disable processing and flush raw input
        current.setWorkflow(false);
        current.update();

        // Enable processing again and apply formatted string
        current.setWorkflow(true);
        current.work_notes = currentNote;
    }
})(current, previous);

NOTE: This was built in a PDI running San Diego, and another PDI running Tokyo. It will likely work in other versions, but I have not tested this in those versions.

Hope this helps! Any questions please let me know.

Kind regards,

David

View original source

https://www.servicenow.com/community/developer-articles/automatically-convert-ticket-numbers-in-work-notes-to-hyperlinks/ta-p/2302129