Skip navigation
1 2 3 Previous Next

Developer Community

108 Posts authored by: Steven Bell

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the Studio IDE, and Scripting in ServiceNow.

____________________________________________________________________________

 

In my last two articles I brought up client and server-side debugging when working with a Scoped Application.  In this article I will be bringing several server-side techniques together, and show what can actually be done from within the Studio!

 

Mini-Lab: Scoped Debugging and Logging – Part 1 (Create a Studio App, and client-side debugging tools and tips)

Mini-Lab: Scoped Debugging and Logging – Part 2 (System Diagnostics Debugger, Script Debugger!)

 

 

Logging

 

In previous articles I mention a variety of ways to do logging.  Here again I want to bring up our old friends:  gs.info, gs.warn, gs.error, gs.debug

 

Remember: gs.log does NOT worked in Scoped Applications.

 

When working with Scoped Applications there is a slight twist to using these.  If you are doing any sort of variable substitution you will need to surround the parameters with square brackets.  Even if there is only one variable to be substituted.

 

Example:

 

     gs.info(‘---> Hello world! {0}-{1}’, [new GlideDate(), ‘My Script Name’]);

 

Notice the brackets?  This is a zero-base array.  The first value would be the current date, and the second the string ‘My Script Name’.  Easy.

 

NOTE: In Global scoping the brackets are not necessary as long as you have no more than five parameters.  These days I always include the brackets regardless.

 

 

Fix Scripts

 

This nifty mechanism gives us a way of modeling code before putting it into our Script Includes or Business Rules.  Even better: These can be run from inside of the Studio! 

 

So, let's come up with an example that will use Fix Scripts to model our code, and then allow us to use that code immediately inside of a Script Include to be consumed by a Business Rule.  Our Script Include would log all of the other Incidents associated by the current Incident’s Problem field.  This would require we set up the data so that our current Incident has an associated Problem record; that in turn has other related Incidents.  Our Script Include would retrieve all records from the Incident table for that given Problem ID. Finally, we would need to call the Script Include from a Business Rule (the one we created in the previous article will do nicely). 

 

Blah, blah.  If that last paragraph didn't make sense, then hang in there!  All will be clear as you move through the lab!

 

Pre-Work:

 

1. Go to your favorite incident.  If the problem field is not on the form you will need to add it.  Copy and record the Problem number.  If one doesn't exist just pick one and add it to the field.

 

2. Click on the information button to the right of the Problem field to bring up the Problem record.

 

3. Right Click on the form header and copy and record the sys_id.  Recording the sys_id and the Problem number is important as we need this data as inputs in our Fix Script.

 

NOTE: In the incident I chose the Problem sys_id was: 9d3a266ac6112287004e37fb2ceb0133, and the problem number was: PRB0000007

 

4. Make sure there are at least two Incidents associated to the Problem record.  You may have to add a couple of Incidents.  We need at least two for testing purposes.

 

 

Building The Fix Script

 

Let’s create a simple Fix Script that we will then build upon toward the final Script Include.

 

1. In the Studio Application from the Part 1 article click on the Create Application File button.

 

2. Type Fix Script in the filter field, and click on the Create button.

 

 

3. Fill in the form with the following:

 

Name: Logging Tester

Active: checked

Run Once: checked

Description: Test of the various logging techniques

Demonstrates: Millisecond Logging, Cumulative Message Logging

Script:

 

var location = 'FS:Logging Tester';
var problemID = ' <<put sysid here>>';
var problemNumber = '<<put problem number here>>'; // ibid.

loggingTest(problemID, problemNumber);

function loggingTest(problemID, problemNumber) {
            gs.info('---> [{2}-{3}]\nID: {0}\nNumber: {1}', 
                        [problemID, problemNumber, 
                         new GlideDateTime().getNumericValue(), location]);
}

 

4. Click on the Submit button to save your work.

 

 

5. Click on the Run Fix Script related link to run your script.  Notice that you do NOT have to jump to your instance to do this!  You can stay in the Studio!

 

6. The Run Fix Script form will be displayed. Click on the Ok button to continue.

 

7. The Warning form will be displayed.  Click on the Proceed button to continue.

 

8. You should get something that looks like this:

 

 

 

9. Click on the Close button to return to the Studio.

 

Pretty cool huh?!  You can test code snippets from inside of the Studio!  Notice that the milliseconds are placed into the text of the logged output.  This is a technique that allows us to order our messages properly inside of the System Log. The need for this will become more obvious as we go along.

 

Next, let’s beef up our function to retrieve all incidents that have that ProblemID.  We will need a GlideRecord, and some way of gathering up the information and putting it into the log.  We will use gs.info with the millisecond technique. 

 

BTW, it is considered a best practice to embed the location that the log message originated from.

 

10. Modify the Script:

 

var problemID = '9d3a266ac6112287004e37fb2ceb0133'; // retrieved from problem table
var problemNumber = 'PRB0000007'; // ibid.

loggingTest(problemID, problemNumber);

function loggingTest(problemID, problemNumber) {
var location = 'FS:Logging Tester';
            gs.info('---> [{2}-{3}]\nID: {0}\nNumber: {1}', 
                                    [problemID, problemNumber, 
                                     new GlideDateTime().getNumericValue(), location]);
            
            var incidents = new GlideRecord('incident');
            incidents.addQuery('problem_id', problemID);
            incidents.query();
            
            while (incidents.next()) {
                        gs.info('---> [{1}-{2}]\nIncident Number: {0}', 
                                                [incidents.getValue('number'), 
                                                 new GlideDateTime().getNumericValue(), location]);
            }
}


 

 

11. Save your Fix Script.

 

12. Run the Fix Script.  Your result should look something like this:

 

 

13. Click on the Close button to continue.

 

14. From your instance navigate to System Logs > System Log > All.

 

15. Filter today’s logs with: ---> [14927

 

16. Order by Message descending.  Your List View should look something like this:

 

 

 

Notice the Created date?  For me I had all three of my new messages entered in the log on the same second!  Without the millisecond entries in the Message field I wouldn’t have a clue about which order they had really been placed there.

 

Let’s go back to our Studio, and try a different technique for preserving the order.  I use a variable to collect the messages; then print them all out at once. It isn’t as safe as just printing each off as we get to them, but it looks nicer in the log (sic. easier to read). We will also use Try/Catch as a best practice around our code, and use gs.error if an error should occur.

 

17. Modify the Script:

 

function loggingTest(problemID, problemNumber) {     
            
            try {
                        var location = 'FS:Logging Tester';
                        var message = '--->\n';
                        message += 'ID: ' + problemID + '\n';
                        message += 'Number: ' + problemNumber + '\n';

                        var incidents = new GlideRecord('incident');
                        incidents.addQuery('problem_id', problemID);
                        incidents.query();

                        while (incidents.next()) {
                                    message += 'Incident Number: ' + incidents.getValue('number') + '\n';
                        }

                        gs.info('---> [{1}-{2}] {0}', 
                                                [message, 
                                                 new GlideDateTime().getNumericValue(), location]);
            }
            catch (err) {
                        gs.error('---> [{2}-{3}]\n{0}\n{1}', 
                                                [err, message, 
                                                 new GlideDateTime().getNumericValue(), location]);
            }
}


 

 

18. Save your Fix Script.

 

19. Run the Fix Script.  Your result should look something like this:

 

 

 

In the log you should see this:

 

 

 

A lot cleaner and neater, but if things blow up you might lose it all.  Thus the reason for writing down what you had so far in the gs.error.

 

With my next example we will swap out our gs.info messages with gs.debug.  This is something that works really well within scoped applications; in that you can have a switch to turn the logging off in Production that is specific to just your scoped application!  Additionally I will demonstrate a technique to merge the two logging methods just demonstrated.

 

20. Modify the Script:

 

var problemID = '9d3a266ac6112287004e37fb2ceb0133'; // retrieved from problem table
var problemNumber = 'PRB0000007'; // ibid.

loggingTest(problemID, problemNumber);

function loggingTest(problemID, problemNumber) {     
            
            try {
                        var location = 'FS:Logging Tester';
                        var message = gs.getMessage('---> [{2}-{3}]\nID: {0}\nNumber: {1}\n', 
                                    [problemID, problemNumber,
                                    new GlideDateTime().getNumericValue(), location]);

                        var incidents = new GlideRecord('incident');
                        incidents.addQuery('problem_id', problemID);
                        incidents.query();

                        while (incidents.next()) {
                                    message += 'Incident Number: ' + incidents.getValue('number') + '\n';
                        }

                        gs.debug(message);
            }
            catch (err) {
                        gs.error('---> {0}\n{1}', [err, message]); 
            }
}


 

NOTE: That even though the gs.getMessage function is normally used for internationalization we can still use its variable replacement capability for our own logging purposes!  This allows us to merge the two techniques and clean up our code a bit at the same time. Notice that the gs.info has been replaced with a gs.debug.  In the next few steps I will show you how to be able to control turning on or off your logging messages.

 

21. Click on the Create Application File button.

 

22. Filter for System Property, and click on the Create button.

 

23. Fill out the form with the following:

 

Suffix: logging.verbosity

Description: Default: db

Values: db, debug

Type string

Value: debug

 

24. Click on Submit to save your new property.

 

 

 

Default this is “db” for your project.  Which is in essence: turn logging off.  When you change the value to debug this turns logging on. This is a great feature to include in your scoped application for debugging in production; should an issue arise.  Use production log messages sparingly however!  You really shouldn’t have a lot of logging messages embedded in your code when it moves to production anyway!  I only target potential problem spots if something worries me.  Consider it fire insurance.  :-)

 

25. Go back and run your Fix Script.

 

26. Your result should look something like this now:

 

 

Note the DEBUG statement that is shown.

 

27. From your Instance take a look at the system logs. You won’t find the message here! This is because gs.debug does not appear to work from within Fix Scripts!

 

Now that we have all the logic worked out in our Fix Script it is time to switch it over to a Script Include.  This is pretty easy actually as we will be pulling the contents of our Fix Script function over intact to the new Script Include.

 

By now you should be really getting used to using a Fix Script from within the Studio!  That was on purpose.  :-)

 

 

Script Include

 

1. From in the Studio click on the Create Application File button.  Filter for Script Include and click on the Create button.

 

2. Fill out the form with the following:

 

Name: LoggingScoped

Description: Usage: new LoggingScoped().loggingTest(id, number)

Accessible From: This application scope only

Active: checked

Script:

 

var LoggingScoped = Class.create();

LoggingScoped.prototype = {
    initialize: function() {
    },
            
            loggingTest: function(problemID, problemNumber) {
                        try {
                                    var location = 'SI:Logging Scoped';
                                    var message = gs.getMessage('---> [{2}-{3}]\nID: {0}\nNumber: {1}\n', 
                                                [problemID, problemNumber,
                                                new GlideDateTime().getNumericValue(), location]);

                                    var incidents = new GlideRecord('incident');
                                    incidents.addQuery('problem_id', problemID);
                                    incidents.query();

                                    while (incidents.next()) {
                                                message += 'Incident Number: ' + incidents.getValue('number') + '\n';
                                    }

                                    gs.debug(message);
                        }
                        catch (err) {
                                    gs.error('---> {0}\n{1}', [err, message]); 
                        }
            },

    type: 'LoggingScoped'
};


 

 

NOTE: You will need to change the location variable to reflect the new location.

 

3.  Click on the Submit button to save your new Script Include.

 

4. Open the Business Rule you created in the last lab (Debugging – Scoped).

 

5. Change the Script:

 

(function executeRule(current, previous /*null when async*/) {
            
            var location = 'BR:Debugging - Scoped';
            var number = current.number;
            gs.debug('---> [{1}-{2}]\nNumber: {0}\n', 
                        [number, new GlideDateTime().getNumericValue(), location]);

            new LoggingScoped().loggingTest(current.problem_id, current.problem_id.number);
            
})(current, previous);


 

NOTE: Because the Problem field on the form is a related field you can drill down to the Problem record to retrieve the Number field contents.  Also, note that the gs.info has been swapped out for gs.debug.

 

Now for our final test. 

 

6. In your instance navigate to Incident > Open, and open your prepared incident (remember the pre-work?).  Change the value of one of the fields and save the form.

 

7. Navigate to System Logs > System Log > All and filter for ---> [149 again.  Order the Messages descending.

 

8. You should see your debug statements listed.

 

 

 

Notice that the Level field in the logs changed from Information to Debug?  This is nice in that it gives you one more thing you can filter on.

 

NOTE: Remember to turn off the debug logging you simply have to change your scoped application’s logging.verbosity property to “db”.

 

There you have it!  Some cool techniques for logging, and some examples of Scoped logging and testing.  I REALLY like that I can model my code for my Script Include inside of the Studio using Fix Scripts!

 

Obviously there are a bunch of variations on everything I have presented here, but my idea was to show you a few of them to give you ideas on what can be done. 

 

Go!  Take it to the next level!

 

Steven Bell

 

Combined Logo Graphic_Operations.png

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the Studio IDE, and Scripting in ServiceNow.

____________________________________________________________________________

 

In my previous article I describe the various console logs, and client-side debugging tools available to developers.  We created a Studio application to help us test these features.  I also described a bit of the Client Script debugging. In this article I will show some of the server-side tools available to the client-side, and how they can be utilized for debugging.

 

Pre-requisite:  Work through the labs in my previous article.

 

 

Client Side Debugging of Scripts

 

The first tool for debugging server-side scripts on the client can be found under System Diagnostics.  This basically allows you to debug Business Rules and a handful of other things.  While not necessarily a Scoped Scripting debugging tool; it still needs to be included in a survey of ServiceNow debugging tools.

 

1. Navigate to System Application > Studio and open your Debugging Logger application from the previous article.

 

2. Click on Create Application File and create a new Business Rule.

 

3. Fill out the form with the following:

 

Name: Debugging – Scoped

Table: Incident

Active: Checked

Advanced: Checked

 

Under the When to Run Tab:

 

When: Before

Update: Checked

Order: 100

 

Under the Advanced Tab:

 

Script:

 

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

     var number = current.number;
     gs.info('---> Number: {0}', [number]);

})(current, previous);

 

4. Click on the Submit button to save your work.

 

 

5. From your Instance navigate to System Diagnostics > Session Debug > Debug Business Rule.  The Business Rule Debugger will be activated.

 

Note: Debug Business Rule (Details) expands, considerably, what is logged to the client-side interface.  I rarely use this feature as there is usually just too much info to sort through.  Try it out though.  It has its uses.

 

 

6. Navigate to Incidents and open your favorite incident.

 

7. To execute our Business Rule we need to update the record.  Change one of the fields and save, don't update, the Incident record.

 

8. On the Incident form scroll to the bottom of the form past the related lists.  You will see debugging information listed.  This is really useful to see if, and in what order, your Business Rules fire.  Note the millisecond indicator on the log entries.  Gosh I wish we had this in the System Log!

 

 

 

9. Scroll down a bit further and you will see that our business rule was executed.  You won’t see the output here though.  That will appear in our system log.

 

 

 

10. Navigate to System Logs > System Log > All and search for message contains “--->”.  You log message should look something like this:

 

 

 

Ok, so that was interesting, and it has its uses.  Now let’s move onto something really cool that was re-introduced with the Istanbul release:  The Script Debugger!

 

 

Script Debugger – Breakpoints

 

Re-introduced in that it made its first appearance in the Fuji release, but the big User Interface changes in Geneva appear to have cause problems, and we lost this really useful tool for a couple of releases.  Now it’s back!  Whoop!

 

So how do we use it?

 

1. From the Studio open our new Business Rule: Debugging – Scoped.

 

2. Click on line number 3 of your script.  A blue highlight will appear.  This is your breakpoint.  You can set one of these per line.

 

3. Now click on the Script Debugger button (last icon on the Script field) to open the debugger window.

 

 

4. When the Script Debugger window opens you will see your Business Rule’s script with the breakpoint highlighted. This window MUST be open in order for debugging to work.  So do not close this window.  It is okay for it to be minimized though.

 

 

 

5. Back in your instance open your favorite Incident again, change something on the form, and save - to cause our Business Rule to fire.  Because we have the Script Debugger window still open it causes the ServiceNow Script Debugger popup to be shown.

 

6. Click on the Start Debugging button.

 

 

 

7. This will cause the Script Debugger to be brought up to the forefront, and will cause execution to stop on the specified breakpoint.

 

 

 

8. Here you will be able to see all sorts of information about everything currently executing.  Note the Call Stack in the upper left corner.  This lets us see what function is calling what (it is in reverse order). Below that is the Transaction Detail. Here you can look down through all of the request parameters used when executing the script.  Finally on the upper right side you can see all of the variables that have been populated.  In this case since we are using a Business Rule you can see both the Current and Previous objects and their property values.  I really find this tool useful.  One note of caution:  If you leave it sitting very long the breakpoint will timeout and execution will cease. 

 

NOTE: You can only stop on breakpoints set in script executed by synchronous calls.  Therefore anything Async (i.e. Ajax, Events, Scheduled Jobs) will not fire the debugger even if you have everything set up right.  Also, the Script Debugger does NOT work in Fix Scripts even though they are Async.

 

9. Click on the Start button to continue and finish execution.  You can close the Script Debugger window now.

 

 

 

That introduces the Script Debugger.  You can set breakpoints and bring up the Script Debugger window from the Studio, but in order to get it all to work you have to execute, some script that has a breakpoint, from the instance.

 

In my next, and final article (Mini-Lab: Scoped Debugging and Logging – Part 3)  on this topic, I will be covering:

 

Scoped Logging

Scoped Debug Logging

Fix Scripts

Server-Side Logging Techniques

 

Steven Bell

 

Combined Logo Graphic_Operations.png

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the Studio IDE, and Scripting in ServiceNow.

____________________________________________________________________________

 

Recently I gave an Ask-the-Expert session on Scoped Debugging.  This set of three articles is a follow-up to that session.  These are for those of you who would like to work through the examples yourself.

 

In this first article I will talk about what is available on the client-side.  In part 2 I will present you with more in-depth debugging of scripts from the Client-Side.  In part 3 I will present what is available on the server-side.

 

 

Create a Scoped Application

 

First we will create a Scoped Application to contain the examples we will be working with.

 

1.  Navigate to System Applications > Studio.  A second browser tab will open with the studio Load Application form.

 

2.  Click on the Create Application button.  The Create Application form will be displayed.

 

3.  Click on the Create button for Start from scratch.

 

 

4.  Fill in the name field on the form with: Debugging Logger

 

 

5.  Click on the Create button to generate your application and open the Studio.

 

 

 

Create a Client Script

 

The first thing I will show is concerning the alert() statement.  This is the bedrock foundation that all browser-side developers use for the “I made it to here” debugging.  Let’s create a simple Client Script to show how this works.

 

1.  Click on the Create Application File button

 

2.  In the Filter field type Client Script, and choose Client Script from the Filter Results

 

3.  Fill out the form with the following:

 

Name: Logging Scoped

Table: Incident

Active: True

UI Type: Desktop

Type: onLoad

Description: Script to test logging and debugging from the browser.

Script:

 

function onLoad() {
            var number = g_form.getValue('number');
            alert('---> Incident Number: ' + number);
}

 

4. Click the Submit button to save your script.

 

 

5. From your Instance navigate to Incident > Open

 

6. Open your favorite incident.  The alert statement will fire and should look something like this:

 

 

Alert provides a way for a developer to indicate how far the code has executed on the Browser side.  I usually do not use this for production code.  Instead I find g_form.addErrorMessage or g_form.addInfoMessage to be better more user-friendly mechanisms. I find myself using alert less-and-less, and instead I use jslog().  jslog() places the statement in the console log instead of interrupting the user with a modal pop-up box!  This is a much preferred method; especially if you forget to remove the log statement before pushing to production!  BTW, an excellent article on what is, and is not, allowed in Scoped client-side scripts is: Supported and unsupported client scripts.

 

7. In the Studio modify the Logging Scoped Client Script. Change the alert statement to a jslog() statement.

 

Script:

 

function onLoad() {

            var number = g_form.getValue('number');
            jslog('---> Incident Number: ' + number);
            
}

 

8. Click on the Update button to save your script modification.  This will automatically place a log message into the console log when we open an incident record.

 

Okay, we now have everything we need to begin testing and debugging.

 

 

 

Client Side Debugging Tools

 

The JavaScript Log and Field Watcher

 

7. Open an incident record, or if you still have it open from the previous testing; right click at the top of the incident form and click on the Reload Form link.  The idea is to trigger the onLoad Client Scripts.  The alert box should no longer be displayed.

 

8. From your instance click on the Systems Settings gear button in the upper right.  This will open the System Settings form.

 

9. Click on the Developer tab.

 

10. Make sure the following switches are set to “on”:

 

  • Show application picker in header
  • Show update set picker in header
  • JavaScript Log and Field Watcher         

 

As a Scoped Application developer I like having the first two turned on to allow me to quickly jump between scopes.  This also allows me to make sure I am in the right Scope AND the right Update Set.  The JavaScript Log and Field Watcher allows me to view the console log specific to ServiceNow, and it will be where our jslog messages will appear.  BTW, I don’t use Field Watcher that much and won’t be covering it in this article, but you might want to check it out and see if it is useful to you.

 

 

11. At the bottom of the form make sure that JavaScript Log is selected, and click on the Medium button.  You may look at Small and Large just to get a feel for what they do.

 

12. Click on the Clear Log button (looks like a circle with a diagonal slash through it).  This will clear the immediate console log. 

 

13. You may have to scroll down a bit in the JavaScript Log to find your jslog() message.  It should look something like this:

 

 

 

Browser Developer Tools

 

An alternative to the ServiceNow JavaScript Log is your browser’s console log.  Most every browser available has some sort of developer tool(s) that allows for this.  These tools display a lot more information about the state of your session than does the ServiceNow JavaScript Logger.  That can be good and bad.  Good in that all errors and warnings are displayed.  Bad in that there are a lot of messages that are displayed that are not ServiceNow specific.  This “noise” can be overwhelming when you first start using this method.  I will briefly cover three of the top browsers: Internet Explorer, Google Chrome, and Mozilla Firefox.

 

Note how in each browser the jslog message is displayed (the red arrows).

 

With Internet Explorer press the F12 button, and the console will be displayed.  Again you may have to scroll down a ways to see your jslog() message, but it will be there. There is no provision to dock it to the side like in other browsers.  However, you can undock it; at which point it will be in its own window which you can then move around or push to the back.  Click on the close (“x”) button or press F12 again to remove the console.

 

 

With Google Chrome pressing the F12 button brings up the console.  Unlike Internet Explorer there is the ability to dock it to the side or at the bottom. Again you may have to scroll a bit or do a search to find your message.

 

 

Even though Chrome is my browser of choice I like the Mozilla FireFox developer tools better for their organization and ease-of-use. Again, F12 brings up the console window, and like Chrome you can dock it to the side or at the bottom.  BTW, the old FireBug plugin for FireFox has been deprecated.

 

 

So that is pretty much it concerning the various client console logs and how to place log statements into them with Script. 

 

In my next article (Mini-Lab: Scoped Debugging and Logging – Part 2) we will talk about how to use the client-side debugging tools to debug our client-side AND server-side scripts!

 

Steven Bell

Combined Logo Graphic_Operations.png

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the Studio IDE, and Scripting in ServiceNow.

____________________________________________________________________________

 

Picking up where I left off in my previous article (Mini-Lab - Analysis Example: Chasing Down Hidden Data - Part 1); we have finished our analysis, and located the patch information.  Now we will make use of it.

 

Tasks:

 

1. Create a Fix Script to do a proof-of-concept (POC) to retrieve the patch information and store it in a variable for further use.

2. Create a UI Action on the Incident Form that will place the patch information into the work notes field.  This will be how we will demonstrate a practical use.

 

So, let’s get moving with development!

 

 

Development

 

Remember the code snippet we found in the previous lab?

 

var g_xmlDoc = null;
var g_url = "";

var g_baseRecord = new GlideRecord('sys_cluster_state');
if (g_baseRecord.get('$[sys_id]')) {
    g_xmlDoc = new XMLDocument(g_baseRecord.stats + '');
    g_http_url = 'http://' + g_xmlDoc.getNodeText('//servlet.hostname') + ':' + g_xmlDoc.getNodeText('//servlet.port');

    var ngr = new GlideRecord('sys_trigger');
    ngr.addQuery('system_id', g_baseRecord.system_id);
    ngr.query();

    g_jobs_assigned = ngr.getRowCount();
}

 

It appears from this code that the information we want is in the sys_cluster_state table, and is found in the stats field.  Further since the XMLDocument object is used to pull the data from the field we can infer that the data there is XML.  This STILL does not give us our patch info, but we are one step closer!

 

1. In the nav filter type: sys_cluster_state.list.  This will again display the list of cluster nodes.

 

2. Click on the Gear icon on the list view header bar.  The list of available fields will be displayed.

 

3. Click the stats field, and move it over into the displayed fields.

 

4. Click OK.  The list view should now show the stats field.

 

5. Double click on the stats field in the list view to edit the field.  Copy the data there and past it into your favorite editor (I use notepad++).

 

 

6. Remember that in the stats.do page the name of the field was “Build tag”.  This will probably be where it is in the XML.  A quick look at the XML in our editor shows that spaces are replaced with periods. So, I infer that it will probably something like: build.tag.

 

 

7. In your editor search for the word build.tag.  You will find it close to the end of the file, AND it will appear to have the patch info!

 

 

Note the name of the XML field the "patch" data is contained in. It should be: <glide.build.tag>. This is the field we will retrieve our version and patch information from.  We also see that the build date is right before it and is contained in: <glide.build.date>.  We can grab that as well!

 

8. Now let's create an application to contain our new features in.  Navigate to System Applications > Studio. The Studio Application List form will be displayed.

 

9. Click on the Create Application button. The Create Application form will be displayed.

 

10. Click on the Start from Scratch Create button. The Create Application naming form will be displayed.

 

11. Fill out the form with the following:

 

  • Name: System Functions
  • The Scope field will auto-fill

 

 

12. Click on the Create button.  The Confirm Application form will be displayed.

 

13. Click on the Ok button to save the new scoped application.  The Application Creation confirmation form will be displayed.  Click on the Back to List button.  The Load Application form will be displayed.

 

14. Open the System Functions application. The Studio will be displayed.

 

15. Click on the Create Application File button. The Create Application File form will be displayed.

 

16. In the Filter field type Fix Script.  The Filter Results will show Fix Script and it will be high-lighted. 

 

17. Click on the Create button.  The New Fix Script form will be displayed.

 

18. Fill out the form with the following:

 

  • Name: Retrieve Patch Info (POC)
  • Description: Retrieve the patch information from the Cluster State table.
  • Script

 

var statsXML;
var clusterState = new GlideRecord('sys_cluster_state');
clusterState.orderByDesc('sys_created');
clusterState.setLimit(1); // grab only the top most recent record
clusterState.query();

if (clusterState.next()) {
     // XMLDocument2 is scope safe
     statsXML = new XMLDocument2();
     // parse the stats field (it is XML)
     statsXML.parseXML(clusterState.getValue('stats'));
     // Retrieve the version build and patch
     versionAndPatch = statsXML.getNodeText('//glide.build.tag') + '';
     // Retrieve the version build date
     buildDate = statsXML.getNodeText('//glide.build.date') + '';
     gs.info('---> \nVersion and Patch: {0}\ndate: {1}', versionAndPatch, buildDate);
}

 

19. Click on the Submit button to save your work.

 

 

20. Click on the Run Fix Script related link to run the Script.  The Run Fix Script form will be displayed. 

 

This is pretty cool, as it is the only testing/running that can be done within the Studio! 

 

21. Click on the Ok button, and then the Proceed button to run the Script.

 

22. Your results should look something like this:

 

23. Click on the Close button to close the Run Fix Script window.

 

24. You may now close out (or save) your notepad session.

 

Okay, there we have it!  We are able to now retrieve the Version, build, patch, and patch date.  Now let’s see about creating the UI Action to add it to the Incident Work Notes field.

 

25. In the Studio click on the Create Application File button.

 

26. Type UI Action in the Filter field.  The UI Action should appear in the Filter Results.

 

27. Click on the Create button.  The New UI Action form will be displayed. 

 

We will be using the script we just developed in the Fix Script.  We will be adding a couple of lines.  One to set the Work Notes value, and the other to update the current record with the information.

 

28. Fill in the form with the following:

 

  • Name: Add Patch Info
  • Table: Incident
  • Form button: checked
  • Active: checked
  • Show update: checked
  • Comments: Add latest instance version, build, and patch info to the Work Notes field.
  • Script:

 

var statsXML;
var clusterState = new GlideRecord('sys_cluster_state');
clusterState.orderByDesc('sys_created');
clusterState.setLimit(1); // grab only the top most recent record
clusterState.query();


if (clusterState.next()) {
     // XMLDocument2 is scope safe
     statsXML = new XMLDocument2();
     // parse the stats field (it is XML)
     statsXML.parseXML(clusterState.getValue('stats'));
     // Retrieve the version build and patch
     versionAndPatch = statsXML.getNodeText('//glide.build.tag') + '';
     // Retrieve the version build date
     buildDate = statsXML.getNodeText('//glide.build.date') + '';
     current.work_notes = 'Version and Patch: ' + versionAndPatch 
          + '\ndate: ' +  buildDate;
     current.update();
}

29. Click on the Submit button to save your work.

 

 

 

TESTING

 

Now that we have finished our new utility up, let’s go test it!

 

1. Navigate to Incident > Open, and open your favorite incident from the list view.  You should now see the new Add Patch Info button at the top of the form.

 

2. Click on the Add Patch Info button.  Two messages will appear at the top of the form informing you that Execute and Write operations have been given cross scope privileges for your application.  This is normal, and automatically added to your System Functions application.  This is in case you ever decide to publish it; the application will have the correct privileges.

 

 

3. Scroll down to the Work Notes field and note that the Version, Build and Patch information along with the Patch Date were added to the work notes.  Cool beans!

 

 

4. You may want to now inactivate that button. 

 

5. You may now also want to close your Studio.

 

 

 

ANALYSIS

 

Since there is only one record we really don’t have to use “setLimit” or “while”.  The setLimit is shown here as an example of how to limit the record set to the first record found.  You will notice that there is an “if” instead of a “while” as we are only dealing with one record.  This gives two techniques to work with only the first record found. 

 

We parse the XML field “stats” into an XMLDocument2 object (which is Scope safe) in order to be able to more easily retrieve the information we want. Notice how easy it is to now reference the version, patch and date information rather than searching one long XML string and parsing it ourselves?

 

Use gs.info instead of gs.log as it is also Scope safe. 

 

There you go!  As you can see things can get somewhat convoluted, but this is a good example of “chasing-the-chain” down to the desired data.

 

Steven Bell

 

Combined Logo Graphic_Operations.png

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the ServiceNow platform, and Scripting in ServiceNow.

____________________________________________________________________________

 

Have you ever wondered how a senior developer, in the ServiceNow platform, comes up with the application information that they do?  What process do they follow?  How is it they chase down information in the platform?

 

Sometimes it is necessary to really dig, into the platform, to find the information you are wanting to display for the user.  In this two-part article you will investigate where certain system information is stored, how to interpret what you have found, and then script it to retrieve the data. 

 

Scenario

 

As an Admin it is possible to retrieve system information via the stats.do page.  This page is not to be found in the UI Pages or UI Macros list views.  So it appears to be built-in, or constituted on-the-fly.  But where is the information found on this page actually stored?  And, how can we access it and turn it to our own purposes?

 

 

Pre-Requisites

 

There are a couple of areas that we will be working with that we will be using in the following labs.  You might want to brush up on them before continuing:

 

Fix Scripts - ServiceNow Scripting 101: Two Methods for Code Development

ServiceNow Studio

 

 

Analysis & Investigation

 

First let's go take a look at the page in question.

 

1. Type stats in the Navigation Filter

 

2. In the navigator click on the System Diagnostics > Stats > Stats.  The Stats.do form will be displayed. 

 

 

You will notice at the top that there is a lot of useful information here.  Build name, build patch, build date, url, and a lot more.  This is the "cluster" information.  There are typically two servers clustered together for each ServiceNow instance.  This allows for fail-over in case something should happen to the "active" node of the cluster.  This will give us something to focus our search on where this information lives:  cluster.

 

NOTE:  If you are ever interested in what open source software is being used by the ServiceNow platform just click on the link: Open Source Software.  This contains all of the various open source titles AND their current versions.  It makes for interesting reading.

 

3. In looking for "hidden" information like this it is sometimes fruitful to go and see what applications are not active. Since we are interested in anything that might have the title "cluster".  First we see if there is perhaps something already available in the nav menu. 

 

NOTE: Another place to look would be to search out the UI Pages, UI Macros, or Script Includes to see if there is any reference to "cluster".  This would be my usual search order for something like this.

 

4. Type cluster in the nav filter.

 

 

You will see that there are only entries under Configuration (CMDB), and MID Server.  Neither of these are pertinent to our quest!

 

Next let's see if perhaps there is something we could use that just isn't turned on in the menu.

 

4. Type Application Menus in the nav filter.

 

5. Navigate to System Definition > Application Menus

 

6. Modify the filter to search for everything with the word Cluster in the Name field, and make sure that active is true or false (default is true). We notice that there is an application listed by the name of System Cluster.  And it is not active!  Looks promising.

 

 

7. Click on System Cluster.  You will note that there are five modules contained here.  Since we know what is being displayed in stats.do is the current cluster node status, the link Node States looks promising.  It shows there is a table called: sys_cluster_state.

 

 

8. In the nav filter type in sys_cluster_state.list.  The list view for that table will be displayed.  This is where the cluster node state information lives!  Ah, better and better!  Only one cluster node is displayed (the one we are logged into), and it has a system id that looks like the one we saw in the stats.do page.

 

 

9. Click on the Info button for that one record.  The Node State form should appear.  And this contains the patch info under the Assigned version field.  Okay, we are cooking with gas now!

 

 

10. Right-click on the Assigned version label.  You will find that there is NO field there!  Right-clicking on Additional details does not show us a field either.  This is generated information that is tacked onto the end of the form.  So let's go find out what is actually being referenced for this information to be displayed.  Whenever you see something like this you will need to suspect it is actually a UI Macro that is being referenced and not a field.

 

11. Right-click on the form header to the display the context menu for the form.

 

12. Navigate to Configure > Form Layout.  The form layout form will be displayed. 

 

 

13. Notice that there is an oddly named field: Cluster State Summary at the end of the "Selected" list.  This is not a real field (column); you won't find it in the "Available" fields.  It is more likely to be a UI Macro.  To be sure, we will check the table dictionary for this table (sys_cluster_state).

 

 

14. Click on the Cancel button.

 

15. Navigate back to sys_cluster_state.list.

 

16. Right-Click on the list view header bar to get the list view context menu.

 

17. Choose Configure > Dictionary. The Dictionary Entries for the table will be displayed.

 

 

18. Order the list by Column name ascending, and look through the list for the field: Cluster State Summary.  Not there.  That pretty much clinches it.  It has to be a UI Macros.  UI Macros provide a way of creating your own container to build a specialized display like this and tack it into a form.  Thus such a container would be my first guess. 

 

 

19.  Alright, time to go look.  In the nav menu type UI Macro.

 

20. Navigate to System UI > UI Macros.  The UI Macros list will appear.

 

21. Change the list filter to look for all names that contain the word: cluster.  Bingo! There is one: cluster_state_summary!

 

 

22. Click on the cluster_state_summary info button. The Macro form will be displayed.

 

23. In the XML section of the macro we observe that there is a section of code that actually shows where the information is being retrieved from! 

 

Ah ha!  We notice that this information is being kept in the sys_cluster_state table in a field named stats.  It was there all along!  The UI Macro just takes some of the data and formats it nicely for display.  So it appears we may have our code example to retrieve the patch information we want! 

 

We shall, um, appropriate it for our own uses.  BTW, this code is not scope safe.  The "$" and XMLDocument are not allowed.  So, we will have to find another way to do the same thing in our code.

 

 

24. Copy ALL of the code inside the <g2:evaluate> tag (to the end of </g2:evaluate>). Just the code, and not the tags. Paste this in your favorite editor for safe-keeping (we will look at it now, and reference it as we construct our code).

 

      var g_xmlDoc = null;
      var g_url = "";


      var g_baseRecord = new GlideRecord('sys_cluster_state');
      if (g_baseRecord.get('$[sys_id]')) {
          g_xmlDoc = new XMLDocument(g_baseRecord.stats + '');
          g_http_url = 'http://' + g_xmlDoc.getNodeText('//servlet.hostname') + ':' + g_xmlDoc.getNodeText('//servlet.port');


          var ngr = new GlideRecord('sys_trigger');
          ngr.addQuery('system_id', g_baseRecord.system_id);
          ngr.query();


          g_jobs_assigned = ngr.getRowCount();
      }


 

And that is it for our investigation/analysis stage of things!

 

Obviously when you get comfortable with where information might be stored in the platform you can skip some of these steps.  What I endeavored to show here were the exhaustive steps so that you would get the idea on what is involved to actually track the desired data down.

 

In my next article I will be showing how to utilize the material we have located, and incorporate it into something for our own use!

 

Steven Bell

 

Combined Logo Graphic_Operations.png

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the Studio IDE in ServiceNow.

____________________________________________________________________________

 

Working inside of the Studio Integrated-Development-Environment (IDE) has some definite benefits!  However, there are some interesting omissions as well.  Some of these center around the IDE, while others around Scoped Applications in general.

 

 

Significant Advantages of the Studio IDE

 

1. Navigation and Tabbed environment

 

Let me gush about this for a bit!  The navigation bar gives us only what is in our application!  This makes is so much easier to find, open, and move between application files.  I am not sure how you are handling this right now, when you are developing in Global, but for me I would normally open several browser tabs to accomplish the tabbed bit.  The browser tab method can be painful, and if I have too many open, it becomes hard to find things.  So, this is a definite improvement.

 

 

 

2. Code Search

 

Here is the ONE thing I wish was available for Globally scoped applications as well!  You can search just your project, or you can search all of the code-base on your instance!  The function returns the results as a tab, and you can open any of the code found in the search to look it over.  NICE feature!

 

 

 

3. Quick Creation of Most Scripts and Files

 

The Create Application File button opens up a search form that allows you to pick from what can be created in the scoped world.  Simply navigate to the object you want to create, or begin typing it in the search text box.  Once you choose the object it is automatically generated in the Application Namespace, and opened as a tab in the IDE.  Once saved you will then see it appear in the left navigation bar.

 

 

 

4. Push to Repository - External Source Control

 

Another really great feature of the IDE.  You can link your project to an external repository like GitLab, GitHub, or BitBucket (to name a few).  Then either manually push your project updates, or they will be automatically pushed when you publish to the local Application Repository.  You can do branching of your project if you have to have a separate set of code (for example: maintenance mods).  In moving from branch-to-branch it will take a bit as the old application is removed and the new one is installed (slow), but still a nice feature.

 

 

 

5. Push to Internal Application Repository – Deployment

 

In a recent article (Community Code Snippets: Scoped Application Distribution) I talked about the various ways to deploy applications.  Well this is how and where it can be done in the IDE (File -> Publish).  The Company, Vendor Prefix, and App Name are all filled in for you (you cannot change the Vendor Prefix - this is a ServiceNow only editable field).  You will need to make sure that the Version number is one that has not yet been used, or when you click on the submit button it will detect the fact and kick it back to you.  It is considered a Best Practice to fill in the Dev Notes field.  Something describing what is in the release is usually preferable.

 

 

 

Outside of or Missing From the Studio IDE

 

So, what do I look at as needing improvement, or needing to be added to the IDE?  What follows are my observations.

 

 

1. For form Construction only the Form Designer is available (no Form Layout)

 

The Form Designer has a problem that keeps me from using it very often:  It does not automatically name new fields from the Label!  It can be great when moving fields around or creating/modifying sections.  When creating new fields I have to remember to go and rename a field or it will get saved with that automatically generated name that has nothing to do with the real function of the column.  Then I have to go drop the field and re-add it.  Ugh.

 

 

 

However, I am a big fan of the Form Layout tool because it automatically names a new field from the label. This is a nice time, and annoyance saver! 

 

Inside the Studio IDE I will not create fields with the Form Designer, but if I have to tweak where things go it is useful.  I will will create new fields in the Table form (where it DOES automatically name a new field from a label).  Anyway, you get the idea. 

 

 

 

2. Previous number check not done when you open the Publish form in the IDE

 

So, not sure why this was not included as a feature inside the IDE.  It works fine from outside via the System Application -> Applications properties form (the "Make App available..." link).  This is basically an inconsistency in the interface.  If you get it wrong and try to re-use a number you will only find out about it after you attempt to publish; the IDE will throw up an error, and ask you to change the version number before continuing.

 

From inside the IDE:

 

 

From the Properties Page:

 

 

 

Which does this; which is quite a bit friendlier:

 

 

 

3. Can only push to an Update Set via the Application Properties Form

 

So while I can't do this from inside the IDE, I can do it outside through my Scoped Application's properties page.  You can only push the project to an update set if you navigate to System Applications -> Applications and go into your Application's property page.  I would like to have this in the Studio.  I do a lot of deployment to not-my-company instances, and it would be handier from inside the IDE.

 

 

 

 

4. Testing of any part of the application

 

In order to test your application you will need to jump out of the Studio IDE.  All testing has to be done on the instance itself.  It would be nice to be able to trigger a test of a form or table modification from inside the IDE!

 

 

5. Cannot protect script Intellectual Property

 

In my article (Community Code Snippets: Scoped Application Distribution) I describe this particular issue.  You can only protect your code from prying eyes if you push it out to your local Application Repository or the ServiceNow Store.  Otherwise, you cannot protect your hard-earned, hard-to-write code.  It is only possible to deploy to other not-your-company instances via the update set mechanism, and this does not contain any IP protection whatsoever.

 

 

6. Cannot add Globally scoped files to your project

 

So basically anything Globally scoped is anathema in the Studio IDE.  You can't add Globally scoped files to your application, but you can add them as data (see #11 below).  This is a real problem especially if you have to create Globally scoped scripts to handle things that scoping will not (such as Package calls - see #8 below).

 

 

7. Cannot have a Globally scoped Studio application

 

I would really, REALLY like to have the ability to organize my Globally Scoped applications in the same manner is my Scoped applications!  I consider the Studio to be a great tool, but it would be even better if it allowed me this functionality!  Perhaps do this, but don't allow me to deploy to the ServiceNow store?  I would like to toss the Update Set process altogether, and this would allow me to do that!  So now, as a developer, I live between two development worlds: The old Global (which is being called legacy out on the development community), and the new: scoped.  I am seeing the vast majority of new development being done in Global still, so I think it is a bit premature to call this legacy.

 

 

8. Scoped applications do not accept any of the Java libraries!

 

I would like to have a list of what is not available inside of Scoped applications.  Instead I am compiling one as I go along.  Nothing about it in the online docs (other than gs.log and Package calls not being allowed).  Anyway, nothing with "Package" on it is available.  There goes Packages.Java.Lang, one of the most useful libraries we have on the platform!  There are several things that do work, but they are mostly from the Glide side of things.

 

 

9. Scoped applications do not accept many of the Global ServiceNow Libraries (JSUtil, J2JS)!

 

Well, mostly because of #8, we lose several useful JavaScript built-in Global libraries.  I have ended up dissecting JSUtil and extracting code into a Scoped Application Library of my own to get back this much used functionality.  I was able to mostly (sans Packages.Java.Lang.String) get back the JSUtil.nil() and .notNil() functions which are heavily used by the ServiceNow Global base code, and which I find are much more reliable than good ol' gs.nil().  Just saying.

 

 

10. Cannot Merge Source Control Branches

 

While I can create branches of my application in my external repository (i.e. GitLab); I cannot merge them.  BTW, it doesn't work to go merge them in the repository itself then download the "new" merged copy back to your instance via the Studio.  ServiceNow places the various components of your project out in the repository as a series of xml files, and merging these does not merge the actual differences.  The merge really would have to happen back in the Studio.  This bit of functionality would round out most of what needs doing with Source Control.

 

 

BTW, for really great reading on this topic, Microsoft has several patterns on branching and merging that do a good job explaining why both are necessary to development:

 

Chapter 5 – Defining Your Branching and Merging Strategy

Branching and Merging Primer

 

 

 

11. Adding data to the project

 

This is a cool, but little known feature of the External portion of the Studio.  It is not available from the IDE as you have have the data listed (which you can't do in the tool).  You have the ability to go to a list view and add records as data to your project.  Simply right-click on any List View header (in List v2), or in the tri-lipse (hamburger) button in List v3, and choose the Create Application Files option.   This will then ask you how you want to add the listed data to your Scoped Project.  BTW, this is the trick for adding Global Scripts to your deployment package.

 

 

 

And that is all I have on this topic for the moment!  The Studio IDE is a great tool, and I would sure like to see more integration!  :-)

 

Steven Bell

 

Combined Logo Graphic_Operations.png

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of Scoped Scripting and the Studio IDE in ServiceNow.

____________________________________________________________________________

 

When training Application Creation I get a few questions concerning distribution of an application:

 

  • How does the Studio application distribution work?  In the class we cover the internal distribution using the Application Repository.
  • What are the other options, if any?

 

In this article I will be describing what is available to you via the Studio.

 

You might want to read the following article if you are unfamiliar with publishing an application: Application sharing.

 

 

Internal Distribution - The Application Repository

 

Built-into the Studio is the ability to publish your scoped applications to your own instances.  This is done through a built-in Application (App) Repository.  This repository is provided as part of your ServiceNow base license.  It is there even if you don't use it.

 

This App Repository provides a central distribution mechanism for your other instance(s) to download a new scoped Studio application, or an application update.  Furthermore, if you have protected your code using the protection policy; i.e. your intellectual property (IP), then this option can be switched on for the instances that install the application from that repository.

 

When you publish your application it essentially moves an update set with all data and files for the project to the App Repository.  Then when you install on another of your company's instances it simply pulls from the repository and installs it on that instance.

 

This is also the mechanism that can be used by Team Development for pushing development changes to the other instances.

 

 

 

Direct From Repository to External Instance

 

So what if you wanted to you use this mechanism with another company's instance?  Simply put: you can't do this from the Studio or your App Repository.  Your company's repository is not visible to any other external instance.  So this particular model is not available.  BTW, as a Partner Product Developer, this is one option I would love to see added!

 

 

Distribution to the ServiceNow Store

 

ServiceNow provides another distribution mechanism called the ServiceNow Store.  This is really intended for the purposes of selling your application to the community at-large.  All IP protection is maintained, and your code is vetted by ServiceNow prior to making it available.  Several great options for potential customers are available by the provider: Get (free), Buy, Request Trial, Try, Contact Seller, and View Profile.  Any updates made to your software, after vetting, are immediately available to your customer base for download.  Pre-requisite:  You have to be a member of the ServiceNow Technology Partner program (PartnerNow | Sales, Services, Technology Partners | ServiceNow).

 

 

Distribution to External Instances

 

You can publish your application to an update set.  This is actually a pretty slick option as the Studio does all the work for you.  You cannot publish to an update set from inside the IDE, but you can from the Application Properties under System Applications -> Applications.  However, when using this option, all of your IP protection is stripped away, and the distribution will actually re-install the development environment to any instance you put it on.  Just like a normal update set (which it is).  You will need to port the update set using the old built-in transfer mechanism we all know and love, or export the update set to an XML file and do the deed manually.  BTW, no auto-alert of updates with this option either; so you will need to handle that as an export to update set as well.

 

 

 

External Repository Distribution

 

You can also distribute your application via your off-instance external repository.  Again, like the update set distribution, all of your IP protection is stripped away, and it basically installs the development environment on the new instance.  This, btw, is also an excellent method of restoring your application development environment from scratch should it get wiped out by a clone or z-boot (been-there done-that; restore worked great!).

 

 

Personal Developer Instance

 

Also, as a side-note, if you are wanting to play with the App Repository on your Personal Developer Instance, you can't.  All publication with the Personal Developer Instance is switched off.  This includes IP protection.  It would require two instances to do this internally and since you are assigned your own unique company code for each instance - you are only allowed one instance right?.  Also, publication to the ServiceNow Store is inactivated.  Not sure about the reason for that other than you have to be a Technology Partner, and I believe you have to have your own instance assigned to you by ServiceNow, via that program, to do a publication to the Store.  You still have the option of publishing to an update set though.

 

 

 

There you have it!  All the methods, that I am aware of, for distributing a Scoped Application via the Studio.

 

Steven Bell

 

Combined Logo Graphic_Operations.png

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of Server-Side Scripting in ServiceNow.

____________________________________________________________________________

 

Recently I have been doing a lot of work with Scoped Applications, and found myself, once-again, wishing for in the System Log, a display of the record create time with milliseconds!  The current display has always been in seconds, and has always been inadequate for determining what was run and when.  Since the platform is so fast for most of what it does (a good thing) it has a tendency to log things down quick as well.  So debug code, etc. will often be displayed all at once within the same second (a BAD thing); which means that the log messages will often arrange themselves out of order (the reason it is a BAD thing).  I have connived, I have whined, I have blustered, I have tried various methods (crying and groveling didn't work either).  To get it included, but to no avail!

 

So not being one to sit still very long on a problem (it has been since the beginning 2012 that I started making noises about this), I decided to do something about it!  For the longest time I utilized a method I have written about before: Community Code Snippets - System Log Ordering Problem Workaround.  That worked fine, but wasn't so great if I had to log call-outs, and the method I was calling contained logging as well.  I would, likely, still have an ordering issue!

 

After noodling on it; I came up with a reasonable way of logging milliseconds in my log messages.  This new method allows me to see when things are really happening, and also gave me the ability to order my messages correctly.

 

Let's take my original example from my Log Ordering article (and do terrible things to it)!

 

Original (not allowed in a Scoped app btw):

 

gs.log('---> 1. Hi there');  
gs.log('---> 2. This is message 2!');  
gs.log('---> 3. This is message 3!');  
gs.log('---> 4. Hi there, again!'); 

 

 

Create a Fix Script and copy that code to it.  If you run that you might see something like this:

 

 

Nice.  Makes you want to cry in frustration doesn't it?  Ok, hang in there!

 

Now using gs.info and variable substitution (see: Community Code Snippets - Logging: Some Notes on Variable Substitution) change the gs.log statements to gs.info (which are Scope safe).  Then add in a location variable (cool tip: you can use the type variable in a Script Include - this.type), and integrate that with our gs.info statements.  Thusly:

 

var location = 'FS: Millisecond logging'; // FS = Fix Script

gs.info('---> [{0}] 1. Hi there', [location]);  
gs.info('---> [{0}] 2. This is message 2!', [location]);  
gs.info('---> [{0}] 3. This is message 3!', [location]);  
gs.info('---> [{0}] 4. Hi there, again!', [location]);  

 

Next let's add in the milliseconds indicator so that we have something to find and sort against:

 

var location = 'FS: Millisecond logging'; // FS = Fix Script

gs.info('---> [{0}-{1}] 1. Hi there', [new GlideDateTime().getNumericValue(), location]);  
gs.info('---> [{0}-{1}] 2. This is message 2!', [new GlideDateTime().getNumericValue(), location]);    
gs.info('---> [{0}-{1}] 3. This is message 3!', [new GlideDateTime().getNumericValue(), location]);  
gs.info('---> [{0}-{1}] 4. Hi there, again!', [new GlideDateTime().getNumericValue(), location]);   

 

Remember the gs.info statement get's finicky inside of a Scoped Application, and thus the reason for the object array inside of the gs.info parameters.  It is best to get into the habit of just using the brackets anyway so that your code is always scope-safe.

 

Okay, if we run this from our Fix Script what does it look like?  Ugly, but you can see the milliseconds in the message now.  Note that the Created column is next to useless here.  Also, Source is useless for debugging when migrating away from gs.log.

 

 

So let's add a filter for ---> [149 and sort by Message descending.  Whoa!  Magic!  Now they are in order!

 

 

However, the code is ugly.  We can clean this up a bit like this (notice any patterns dropping out of the code?):

 

var location = 'FS: Millisecond logging'; // FS = Fix Script

gs.info('---> [{0}-{1}]\n1. Hi there', 
  [new GlideDateTime().getNumericValue(), location]);  
gs.info('---> [{0}-{1}]\n2. This is message 2!', 
  [new GlideDateTime().getNumericValue(), location]);    
gs.info('---> [{0}-{1}]\n3. This is message 3!', 
  [new GlideDateTime().getNumericValue(), location]);  
gs.info('---> [{0}-{1}]\n4. Hi there, again!', 
  [new GlideDateTime().getNumericValue(), location]); 

 

Which will give us this:

 

 

Better, but still the code looks a little cluttered.  Also, we have a pattern!  That means at the very minimum: function!  Since the parameters are listed as an array we can use a combination of message, parameter values, and location and feed those into a function which will then handle the rest.  Something like this:

 

var location = 'FS: Millisecond logging'; // FS = Fix Script

logInfo('1. Hi there: {0}', location, ['Steve']); 
logInfo('2. This is message 2!', location);    
logInfo('3. This is message 3!', location);  
logInfo('4. Hi there, again {0}', location, ['Steve']);

function logInfo(message, location, parms) {
  message = '---> [' + new GlideDateTime().getNumericValue() + '-' + location + ']\n' + message;
  gs.info(message, parms);
}

 

And that would give you exactly the same thing as earlier, but the log code is much cleaner (i.e. easier to maintain).

 

 

Obviously you can do all sorts of things to rearrange the function how you please.  My purpose was to give you an idea of how to do this sort of thing.  You could always put the function into a Script Include (and it is scope safe) as part of a function library.  You could also rig this up to handle errors and warnings as well.

 

Regardless of how you go about it you can see the value of having milliseconds in the log.  Maybe someday we will get this as part of the Created and Updated columns...maybe. Until then this your best bet.  :-)

 

Steven Bell

 

Combined Logo Graphic_Operations.png

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of the Studio IDE in ServiceNow.

____________________________________________________________________________

 

A short time ago I had need of changing my repository URL for one of my projects in the Studio IDE, and much to my surprise found that I was not allowed to do it!  The URL field was read-only!?!  Off to the ServiceNow documentation site I went.  However, no solution was published there.  I promptly went to the Community to find out if this had been mentioned by anyone else.  Sure enough, the question was asked, and ctomasi was able to get an answer (How do you edit/remove a source control repository?).  But the answer there bothered me.  1) it was a work-around, 2) you lost all historical information with the work-around.  After my third project re-point, using this method, I began looking around for the best way to hack it (I was that unhappy).  I began investigating all the pieces I could find concerning where the URL might be located, and what I would have to write in the way of a Fix Script to do the actual hack. 

 

Well, sometimes the complex way is NOT the solution! 

 

While messing around looking at tables and their interactions I tripped across a method for doing this that looks like it was intentional; sic. out-of-the-box.  It was too easy.  This method does not hang onto the Source Control Branch or Source Control Tags (a minor loss to me), but just so you are warned. 

 

This article explains how to change the URL using that method.

 

So WHY would you want to change the URL in the first place?  Here are some use-cases I can think of just off the top of my head:

 

  • Changing from a personal repository to a corporate repository.  My original reason for changing the URL.
  • Reorganization of the repository's directory structure requires changing of the Studio url.  This is probably the most important.
  • Unbinding source control from the Studio project.
  • Changing the repository provider.  Let's say from GitLab to GitHub or the reverse.

 

And I am sure there are more.

 

 

BEST PRACTICE: First make a backup of your entire project to an Update Set

 

1. Navigate to System Applications -> Applications. The Applications list view will be displayed.

 

2. Click on the link to your Studio Application. This will display the Custom Application properties form.

 

 

3. Change the Current Application setting to be the application you are working with. This will activate the Related Links on the form.

 

 

4. Scroll down the form to view the related links and click on the Publish to Update Set… link. The Publish to Update Set

 

 

5. Fill in the form:

 

Version: <<keep what was displayed>>

Description: Repoint to new repository.

Click on the Publish button.

 

 

6. Wait for the Progress bar to complete and click the Done button. The new update set will be displayed.

 

7. Under Related Links click the Export to XML button. This will save a copy of the entire project to your local disk.  You might want to rename this with your Application name and version just to be safe.

 

 

To Clear out the Repository URL For an Application

 

Now, on to the good stuff!

 

1. Navigate to sys_repo_branch.list. The Source Control Branches list view will be displayed.

 

2. Find the Application you wish to reset the repository URL for, and open that record. The Source Control Branch form will be displayed.

 

 

3. Click on the Repository Configuration link button. The Repository Configuration Record will be displayed.

 

 

4. Click on the Delete button in the upper right corner of the form. The Confirmation form will appear.

 

 

5. Click on the delete button.

 

 

6. A “Record Not Found” message will be displayed.  Ignore this, and go back to the Studio to open your application.

 

 

7. This clears out the repository URL and information for the Application. You may now use the normal linking of a repository procedure inside of Studio; to point at your new URL.

 

And there you go!  An out-of-the-box method for clearing and/or changing the repository URL for ServiceNow Studio.  Maybe in a future release we will get a menu-item or editable URL field inside the Studio to do this, but until then this is the best way.


Steven Bell

 

Combined Logo Graphic_Operations.png

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of Client and Server-Side Scripting in ServiceNow.

____________________________________________________________________________

 

In my previous article (Mini-Lab: Converting a UI Script Library From Global to Scoped - Part 1) I described the whys and wherefores of using the Scoped environment and the Studio versus the old Global environment.  In this article I will present how to convert my old global UI Script function library into a Scoped UI Script library.

 

Pre-requisite:

 

The old UI Script library: Mini-Lab: Using Ajax and JSON to Send Objects to the Server

 

The purpose of this UI Script is to provide a generic AJAX interface between client side (sic. browser) code and server-side functions.

 

 

Lab 1.1: Using JSON and AJAX to Transmit a Complex Object to the Server

 

Based on the design from my Using Ajax and JSON article; we re-work it slightly to indicate the scoped components.

 

 

We will be working the application from the bottom-up.  Building from the Script Include function class to finally the UI Action that will be calling the whole chain.  This entire lab was written on my personal developer instance that has been upgraded to Istanbul Patch 1.

 

 

The Scoped Application

 

 

The Studio, and the UI Script

 

So, let's get started.  First we will need to create a scoped application.

 

1. Navigate to System Applications -> Studio.  The Load Application form will be displayed.

 

 

2. Click on the Create Application Button.  The Create Application choice form will be displayed.

3. We will be using the Start from scratch option.  Click on the Create button.  The Create Application property form will be displayed.

 

 

4. Fill in the form with the following:

 

Name: Using Ajax and JSON to Send Objects to the Server

Scope: x_1234_using_ajax

 

Note:  The Scope field will auto-fill using your "company code".  This will be different than the 1234 in my example above.

 

 

5. Click on the Create button.  The Studio form will be displayed.

 

6. Click on the Create Application File button.  The Create Application File choice form will be displayed.

 

 

7. Enter Script Include in the search filter text box.  The Filter Results will show Script Include.

 

8. Click on the Create button.  The new Script Include form will be displayed.

 

 

9. Fill in the form with the following:

 

Name: JavascriptUtils

Accessable from: All application scopes

Active: checked

Description: Various useful utilities. Note: These were adapted from the JSUtil library

Protection Policy: -- None -- (automatically set for personal instances)

Script:

 

var JavascriptUtils = Class.create();

// This nil check works for Scoped applications
JavascriptUtils.nil = function(item) {
  var nilCheck = true;
  try {
    nilCheck = !item 
      || (item == null) 
      || (typeof item == 'undefined') 
      || ('' == '' + item) 
      || (item == 'undefined');
  }
  catch(err) {
    gs.error('---> [{1}-{2}] \n{0}', 
      [err, new GlideDateTime().getNumericValue()], 'SI:' + this.type + '.nil');
  }

  return nilCheck;
};

JavascriptUtils.notNil = function(item) {
  return !this.nil(item);
};


 

 

10. Click the submit button to save your work.

 

 

Note:  This is our "bottom-most" library.  We will be using this in our other Script Includes.  Since JSUtil does not work in the scoped environment (it contains a Packages.Java.String call), we needed to have an analog that would work.  This is a good, not best, but good workaround.

 

11. Now repeat this step and create another Script Include.

 

12. Fill out the form with the following:

 

Name: ArrayHandlingUtils

Accessable from: All application scopes

Active: checked

Description:

Various array utilities

 

Contains:

getEmailsFromSysIDs - retrieve all user records in the list of sys_ids

 

Protection Policy: -- None -- (automatically set for personal instances)

Script:

 

var JSUtil = JavascriptUtils;

var ArrayHandlingUtils = Class.create();

ArrayHandlingUtils.prototype = {
    initialize: function() {},
        
    //retrieve all user records in the list of sys_ids
    getEmailsFromSysIDs : function(listInfo) {
        
    try {
    
        var sysIDList = listInfo.list;
        gs.info('---> [{1}-{2}] \n{0}', 
            [sysIDList.length, 
             new GlideDateTime().getNumericValue(), 
             this.type + '.getEmailsFromSysIDs']);

        var userRecords = new GlideRecord('sys_user');
        userRecords.addQuery('sys_id', 'IN', sysIDList);
        userRecords.query();

        var userList = [];

        while (userRecords.next()) {
            var user = {};
            user.name = userRecords.name + '';
            var email = userRecords.email + '';
            user.email = JSUtil.notNil(email) ? email : 'no email found';

            userList.push(user);
        }
    
        var message = '';
        message += 'Location: ' + listInfo.location + '\n';

        for (var i=0; i < userList.length; i++) {
            message += 'EMail[' + i + ']: '
                + userList[i].name + ' - '
                + userList[i].email + '\n';
        }

        gs.info('---> [{1}-{2}] \n{0}', 
            [message, 
             new GlideDateTime().getNumericValue(), 
             this.type + '.getEmailsFromSysIDs']);
        }
        catch(err) {
            gs.error('---> [{1}-{2}] \n{0}', 
                [err, 
                  new GlideDateTime().getNumericValue(), 
                  this.type + '.getEmailsFromSysIDs']);
        }
    },
    
    type: 'ArrayHandlingUtils'
};

 

Note: We do not have to use a qualified call to the JavascriptUtils function to include it since it is already in our namespace.  No need to do gs.include() either. 

 

13. Click on the Submit button to save your work.

 

14. Ok, repeat one last time to create our Ajax Script Include that will be called from the client.

 

15. Fill out the form with the following:

 

Name: AjaxUtilsServer

Accessable from: All application scopes

Active: checked

Client callable: checked

Description: Description: Method for decoding the passed JSON object and writing it to the system log

Protection Policy: -- None -- (automatically set for personal instances)

Script:

 

 

var AjaxUtilsServer = Class.create();
AjaxUtilsServer.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
    
    pushListToSysLog: function() {
        var listInfo = global.JSON.parse(this.getParameter('sysparm_listInfo'));
        
        new ArrayHandlingUtils().getEmailsFromSysIDs(listInfo);
    },
    
    type: 'AjaxUtilsServer'
});

 

NOTE: Write down the API Name. You will be using this name to call into this Script Include from your UI Script.

 

16. Click the Submit button to save your work.

 

 

17. Click on Create Application File, and create a new UI Script.

 

18. Fill out the form with the following:

 

Script Name: AjaxUtilsClient

Description: Transmits a list of sys_ids to the server to be pushed to the System Log. 

Use Scoped Format: unchecked.

Script:

 

var utils = Class.create();

utils.sendListToSysLog = function(list, location) {
    try {
        var infoOnList = {};
        infoOnList.location = location;
        infoOnList.list = list.split(',');

        var systemLogger = new GlideAjax('x_1234_using_ajax.AjaxUtilsServer');
        systemLogger.addParam('sysparm_name','pushListToSysLog');
        systemLogger.addParam('sysparm_listInfo', JSON.stringify(infoOnList));


        systemLogger.getXML(function(result){ });  // no callback action
    }
    catch(err) {
        alert('---> ERROR: ' + err);
    }
};


 

NOTE: Replace the x_1234 in line 6 with your own ServiceNow company code.

 

NOTE: When you save the system will throw an error and tell you that the script is missing the Immediately Invoked code.  Ignore this error, and continue with the save.  The error is bogus.  It appears that the system does not want or like you to use this type of script which is nonsense.

 

19. Lastly, let's create a UI Action to drive the entire chain.  Click on Create Application File, and create a new UI Action.

NOTE: We will build this as part of our scoped application, but it does not necessarily have to be part of it.  It could be built in the Global namespace.

 

20. Fill out the form with the following:

 

Name: Transfer Watchlist

Table: Incident

Active: checked

Show Insert: checked

Show Update: checked

Client: checked

Form Button: checked

OnClick: sendWatchList()

Form Action: Checked (done automatically).

Script:

 

function sendWatchList() {
    
    try {
        // this is the only way to load a UI Script Library into a scoped Client Script!
        // loads the utils library
        ScriptLoader.getScripts('x_0462_using_ajax.AjaxUtilsClient.jsdbx', followup);
    }
    catch (err) {
        alert(err);
    }
    
    // After the UI Script Library has been loaded then continue
    function followup(result) {
        try {
          utils.sendListToSysLog(g_form.getValue('watch_list'), 'UIA:Transfer Watch List');
        }
        catch (err) {
          alert('---> ERROR: ' + err);
        }
    }
}


 

NOTE: Replace the x_1234 in line 6 with your own ServiceNow company code.  Also, notice how much this script differs from how it would be done in the Global environment.  In addition see that there is now a loader step that occurs prior to being able to use the library.  This is done via an asynchronous (async) call-back that loads the script from the server (the on-demand bit).

 

21.  Click the Submit button to save your work.  We are now done with our Scoped application, and it is time to do some testing!

 

 

 

Unit Test

 

Our unit test will be what we had in the previous article.

 

  1. First go and in-activate the original UI Action; if you did the previous article.  We will only be using the new (same named) UI Action we created in the Studio.
  2. Navigate to Incidents -> Open.  The list view of open Incidents will be displayed.
  3. Open one of the Incidents.  You should have a new button labeled: Transfer Watchlist.


  4. Make sure there are four or five people on the watchlist.  Add some if you have to.


  5. Click on the Transfer Watchlist button.  Observe that the spinner (activity indicator) in the upper right of the form activates for a second or two then goes away.  That is your only indicator that something happened.  I guess we could always go back and put in an alert that things have finished, but this is supposed be a lab that emulates something you might do for real! 



  6. Navigate to System Log -> All.  Filter on Message starts with --->.  Order by Message descending.  Note how I placed milliseconds into the gs.info messages?  This allows me to sort by true date descending.  This is REALLY important if you are trying to figure out exactly in which order things occurred in your code!
  7. Expected Result:  A list of names and emails matching the watchlist should be present as a log entry.

 

 

And there you have it!  Conversion of a Global UI Script and all of it's call chain into a Scoped application.

 

Steven Bell

Combined Logo Graphic_Operations.png

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes basic knowledge and/or familiarity of Client and Server-Side Scripting in ServiceNow.

____________________________________________________________________________

 

I had already done this particular subject as an Ask-the-Expert session, but thought I would write it up for easier pondering and implementation. 

 

With Part 1 I will be describing the thought processes that are necessary in understanding the "reasons-why" this particular topic is important.  In Part 2 I will present the actual labs for converting the UI Script described in my article Mini-Lab: Using Ajax and JSON to Send Objects to the Server from the Global namespace to it's own Scoped namespace.

 

Suggested pre-reading:

 

Pragmatic Patterns: Ajax - Architecting Your Code

Mini-Lab: Using Ajax and JSON to Send Objects to the Server

Mini-Lab: Writing Entries Into the System Log Using Ajax and JSON

 

A couple of Ajax best-practice mentions here:

 

1) Keep Synchronous calls from the client to the server at an absolute minimum.  This is to limit the impact to the user experience.

2) Synchronous calls are okay if you are doing some type of data retrieval with an onSubmit Client Script.  This is pretty much it, though as far as I'm concerned.  Really, though with something like this I would consider converting it into a Before Business Rule, or perhaps pre-loading the data with a Display Business Rule keeping it on the Server in both cases.

 

 

Common Practice

 

So let me describe a common practice with Client Scripts.  I have already gone into some detail on this with my pattern article on Ajax, but I will repeat a bit of it here.

 

Basically, most developers write their Ajax Client Scripts as a simple very specific one-to-one call from the browser to the server, to handle, and perhaps retrieve data.  This isn't bad, but it isn't very extensible.

 

 

You have no normal ability to share a client script between different non-inherited tables, and thus between forms.  So even if you did have some reusable code you would have to at the least create two different Client Scripts, and maintain them, even though the code was the same.  This is a bad practice.  You want to consolidate these into a single reusable client-side library that is loaded and available from the Form. 

 

The same problem also presents itself from the server-side.  Here an Ajax Script Include is pretty specific in what it is for, and really does not present itself well as a function library.

 

 

A Methodology Shift

 

So, the solution to the problem of a client-side function library has always been the UI Script mechanism.  Here, ServiceNow has provided us a way to create reusable code libraries that are loaded with a form.  This allows the developer to concentrate often reused functions into a single maintainable location (a serious best-practice).  This keeps the maintenance to a single location, and reduces development time-to-completion with reusable code.  The down-side: UI Script libraries are always loaded with the form.  So you get them whether you use them or not.  These are usually quite small in the amount of client-side memory they use, and the load-time involved; so it is generally not an issue.

 

On the server-side we can push reusable code down a layer by creating a Script Include Function Library or a Script Include Class (yep, there are two flavors).  Either approach is fine; the idea being to centralize reusable code, and reduce maintenance and time-to-completion.

 

In all cases an attempt should be made to put the calculation and database heavy-lifting on the shoulders of the server, and keep the client-side script to a minimum (a.k.a. thin).

 

 

 

The Scoped Environment

 

With the advent of Scoped Scripting in the Fuji release we now have a way of isolating our code from the rest of the ServiceNow environment.  This brings along several benefits:

 

1) The developer can now create code that is easy to install, and remove with minimal affect on the ServiceNow platform.

2) The application can be protected (i.e. black-boxed) in such a way as to keep it from inter-mixing with the Global code-base.  An important concept of name-spaces.

3) The intellectual property can, in-theory, be protected.  In a future article I will show that this is limited, or frankly not the case in some release instances.

4) A way to organize an application project, and easily access the scoped applications.

5) Deployment to a local ServiceNow Application Store that allowed for much easier inter-company instance deployment than the old update set model.

6) More functionality in the Studio (such as an improved code search!)

7) The ability to connect to an external cloud-based repository such as GitLab, GitHub, or BitBucket (to name a few).

8) Good overall core documentation, and API info in both the product documentation, and the Developer community.

 

This is obviously not an exhaustive list.

 

The biggies for me were #'s 1, 2, 4, 5, and 7.  Shoot, actually I like the whole list. :-)

 

So those are the pro's.  What are the con's?

 

1) Only a subset of the Global-side JavaScript and ServiceNow out-of-the-box (OOB) libraries are available.  There are some interesting, and sometimes frustrating omissions!

2) NONE of the Packages.<<function>> libraries are available (this is a huge deal, and for some of you may represent the reason for not implementing scoped applications - it was a close call for me).

3) Few examples of how-to do certain things in the scoped environment (thus the reason for this and future articles).

4) Inability to re-point a repository URL from the Studio.  No documentation exists on how to do this outside of the studio as well.

5) Intellectual Property protection is only available with internal distribution via the Studio Publish function, or with the ServiceNow store.  This protection switch is not available in any other way.  If you push your application to an update set and upload it to a different (sic. customer) instance your code will be visible to anyone who wishes to look at it.  For more information on this issue see my ask the expert session: Ask the Expert: Scoped Libraries with Steve Bell.

 

 

The Scoped Library

 

Because of the benefits of isolation and organization, that the Studio and Scoped environment bring, it is of great use to build function libraries with these tools.

 

An interesting, and to me a great feature when moving to a Scoped UI Script; is the on-demand nature of the library.  It is only loaded when called.  The only down-side: there was no documentation on how to do this.  The Global "way" of implementing UI Scripts absolutely fails in the scoped environment! 

 

Doing a search on the community popped up with the clues I needed on how to do the conversion. coryseering answered a question back in 2015 that actually provided what was needed to make this work (Re: Unable to create UI Action that calls UI Script in Fuji Scoped Application ).  You might want to also check out his other great articles: Scoped Applications and Client Scripts: A Primer, and Client-side GlideRecord replacement for Scoped Applications (sort of).

 

So, the ScriptLoader function is the key.  It is the mechanism (undocumented) to load a Scoped UI Script library, and it does it on demand!

 

Then the only change to the server-side is to convert any gs.log messages, which do not work in a Scoped app, into the gs.info/gs.warn/gs.error analog.  The scoped class/library calling turns out to be pretty straight-forward on the server-side.

 

 

 

In my next article (Mini-Lab: Converting a UI Script Library From Global to Scoped - Part 2) I will be describing how to take a Global UI Script library and turn it into a Scoped UI Script library that can be used by both Global and Scoped Client-side scripts.

 

Steven Bell

Combined Logo Graphic_Operations.png

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  ADVANCED

Assumes very good knowledge and/or familiarity of scripting, and user interfaces in ServiceNow.

____________________________________________________________________________

 

The other day I was working on a gnarly UI problem with lisse (Calisse Voltz), and we found that we needed to add a Slush Bucket control to a GlideDialog form.  We went looking in the community, and on the web for information on how to best accomplish this task, and what did we find?  Nuttin - technical term meaning null.  Well, a GlideDialog executes a UI Page, and there are a few UI Page examples in the Out-Of-The-Box (OOB) ServiceNow platform, and one of them should contain a Slush Bucket, right?  Well, one did!  With that we were then able to figure it out.  I thought I would share.

 

So, what is needed?

 

Design:

 

  1. Create a simple UI Page that contains a Slush Bucket control.
  2. Create a Client Script on that UI Page that populates the Slush Bucket.
  3. Create a Ajax Script Include to provide the data for the Slush Bucket - for demo purposes.
  4. Create a UI Script that pulls the picked data from the Slush Bucket and allows us to use it.
  5. Create a UI Action to fire the UI Page as a GlideDialog - for demo purposes.



Lab 1.1: Create the Ajax Script Include

 

This will be the driver that fills our Slush Bucket with test data.  Of course this would change should you want to really do something special with the example, but I put this here to give you an idea of how it can be done.

 

1. Navigate to System Definitions -> Script Includes.  The Script Includes list view will be displayed.

2. Click on the New button to create a new Script Include.  The new Script Include form will be displayed.

3. Fill out the form with the following:

a. Name: GetIncidentsAjax

b. Client callable: checked

c. Active: checked

d. Accessible from:  All application scopes

e. Description: Driver to fill GlideDialog Slush Bucket with test data

f. Script:

 

var GetIncidentsAjax = Class.create();
GetIncidentsAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {

  getIncidentList : function() {
    var incidentList = [];
   
    var incidentRecords = new GlideRecord('incident');
    incidentRecords.setLimit(10);
    incidentRecords.orderByDesc('number');
    incidentRecords.query();
   
    while (incidentRecords.next()) {
      var incident = {};
      incident.number = incidentRecords.getValue('number');
      incident.sys_id = incidentRecords.getValue('sys_id');
      incidentList.push(incident);
    }
   
    return new JSON().encode(incidentList);
   
  },

    type: 'GetIncidentsAjax'
});

 

g. Submit to save your work

 

 

 

Lab 1.2: Create the UI Page and Client Script

 

Next we want to create the actual UI Page that will act as our GlideDialog.  This will contain the Slush Bucket control, a couple of buttons (Cancel, Submit), and our Client Script to fill the Slush Bucket on load of the form.  You will note, in the example below, that the Slush Bucket is a single inclusion line, and contains only a name.  I am also using some of the fancier button definitions I have found in the OOB code.  For those of you who didn't know: If you use gs.getMessage you can localize the example.  You need to do a requires tag to link in the UI Script library, and make it available to the page.  Read the comments in the code as I explain a lot of what's happening there.

 

 

NOTE: Yeah, using Jelly as that is still the default when creating a new UI Page in Helsinki.  I will let b-rad (Brad Tilton) set this up in Angular, or may do it myself later.

 

1. Navigate to System UI -> UI Scripts.  The UI Scripts list view will be displayed.

2. Click on the New button to create a new UI Script.  The new UI Script form will be displayed.

3. Fill out the form with the following:

a. Name: SlushBucketDialog

b. Category: General

c. Description: UI Page for GlideDialog Slush Bucket example

d. Direct: unchecked

e. HTML:

 

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
  <form id="bucketStuff">
    <g:requires name="SlushBucketProcessor.jsdbx"/>
    <div id="page">
      <table width="100%">
        <tr>
          <td>
            <g:ui_slushbucket name="slushIncidents" />
          </td>
        </tr>
        <tr>
          <td style="text-align:center" class="pull-center">
            <button type="cancel" class="btn btn-default action_contex" id="cancel_dialog" name="cancel_dialog" >
              ${gs.getMessage('Cancel')}
            </button>
            $[SP]
            <button type="submit" class="btn btn-primary action_contex" id="submit_dialog" name="submit_dialog">
              ${gs.getMessage('Submit')}
            </button>
          </td>
        </tr>
      </table>
    </div>
  </form>
</j:jelly>

 

f. Client Script:

 

// JQuery function to load up our processor UI Script and initialize it with our UI Page info
// this will make all of the functionality of the UI Script available.
$j(function () {
  try {
    window.slushBucketProcessor = new SlushBucketProcessor(GlideDialogWindow.get());
  }
  catch(err){
    alert(err);
  }
});

// Called when the form loads
addLoadEvent(function() {
  // clear the slush bucket
  slushIncidents.clear();
  
  // show loading dialog until we get a response from our ajax call to retrieve a list of incidents
  showLoadingDialog();

  // load the slushbucket with test data
  var incidentAjax = new GlideAjax('GetIncidentsAjax');
  incidentAjax.addParam('sysparm_name', 'getIncidentList');
  incidentAjax.getXML(incidentResults);
  
  function incidentResults(response){
      hideLoadingDialog();

    var incidentList = JSON.parse(response.responseXML.documentElement.getAttribute("answer"));

    // Fill the Slush Bucket control with the test data
    // Note that the value is the sys_id, and the "label" is the number
    for (var i=0; i < incidentList.length; i++) {
      slushIncidents.addLeftChoice(incidentList[i].sys_id, incidentList[i].number);
    }
  }
});

 

 

g. Submit to save your work

 

 

 

Lab 1.3: Create the UI Script Processor

 

We need to create a processor script to add functionality to our UI Page.  When called from the UI Page this script will be merged into the page functionality.  BTW, this is a good practice to keep your UI Page Client Scripts "thin", and allow for possible re-use of code.  UI Scripts are the Client-Side library containers.  If you keep it generic enough you can create some nice libraries for later use in other projects.  Don't forget to read my comments in the code.

 

1. Navigate to System UI -> UI Scripts.  The UI Scripts list view will be displayed.

2. Click on the New button to create a new UI Script.  This will display the new UI Script form.

3. Fill in the form with the following:

a. Name: SlushBucketProcessor

b. Global: unchecked

c. Active: checked

d. Description: UI Page that will act as the GlideDialog.  This will contain the Slush Bucket control, a couple of buttons (Cancel, Submit), and our Client Script to fill the Slush Bucket on load of the form.

e. Script:

 

var SlushBucketProcessor = function(dialogWindow) {
  var $btnCancelDialog = null;
  var $frmSubmitDialog = null;
  
  //Initialize the window
  _init();
  
  function _init() {
    _initForm();
    _initEventHandlers();
  }
  
  function _initEventHandlers() {
    // cancel button event
    $btnCancelDialog.click(function(event) {
      submitCancel(event);
    });
    
    // form submit event
    $frmSubmitDialog.submit(function(event) {
      onFormSubmit(event, this);
    });
  }
  
  // set up the form submit, and the cancel action
  // JQuery to pull in the form objects by id
  function _initForm() {
    $btnCancelDialog = $j("#cancel_dialog");
    $frmSubmitDialog = $j("#bucketStuff");
  }
  
  function submitCancel(event) {
    event.preventDefault();
    
    // tear down the dialog
    dialogWindow.destroy();
    return false;
  }
  
  function onFormSubmit(event, form) {
    event.preventDefault();

    var slushIncidents = [];
    
    // extract the results from the slush bucket control
    var slushIncidents_right = form.slushIncidents_right;
    
    // how many did we end up with?
    alert(slushIncidents_right.length);
    
    // do something with the results.  The name is the innerHTML, the sys_id is the value
    for (var i=0; i < slushIncidents_right.length; i++) {
      slushIncidents.push(slushIncidents_right[i].innerHTML + '-' + slushIncidents_right[i].value);
    }
    
    // display our array
    alert(JSON.stringify(slushIncidents));
    
    // tear down the dialog
    dialogWindow.destroy();
    return true;
  }
};

 

 

f. Submit to save your work

 

 

 

Lab 1.4: Create the UI Action to Fire the Dialog

 

Finally we need to create some sort of action to actually test our new GlideDialog UI Page with Slush Bucket!  I chose to create a UI Action attached to the Incident form.  It really doesn't do anything with the form, but gives the impression it does!

 

NOTE: I use another good OOB technique here by using a class to fire off the GlideDialog.  This allows me to use the "this" construct, and other functionality that is available to a class that wouldn't be available to normal function.

 

a. Name: Dialog Slushbucket

b. Table: Incident [incident]

c. Order: 100

d. Action name: dialog_slushbucket

e. Active: checked

f. Show insert: checked

g. Show update: checked

h. Client: checked

i. Form button: checked:

 

Everthing else unchecked

 

j. Comments: Form button to test the functionality of the Slush Bucket GlideDialog UI Page

k. Onclick: (new slushBucketHandler(g_form)).showDialog();

l. Script:

 

var slushBucketHandler = Class.create({
  showDialog: function() {
    // fire off the dialog using our UI Script name
    this.dialog = new GlideDialogWindow("SlushBucketDialog");
    this.dialog.setTitle('Slushbucket Dialog');
    this.dialog.setWidth(400);
    this.dialog.render(); // Open the dialog box
  }
});



m. Submit to save your work

 

 

 

Lab 1.5: Testing the GlideDialog

 

We have arrived!  All the bits are done.  Now let's test our dialog page!

 

1. Navigate to Incident -> All.  The Incident list view will be displayed.

2. Open up any incident.  Note that the Dialog Slushbucket button appears on the form.

 

3. Click the button.  The dialog should be displayed, and the slush bucket control will populate.  It may take a moment, and you might actually see the LoadingDialog box appear briefly.

 

 

4. Pick as many incident numbers as you want, and transfer them to the right side of the control.

 

 

5. Click the Submit button.  1) an alert will appear showing how many incident numbers you had selected.  2) a second alert will appear showing a compressed view of the array of your incident number-sys_id combos.  Click on the ok button to close each respectively.

 

 

6. Click ok, on the last alert box, and the dialog should disappear.

 

And you are done!  You might want to test out the cancel button to check that functionality as well (unit test everything).

 

Some extra reading:

https://learn.jquery.com/using-jquery-core/avoid-conflicts-other-libraries/

UI Pages - ServiceNow Wiki

UI Scripts - ServiceNow Wiki

Displaying a Custom Dialog - ServiceNow Wiki

Slushbucket - ServiceNow Wiki

GlideAjax - ServiceNow Wiki

 

Steven Bell

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!



NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes some knowledge and/or familiarity of scripting, and REST web services in ServiceNow

____________________________________________________________________________

 

Recently I had to create a REST Web Message to retrieve information from a server.  It occurred to me that there really had not been many examples written on this for ServiceNow.  At least from the code perspective.  So I thought I would share.  This really isn’t that complicated, and you should be able to glean what you need from the ServiceNow wiki, but I thought I would present a working example to play with anyway.

 

In this example we will be using a test REST web service site called: ‘cat-facts’.  This will return a success string (true/false), and from 1 to any number we specify; of facts about cats.

 

The idea here is to retrieve data from a REST Web Service and then parse the results into a JSON object for further use.

 

What is needed:

 

  1. Web Service end-point.  This will be:
    1. http://www.programmableweb.com/api/cat-facts?number=<<some number>>
    2. If the number is not provided it is automatically assumed to be 1.
  2. ServiceNow REST Message
  3. Fix Script to run our test code and parse the results

 


Lab 1.1: Creating the Rest Message

 

  1. Navigate to System Web Services -> Outbound -> REST Message.  The REST Message list view will be displayed.
  2. Click on the New button to create a new REST Message.  The new REST Message form will appear.
  3. Fill in the form with the following:
    1. Name: CatFacts
    2. Endpoint: http://catfacts-api.appspot.com/api/facts
    3. Accessible from: All application scopes
    4. Right-click on the form header and choose Save.  This will auto-generate four HTTP methods (get, put, post, delete).  We will only be using “get” for the purposes of this article.



5. In the HTTP Methods related list click on the “get” method.  This will open the Method form.

6. Change the Endpoint to: http://catfacts-api.appspot.com/api/facts?number=${number}



7. Right-click on the form header, and save your work.

8. Scroll to the bottom of the form and from the Variable Substitution related list click the New button. 

This will display the New Variable form.

9. Fill out the form with the following:

a. Name: number

b. Test Value: 5

Note: we will be pulling back five facts about cats.

c. Click Submit to save the variable.  This will take you back to the Method form.



10. Scroll down to the bottom of the Method form, and choose the “Test” link under the related links. 

This will execute the REST Message with the number value of 5. 



11. You should get a 200 result (success), and five facts about cats!



 

Our REST Message is now ready to be consumed - a fancy-smancy technical term meaning “used”.

 

 

Lab 1.2: Fix Script to Run Consume the REST Message

 

So now comes the cool stuff.  We will be creating code to execute the REST Message, and then convert the response to a JSON object so that we can actually make some use of it.

 

  1. Navigate to Fix Scripts.  This will display the Fix Scripts list view.
  2. Click the New button.  This will display the New Fix Script form.
  3. Fill out the form with the following:
    1. Name: Cat Facts
    2. Active: true
    3. Description: Test script to consume the CatFacts REST Message
    4. Script:

 

var webService = 'CatFacts';
var command = 'get'; // case sensitive
var number = 5; 

var restMessage = new sn_ws.RESTMessageV2(webService, command);
restMessage.setStringParameter('number', parseInt(number));
var response = restMessage.execute();  // REST return
var httpStatus = response.getStatusCode(); // response status code

var responseBody = response.getBody(); // stringified JSON returned info

var parsed = JSON.parse(responseBody); // turn it into a true JSON object

// Now we can begin using the response
gs.print(responseBody);

var facts = parsed.facts;
var success = parsed.success == 'true';

gs.print(facts.length);
gs.print(parsed.success);

if (success) {
    for (var i=0; i < facts.length; i++) {
        gs.print(facts[i]);
    }
}


 

 

5. Right-click on the form header and Save your work.



 

6. Execute the Fix Script (Related Links -> Run Fix Script)g. You should get a result something like this:



And there you have it!  In this article I demonstrated:

 

  1. How to create a REST Message
  2. How to add a variable to the REST Message method
  3. How to test a REST Message method
  4. How to call a REST Message from a script
  5. How to use a variable in a REST Message from a script
  6. How to parse the returned results into JSON from the REST Message
  7. How to use the JSON results

 

Steven Bell

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  ADVANCED to EXPERT

Assumes very good knowledge and/or familiarity of scripting, and workflows in ServiceNow.

____________________________________________________________________________

 

Recently I had the need to port a large JSON object (not a GlideRecord) from code to a called Workflow.  Simple, I said, I will just pass it through the variables parameter of my startFlow command.  Not.  startFlow, it appears, replaces all sorts of important characters in the object; scrambling it and making it garbage on the receiving end!  It helps you!  Arrrgh! - a technical term expressing frustration.  Well, a need-is-a-need and I had to have a solution.  Here is what I came up with.

 

Caveat:

  1. DO NOT BOTHER USING THIS METHOD TO SEND A CONVERTED GLIDERECORD.  You can just go get this via a GlideRecord call from the workflow.  We ARE all server-side here after all!  So in my examples...what did I do?  I converted a couple of GlideRecords to give me some test data.  Do as I say, not as I do!  :-)
  2. Remember to play with these techniques in a sandbox or in your personal developer instance.  Please don't play with code like this in Production!  Seriously.


Design:

 

So the process will be to take our object, convert it to JSON, convert THAT to a string, then URL encode it (the secret).  However, you still have to tweak the encode a bit to get it to work (some character replacement).  Then on the workflow side-of-things we reverse the process.  Think Star Trek Transporter!  :-)

 

From the calling script:

 

  1. Create an object, or object array
  2. Convert the object to JSON
  3. JSON Stringify the object
  4. URL Encode the string
  5. Fix up the encoded URL


From the workflow:

 

  1. URL Decode the string
  2. Unfix the decoded string
  3. JSON Parse the object
  4. Begin using the object

 


Lab 1.1: The Calling Script

 

The following code can be run on any server-side script (Business Rules, Script Includes, Scheduled Jobs, Script Actions; to name a few).  We will be using a Fix Script to do the work for us.  I will be incorporating a couple of other articles I had written some time back to give us some test data to play with.

 

Community Code Snippets - Current Factory

Mini-Lab: Extending the GlideRecord Object

 

I have attached the code for both of these to this article.  I would suggest reading both if you want to understand how they actually work, but it is not necessary if you just want focus on this article.  Import the attached files to bring these libraries into your developer instance.

 

  1. Navigate to Fix Scripts.  The Fix Scripts List View will be displayed.
  2. Click on the New button.  This will display a New Fix Script form.
  3. Fill out the form with the following:
    1. Name: Send JSON to Workflow
    2. Active: checked
    3. Description: Example of sending a JSON object to a workflow
    4. Script:

 

gs.include('ACNTableUtils'); // currentFactory, listFactory
// this has been improved since the article was written
gs.include('ACNGlideRecordExtensions'); // toObject, toObjectList

// get a single incident record and convert it to an object
var current = ACNTableUtils.currentFactory('incident').toObject();

// got get a bunch of records and convert it to an object array
var order = {};
order.type = 'descending';
order.field = 'number';
// just showing off here - the toObjectList part isn't really necessary  :-)
var incidentList = ACNTableUtils.listFactory('incident', 5, order).toObjectList();

// there is a LOT of stuff in a GlideRecord so let's pick off a couple of things
// we could pass across for a test
var incidents = [];
for (var i=0; i < incidentList.length; i++) {
    var incident = {};
    incident.number = incidentList[i].number;
    incident.sys_id = incidentList[i].sys_id.toString();
    incident.short_description = global.JSUtil.notNil(incidentList[i].short_description) ? incidentList[i].short_description : 'nuttin here';
    incidents.push(incident);
}
// end of test data setup

var short_description = current.short_description;
var number = current.number + '';
var testList = ['cows', 'chickens', 'pigs', 'horses'];

// From this point is where we set up the send
var payload = {};
payload.short_description = short_description;  // single value
payload.number = number; 
payload.testList = testList; // simple array
payload.incident = current; // complex object
payload.incidentList = incidents; // object array

// turn the payload into a JSON object and stringify it
payload = global.JSON.stringify(payload); 

var workflow = new Workflow();
var workflowID = workflow.getWorkflowFromName('Send JSON to Workflow');

var vars = {};
// now we encode it and fix it up before startFlow can destroy it
// colens and brackets are problems. Oddly you have to reconstitute the brackes here,
// and replace the colens.  startFlow has a lot to answer for
// place the string into our inbound variable.
vars.u_payload = encodeURI(payload).replace(/%5b/g, '[').replace(/%5D/g, ']').replace(/:/g, '%3A');

// now call the workflow and pass our object
workflow.startFlow(workflowID, null, null, vars);





5. Save your work.

 


 

Lab 1.2: The Workflow

 

1. Navigate to Workflow -> Workflow Editor

2. Create a new Workflow

    1. Name: Send JSON to Workflow
    2. Table: Global

 

3. Create a new input variable: u_payload, length 8000. 

NOTE: you might want to make this bigger if you have a lot of data you are bringing across!  Otherwise your string gets truncated and you get strange conversion errors from the JSON decode!

 

4. Pull out a Run Script Activity

    1. Name: Initialize
    2. Script:

 

var location = context.name + '.' + activity.name;
var payloadDecode = decodeURI(workflow.variables.u_payload);
// prep the string prior to reconstituting the JSON. 
// Colens are the only issue, all the rest get handled by the decodeURI
payloadDecode = payloadDecode.replace(/%3A/g, ':');  

// we can now parse the string into a JSON object
var payload = JSON.parse(payloadDecode);

// now we can start using the values
var short_description = payload.short_description;
var number = payload.number;
var testList = payload.testList; // simple array
var incident = payload.incident; // complex object
var incidentList = payload.incidentList; // object array

var message = '--->\n';

message += '\tshort_description: ' + short_description + '\n';
message += '\tNumber: ' + number + '\n';
message += '\tTestList length: ' + testList.length + '\n';

for (var i=0; i < testList.length; i++) {
  message += '\t- ' + testList[i];
}
message += '\n';

message += '--- incident\n';
message += 'number: ' + incident.number;
message += '\tsys_id: ' + incident.sys_id + '\n';

message += '--- incidentList\n';
for (var j=0; j < incidentList.length; j++) {
  var incident = incidentList[j];
  message += 'number: ' 
  + incident.number 
  + '\t- short_description: ' 
  + incident.short_description.replace(/~/g,' ') + '\n';
}

gs.log(message, location);



 


    5. Click update.

 


 

Lab 1.3: Test

 

  1. Go back to your fix script, and run it.  You should see something like the following results:

 

 

And there you go!  Remember:  If you get strange conversion errors in the workflow code it is probably because you overran 8000 characters in your string.  Just increase the u_payload size to correct the issue.

 

Steven Bell

 

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE to ADVANCED

Assumes good knowledge and/or familiarity of scripting in ServiceNow.

____________________________________________________________________________

 

In this article I will present a couple of different methods I use to locate a record in a complex object array.

 

This is the third of three articles I wanted to write concerning object arrays.  My previous two were:

 

Community Code Snippets - Three Methods to Sort an Object Array

Community Code Snippets - Removing Duplicates From an Object Array

 

I would do both before playing with the code in this example.

 

You will need to do the following Mini-Labs in order to use underscore.js and the JavaScriptExtensions code in the following example:

 

Mini-Lab: Adding Underscore.js Into ServiceNow

Mini-Lab: Extending the JavaScript Array Object with findIndex

 

So, now that we have the base libraries, let me present the examples!

 

I have attached the Fix Script I used for this article.  It contains the getIncidentList(...) code, but does not include the underscore.js or Array.polyfill.findIndex code.  You will need to do the labs to bring that functionality in.

 

// Set up for all of the following examples.  Get 10 records from the Incident table

var incidentList = getIncidentList(10);

var incidentID = '0c43b55ac6112275019abd2b247baf31';

 

EXAMPLE 1:

 

This is my preferred method.  It does not require a library, and works with existing out-of-the-box ServiceNow JavaScript libraries.

 

var incident = incidentList.filter(function(element) { return element.sys_id === incidentID; })[0];

 

gs.print('---> sys_id1: ' + incident.sys_id + ' - ' + incident.name);

 

This gives the following result:

 

*** Script: ---> sys_id1: 0c43b55ac6112275019abd2b247baf31 - EFOWEB

 

However, you don’t get the actual index location back; if you care about it that is.

 

 

EXAMPLE 2:

 

So, let me demonstrate a way of doing the same thing with the underscore.js library.  Here you can see that there is a way of retrieving the location of the record based on the ID. 

 

gs.include('underscorejs.min');

var index = _.chain(incidentList).pluck("sys_id").indexOf(incidentID).value();

var incident2 = incidentList[index];

 

gs.print('---> sys_id2: ' + incident2.sys_id + ' - ' + incident2.name);

 

Produces the exact same results:

 

*** Script: ---> index2: 5

*** Script: ---> sys_id2: 0c43b55ac6112275019abd2b247baf31 - EFOWEB

 

The only problem I have with this example is in maintenance.  I have to comment it a bit so that it explains what exactly is happening.

 

 

EXAMPLE 3:

 

So finally, if you implement the new ECMA6 polyfill for Array.findIndex you get the same functionality as the underscore.js example, but it is easier to implement (and future-proof; as when ServiceNow finally implements the ECMA6 functionality). 

 

gs.include('JavaScriptExtensions');

var index = incidentList.findIndex(function(element) { return element.sys_id == incidentID; });

var incident3 = incidentList[index];

 

gs.print('---> sys_id3: ' + incident3.sys_id + ' - ' + incident3.name);

 

Produces the exact same results:

 

*** Script: ---> index3: 5

*** Script: ---> sys_id3: 0c43b55ac6112275019abd2b247baf31 - EFOWEB

 

Again, this example would require some commenting just to make it clear to a maintenance coder what is going on.

 

 

OTHER FUTURE EXAMPLES:

 

The following are examples that will work when ECMA6 is implemented in ServiceNow:

 

// ecma 6 --- doesn't work yet ... sniff!

var incident4 = incidentList.map(x => x.says_id).indexOf(incidentID);

 

// ecma 6 --- nor this!  makes me want to weep...

var incident5 = incidentList.findIndex(x => x.sys_id === incidentID);

 

// ecma 6 --- or even this! gad!

var index = incidentList.reduce((i, item, index) => item.sys_id === incidentID ? index : i, -1);

var incident7 = incidentList[index];


So you can see that there are a several ways to retrieve a record from an object array.  Explore this a bit, and you might find other ways (yes, there are more).

 

Some "light" additional reading:

 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex


Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

Filter Blog

By date: By tag: