Skip navigation

Have you ever wanted to be able to send errors to the System Log from a Client Script?  a UI Action?  a UI Policy?  Well, in this mini-lab article I will show you a method for doing so.

 

I had been wanting gs.log on the Client (i.e. Browser) for some time when one day it occurred to me that I could write a simple reusable UI Script / Ajax Script Include combo that would give me that capability.

 

I will be using techniques that I described in my previous two articles:

 

Pragmatic Patterns: Ajax - Architecting Your Code

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

 

Requirements

 

  1. Ability to write to the system log from a Client Script, UI Action, or UI Policy script.
  2. Must be re-usable from any of those mechanisms (i.e. emulate gs.log/gs.info on the server side)

 

Design

 

  1. Create an Ajax Script Include with a function that will take a "message" string and write it to the System Log using gs.log, gs.info, gs.debug, gs.error, or gs.warn.
    • The gs.log part will be straight forward, we simply do something like log(string, location) and translate that on the server side to gs.log(string, location).
    • The gs.info et. al. part is a bit trickier.  Here we will have to be a bit innovative.  Something like logextended(string, [array of values], type) may need to be done.
  2. Create a UI Script with a function that will take a string and communicate it via Ajax to an Ajax Script Include.
    • Make this a Function Library that will be callable similar to gs.
  3. Write an example onLoad Client Script that will call the UI Script function.  This will be used in testing.

 

 

NOTE: In examining the design I determined that a Script Include Function Library would be overkill as the number of code lines needed to write to the System Log would be too few to warrant extraction.

 

 

 

Lab 1.1:  Writing Log Entries Into The System Log From the Browser

 

Development

 

We will start with a bottom up development approach.

 

Ajax Script Include (Design Element #1)

 

1. Navigate to System Definition -> Script Includes.  This will display the Script Includes list view.

2. Click on the New button.  This will display a blank Script Include form.

3. Fill in the form with the following:

    • Name: SystemLoggerAjax
    • Client Callable: Checked (this will cause the Ajax template to be added to the Script field).
    • Accessible from: All application scopes
    • Active: checked
    • Description:  System Logging functions
    • Script:

 

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

    // simple logging function
    systemLog: function() {
        var message = this.getParameter('sysparm_message');
        var loggingSource = this.getParameter('sysparm_logging_source');
        
        gs.log(message, loggingSource);
    },

    // complex logging function
    systemLogExtended: function() {
        var message = this.getParameter('sysparm_message');
        // convert the JSON object back to our array
        var messageArgs = JSON.parse(this.getParameter('sysparm_args'));
        // type of log message to be executed
        var messageType = this.getParameter('sysparm_type').toLowerCase();
        
        switch (true) {
            case messageType == 'info':
                gs.info(message, messageArgs);
                break;
            case messageType == 'debug':
                gs.debug(message, messageArgs);
                break;
            case messageType == 'warn':
                gs.warn(message, messageArgs);
                break;
            case messageType == 'error':
                gs.error(message, messageArgs);
                break;
        }
    },

   type: 'SystemLoggerAjax'

});


 

 

UI Script (Design Element #2)

 

1. Navigate to System UI -> UI Scripts.  This will display the UI Scripts list view.

2. Click on the New button.  This will display a blank UI Script form.

3. Fill in the form with the following:

    • Name: LoggingUtils
    • Global: Checked
    • Active: Checked
    • Description: System Logging Functions
    • Script:

 

var log = Class.create();

// Simple logging function
log.log = function(message, loggingSource) {
    loggingSource = '[BROWSER] ' + loggingSource; // spiff up the source a bit

    var systemLogger = new GlideAjax('SystemLoggerAjax');
    systemLogger.addParam('sysparm_name','systemLog');
    systemLogger.addParam('sysparm_message', message);
    systemLogger.addParam('sysparm_logging_source', loggingSource);
    systemLogger.getXML(function(result){ });  // no callback action
};

// Complex logging functions - can probably be refactored
log.info = function(message) {
    _syslogExt(message, _toArray(arguments), 'info');
};

log.warn = function(message) {
    _syslogExt(message, _toArray(arguments), 'warn');
};

log.debug = function(message) {
    _syslogExt(message, _toArray(arguments), 'debug');
};

log.error = function(message) {
    _syslogExt(message, _toArray(arguments), 'error');
};

// Transmit complex logging task to server side
function _syslogExt(message, messageArgs, messageType) {
    message = '[BROWSER] ' + message; // spiff up the message
    
    var systemLogger = new GlideAjax('SystemLoggerAjax');
    systemLogger.addParam('sysparm_name','systemLogExtended');
    systemLogger.addParam('sysparm_message', message);
    // method for transmitting complex objects to the server
    systemLogger.addParam('sysparm_args', JSON.stringify(messageArgs));
    systemLogger.addParam('sysparm_type', messageType);
    systemLogger.getXML(function(result){ });  // no callback action
}

// Simple function to convert "argument" arrays into a true array for manipulation
function _toArray(source) {
    var target = [];
    // shave off the first argument - this is our message line
    for (var i=1; i < source.length; i++) {
        target.push(source[i] + '');
    }
    return target;
}



 

 

Client Script (Design Element #3)

 

1. Navigate to System Definition -> Client Scripts.  This will display the Client Script List View.

2. Click on the New button.  This will display a blank Client Script form.

3. Fill in the form with the following:

    • Name: syslog Example
    • Table: Incident
    • UI Type: Desktop
    • Type: onLoad
    • Active: checked
    • Inherited: unchecked
    • Global: checked
    • Description:  System Logger Example
    • Script:

 

function onLoad() {
    var incidentNumber = g_form.getValue('number');
    var userName = g_user.userName;
    
    // Test the simple logging function
    log.log('---> The form has finished loading and is ready for user input. Incident: ' + incidentNumber, 'CS:syslog example');
    
    // Test the complex logging function
    log.info('---> Test {0}: form has finished. userID = {1}, incident = {2}', '1', userName, incidentNumber);
    log.error('---> Test {0}: form has finished. userID = {1}, incident = {2}', '2', userName, incidentNumber);
}


 

 

UNIT TESTING

 

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

2. Choose an open Incident (click on the incident number to open the form).

3. Navigate to System Logs -> System Log -> All.  The System Log will be displayed.

4. Order by Created Date descending.

5. Filter the Message column with: *--->

6. Note the two new entries.

 

 

And there you have it!  A way to write entries to the System Log from the Client (browser) side!

 

Best practices demonstrated:

  1. Maintainability. 
  2. Extensibility.
  3. Coding Best Practice: Another method of logging unit test results.

 

Steven Bell

 

 

If you find this article helps you, don't forget to log in and "like" it!

 

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

yoda browser.jpeg

It can be frustrating trying to figure out which browsers are our friends and which browser are our foes. When working with the Geneva release, some of the application and features actually stray from general ServiceNow browser support.

 

With the release of Geneva there are new requirements for which browsers will be compatible with UI16, certain applications and features in this release will not work with some browsers so keep this in mind before you file a ticket with customer support! When in doubt switch browsers!

 

Browser support for System Properties in Geneva

glide.ui.dirty_form_support

This property enables or disables the display of a confirmation message when a form has unsaved changes and the user leaves the form in any way beside a submit ( like using the back arrow, any form button, etc.)

    • This property isn't supported in Safari. It could look something like this: leave page message.jpg

 

glide.ui.html.editor.toolbar.line1 and glide.ui.html.editor.toolbar.line2

These configure the editing toolbar for the HTMl fields. Of the available buttons, this is a comma separated list without any spaces. This property in fullscreen is not supported on Internet explorer

 

Browser support for Applications in Geneva

Financial Management

This application will not be supported by versions of Internet Explore prior to IE11. All other browsers are expected to work.

 

Legacy Business Service Management Map

There are more limitations when using internet explorer and the ServiceNow BSM map

  • To access the BSM map using IE 7 or * you must install Adobe Flash Player 10.1 or higher *(Note: Geneva will be the last release we will support IE 7 and 8 on for BSM maps)
  • You cannot export images from the map in IE 7
  • Map view images saved in a browser other than IE 8 might not display properly in Internet Explorer 8.
  • IE 7 can only display the version numbers of saved views and not the images.

Here is a neat trick I thought I would pass on.  How to push an object such as an object array to the server using Ajax and JSON.  With this example I will also be demonstrating usage of the Ajax pattern I described in my last article.

 

 

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

 

So let's do this right, and follow the development process.  We will need a requirement statement, a design (we can skip the analysis for such a short demonstration), and development.  Then finally test.

 

Requirements

 

  1. Create a button on the Incident form that transmits the incident's watch list emails to the system log.

 

Design

 

  1. New non-client enabled Script Include Function Library.
    • Name: ArrayHandlingUtils.
    • Client: Not checked.
    • Method: getEmailsFromSysIDs.  Will take a list of user sys_ids and return a list of their respective names and emails.  Make sure to take into account the possibility of no email being present for a given user.  This must be generic.
  2. New client enabled Script Include.
    • Name: AjaxUtilsServer.
    • Client: Checked.
    • Method: pushListToSysLog.  Will take an Ajax parameter that is a JSON encoded object that contains a "location" field that represents the calling UI Action/UI Policy/Client Script, and an array of watch list sys_ids garnered from any form, and then push this list to the System Log.  Notice this MUST be generic.
  3. New UI Script to contain generic functions for reuse.
    • Name: AjaxUtilsClient.
    • Method: sendListToSysLog.  Will take a location parameter, and a comma delimited list of sys_ids (this can also be an array of sys_ids).  The location parameter will contain a string representing name of the calling control (UI Action/UI Policy/Client Script).  The list of sys_ids can be any list of comma delimited sys_ids (or an array of sys_ids).  The comma delimited list of sys_ids will be converted into an array.  The location, and array will be placed into a listInfo object and using JSON and Ajax will be sent to the AjaxUtilsServer.pushListToSysLog method.
  4. New UI Action associated to the Incident form.
    • Name:  Transfer Watchlist.
    • Table:  Incident.
    • Client: Checked.
    • Method:  sendWatchList.  This will pull the watch list from the Incident form and call the UI Script method AjaxUtilsClient.sendListToSysLog.
  5. Unit Test
    1. Navigate to Incidents -> Open.  The list view of open Incidents will be displayed.
    2. Open one of the Incidents.  You should have a new button labeled: Transmit Watchlist.
    3. Make sure there are four or five people on the watchlist.  Add some if you have to.
    4. Click on the Transmit Watchlist button.
    5. Navigate to System Log.  Order by Created date descending.
    6. Expected Result:  A list of names and emails matching the watchlist should be present as a log entry.

 

 

Development

 

So we have a couple of ways to do this:  1) Top-down,  or 2) Bottom-up.  Either way will work.  I will use top-down for this lab.

 

  1. Navigate to Incidents -> Open.  A list of open incidents will appear.
  2. Click on any Incident number to edit that incident.  The incident form will appear.
  3. Right-click on the form header to bring up the form context menu.
  4. Pick Configure -> UI Actions from the menu.  The Incident UI Action list view will be displayed.
  5. Click on the New button.  The New UI Action form will be displayed.
  6. Fill out the form with the following:
    • Name: Transfer Watchlist
    • Table: Incident
    • Active: checked
    • Show Insert: checked
    • Show Update: checked
    • Client: checked
    • Form Button: checked
    • OnClick: sendWatchList()
    • Code:

 

function sendWatchList() {
  sendListToSysLog(g_form.getValue('watch_list'), 'UIA:Transfer Watch List');
}

 

 

  7.  Click the Submit button to save your work.  [Design element #4 is complete]

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

  9.  Click on the New button.  The New UI Script form will be displayed.

10.  Fill out the form with the following:

    • Name: AjaxUtilsClient
    • Global: checked
    • Active: checked
    • Code:

 

// Transmits a list of sys_ids to the server to be pushed to the System Log.
function sendListToSysLog(list, location) {

  // build the object to be transmitted to the server-side. 
  var infoOnList = {};
  infoOnList.location = location;

  // we are turning this into an array just for demonstration purposes
  // it really isn't necessary to do this.
  infoOnList.list = list.split(','); // turn it into an array


  // set up the Ajax call to the server.
  var systemLogger = new GlideAjax('AjaxUtilsServer');
  // the first parameter is always the name of the function we are calling
  systemLogger.addParam('sysparm_name','pushListToSysLog');
  // here is the secret sauce!  
  // JSON.stringify will package up your object and allow it to be 
  // transmitted to the server where it will be re-constituted into the same object!
  systemLogger.addParam('sysparm_listInfo', JSON.stringify(infoOnList));

  // this is one way of creating an Asynch call with a do-nothing callback.
  systemLogger.getXML(function(result){ });  // no callback action
}



 

 

11.  Click the Submit button to save your work. [Design element #3 is complete]

12.  Navigate to System Definition -> Script Includes.  The list view of Script Includes will be displayed.

13.  Click the New button.  The New Script Include form will be displayed.

14.  Fill out the form with the following:

    • Name: AjaxUtilsServer
    • Client callable: checked.  When you check this box the code template will switch to extending the AbstractAjaxProcessor.
    • Accessible from: all application scopes
    • Active: checked
    • Code:

 

 

 

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

  // method for decoding the passed JSON object and writing it to the system log
  pushListToSysLog: function() {
     // this is how you re-constitute a JSON encoded object.
     // This will rebuild our listInfo object from the front-end (browser)
     var listInfo = JSON.parse(this.getParameter('sysparm_listInfo'));

     // use a message variable to package up our log message.
     var message = '---> \n';
     message += 'Location: ' + listInfo.location + '\n';

     // call our function to go retrieve an object list of names 
     // and emails for the given list of sys_ids
     var userList = ArrayHandlingUtils.getEmailsFromSysIDs(listInfo.list);

     // loop through our object array and build the log entry
     for (var i=0; i < userList.length; i++) {
        message += 'EMail[' + i + ']: ' 
        + userList[i].name + ' - ' 
        + userList[i].email + '\n';
     }
    
     // Write everything to the system log!
     gs.log(message, 'SI:AjaxUtilsServer.pushListToSysLog');
  },

  type: 'AjaxUtilsServer'
});


 

 

15.  Click the Submit button to save your work.  The list view of Script Includes will be re-displayed. [Design element #2 is complete]

16.  Click on the New button again.  The New Script include form will be displayed.

17.  Fill out the form with the following:

    • Name: ArrayHandlingUtils
    • Client callable: un-checked.
    • Accessible from: all application scopes
    • Active: checked
    • Code:

 

var ArrayHandlingUtils = Class.create();

ArrayHandlingUtils.getEmailsFromSysIDs = function(sysIDList) {
    // retrieve all user records in the list of sys_ids
    var userRecords = new GlideRecord('sys_user');
    userRecords.addQuery('sys_id', 'IN', sysIDList);
    userRecords.query();

    var userList = [];
    // loop through all the user records and break out the name 
    // and email into the user object. 
    while (userRecords.next()) {
       var user = {}; // always initialize your object before using!
       user.name = userRecords.name + '';
       var email = userRecords.email + '';
       user.email = JSUtil.notNil(email) ? email : 'no email found';

       // place the user object into the object array
       userList.push(user);
  }

  return userList;
};

 

 

18.  Click the Submit button to save your work. [Design element #1 is complete]

 

 

Unit Test

 

Our unit test should be pretty much what we had in our design [element #5]:

 

  1. Navigate to Incidents -> Open.  The list view of open Incidents will be displayed.
  2. Open one of the Incidents.  You should have a new button labeled: Transfer Watchlist.



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



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



  5. Navigate to System Log -> All.  Order by Created date descending.
  6. Expected Result:  A list of names and emails matching the watchlist should be present as a log entry.

 

 

And there you have it!  We created the code based on the Ajax pattern to organize everything nicely.  The code is generic enough that the UI Script through the Script Include function can be reused and expanded at a later time (extensibility).  Nothing present is hard-coded, but rather data driven.  Finally we demonstrated the method for JSON encoding an object on the front-end (browser), transmitting that object via Ajax to the back-end (server), and JSON decoding the object for use.

 

Best Practices employed:

  • Maintainability
  • Reusability
  • Extensibility
  • Efficient code

 

For more information on JavaScript JSON stringify and parse.

 

Steven Bell

 

 

If you find this article helps you, don't forget to log in and "like" it!

 

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

I do a lot of ServiceNow architecture work for my company.  Every now and then a particular coding practice pattern consistantly drops out of that work.  So I thought I would pass one of these along to you:  How to best organize Ajax code for reusability, and maintainability.

 

As an architect I look for patterns.  What is a pattern?  It is best described as a way to organize a system to be at its best in efficiency, maintainability, and reusability.  Systems are best described as components of an application. 

 

Architecting Rules

 

  1. The solution must be maintainable (read: simple).  Try to keep the number of "moving parts" as small as you can.  Reduce duplicate code to the absolute minimum.  In my book duplicate code equals a refactoring (a fancy coding term for simplifying) opportunity.
  2. The solution must utilize reusable (read: generic data-driven) components.  Being able to reuse components from one solution on another is a time/money/life saver!  Sometimes this will violate rule number one just a bit as you may be forced to add components to achieve reusability.
  3. The solution must be efficient (read: fast-baby-fast!).  The user experience must be as little-impacted as possible.

 

I am a huge proponent of maintainable code.  The old adage of:  "If it was hard to write it ought to be hard to read!" simply does not work as a software engineering practice!  If you can refactor an application so that it is easy to recognize what is being accomplished with the code, AND make it reusable then you have won the battle!  Now if you can go on to make this a standard in the way coders do the work then that becomes a "pattern"!

 

Now that we have gotten some of the definitions out of the way let's talk about the Ajax pattern I use.

 

 

The Ajax Pattern

 

This pattern organizes code and communication between the ServiceNow browser-side application, and the ServiceNow server-side.

 

Consider the following diagram:

 

 

Here I describe a pattern that seems to be more work than you would want to do.  Why would I bother with a UI Script on the front-end?  Why not just have the UI Action use Ajax to call the server-side?  Why would I then bother to have the server-side Ajax Script Include call yet another Script Include?

 

  1. The UI interface components (Client Script, UI Action, UI Policy) all contain minimal code, and call data-driven functions in a UI Script.
  2. The UI Script contains the minimal data-driven code to communicate with the server-side.  This is for maintainability, and reusability.
  3. The Ajax Script Include contains the minimum code necessary to work with Ajax while calling data-driven functions in either a Script Include Class or Function Library.

 

So, back to the architecting bit.  We need to keep in mind some of the basic rules of interface programming:

 

  1. Do not impact the user experience: The wait-cursor is your enemy!
  2. Thin out your browser-side code.  Make the server do as much work as possible.  This has the benefit of reducing the browser footprint in memory, AND helping you to think in terms of reusability.  Generic coding means that the data is the "customization"; not the code.  You want, if possible, data to cause the code to react in certain ways.  To begin to make code reusable do so by "thinning" out your Client Scripts, UI Actions, UI Policies, etc., and pushing the code to UI Scripts.  At which point you may find that some of that code can be pushed further to the server-side.
  3. Use asynchronous Ajax calls to the server, if possible, to free up the user experience.  Don't hold up the user and make them wait while the browser is communicating with the server across the web (synchronous).  Asynchronous good.  Synchronous bad.  Synchronous has its uses, but they should be rare! 
  4. And the final rule: Refactor, Refactor, Refactor.  You want your code to be easy to maintain.  Do NOT have multiple copies of the same code floating around.  You might miss one if you find a bug; at which point the nightmare of finding the bug begins!  I hate nightmares.

 

With the server-side code it becomes a judgement call:

 

  1. If the code is really simple then there is no reason to try to extract functions out of the Ajax Script Include into a Script Include function library.  I mean there is a minimum point; use common sense. :-)
  2. If the code is specific to only that Ajax Script Include, but can be refactored into a function, then keep it in the Ajax Script Include, and turn it into a function.
  3. If the code can be refactored into a set of generic data-driven functions then look at using a Script Include class or perhaps a function library.  Again, I try to envision generic functions that are data-driven and can be reused by future scripts.

 

Remember: Engineer your code.  Think through the process.  The best code is maintainable, flexible, and well organized.  Don't be afraid of doing a bit of extra work to achieve clean code.

 

Steven Bell

 

 

If you find this article helps you, don't forget to log in and "like" it!

 

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

If you've been following our blogs, you know what's new in Geneva, released Dec 8. I'm excited to tell you about Service 360 if you haven't heard about it yet. Service 360 fills a critical gap in the IT Executive's daily view of his/her organization.  We all know organizations today require that their IT be more agile, cost efficient and operate at the speed of the business. Having the right information at the right time has often been a challenge for IT Executives and business service owners. IT Executives want to be on top of things that keep their critical operations humming along:

 

  • Are my services operational?
  • Are my services cost effective?
  • Are my services governed?

 

These are traditionally difficult questions to answer given the complexity of the IT organization.  However, this has been made easy with ServiceNow's introduction of Service 360 with its Geneva release last month.  Here’s some more information about how Service 360 can help you.

 

Using Service 360 and Performance Analytics together enhances the quality of an organization’s Service Portfolio by allowing executives and business service owners to quickly move from strategic to tactical by drilling down to specific records—all with just a few clicks. In fact, there are two reporting layers to Service 360:

  1. 1. A dynamic treemap that is a D3 rendering of all business services across an entire organization based on a score from a specific Category and an Indicator from the Category.
  2. 2. A dashboard, which is a scorecard, that leverages Performance Analytics to render reports from key performance indicators to provide a true 360-degree view into a single Business Service.

 

Layer 1.png

 

Layer2.png

The bottom-line: Service 360 demonstrates the power of leveraging the ServiceNow Platform by being able to pull data from several source solutions into one simple dynamic reporting dashboards. This allows IT executives to focus on resolving operational or risk issues along with viewing key financial or investments of business services with real-time data.

 

 

For more information on Service 360, see:

 

ServiceNow product documentation:

https://docs.servicenow.com/product/service_360/concept/c_Overview.html

 

For general information about ServiceNow, visit: www.servicenow.com

In this episode of ServiceNow Techbytes, bryanbarnard and cesar.sandoval talk about building an API for your ServiceNow app using the new Scripted REST APIs functionality available in the Geneva release.

Podcast+Icon.png

 

volume_icon.png

Listen

 

 

Subscribe

 

to iTunes

 

This episode covers:

  • An overview of Scripted REST APIs
  • Versioning Scripted REST APIs
  • Importance of documenting Scripting REST APIs
  • Use cases for Scripted REST APIs

 

For more information on Scripted REST APIs, please see:

 

Your feedback helps us better serve you! Did you find this podcast helpful? Leave us a comment to tell us why or why not.

 

LISTEN BELOW

 

To catch clips behind the scenes with our podcast guests, and find out what topics we'll be covering next before they are posted, follow @NOWsupport on Twitter. You can also search Twitter using the hashtag #SNtechbytes for all previous podcasts, video clips and pictures.

If your reports are not displaying correctly under the My Reports section, this may due to a recent change to your login information. If your user info has been changed in Fuji since you initially created the report, for example, you have a new primary email address and new username/login, these reports will not show up if you try to view My Reports.

 

For example, if your username was once "Bridget" and you change it to "BridgetB," then the reports you created as Bridget do not show up. They appear missing when you log in with your new credentials and go to the report_home section to view your reports. The reason for this it that there is no longer a match between the [sys_user] and [sys_report] tables.

 

To view the reports you created from your previous username, you can have an admin manually edit the Created By field in the [sys_report] table with the new value of the username. You'll need an admin since this update requires a temporary change to the base system, which should then be reverted back once the update is complete. Here are the easy steps to do this:

 

To enable list editing on the [sys_report] table:

  1. In the navigation filter, enter System UI.
    system_ui.jpg
  2. Go to List Control, and search for sys_report.
  3. Open record and change the List edit type from Disable list editing to Save immediately (cell edit mode).
    List_control.jpg
  4. Navigate to the list view of the [sys_report] table and sort or filter by the Created By field. Replace the old username value with the new username value.

    Reports_Table.jpg
  5. Go back to List Controls and set the List edit type value back to Disable list editing for the [sys_report] table.

Although this works well for selected usernames, keep in mind that if you have thousands of records in the [sys_report] table where the username has been changed, then this workaround is probably not the best fit for a large volume of updates.

 

Having other issues with reporting? Check out these KB articles:

Watch the LIVE demo on January 27, 2016, 11am PT

with Chuck Tomasi, Kreg Steppe, and Dave Slusher

 

In this event, you will be guided through the latest features of the ServiceNow iOS application. The app is more than just an embedded browser, it features quick access to your favorites, the camera, geolocation, fast data entry and response, and more. Watch and learn.

 

Click "Join the Discussion" to Watch the Demo and post your questions for Chuck, Kreg, and Dave.

join-image.png

 

Featured Experts

 

profile-image-display.jspa?imageID=2091&size=200Chuck Tomasi is a Platform Architect for ServiceNow.  He is a computer science major with over 30 years of IT experience. As a former ServiceNow customer, Chuck won the first Innovation of the Year Award at Knowledge 10. Since joining ServiceNow in 2010 as a Technical Consultant, he has done many large scale ITSM implementations and custom applications, acted as an adjunct instructor for Education Services, created and lead the Technical Best Practices program, and co-hosts the ServiceNow series “TechNow”.

 

 

profile-image-display.jspa?imageID=3603&size=200Kreg Steppe is the Orchestration Product Principal for ITOM within ServiceNow. He specializes in developing integration

solutions and automating repeatable processes. Kreg's prior experience includes managing Network Support and Application Development on cloud based networks and DNS maintenance for Manager Tools.

 

 

 

profile-image-display.jspa?imageID=10516&size=200Dave Slusher has been developing software for 20 years for companies such as Intel, Orbitz, Dell Secureworks and many startups lost to history. He has been with ServiceNow for two years, first in Expert Services and now as the Developer Evangelist for the developer community and portal. He earned his BS from Georgia Tech and his MS in Computer Science from the University of Louisiana - Lafayette.

In my previous article we enhanced the C#/.NET webservice example on the wiki using Visual Studio Community 2015.  I introduced some key concepts like WPF instead of Windows Forms, and using the App.config file to contain your userid and password.  Additionally I changed it up a bit and gave an example of a get instead of an insert.  This included how to receive the information coming back from ServiceNow, and make use of it.  Finally, I added in a sprinkling of best and good practices.

 

Wiki article:  Web Services C Sharp .NET End to End Tutorial

My article:   Mini-Lab: Visual Studio Community 2015 and the C# Web Service Example

 

In this article we will continue with this example.  I will be showing how to expand the code and UI to incorporate the recommended best practices from the first part of another Wiki article:

 

Retrieving A Large Number Of Records Using SOAP - ServiceNow Wiki

 

Specifically section 2.1 and 2.1.3: Using Filters to Limit the Number of Results.

 

Well...I will be demonstrating the how, but not exactly like it is described.  I will leave that for you to do! 

 

NOTE: I highly recommend performing both labs in my previous article before continuing with the lab in this article.

 

Steps:

 

1. If you haven't already; you will need to install VS Community 2015.  See my previous article for the links to download and the how-to.

2. Create a new VS WPF Project.

3. Test.

 

 

Lab 2.1: Creating the ServiceNow Filter Limiter Project

 

1. Read through section 2.1 of the ServiceNow Wiki article.  This shows three approaches for limiting the number of records being retrieved.  We will be focusing on the third: Best Practice.

 

2. In the VS IDE navigate to File -> New -> Project.  The new project form will be displayed.

 

3. Fill in the form with the following:

a. Choose: WPF Application

b. Name: ServiceNowFilterLimiter 

c. Click on the OK button to create your project.

 

 

4. A new blank WPF project will be created.

 

5. From the Toolbox tab on the left pull out the following controls:

 

a. RichTextbox control and a button control onto your new form.  You will find the RichTextbox control under the All WPF Controls tree.

 

b. Five Labels

 

c. Five Combo Box controls

 

d. One button control

 

 

e. Arrange them to look something like this (yeah I played with the color on the rtb control):

 

 

f. Change the text of the labels, checkbox and button to be that as you see on the image above.

 

g. Change the names of the controls to the following:

i.     RichTextBox:  rtbResults

ii.    Button:  btnSendQuery

iii.   Checkbox:  chkActive

iv.   Impact Combo:  cmbImpact

v.    Urgency Combo:  cmbUrgency

vi.   Priority Combo:  cmbPriority

vii.  State Combo:  cmbState

viii. User Combo:  cmbUser

 

NOTE: It is also a good practice to change the names on your labels as well.

 

h. This is probably a good point to save everything.  Navigate to File -> Save All.

 

6.  Now right-click on the ServiceNowFilterLimiter project to bring up the project context menu.

 

7. Navigate to Add -> Service Reference.  This will bring up the Add Service Reference Form.

 

 

8. Fill in the form with the following:

a. Address: https://<<servicenow instance name>>.service-now.com?incident.do?wsdl

b. Namespace: incidentTable

 

 

9. Click the Go button.  A popup will ask you to fill in the userid and password.  BEWARE: It will send these in cleartext to ServiceNow.  Don't give that user too much authority.  Read should be sufficient.  We will be adding security next.

 

10. After a proper connection has been made the Add Service Reference form should look like this:

 

 

 

11. Click the OK button.  A new Service Reference will be added to the project.

 

 

12. In the solution Explorer double-click on the App.config entry.  This will display the App.config file in the editor.  We will be adding some security, and the uid/pw data.

 

a.  Replace the the following XML to the <Security mode=... /> line with the following code:

 

 

                    <security mode="Transport">
                        <transport clientCredentialType="Basic" proxyCredentialType="Basic" realm="">
                            <extendedProtectionPolicy policyEnforcement="Never" />
                        </transport>
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>

 

 

 

b. Add in a new section at the bottom after the </system_serviceModel> tag.  I used my uid/pw combo that I created in a previousarticle.  BTW, this is a best practice!  Never hard-code this stuff in your code!

 

 

 

    <appSettings>
        <add key="userID" value="svcuser" />
        <add key="password" value="Pragmat1$t10"/>
    </appSettings>

 

 

NOTE: If you REALLY want to crank down on the security of this file I found the following article excellent reading:

Jon Galloway - Encrypting Passwords in a .NET app.config File

 

c. Save the file (ctl-s).  The file should look something like this:

 

 

 

13. Next we will add the Configuration reference to our project.  This is not (and in my book should be) done for you automatically.

 

a. Right-click on the References section in the Solution Explorer, and choose Add Reference from the context pop-up.  This will display the Add Reference form.

 

 

b. Scroll down and find the System.Configuration entry.  Click on this; a check box will be displayed.  Make sure this is checked.

 

 

c. Click OK to save your work.

 

14. Right-Click again on the ServiceNowFilterLimiter project to bring up the project context menu.

 

15. Navigate to Add -> New Item.  This will bring up the New Item Form.  We will be adding a class that will be used as an object to contain our listed items in each combo box.

 

a. Choose Class, and make the name ComboBoxItem.cs

b. Click on the Add button.  This will display an empty class form.

 

c. Replace all the code on the form with the following:

 

 

namespace ServiceNowFilterLimiter
{
    public class ComboBoxItem
    {
        public string Value = "";
        public string Text = "";


        public ComboBoxItem(string val, string text)
        {
            Value = val;
            Text = text;
        }

        public override string ToString()
        {
            return Text;
        }
    }
}

 

 

16. Now we will be adding in the core code that will do the work. 

 

a. Double-click on the Send Command button.  This will bring up the MainWindow code file.  You can also do this from the Solution Explorer.

 

 

 

b. Replace both the MainWindow() and btnSend_Click() methods with the following code:

 

NOTE: I have added in comments to clarify what the code does.

 

Best Practice:  Add #region tags into your C# code to help organize sections that are related.  This also helps you "minimize" sections of code to help manage large code files.

 

 

        public MainWindow()
        {
            InitializeComponent();
            initializeCombos();
            initializeResults();

            chkActive.SetCurrentValue(CheckBox.IsCheckedProperty, true);  // default active to checked
        }

        private void btnSendQuery_Click(object sender, RoutedEventArgs e)
        {
            // Clear out our results (otherwise each query would tack onto the next)
            rtbResults.Document.Blocks.Clear();

            // declare our soap object for transmission
            incidentTable.ServiceNowSoapClient soapClient = new incidentTable.ServiceNowSoapClient();

            // pull the cred info from the app.config
            soapClient.ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["userID"];
            soapClient.ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["password"];

            // Initialize our query
            incidentTable.getRecords getRecordsQuery = new incidentTable.getRecords();

            // Initialize our response. And since we don't know how many will be returned put it into a List object
            List<incidentTable.getRecordsResponseGetRecordsResult> responseList = new List<incidentTable.getRecordsResponseGetRecordsResult>();

            // Activate query components based on what was chosen by the user in the UI
            getRecordsQuery.assigned_to = cmbUser.SelectedValue != null && ((ComboBoxItem)cmbUser.SelectedValue).Value != "" ? ((ComboBoxItem)cmbUser.SelectedValue).Value : null;
            getRecordsQuery.state = cmbState.SelectedValue != null && ((ComboBoxItem)cmbState.SelectedValue).Value != "" ? ((ComboBoxItem)cmbState.SelectedValue).Value : null;
            getRecordsQuery.priority = cmbPriority.SelectedValue != null && ((ComboBoxItem)cmbPriority.SelectedValue).Value != "" ? ((ComboBoxItem)cmbPriority.SelectedValue).Value : null;
            getRecordsQuery.urgency = cmbUrgency.SelectedValue != null && ((ComboBoxItem)cmbUrgency.SelectedValue).Value != "" ? ((ComboBoxItem)cmbUrgency.SelectedValue).Value : null;
            getRecordsQuery.impact = cmbImpact.SelectedValue != null && ((ComboBoxItem)cmbImpact.SelectedValue).Value != "" ? ((ComboBoxItem)cmbImpact.SelectedValue).Value : null;

            try
            {
                // Now fire off our query.  Use a LINQ command to Order the results
                responseList = soapClient.getRecords(getRecordsQuery).OrderBy(o => o.number).ToList();

                // use a StringBuilder so that we can take advantage of the built-in formatter
                StringBuilder results = new StringBuilder();
                results.AppendFormat("Total records found: {0}", responseList.Count);

                // Loop through our response records and print them to the results window
                foreach (incidentTable.getRecordsResponseGetRecordsResult response in responseList)
                {
                    results.AppendFormat("\n{0}\t{1}\t{2}", 
                        response.number, 
                        response.severity, 
                        response.short_description.TrimEnd());
                }

                // Post the results for display
                rtbResults.AppendText(results.ToString());
            }
            catch (Exception error)
            {
                // something bad happened!
                rtbResults.AppendText(error.Message);
            }
        }

        #region Initializers
        /// <summary>
        /// Method for initializing all of the Combo boxes on the form.
        /// </summary>
        private void initializeCombos()
        {
            // allow only values from the checkbox lists
            cmbImpact.IsEditable = false;
            cmbPriority.IsEditable = false;
            cmbUrgency.IsEditable = false;
            cmbState.IsEditable = false;

            cmbImpact.Items.Clear();
            cmbImpact.Items.Add(new ComboBoxItem("", ""));
            cmbImpact.Items.Add(new ComboBoxItem("1", "High"));
            cmbImpact.Items.Add(new ComboBoxItem("2", "Medium"));
            cmbImpact.Items.Add(new ComboBoxItem("3", "Low"));

            cmbPriority.Items.Clear();
            cmbPriority.Items.Add(new ComboBoxItem("", ""));
            cmbPriority.Items.Add(new ComboBoxItem("1", "High"));
            cmbPriority.Items.Add(new ComboBoxItem("2", "Medium"));
            cmbPriority.Items.Add(new ComboBoxItem("3", "Low"));

            cmbUrgency.Items.Clear();
            cmbUrgency.Items.Add(new ComboBoxItem("", ""));
            cmbUrgency.Items.Add(new ComboBoxItem("1", "Critical"));
            cmbUrgency.Items.Add(new ComboBoxItem("2", "High"));
            cmbUrgency.Items.Add(new ComboBoxItem("3", "Medium"));
            cmbUrgency.Items.Add(new ComboBoxItem("4", "Low"));
            cmbUrgency.Items.Add(new ComboBoxItem("5", "Planning"));

            cmbState.Items.Clear();
            cmbState.Items.Add(new ComboBoxItem("", ""));
            cmbState.Items.Add(new ComboBoxItem("1", "New"));
            cmbState.Items.Add(new ComboBoxItem("2", "Active"));
            cmbState.Items.Add(new ComboBoxItem("3", "Awaiting Problem"));
            cmbState.Items.Add(new ComboBoxItem("4", "Awaiting User Info"));
            cmbState.Items.Add(new ComboBoxItem("5", "Awaiting Evidence"));
            cmbState.Items.Add(new ComboBoxItem("6", "Resolved"));
            cmbState.Items.Add(new ComboBoxItem("7", "Closed"));

            cmbUser.Items.Clear();
            cmbUser.Items.Add(new ComboBoxItem("", ""));
            cmbUser.Items.Add(new ComboBoxItem("46d44a23a9fe19810012d100cca80666", "Beth Anglin"));

        }

        /// <summary>
        /// Method for setting up the results box at program load.
        /// </summary>
        private void initializeResults()
        {
            // clear out the rich text box
            rtbResults.Document.Blocks.Clear();

            // make the scrollbar to be visible if results larger than display
            rtbResults.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;

            // kill the pesky extra new lines style
            Style noSpaceStyle = new Style(typeof(Paragraph));
            noSpaceStyle.Setters.Add(new Setter(Paragraph.MarginProperty, new Thickness(0)));
            rtbResults.Resources.Add(typeof(Paragraph), noSpaceStyle);
        }
        #endregion

 

17. We are ready to test!  Navigate to Debug -> Start Debugging.  This will run our application and display the form.

 

a.  Impact: High

b.  State: Active

c.  Make sure Active is checked (should be defaulted according to the code right?!)

d. User: Beth Anglin

e. The results should appear in the window on the form.

 

18. Click on the Send Command button.  My results looked like this:

 

 

NOTE: If you get nothing back then go over to your instance and see if there are any records you can retrieve using the above settings.

 

19. Finally always clean up your unused Usings (another best practice)!  See my previous article on how to do this.

 

 

That's it!  You have now implemented a more complex form for limiting the results to be returned from ServiceNow!  Cool, huh?!  :-D

 

Steven Bell

 

 

If you find this article helps you, don't forget to log in and "like" it!

 

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: