Skip navigation

It's always a good idea to write and maintain the minimum amount of code as possible, so minimizing the number of Email Scripts is helpful.  But can we have just one script AND output different content based on the target audience of the notification?  You want to stay away from writing different scripts to output slightly different text, like an additional field for example, based on the target audience.  Email Scripts do not support passing parameters, which would be helpful in this scenario, but we can get around that by adding the parameter on the Notification record instead, which is where you define your target audience anyways.

 

First, add a new field on the Notification form:

It can be as simple as a string field or you can create a Choice List for it as well.

 

Create the Mail Script that will contain the text you want to output as well as the logic for supporting slightly different text:

My example is not very useful, but you get the idea.

 

And lastly, in the Notification record, call the Mail Script from within the HTML field:

Depending on your own use case, you could even create an Email Template that contains the default text and the call to the Mail Script to really keep maintenance to a minimum.

 

So when the Email Script is run, it will check the field on the Notification record and output different text based on that value.  Here's a preview of 3 different Notifications I created, all using the same Email Script, but with different values in the Custom parameter field.  They all output something different from that 1 script.

 

First Notification:

 

Second:

 

Third:

*** Please Like and/or tag responses as being Correct.
And don't be shy about tagging reponses as Helpful if they were, even if it was not a response to one of your own questions ***

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!

Today, I would like to explain how to only display the categories that are related to a catalog.

 

For this we need to update SC categories widget, SC Category Page as well as SC Popular items OOB widgets.

 

Before going into the widget, I would like to shed some light on a method available in $sp API getParameter returns the value of given key. This methods helps to retrieve the sys id of the selected catalog.

 

If you have observed in the part 1 of this article series, the anchor tag has the sys id of catalog in URL.

<a ng-href="?id=sc_home2&catalog_sys_id={{inc.sysid}}" class="panel-body block">

 

And $sp.getParameter('catalog_sys_id');  will get the sys id of the catalog.

 

Once we have the sys id of the catalog all we need to do is update the existing widgets with the above method and filter the categories.

 

Below is the Updated server code of the SC categories widget

 

(function() {
// populate the 'data' object
data.categories = [];

data.sc_catalog = $sp.getParameter('catalog_sys_id'); //$sp.getValue('sc_catalog');
   
  var sc_cat = new GlideRecord('sc_catalog');
  sc_cat.addQuery('sys_id', data.sc_catalog);
  sc_cat.query();


  while(sc_cat.next())
  {
  data.catalog_name = sc_cat.title.toString();
  } 

options.category_layout = options.category_layout || "Nested";


if (options.page) {
  var pageGR = new GlideRecord("sp_page");
  options.page = (pageGR.get(options.page)) ? pageGR.getValue("id") : null;
} else {
  options.page = 'sc_category';
}


if (input && input.action === "retrieve_nested_categories") {
  var childCategoriesGR = buildSubcategoryGR(input.parentID);
  data.subcategories = retrieveCategoriesFromGR(childCategoriesGR);
  return ;
}


var sc = new GlideRecord('sc_category');
sc.addQuery('sys_class_name', 'sc_category');
sc.addActiveQuery();
sc.orderBy('title');
//data.sc_catalog = $sp.getParameter('catalog_sys_id'); //$sp.getValue('sc_catalog');
if (data.sc_catalog)
  sc.addQuery('sc_catalog', data.sc_catalog);
if (options.category_layout === "Nested")
  sc.addQuery('parent', '');
sc.query();
data.categories = retrieveCategoriesFromGR(sc);


// If the selected category is a subcategory, we need to 
// open all it's parent categories
var selectedCategory = new GlideRecord("sc_category");
var categoryID = $sp.getParameter("sys_id");
if (!categoryID || !selectedCategory.get(categoryID))
  return;


var parentArr;
if (options.category_layout !== "Nested" || !selectedCategory.parent)
  parentArr = data.categories;
else
  parentArr = openParent(selectedCategory.getElement("parent").getRefRecord());


var selectedCategoryItem = findElementBySysID(parentArr, selectedCategory.getUniqueValue());
if (selectedCategoryItem)
  selectedCategoryItem.selected = true;


function openParent(gr) {
  var catItem;

  if (!gr.parent) {
  catItem = findElementBySysID(data.categories, gr.getUniqueValue());
  } else {
  var parentCategoryArr = openParent(gr.getElement("parent").getRefRecord());
  catItem = findElementBySysID(parentCategoryArr, gr.getUniqueValue());
  }

  if (!catItem)
  return [];

  var subcategoryGR = buildSubcategoryGR(catItem.sys_id);
  catItem.subcategories = retrieveCategoriesFromGR(subcategoryGR);
  catItem.showSubcategories = true;
  return catItem.subcategories;
}


function findElementBySysID(arr, id) {
  var foundElements = arr.filter(function(item) {
  return item.sys_id === id;
  });

  return (foundElements.length > 0) ? foundElements[0] : null;
}


function retrieveCategoriesFromGR(gr) {
  var categories = []
  while (gr.next()) {
  var category = retrieveCategoryFromGR(gr);
  if (category)
  categories.push(category);
  }

  return categories;
}


function retrieveCategoryFromGR(gr) {
  if (!$sp.canReadRecord("sc_category", gr.getUniqueValue()))
  return null;


  if (options.check_can_view != true && options.check_can_view != "true") {
  // use GlideAggregate by way of GlideRecordCounter, doesn't check canView on each item
  var count = new GlideRecordCounter('sc_cat_item_category');
  prepQuery(count, gr.getUniqueValue());
  var item_count = count.getCount();
  if (item_count > 0) {
  var cat = {};
  cat.title = gr.title.getDisplayValue();
  cat.sys_id = gr.getUniqueValue();
  cat.catalog_sys_id = gr.sc_catalog.sys_id.toString();
  cat.count = item_count;
  cat.parent = gr.parent.getDisplayValue();
  if (options.category_layout === "Nested")
  cat.isParentCategory = checkIsParentCategory(gr);
  return cat;
  }
  }


  if (options.check_can_view == true || options.check_can_view == "true") {
  // use GlideRecord, checking canView on each item
  var itemCat = new GlideRecord('sc_cat_item_category');
  prepQuery(itemCat, gr.getUniqueValue());
  itemCat.query();
  var validatedCount = 0;
  var checked = 0;
  while (itemCat.next()) {
  checked++;
  if ($sp.canReadRecord("sc_cat_item", itemCat.sc_cat_item))
  validatedCount++;


  // if user can't see the first 50 items in this category, give up
  if (validatedCount == 0 && checked == 50)
  break;


  // if omitting badges, and if we found one, work is done
  if (validatedCount > 0 && options.omit_badges)
  break;
  }


  if (validatedCount > 0) {
  var cat = {};
  cat.title = gr.title.getDisplayValue();
  cat.sys_id = gr.getUniqueValue();
  cat.catalog_sys_id = gr.sc_catalog.sys_id.toString();
  cat.count = validatedCount;
  cat.parent = gr.parent.getDisplayValue();
  if (options.category_layout === "Nested")
  cat.isParentCategory = checkIsParentCategory(gr);
  return cat;
  }
  }

  return null;
}


function prepQuery(gr, scUniqueValue) {
  gr.addQuery('sc_category', scUniqueValue);
  gr.addQuery('sc_cat_item.active', true);
  gr.addQuery('sc_cat_item.visible_standalone', true);
  gr.addQuery('sc_cat_item.sys_class_name', 'NOT IN', 'sc_cat_item_wizard');
}


function checkIsParentCategory(cat) {
  var count = new GlideRecordCounter('sc_category');
  count.addQuery('active', true);
  count.addQuery('parent', cat.getUniqueValue());
  return count.getCount() > 0;
}


function buildSubcategoryGR(parentID) {
  var subcategoryGR = new GlideRecord("sc_category");
  subcategoryGR.addActiveQuery();
  subcategoryGR.orderBy('title');
  var sc_catalog = data.sc_catalog; //$sp.getValue('sc_catalog');
  if (sc_catalog)
  subcategoryGR.addQuery('sc_catalog', sc_catalog);
  subcategoryGR.addQuery('parent', parentID);
  subcategoryGR.query();
  return subcategoryGR;
}
})();

 

 

Also, we need to update spCategoryListItem angular provider to show only the categories of a selected catalog

 

Below is the updated client script of category angular provider

function spCategoryListItem() {
return {
restrict: 'E',
scope: {
category: "=",
omitBadges: "=",
level: "=",
page: "=?"
},
replace: true,
template:   '<div ng-class="{\'indent-category\': indentCategory}">' +
'<a class="list-group-item" ng-class="{selected: category.selected}" href="?id={{page}}&sys_id={{::category.sys_id}}&catalog_sys_id={{::category.catalog_sys_id}}">' +
'<span ng-if="!omitBadges" class="label label-as-badge label-primary">{{::category.count}}</span>' +
'<i class="fa fa-fw text-muted" ng-class="{\'fa-folder\': !category.showSubcategories, \'fa-folder-open\': category.showSubcategories}" ng-if="category.isParentCategory" ng-click="toggleShowSubcategories($event)"></i>{{::category.title}}' +
'</a>' +
'<sp-category-list-item ng-if="category.showSubcategories" ng-repeat="subcategory in category.subcategories" category="subcategory" omit-badges="omitBadges" level="level + 1"></sp-category-list-item>' +
'</div>',
controller: function($scope) {
// We have to eventually stop indenting the categories.
// So, we're choosing to indent up to 3 times. Otherwise,
// there won't be enough room to show the category name.
$scope.indentCategory = ($scope.level > 0 && $scope.level < 4);
$scope.page = $scope.page || 'sc_category';
$scope.toggleShowSubcategories = function(e) {
e.originalEvent.stopPropagation();
e.originalEvent.preventDefault();

$scope.$emit("$sp.sc_category.retrieve_subcategories", $scope.category);
$scope.category.showSubcategories = !$scope.category.showSubcategories;
}
}
}
}

 

Once we have the widget ready, create a new page with id sc_home2 and add updated SC category widget the the left…

One may follow the similar steps to get the popular items. But keep in mind that existing popular items widget will only work for catalog items and not for record producers.

 

Screenshot pointing the the page id of a catalog homepage

 

                                                                                                  Catalog Homepage

 

 

Note: If a different name is used for page id other than sc_home2, update the same in multi catalog widget that was created in part 1 of this article

 

 

After user landed on the desired Catalog homepage and when the user picks any category on the left then URL will redirect to sc_category page. Below is the screenshot of the sc_category page with the widgets.

In order to support multi portal functionality, the category page needs to be updated with the widget mentioned below.

 

 

Below is the updated code for SC Category Page Widget

 

Server Code

(function() {
  data.category_id = $sp.getParameter("sys_id");
  data.showPrices = $sp.showCatalogPrices();
  if (options && options.sys_id)
  data.category_id = options.sys_id;


  data.sc_catalog = $sp.getParameter('catalog_sys_id');

  var sc_cat = new GlideRecord('sc_catalog');
  sc_cat.addQuery('sys_id', data.sc_catalog);
  sc_cat.query();


  while(sc_cat.next())
  {
  data.catalog_name = sc_cat.title.toString();
  data.catalog_url = 'sc_home2&catalog_sys_id=' + sc_cat.sys_id.toString();


  } 

// data.sc_catalog_page = $sp.getDisplayValue("sc_catalog_page") || "oa_sc_home2";
  // Does user have permission to see this category?
  if (!$sp.canReadRecord("sc_category", data.category_id)) {
  data.error = "You do not have permission to see this category";
  return;
  } 


  var cat = new GlideRecord('sc_category');
  cat.get(data.category_id);
  data.category = cat.getDisplayValue('title');
  var items = data.items = [];
  var sc = new GlideRecord('sc_cat_item_category');
  if (data.category_id) 
  sc.addQuery('sc_category', data.category_id);


  sc.addQuery('sc_cat_item.active',true);
  sc.addQuery('sc_cat_item.sys_class_name', 'NOT IN', 'sc_cat_item_wizard');
  sc.orderBy('sc_cat_item.order');
  sc.orderBy('sc_cat_item.name');
  sc.query();
  while (sc.next()) {
  // Does user have permission to see this item?
  if (!$sp.canReadRecord("sc_cat_item", sc.sc_cat_item.sys_id.getDisplayValue()))
  continue;


  var item = {};
  var gr = new GlideRecord('sc_cat_item');
  gr.get(sc.sc_cat_item);
  gr = GlideScriptRecordUtil.get(gr).getRealRecord();
  $sp.getRecordDisplayValues(item, gr, 'name,short_description,picture,price,sys_id');
  item.sys_class_name = sc.sc_cat_item.sys_class_name + "";
  item.page = 'sc_cat_item';
  if (item.sys_class_name == 'sc_cat_item_guide')
  item.page = 'sc_cat_item_guide';
  else if (item.sys_class_name == 'sc_cat_item_content') {
  $sp.getRecordValues(item, gr, 'url,content_type,kb_article');
  if (item.content_type == 'kb') {
  item.page = 'kb_article';
  item.sys_id = item.kb_article;
  } else if (item.content_type == 'literal') {
  item.page = 'sc_cat_item';
  } else if (item.content_type == 'external')
  item.target = '_blank';
  }


  items.push(item);
  }
})()

 

 

Once we have all the widgets needed, all we need to do is use the updated widgets on the page.

 

For this, follow the below steps

  1. Open sc_category page
  2. Open the Instance of SC categories widget and update the widget  reference field with multi categories widget that was created earlier.

 

 

this concludes the part 2 of multi catalog functionality in Service Portal. In the next part, I will delve into Search and Breadcrumbs functionalities that supports multi catalogs.

 

 

Attached are the XML files of the following Widgets

  • Multi SC Categories
  • Multi SC Popular Items
  • Multi SC Category Page

 

Blogs in this series:

Portal diaries: Service Portal – Multiple Catalogs (Part 1)

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!

We noticed a very odd issue yesterday when running a "no brainer" script to update items in a GlideRecord while next loop:

 

var gr = new GlideRecord("cmdb_ci");
  gr.addNotNullQuery('u_vulnerability_remediation_group');
  gr.query();

  while ( gr.next() ) {
       try {
            gr.setValue('u_vulnerability_remediation_group',  '');
            gr.update();
       } catch(e) {
            gs.print(e.message);
       }
  }

 

I have used code like this for years in SNOW and it just works (err.. worked).  I have some examples just like this actually checked into source control!

 

Under Helsinki, the actual results of this script were "intriguing":  It deleted a single item and then exited the loop. 

 

After some tinkering I discovered that a function wrapper solved the problem.

 

//After wrapping the code in a IFFE, the loop continues to update items as expected.
(function() {
  var gr = new GlideRecord("cmdb_ci");
  gr.addNotNullQuery('u_vulnerability_remediation_group');
  gr.query();
  while ( gr.next() ) {
       try{
            gr.setValue('u_vulnerability_remediation_group',  '');
            gr.update();
       } catch(e) {
            gs.print(e.message);
       }
  }
}());

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!

When testing APIs (Application Interfaces), it is always very helpful to get an independent view of the interfacing application. For example, if you are interfacing two ServiceNow instances, and it is not working as expected - how will you know where the error is - the source or the target of the integration? In this case, you will employ a 3rd party tool, such as the Firefox RESTClient, or Chrome's POSTMAN to test the REST integration. You can build tests to verify functionality such as POSTMAN.

 

For SOAP integrations, we typically use SOAP UI, which is already documented very well in Mini-Lab: Web Services – Part 1: Using SoapUI to Test ServiceNow WSDL. I will give you details on how to use RESTClient and POSTMAN for REST integrations with ServiceNow.

 

Let's start with the RESTClient, that can be downloaded into your Firefox browser as a plugin. For REST integrations, you will have a URL and a method. The standard methods are automatically available in the REST client including: GET, POST, DELETE, OPTIONS,HEAD, TRACE, and CONNECT.

rest integrations1.jpg

You can read up on these methods (also called HTTP verbs) here: Hypertext Transfer Protocol - Wikipedia

 

How to retrieve a record from your Instance

To retrieve a record from your ServiceNow instance, you now have to do the following:

  1. Enter your URL (hint: look up the URL in your instance's REST API Explorer):

    rest api explorer1.jpg

  2. You can click for example on cURL to get the URL:

    curl "https://instance.service-now.com/api/now/table/incident?sysparm_query=active%3Dtrue&sysparm_display_value=true&sysparm_limit=1" \

    --request GET \

    --header "Accept:application/json" \

    --user 'user':'password'

  3. In RESTClient, this will translate to:

    restclient1.jpg

The URL can be copied from the curl URL above.

 

The Content-Type header is added through the menu item Headers > Custom Headers and then filled as shown above, the Authorization Header is added through the menu item Authentication > Basic Authentication (add user name and password).

 

Other possible header values are found here: Supported REST API headers  and other possible Authorization methods can be found here: REST API security

 

The response is in the JSON (Key-Value-Pair) format.

{

"result":

[

{

"parent": "",

"made_sla": "false",

"caused_by": "",

"watch_list": "",

"u_mobilecategory": null,

"u_alternate_phone": "",

"upon_reject": null,

"sys_updated_on": "2016-12-08 08:03",

"child_incidents": "",

"approval_history": "",

"skills": "",

"number": "INC0000002",

"resolved_by": "",

"sys_updated_by": "system",

"opened_by":

{

"display_value": "Joe Employee",

"link": "https://instance.service-now.com/api/now/table/sys_user/681ccaf9c0a8016400b98a06818d57c7"

},

"user_input": "",

"sys_created_on": "2011-03-25 15:30",

"sys_domain":

{

"display_value": "global",

"link": "https://instance.service-now.com/api/now/table/sys_user_group/global"

},

"state": "New",

"sys_created_by": "pat",

"knowledge": "false",

"order": "",

"u_cicat": "",

"calendar_stc": "",

"closed_at": "",

"cmdb_ci": "",

"delivery_plan": "",

"contract": "",

"impact": "1 - High",

"active": "true",

"work_notes_list": "",

"business_service": "",

"priority": "1 - Critical",

"rfc": "",

"time_worked": "",

"expected_start": "",

"rejection_goto": "",

"opened_at": "2012-08-28 16:07",

"u_eriktest": "",

"business_duration": "",

"group_list": "",

"work_end": "",

"caller_id": "",

"resolved_at": "",

"approval_set": "",

"subcategory": "Operating System",

"wf_activity": "",

"work_notes": "2013-04-04 08:00 - Service-now: Person [maint,admin,itil] (Work notes) test  2013-04-04 07:57 -

"short_description": "Can't get to network file shares",

"close_code": null,

"correlation_display": "",

"delivery_task": "",

"work_start": "",

"assignment_group":

{

"display_value": "Software",

"link": "https://instance.service-now.com/api/now/table/sys_user_group/8a4dde73c6112278017a6a4baf547aa7"

},

"u_eriktest2": null,

"additional_assignee_list": "",

"business_stc": "",

"description": "User can't get to any of his files on the file server.",

"calendar_duration": "",

"u_eriktest3": "",

"close_notes": "",

"notify": "Do Not Notify",

"sys_class_name": "Incident",

"closed_by": "",

"follow_up": "",

"parent_incident": "",

"sys_id": "9d385017c611228701d22104cc95c371",

"u_cisubcategory": "",

"contact_type": "Phone",

"incident_state": "Awaiting Problem",

"urgency": "1 - High",

"problem_id":

{

"display_value": "PRB0000007",

"link": "https://instance.service-now.com/api/now/table/problem/9d3a266ac6112287004e37fb2ceb0133"

},

"company": "",

"reassignment_count": "2",

"activity_due": "2016-12-08 10:03",

"assigned_to":

{

"display_value": "Howard Johnson",

"link": "https://instance.service-now.com/api/now/table/sys_user/46ca0887a9fe19810191e08e51927ebf"

},

"severity": "1 - High",


            {

                "display_value": "Salem OR",

                "link": "https://empigeist2.service-now.com/api/now/table/cmn_location/108486c7c611227500b093211aa88dcc"

            },

            "category": "software"

        }

    ]

}

 

Now that we know how to select a record or set of records, next we will create a new record in ServiceNow using the POST method:

POST method1.jpg

 

The URL is the simple REST URL as documented, the Content Type and Authorization are the same as used in the GET method. The difference is in the Body - here we will fill the fields - in key value pairs defined by the JSON format. For example:

{"active":"true","category":"software","caller_id":"Abel Tuter","short_description":"REST POST DEMO"}

Will fill in the active, category, caller, and short description for the new incident.

 

The response shows the fields within the newly created record:

{

    "result":

    {

        "parent": "",

        "made_sla": "true",

        "caused_by": "",

        "watch_list": "",

        "u_mobilecategory": "",

        "u_alternate_phone": "",

        "upon_reject": "cancel",

        "sys_updated_on": "2017-03-23 19:12:16",

        "child_incidents": "0",

        "approval_history": "",

        "skills": "",

        "number": "INC0010098",

        "resolved_by": "",

        "sys_updated_by": "admin",

        "opened_by":

        {

            "link": "https://instance.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441",

            "value": "6816f79cc0a8016401c5a33be04be441"

        },

        "user_input": "",

        "sys_created_on": "2017-03-23 19:12:16",

        "sys_domain":

        {

            "link": "https://instance.service-now.com/api/now/table/sys_user_group/global",

            "value": "global"

        },

        "state": "1",

        "sys_created_by": "admin",

        "knowledge": "false",

        "order": "",

        "u_cicat": "",

        "calendar_stc": "",

        "closed_at": "",

        "cmdb_ci": "",

        "delivery_plan": "",

        "contract": "",

        "impact": "3",

        "active": "true",

        "work_notes_list": "",

        "business_service": "",

        "priority": "5",

        "rfc": "",

        "time_worked": "",

        "expected_start": "",

        "rejection_goto": "",

        "opened_at": "2017-03-23 19:12:00",

        "u_eriktest": "",

        "business_duration": "",

        "group_list": "",

        "work_end": "",

        "caller_id":

        {

            "link": "https://instance.service-now.com/api/now/table/sys_user/62826bf03710200044e0bfc8bcbe5df1",

            "value": "62826bf03710200044e0bfc8bcbe5df1"

        },

        "resolved_at": "",

        "approval_set": "",

        "subcategory": "",

        "wf_activity": "",

        "work_notes": "",

        "short_description": "REST POST DEMO",

        "close_code": "",

        "correlation_display": "",

        "delivery_task": "",

        "work_start": "",

        "assignment_group":

        {

            "link": "https://instance.service-now.com/api/now/table/sys_user_group/287ee6fea9fe198100ada7950d0b1b73",

            "value": "287ee6fea9fe198100ada7950d0b1b73"

        },

        "u_eriktest2": "",

        "additional_assignee_list": "",

        "business_stc": "",

        "description": "",

        "calendar_duration": "",

        "u_eriktest3": "",

        "close_notes": "",

        "notify": "1",

        "sys_class_name": "incident",

        "closed_by": "",

        "follow_up": "",

        "parent_incident": "",

        "sys_id": "207d25b313a9b200d57c50f32244b07b",

        "u_cisubcategory": "",

        "contact_type": "phone",

        "incident_state": "1",

        "urgency": "3",

        "problem_id": "",

        "company":

        {

            "link": "https://instance.service-now.com/api/now/table/core_company/227cdfb03710200044e0bfc8bcbe5d6b",

            "value": "227cdfb03710200044e0bfc8bcbe5d6b"

        },

        "reassignment_count": "0",

        "activity_due": "",

        "assigned_to": "",

        "severity": "3",

        "comments": "",

        "u_categorytier3": "",

        "u_sla_start_time": "",

        "approval": "not requested",

        "sla_due": "",

        "comments_and_work_notes": "",

        "due_date": "",

        "sys_mod_count": "0",

        "reopen_count": "0",

        "sys_tags": "",

        "escalation": "0",

        "upon_approval": "proceed",

        "correlation_id": "",

        "location": "",

        "category": "Software"

    }

}

 

The caller, which we entered by its display name of "Abel Tuter" is stored as a reference with the user record's sys_id, and default values that were filled via dictionary or business rules are also filled in.

 

Using external tools to validate your REST request and response is extremely helpful when troubleshooting REST integrations. You can compare your request and the response you receive within the external tool, to the results you expect in ServiceNow. These tools allow you to independently test both applications within an integration to ensure they are producing the expected results.

 

So, in a nutshell, that is how to use the RESTclient plugin. I am looking forward to providing a deep dive into the other frequently used external tool - POSTMAN (coming soon).

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!

Filter Blog

By date: By tag: