Skip navigation

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 Scripting in ServiceNow.

 

NOTE: I have updated the text of this article to reflect the latest version of ServiceNow.  SBell - 9/12/16

____________________________________________________________________________

 

 

One of the many topics I cover, when teaching the ServiceNow Scripting Class, is Event Handling.  Students are sometimes confused by how this is handled inside of ServiceNow.  It is actually pretty straightforward, and I will demonstrate with a simple diagram.

 

First let me give you an example of the "why".  As in "why" would you use events in the first place.  Events allow you to disconnect separate processes from each other.  Suppose a Business Rule (BR) is running and needs to make a call to a Script Include (SI) to update a series of records (which could take 30 seconds or longer).  If my BR should directly call the SI to run the code it would hold up the entire process (and I may not particularly need any sort of return from the SI).  Rather than hold up the user experience waiting for things to finish I can instead call an event and run the SI in the background which would immediately allow the BR to continue and complete; returning control to the user (who is happy because they didn't have to wait!).

 

So here is the picture I now use in my class:

 

Event Handling in a Picture.png

 

0. I register my new event in the Registry, create my Script Action associated to that event, and if needed my Script Include which could be called by the Script Action.  Registering my event tells the Worker to listen for that event, and that it will be expected to do something with it.

1. Something executes a gs.eventQueue statement which writes a event record on the queue.  BTW, this is not an exhaustive list.

2,3,4. The event worker(s), whose job it is to listen for events listed in the Registry, picks up the event and sees if there is a Script Action(s) associated with the registered event.

5,6. If a Script Action is found to run then it is executed which in turn may execute my Script Include if I choose.

 

So, there you have it!

 

Steven Bell.

 

P.S. Oh yeah, and events are used for notifications as well, but more on that later. :-)

 

 

Combined Logo Graphic.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!

angularjs servicenow.jpegAngularJS is a powerful framework developed to make HTML more usable for web applications. It can be used in ServiceNow instances to build powerful and dynamic single-page apps in the CMS, in scoped applications, or just as a standalone UI page. It is also a great alternative to building Jelly templates, a trait many SN developers cheer heartily.


JohnAndersonMX42 has written a few very useful blog posts about AngularJS in ServiceNow as well, such as Adding AngularJS and other Javascript Libraries to your Instance and a mini-series on his personal site http://www.john-james-andersen.com. Be sure to check those out if interested.


I'm a firm believer in baby steps, so our goal in this tutorial will be very simple: to install the AngularJS framework and create a single-UI-page Angular app that returns a message "Hello from Angular!" from your Angular controller.


At the end of this tutorial you will:

  • Have the AngularJS library installed on your instance
  • Know the right syntax to include Angular in a UI page or application
  • Know how to declare an Angular module and controller, and get it to successfully update your view (your HTML).

 

1. Install the AngularJS Library

 

The AngularJS library does not come pre-installed in ServiceNow. To develop with AngularJS, we will need to install it manually as a UI Script.

  1. Go to https://angularjs.org/ and click the blue Download button. 
  2. For branch, pick the latest, then copy the link for the CDN. Paste this url into your browser's address bar.
  3. Select the entire contents of that script and copy it to your clipboard. 
  4. Now go to your instance, and go to UI Scripts.
  5. Click "New". Name it "angular.min", set to Global, set to Active, and click Submit.
  6. Now that you are back in the list view on the UI Scripts table, make sure you can view the "Script" column on your view. Double-click the Script cell on your angular.min row and paste the contents of your clipboard into it.
  7. Hit enter.

 

The library is now installed on your instance!

Screen Shot 2015-08-27 at 3.47.19 PM.png

 

2. Create a new UI page


Now that the AngularJS library is installed on your instance, create a new UI page by going to System UI > UI Pages. Name it “Angular Hello World.” On line 3, under the jelly start tag, we are going to include the Angular library with a g:requires tag. Put this code on line 3 to require our Angular library when the UI page loads:

 

<g:requires name="angular.min.jsdbx"/>



















 

 

3. Define your Angular module


Now that we are including AngularJS in our UI page, we are going to declare our angular module. You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.

 

At line 4, add this code to declare your Angular module. We are going to call our module angularHelloWorldApp.

 

<!-- angular modules -->
<script type="text/javascript">var app = angular.module("angularHelloWorldApp",[]);</script>



















 

And save. Our Angular module is now defined.


Now we need to link our module to our DOM. We do this by putting "ng-app" into an HTML element. The "ng-app" is a directive to auto-bootstrap an AngularJS application. The ngApp directive designates the root element of the application and is typically placed near the root element of the page - e.g. on the <body> or <html> tags. (more)

 

Insert a body tag on line 4 just under your <g:requires> tag, and link the app to it like this:

 

<body ng-app="angularHelloWorldApp">
</body>



















 

 

4. Define the Controller


The ngController directive attaches a controller class to the view. This is a key aspect of how angular supports the principles behind the Model-View-Controller design pattern. (more) We will be using our controller as a place to store properties with values that we will later send into our view (our HTML). On line 8, just above your </j:jelly> closing tag and below your module, put:

 

<!-- angular controllers -->
<script type="text/javascript">
  app.controller('MainController', ['$scope', function($scope) {
      $scope.greeting = "Hello from Angular!";
  }]);
</script>



















 

Our $scope data property will allow us to access data from Angular in our DOM. We will be able to access our greeting property in our HTML soon. Now, we'll tie the controller to our DOM. On line 4, insert these on new lines inside of the <body></body> tags:

 

  <div ng-controller="MainController">
      <h1>{{ greeting }}</h1>
  </div>



















 

We just tied our controller to this div, then inside of an h1 tag we used an Angular expression which will query our Angular scope data for a property named greeting, and should return the value of it, rendering it as HTML.

Now that we have defined our Angular module, bound it to our HTML body element, defined our controller, bound that to a div tag, and included an expression, it's time to try it out! Click Try It.


If you see Hello from Angular! then congratulations! You just used Angular to populate and manipulate your HTML document.


Here is what my final markup looks like on the UI Page:

<?xml version="1.0" encoding="utf-8"?>
<j:jelly xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null" trim="false">
  <g:requires name="angular.min.jsdbx"/>
    <body ng-app="angularHelloWorldApp">
        <div ng-controller="MainController">
            <h1>{{ greeting }}</h1>
        </div>
    </body>
    <!-- angular modules -->
    <script type="text/javascript">var app = angular.module("angularHelloWorldApp",[]);</script>
    <!-- angular controllers -->
    <script type="text/javascript">
      app.controller('MainController', ['$scope', function($scope) {
            $scope.greeting = "Hello from Angular!";
      }]);
  </script>
</j:jelly>





 

For more information, review:

If you don’t already know about the favorites functionality of the ServiceNow mobile UI or you just haven’t gotten around to setting them up, you should because it will save you a ton of time and make you more efficient.

 

The mobile user interface Favorites feature is a list of icons that appears on your mobile homepage. These can be used to create a shortcut to any record, list, form or catalog item. When users log into the mobile UI for the first time, they’ll see some instance generated defaults that can be deleted or modified without affecting any other users. This means that you can skip the filtering and breadcrumbing and go straight to the record you want. If it is a form or catalog item you view often, this can save a lot of hassle much like having your mom and dad on speed dial.

 

Favorites make accessing your frequently used customized lists easier. For example, let's say you have a list you use on a regular basis and every time you log back into your instance you have to reapply your filters. This can be really frustrating (especially if you have big ol' meaty fingers), using a mobile favorite can eliminate this process. Maybe you also have catalog items you use frequently or a specific record you need to keep an eye on, both of these are also great candidates for your mobile interface favorites list. The easiest way to think about it is like bookmarking and creating shortcuts on your desktop for the sites you use the most often, but in this case they are ServiceNow items.

 

How to setup your Favorites on your Mobile device:

  1. Browse to the desired record, list, form, or catalog item.
  2. Tap and hold the homepage button on the lower left of the screen. home icon.png
  3. A pop-up will appear where the Title, Color, and Icon of the new favorite can be set.
  4. Pick a color, name your favorite, and click save.

home favorites.png

 

Setting up default Mobile home page favorites

Your favorites can also be accessed directly through your instance by navigating to System Mobile UI → Home Page Favorites. This list records the favorites for all users. You may notice some records with a blank user field, these are default favorites given to all users.

 

You can create default favorites for your users by using the process above, then clearing out the user field in the System Mobile UI → Home Page Favorites settings in your desktop UI. You can also manually add favorites here by clicking the New button. Creating a new default favorite won’t automatically push this to existing users. Though you can still manually add these favorites for existing users. This is done by either going to the record and using the Insert to duplicate the record, then adding the users name. Or you can delete the existing users current favorites which will reset them to new default favorites like they're a new user again.

 

Trying to add favorites to multiple accounts can be a difficult and time consuming process, which is why we don't recommend it. The favorites functionality is designed to set-up customization for individual user by the user. Not everyone accesses the same record, form, or list item throughout a 200-person company.

 

Setting up your Mobile UI favorites is quick and easy. Taking five minutes to set up your customizations can save you the pain and time of going in a reapplying those pesky filters and searching through your service catalog to find that frequently used item. Now you can use that time for other things, such as a quick game of ping-pong with the co-workers or having a walking a meeting with your team mate.

When testing out new code in either Fix Scripts or Scripts - Background I find it difficult to test Background Scripts or gs.eventQueue code without having the current object around.  There are a couple of approaches you could take when creating this.  One is only OK, and the other is well...right on!

 

The OK approach:

 

var current = {};
current.assigned_to = {};
current.assigned_to.sys_id = "<<your favorite user's sysid here>>";
current.assigned_to.name = "Fred Flintstone";
current.assigned_to.manager = {};
current.assigned_to.manager.sys_id = "<<your favorite user's manager sysid here>>";
current.assigned_to.manager.name = "Wilma Flintstone";
current.number = 'INC1234567';
current.state = 1;
current.short_description = 'This is a test.';
// ... and so on.

gs.print('Assigned To Manager: ' + current.assigned_to.manager.name);




 

Yuck.  Oh, and did I mention this approach, while interesting, does not create a true GlideRecord object?  It has it's uses.

 

My favorite approach is to create a current "Factory" that will produce a current object according to my specifications.  You could place this Factory into a Function Script Include and call it as needed from your Fix Script or Scripts-Background.

 

var tableName = 'incident';
var order = {type:'descending', field:'sys_updated'};
var encodedQuery = ''; // in case there is a constraint needed like: sys_id=...

var current = currentFactory(tableName, order, encodedQuery);

gs.print('--->\Number: ' + current.number + '\nState: ' + current.state + '\nShort Description:' + current.short_description);


function currentFactory(tableName, order, encodedQuery) {
  var currentRecords = new GlideRecord(tableName);

  if (JSUtil.notNil(encodedQuery)) {
    currentRecords.addEncodedQuery(encodedQuery);
  }

  if (JSUtil.notNil(order)) {
    if (order.type == 'descending') {
      currentRecords.orderByDesc(order.field);
    }
    else {
      currentRecords.orderBy(order.field);
    }
  }

  currentRecords.setLimit(1); // there is always ONLY one record in current
  currentRecords.query();

  if (currentRecords.hasNext()) {
    currentRecords.next();
  }

  return currentRecords;
}




 

I have added this functionality to a Script Include utility and placed it out on ServiceNow Share.

 

Anything to make unit testing easier!

 

Steven Bell

 


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

Being able to debug your scripts on the desktop can save a lot of time. Troubleshooting the mobile user interface on a desktop gives you access to your desktop’s full sized keyboard and monitor, and browser's developer tools. Using your desktop to debug your mobile interface issues makes troubleshooting easier in situations where a physical mobile device is not available to you. I will show you how to access the supported Mobile and Tablet user interface on a desktop browser, as well as some information on tools that can be used to help with the troubleshooting process.

 

 

Using the Mobile UI on a Desktop Browser

Both the Mobile and Tablet UI can be accessed on any supported desktop browser. Going through the steps on a desktop will give you all of the functionalities of the desktop but on the limited interface of the device. Sounds like a strange approach? Not quite. Using your company's mobile interface on a desktop can help find any holes in functionality or troubleshoot issues that arise.

Mobile+on+Desktop.jpg

    Above:  The ServiceNow Mobile UI within a Chrome desktop browser

 

 

You can access the mobile device interface by using these URLs, just add your instance name where indicated:

  • If you want the smartphone mobile UI add /$m.do to the end of your instance name: https://<instance_name>.service-now.com/$m.do
  • If you want the tablet interface, you add $tablet.do to the end: https://<instance_name>.service-now.com/$tablet.do

 

Once you're in either of these alternate UIs, you can get back to the desktop UI by using the sysparm_device=doctype, like this:

https://<instance_name>.service-now.com?sysparm_device=doctype

 

While the look and feel won't be exactly the same, you will be able to interact with the instance in the same way you would on a mobile device.  Right-clicking can be used to simulate a 'tap and hold' action.

 

 

Mobile Device Emulation

There are a couple of options that can be used to more closely model a smartphone or tablet's appearance and behavior. This can be important when you want an accurate representation of how something will look on a mobile device.  These options allow you to do things like set the display to a phone's native resolution, or to jump between landscape and portrait format without manually resizing you browser window.  Chrome and Xcode provide developer tools for Macs and PCs to emulate different devices. Using these options provides a more accurate representation of how your changes will appear on your device of choice.

 

debugging mobile.jpg

Above:  The XCode iOS Simulator

 

 

Chrome (Mac and PC)

Chrome has mobile device emulation built into its developer tools. This can be accessed using the following steps:

  1. Access the developer tools:
    1. In Windows: Control + Shift + I
    2. In Mac: Press option + ⌘ + J
  2. Press escape, then select the “Emulation” tab.
  3. Here there are a number of options, but the 'Model' selector will allow you to quickly emulate a number of devices.

Chrome Mobile Emulator.png

Above:  The Chrome Developer Tools window, with the Emulation tab shown

 

 

XCode (Mac Users)

XCode is an IDE suite for OSX and iOS that includes an iOS simulator. XCode can be used to simulate a variety of iPhone and iPad devices.  Due to an issue with the way XCode handles its user string, this option may not work on Eureka Instances, but will perform as expected in Fuji, as well as instances still using the legacy mobile UI. Details on the use of this tool can be found on Apple’s developer site.

 

 

Debugging a Physical Mobile Device with a Desktop Computer (Mac Only).

In some rare cases, you'll run into an issue that isn't reproducible on the desktop browser, but does occur on your phone or tablet.  In these cases, iOS devices can be connected to a PC/Laptop and debugged with the Safari browser's Web Inspector.  Unfortunately  was introduced in Safari 6, after Apple stopped updating Safari on Windows.  So, you’ll need a mac using OSX 10.7.4 or later to do this.

 

  1. Connect your iPhone / iPad to your computer using a USB cable.
  2. Open Safari on your Mac
  3. Browse to Safari → Preferences
  4. Click on the Advanced tab
  5. Check the checkbox next to “Show Develop menu in menu bar"
  6. On your iPhone, open your browser and log onto your instance.
  7. On your Mac, Click on Develop → iPhone → <site name>

 

This will open the Web Inspector, while will allow you to inspect elements of the page, as well as view console logs.

Safari Develop Tools.png

Above:  The Safari Develop Menu.  With the connected iPhone highlighted

 

In the image above, you can see the connected iPhone listed in the Develop Menu.  Note that just above it is the iOS Simulator mentioned earlier.  If you're using the Simulator, you can also use Safari’s developer tools to debug the iOS simulator using the same steps.

 

debugging on mobile.jpg

  Above:  The iOS Simulator alongside the Safari Web Inspector

 

More information see Web Inspector.

 

The above information should allow you to access the Mobile and Tablet UI on your desktop, giving you the ability to test and debug on your instance.  This should speed up your development process and make it much easier to identify and correct client side issues that come up during the creation of new content for mobile.

 

Related Articles

 

KB0549598:  Known Error:  console.log statements can cause errors in Internet Explorer 9 if the browser console is not open.

KB0546854:  Info on troubleshooting UI Policies.  Examples include the use of the developer console to find an error.

KB0547069:  Determining if there are client script errors on your instance.

KB0550574:  Troubleshooting form issues

Using the Smartphone Interface - ServiceNow Wiki

I see this particular coding technique from time-to-time.  Use of the eval(...) statement to be able to dynamically get and/or set a GlideRecord record field.

 

var incidentRecords = new GlideRecord('incident');
incidentRecords.query();

while (incidentRecords.next()) {
    var incidentField = 'incidentRecords.impact';
    var incidentFieldValue = eval(incidentField) + '';  // don't do this please.
    gs.print(incidentFieldValue);
}

 

The value could be accessed without using eval by using the array bracket notation:

 

var incidentRecords = new GlideRecord('incident');
incidentRecords.query();

while (incidentRecords.next()) {
    var incidentField = 'impact';
    var incidentFieldValue = incidentRecords[incidentField] + '';  // this is a best practice
    gs.print(incidentFieldValue);
}

 

Okay, so here is a use case example:

 

var tableName = 'incident';
var fieldName = 'impact';
var fieldValue = 1;
var checkName = 'number';
var checkValue = 'INC0010023';

setFieldValue(tableName, fieldName, fieldValue, checkName, checkValue);

function setFieldValue(table, field, value, check, checkValue) {
    var genericTable = new GlideRecord(table);
    genericTable.addQuery(check, checkValue);
    genericTable.query();

    while (genericTable.next()) {
       genericTable[field] = fieldValue + '';
       // genericTable.update();
       gs.info('---> table: {0}, field: {1}, field value: {2}', table, field, genericTable[field]);
    }
}

 

 

As you can see you could really get wild with this if you had some sort of driver table that you pulled the information from that you wanted to pass to the setFieldValue function.

 

If you want to know more about why eval should be avoided read in the book 'JavaScript: The Good Parts' by Douglas Crockford (link).  The big one is:  "It compromises the security of your application because it grants too much authority to the eval'd text." p.111.

 

Remember: eval is evil!  :-)

 

Steven Bell

Processors provide an end-point (URL) allowing you to format ServiceNow data in almost any way imaginable. You are likely already using processors and not even aware of them. We’ll show you some common usage and then demonstrate how to unleash this amazing feature. ctomasi and steppek dig deep in to one of the most powerful and flexible features of ServiceNow – processors.

 

Chuck and Kreg's agenda include:

 

 

For more information, see:

Creating a custom processor

ServiceNow Demo Channel - YouTube

 

 

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

In the latest episode of TechBytes, grant.hulbert explains how developers can add their innovative applications to the ServiceNow Store and sell them or offer them for free to others in the ServiceNow ecosystem.

 

volume_icon.png

Listen

Subscribeto iTunes

News Bulletin:

ServiceNow | Delivering Enterprise Value with Service Management

KPMG brings ServiceNow guru to New Zealand | Scoop News

Submit your ServiceNow Selfie!

 

For more information on the ServiceNow Technology Partner Program, see:

ServiceNow Developer Portal

CreatorCon at Knowledge16

ServiceNow Store

Technology Partner Program

NOWsupport - YouTube

ServiceNow Developer Site

 

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

Another entry to the useful tools series ("Preview GlideRecord Script" Tool, "Grab Grouped Information" Tool, "View Data to Preserve" Tool and "Clear User Settings" Tool), here is the "Create Module From Query" tool.  It came to be because I was searching through some records and thought that I really should add a Module for the admins and then I wished I could just turn this query into a Module.

 

A UI Context Menu, it allows you to create a module from a List View that you have filtered down (or not) to a specific set of criteria.  Just go to a list of records, filter it down to what records you want to see and then select the "Create Module From Query" menu item from the List View's header context menu (it only appears for users with the "admin" role):

_Screenshot_001.png

You'll get a confirmation dialog...

_Screenshot_002.png

...and once you confirm, the Module record will be created and you'll be redirected to the record to finish configuring it:

_Screenshot_003.png

It creates the record with the table name, view and condition(s).  Add the Title, the Application menu it should show up in, the Order, etc... and you are all set.  I tried to write it without creating the record first, but the Filter field would not default properly past the first condition.

 

Here's the details for the record you need to create:

System UI \ UI Context Menus

Table:     Global [global]

Menu:      List Header

Type:      Action

Name:      Create Module From Query

Order:     115,000

Active:    Checked

Condition: gs.hasRole("admin")

Action Script:

(function u_createModuleFromQuery(){
    if(confirm("Are you sure you want to create a new Module?\n\nA new record will be created and you will be redirected to it in order to finish its configuration.")) {
        //show a dialog so the user knows something is happening
        showLoadingDialog();
        //create the record
        var gr = new GlideRecord("sys_app_module");
        gr.initialize();
        gr.link_type = "LIST";
        gr.name = g_list.tableName;
        gr.filter = g_list.getQuery();
        gr.view_name = g_list.getView();
        var newModule = gr.insert();
        //and then redirect the user to the new record
        window.location = "sys_app_module.do?sysparm_query=sys_id=" + newModule;
    }
})();

 

You will not use it often, but it's worth having for those times you do need it. 

*** 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 ***

So a different problem presented itself awhile ago, and I thought I would share the solution.

 

How do I do a distinct on multiple fields inside of a GlideRecord?

 

As you are aware a GlideRecord does what amounts to a SQL "SELECT * FROM...". So the problem was how do you do something like the following:

 

SELECT DISTINCT(*) FROM incident WHERE active=1

 

Well, you can't exactly, but you can come close:

 

var incidentRecords = new GlideRecord('incident');
incidentRecords.addActiveQuery();
incidentRecords.query();

var incidentList = [];

while (incidentRecords.next()) {
   // combine the fields into a single record and push onto the one-dimensional array
   incidentList.push(incidentRecords.cmdb_ci + '|' + incidentRecords.assigned_to);
}

// now we can run the distinct
incidentList = new ArrayUtil().unique(incidentList);

// we have the result set with all dups removed and can now break it apart to do work on it.
for (var i=0; i < incidentList.length; i++) {
   // now you can reconstitute the fields    
   var incidentSplit = incidentList[i].split('|');
   var cmdb_ci = incidentSplit[0];
   var assigned_to = incidentSplit[1];

   // do more work
}





 

 

BTW, I hash this out in more detail in an article I wrote here: ServiceNow Admin 101: You Too Can Do DISTINCT Queries Using GlideRecord - Cloud Sherpas

 

Steven Bell

 

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

I've had this be a pain for me from time-to-time.  I will put gs.log or gs.info statements all over my code to flag values and push them to the log.  gs.log and gs.info places these messages onto the worker queue where they are picked up by the next available worker who then dutifully writes the entry out to the log...but, not necessarily in order!

 

For example:

 

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!');

 

You may get results like this:

 

2015-08-19 14:47:49   Information ---> 3. This is message 3! *** Script

2015-08-19 14:47:49   Information ---> 2. This is message 2! *** Script

2015-08-19 14:47:49   Information ---> 1. Hi there *** Script

2015-08-19 14:47:49   Information ---> 4. Hi there, again! *** Script

 

With lots of other entries placed in between.  Not optimal.  :-/

 

Okay, so i want these in order!  To overcome this I use something like the following:

 

var message = '--->\n';
message += '1. Hi there\n';
message += '2. This is message 2!\n';
message += '3. This is message 3!\n';
message += '4. Hi there, again!\n';

gs.log(message);

 

And order is restored (and all of the messages are placed in a single log entry)!

 

2015-08-19 14:53:05 Information

--->

1. Hi there

2. This is message 2!

3. This is message 3!

4. Hi there, again!

 

So what might this look like for real?  Here is a possible use-case:

 

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

var incidentRecords = new GlideRecord('incident');
incidentRecords.setLimit(5);
incidentRecords.addActiveQuery();
incidentRecords.query();

while(incidentRecords.next()) {
    try {
      message += 'Incident number: ' + incidentRecords.number + '\n';
      message += '\tState: ' + incidentRecords.state.getDisplayValue() + '\n';
      message += '\tImpact: ' + incidentRecords.impact.getDisplayValue() + '\n';
    }
    catch(err) {
      message += 'ERROR! Something freakishly terrible happened! Error: ' + err + '\n';
    }    
}

gs.log(message, 'My really great message');

 

2015-08-19 15:00:11 Information

--->

Incident number: INC0000002

  State: Awaiting Problem

  Impact: 1 - High

Incident number: INC0000003

  State: Active

  Impact: 1 - High

Incident number: INC0000007

  State: Awaiting User Info

  Impact: 1 - High

Incident number: INC0000015

  State: Active

  Impact: 1 - High

Incident number: INC0000016

  State: Active

  Impact: 1 - High

 

And there you go!  An alternative to the random order logging problem!

 

Steven Bell

Having separate admin accounts has been bugging me for a while now.  Why should we login with one account to do certain work and then another for other work.  We have LDAP/SSO-linked accounts that are usually members of assignment groups with specific roles so we can perform our day-to-day work but have to login with our "admin" account to perform any admin-type work.  Plus it costs 2 licences per administrator.  That's crazy.  So I decided to dig around a bit and came up with a way for your administrators to have just one account and it works quite well.

 

Try this in dev first, but the following is what you would end up doing in your production instance:

  1. check the "Elevated privilege" on the "admin" Role record and save it
  2. in case you have not done this already, create a Group called "ServiceNow Platform Administrators" or something similar
  3. add the "admin" and "security_admin" Roles to the Group
  4. add your administrators to the Group (their LDAP/SSO-linked accounts)
  5. the users will inherit the 2 Roles from the Group
  6. add passwords to the administrators accounts and check the "Password needs reset" field
  7. have your admins log into the instance through the side_door.do page using their LDAP/SSO User ID and new ServiceNow password
  8. they'll be asked to change their password
  9. now they are logged into ServiceNow, bypassing any external authentication, with their "regular" everyday permissions
  10. if they need to do any admin-type work, they can just elevate their privileges

 

This is basically how the "security_admin" role is setup now.  You have to elevate your privileges in order to perform certain tasks.  Why should it be any different for the "admin" role?

 

You can even add a few of the other "*_admin" roles, like "user_admin", "itil_admin", "knowledge_admin", etc... to the Group record so your admins have a little bit more permissions than they would with just "itil" so they do not have to elevate to "admin" as often.

 

That is all fine and dandy for your production instance, but what about your sub-prod ones?  You do not want to log into dev and then have to elevate to your admin privileges all the time, right?  There's an easy fix for that - just create a Clone Data Preserver record to preserve your "admin" role record which would not have the "Elevated privilege" field checked.  This way you log into the sub-prod instances and you are an admin immediately without having to elevate.     If you do go this route, check out this post - "View Data to Preserve" Tool.

 

Benefits:

  • simple - 1 user = 1 account = 1 license
  • save some $$$ on licensing
  • protects us from mistakes, just like the "security_admin" role setup
  • cannot bypass built-in processes
  • allows you to login using just 1 account with external or internal authentication (crucial if your LDAP/SSO integration is down)
  • gives us "admin" role in sub-prod instances immediately

 

Drawbacks:

  • can't think of any

 

Thoughts?  I'd appreciate any feedback in case I missed something here.

*** 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 ***

Understanding how filtering works within the Mobile user interface is helpful as it will allow you to customize the available options for filtering, and ensure that users accessing your instance on the go can find the information they need quickly.  The mobile UI has a more streamlined subset of the filtering options, designed to be usable within the more limited hardware and screen space and of a mobile device.  As a result, there are some differences in the way filtering functions.

 

 

Accessing the List Filter on a mobile device

The desktop UI has a “funnel” icon above the list to see filter options.  In mobile, filtering is done by tapping the list's table name, which appears in blue in the upper left above the list.

 

Here you’ll see a list of available fields used for filtering (Not all fields will be visible, as they are in the Desktop UI, more on that below).  Clicking on any of these fields will then display a list of operators.  Tapping on an operator will display a list of appropriate options, or a text box for manual input.  There are some limitations and differences on the mobile version of the filter detailed below.

Filter_Location.png

 

You will notice that there is no 'or' condition when trying to filter in the mobile interface. Mobile filtering is a little less comprehensive, so there’s not a method to add 'or' to your conditions yet.  To get around this, consider using the “is one of” operator to filter for multiple choices.

 

 

Selecting multiple options on the same field

For operators like “is one of,” a text box is displayed rather than a list of available items.  The text box can be used to enter multiple comma-separated items.  The example below shows the selection of two categories for the incident table.

Screen Shot 2015-08-13 at 10.07.33 AM.png


Where are the rest of the fields in filter options?

The fields displayed in the mobile filter options are the same fields that appear on that table’s list using the mobile view.  There are a few other fields shown in addition to these, for example Sys ID and Updated.

 

For example, take the incident table.  In the desktop UI, open the incident list and select the mobile view.  Out of box, you’ll see the Number, Category, Short Description, and Priority fields.

Screen Shot 2015-08-13 at 10.16.54 AM.png

These are the same fields we see as filter options in mobile view.  Configuring and adding fields to this list will also make them available as filter options in the Mobile UI.  The only exception here is dot-walked fields, which currently do not show in mobile filter options (this is being addressed in problem PRB613371).  Other than this exception, any fields added to this list will become available as filter options for your mobile lists.

 

 

 

Sorting is done using the field drop down

Currently sorting is limited to the drop down located just above the search box.  The fields available in this box are also limited to the ones on the mobile list for that table, as discussed above, so adding fields to the table’s mobile view list will also make them visible here.  Tapping the drop down text will also reverse the sort order.

Screen Shot 2015-08-17 at 1.26.51 PM.png

 

 

There are a few known issues with filtering in the Mobile UI.  Remember, the mobile interface is designed to be usable within the more limited hardware and screen space and of a mobile device. Not everything you can do on a desktop can be done from a mobile device. We have condensed the experience to make it easier to use from a go-anywhere perspective.

 

 

 

With the above information, you should have an understanding of filtering within the mobile UI, be able to customize the field availability on your mobile lists, and ensure that your users have the information they need available to them.

Here is a nifty bit of functionality to add to your toolbox. 

 

JavaScript passes most variable parameters by value.  What this means is that if you pass in a string, integer, float or anything it comes in as a copy.  Most other objects are passed by reference; such as user defined, and GlideRecord objects.

 

Example of passing by value:

 

var numberOfPeople = 5;
gs.info('---> Number of people: {0}', numberOfPeople);  // will print 5.

changeNumberOfPeople(numberOfPeople);

gs.info('---> [After] Number of people: {0}', numberOfPeople);  // will print 5.

function changeNumberOfPeople(numberOfPeople) {
    numberOfPeople = 10;
    gs.info('---> [inside function] Number of people: {0}', numberOfPeople);  // will print 10.
}

 

Results:

 

*** Script: ---> Number of people: 5

*** Script: ---> [inside function] Number of people: 10

*** Script: ---> [After] Number of people: 5

 

 

As you can see, it is a copy.  The scope is either "in" or "outside of" the function and one does not see the other.  If you change the inside value it does NOT affect the outside value.

 

Now let's take a look at an object passing by reference.  A pointer to the original is passed; not a copy.  Therefore if you modify the passed in object it modifies the original.

 

Example of passing by reference:

 

var person = {};
person.arms = 2;
person.legs = 2;
person.heads = 1;

gs.info('---> [outside] number of heads: {0}', person.heads);

changePerson(person);

gs.info('---> [after] number of heads: {0}', person.heads);

function changePerson(personToChange) {
    personToChange.heads = 2;
}

 

Results:

 

*** Script: ---> [outside] number of heads: 1

*** Script: ---> [after] number of heads: 2

 

 

This works the same way with GlideRecords:

 

var incidentRecords = new GlideRecord('incident');
if (incidentRecords.get('number','INC0010023')) {
    gs.info('---> [before] impact: {0}', incidentRecords.impact.getDisplayValue());
    changeIncident(incidentRecords);
    gs.info('---> [after] impact: {0}', incidentRecords.impact.getDisplayValue());
    incidentRecords.update();
}

function changeIncident(incident) {
    incident.impact = 1;
}

 

Results:

 

*** Script: ---> [before] impact: 3 - Low

*** Script: ---> [after] impact: 1 - High

 

Cool, huh?  :-)

 

Steven Bell.

 

 

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

Have you experienced an email configuration outage on your instance? You can request your email be re-instated through the Service Catalog. This video describes how to request email reprovisioning for an instance.

 

Role required: customer_admin

 

For best video quality, increase your player resolution to 1080p.

 

 

What to know before requesting email reprovision:

  • Only request email reprovision for an instance experiencing an email configuration outage
  • Email will be temporarily disabled after provisioning process
  • Can be requested through the Service Catalog or Manage Instances page

 

For more information on requesting email reprovisioning for your instance, see:

Requesting e-mail reprovisioning for your instance (KB0540748)

Requesting email re-provisioning for your instance

 

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

Just thought I would point out that it is possible to dot walk a reference field inside of an addQuery.  There does not appear to be any depth restrictions.

 

Note the gs.info statement in the while loop.  JavaScript has no continuation character so you can put things on lines under it.  In cases like this it is a best practice while coding.  It Makes the code more maintainable (better readability).  White space does not impact performance noticeably.  Your editor may not like it and may throw warnings.  Ignore them should this happen. :-)


Steven Bell

 


var incidentRecords = new GlideRecord('incident');


// Note that the comparison string is not case sensitive (as SQL syntax is not this makes sense)

incidentRecords.addQuery('location.name', 'san diego');

incidentRecords.addQuery('assigned_to.manager.name', 'bud richman');

incidentRecords.addActiveQuery();

incidentRecords.query();


gs.info('---> Total count of San Diego incident records: {0}', incidentRecords.getRowCount());


while (incidentRecords.next()) {

        // Remember that gs.log is not currently scope safe.

        gs.info('---> Incident {0} originated at the {1} location. Assigned to: {2}, manager: {3}',

                incidentRecords.number,

                incidentRecords.location.name,

                incidentRecords.assigned_to.name,

                incidentRecords.assigned_to.manager.name);

}

 


RESULTS:


*** Script: ---> Total count of San Diego incident records: 1

*** Script: ---> Incident INC0000007 originated at the San Diego location. Assigned to: David Loo, manager: Bud Richman

 

 


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

Did you know that there are several ways to do OR queries in a GlideRecord? 

 

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

 

Thanks,

 

Steven.

 

 

// Regular OR condition
var incidentRecords = new GlideRecord('incident');
var incQuery = incidentRecords.addQuery('location.name', 'San Diego');
incQuery.addOrCondition('location.name', 'Salt Lake City');
incidentRecords.query();

gs.info('Regular OR Incident count: {0}', incidentRecords.getRowCount());


// Can be rewritten with chaining

var incidentRecords = new GlideRecord('incident');
incidentRecords.addQuery('location.name', 'San Diego').addOrCondition('location.name', 'Salt Lake City');
incidentRecords.query();

gs.info('Chaining OR Incident count: {0}', incidentRecords.getRowCount());

 


// Also this can be done as an encoded query if you want

var incidentRecords = new GlideRecord('incident');
incidentRecords.addEncodedQuery('location.name=SAN DIEGO^ORlocation.name=SALT LAKE CITY');
incidentRecords.query();

gs.info('Using an encoded query Incident count: {0}', incidentRecords.getRowCount());

// Can finally be rewritten with "IN"
var locationList = ['San Diego', 'Salt Lake City'];

 

var incidentRecords = new GlideRecord('incident');
incidentRecords.addQuery('location.name', 'IN', locationList);
incidentRecords.query();

gs.info('Using IN OR Incident count: {0}', incidentRecords.getRowCount());

Best practices for Import Set performance strongly recommends the usage of indexed fields as coalesce fields. In an import, coalescing on a field (or set of fields) means the field will be used as a unique key. If a match is found using the coalesce field, the existing record will be updated with the information being imported. If a match is not found, then a new record will be inserted into the database. If the indexes are missing, it can lead to poor performance issues especially if large amount of data processing is involved. Starting with Fuji release, Administrators have the option to create indexes on coalesce fields from the user interface.

 

Example of how to create indexes on coalesce fields from the UI:

Let's say you have an admin that is creating a transform map and you identify a coalesce field(s).

fieldmap.png

As soon as a coalesce setting is changed, users will be prompted with a message saying "Coalesce settings changed. After all coalesce fields are configured, use the Index Coalesce Fields related link to check if a new database index is required."  Hop down to the related links and click on "Index Coalesce Fields."

coalesce.png

Clicking on "Index coalesce fields" will take us to the "create a new index" screen where the admin can confirm the index and schedule it for creation in the system. Here, you can generate a new index that will continue to work while the index is being run. When the index is finished, you will receive a confirmation email. Just click "Ok" to continue.

index.pngTrying to coalesce on non-indexed fields could cause performance issues. To avoid performance issues on the database, you should coalesce on a field that is unique and already indexed. Index creation can be confirmed from "Tables and Columns view by going to System Definition > Tables & Columns and looking for the blue "i" icon.

 

What if the coalesce fields are already indexed?

If at least one of the fields is fixed, no additional indexes are required. You will be notified that the fields are already indexed.

index_already.png

This feature can be used for existing and new transform maps. It can be initiated any time to create indexes on coalesce fields. If there are no coalesce fields defined in the transform map, no message about index creation will be displayed. Existing transform maps will not have indexes created automatically, administrators need to click the link to make use of the feature. If an admin edits an existing transform map with a coalesce field, following message will be displayed to prompt the index creation.

Screen Shot 2015-08-09 at 5.22.59 PM.png

 

This feature will help administrators to proactively create indexes before running into import set related performance issues. There are other ways to create indexes on Fuji instances. Please check out the latest blog post by Bill Tang where he shares best practices and tips on Improving your ServiceNow instance performance by creating database indexes via the User Interface.

NOTE: I've broken this post up into 2 new posts in order to try to keep things cleaner and to the point as this post ended up taking a couple twists and turns:

"Related Attachments" Related List

Improving the Attachments List View

You may want to read them instead of continuing on with this outdated post.

 

I've been working with a great new client lately and we wanted to show all attachments related to a Requested Item, whether they're on the parent RITM record or on any of the associated Catalog Tasks, all in one place.  I wrote about this previously (Showing Requested Item Attachments on the Catalog Task Form), but I thought of a better way to display them.

 

First, we start by adding two Relationship records (System Definition \ Relationships), one for the Requested Item and the other on the Catalog Tasks table:

 

Name:                   All Attachments

Applies to table:       Requested Item [sc_req_item]

Queries from table:     Attachment [sys_attachment]

Query with:

(function(){
  var queryString = "table_nameINsc_req_item,sc_task^table_sys_idIN" + parent.getValue("sys_id");
  var gr = new GlideRecord("sc_task");
  gr.addQuery("request_item", parent.getValue("sys_id"));
  gr.query();
  while (gr.next()){
  queryString += "," + gr.getValue("sys_id");
  }
  current.addEncodedQuery(queryString);
})();
 

 

 

Name:                   All Attachments

Applies to table:       Catalog Task [sc_task]

Queries from table:     Attachment [sys_attachment]

Query with:

(function(){
  var queryString = "table_nameINsc_req_item,sc_task^table_sys_idIN" + parent.getValue("request_item");
  var gr = new GlideRecord("sc_task");
  gr.addQuery("request_item", parent.getValue("request_item"));
  gr.query();
  while (gr.next()){
    queryString += "," + gr.sys_id.toString()
  }
  current.addEncodedQuery(queryString);
})();
 

 

They both get a list of sys_ids for the Requested Item and any child Catalog Task.  If we add the "All Attachments" Related List to the Requested Item and Catalog Task forms, we get the following:

 

ServiceNow.png

 

It's nice to have all the attachments in one place, but the "Table name" and "Table sys ID" fields are not very useful.  Luckily we can improve on that.

 

First, we need to create a new field called "Record" of type "Document ID" on the Attachment table.  To do this, enter "sys_attachment.list" in the Navigator filter - this will display a list of attachment records.  Then right-click on the list header and select Configure \ Dictionary.  Create a new record with the following settings:

 

Table:                   Attachment [sys_attachment]

Type:                    Document ID

Column label:            Record (or whatever else you prefer)

Use dependent field:     checked  (you may have to click on the Advanced View Related Link to show these fields)

Dependent on field:      Table name

 

You can also create the new column with the System Definition \ Tables module.

 

Next, we create a Business Rule on the Attachments table to populate that field:

 

Name:       Custom - Populate Record Field

Table:      Attachment [sys_attachment]

Active:     checked

Advanced:   checked

When:       before

Insert:     checked

Update:     checked

Condition:  current.table_sys_id.changes()

Script:

function onBefore(current, previous) {
  current.u_record = current.table_sys_id;
}
 

 

The Business Rule simply copies the table_sys_id field into the new one.  Now we can have a useful Related List on the Requested Item and Catalog Task tables by replacing the fields that are displayed (right-click Configure \ List Layout) by removing the "Table name" and "Table sys ID" fields and adding the new "Record" field:

NewAttachments.png

 

Now you can see the record the attachment is actually on, and even click on the link to go to that particular record.  The new Record field will be populated when any new attachments are added.  You can run the the following Background Script after hours to populate it on your existing records:

 

(function() {
    var gr = new GlideRecord('sys_attachment');
    gr.autoSysFields(false);
    gr.query();
    while (gr.next()) {
        gr.u_record = gr.table_sys_id;
        gr.update();
    } 
})();
 

 

Remember, test any script in your development or sandbox instance first.

 

This solution can also be modified for use with the SDLC and PPM applications.

*** 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 ***

Something I see quite often in the codebase is a GlideRecord call followed by the requisite While-Loop with another GlideRecord call in the loop.  This is super inefficient.  Not only does it have the possibility of taking a long time to complete the code (regular Business Rules would be the worse as they could impact the user experience), but it could possibly bring the instance it is running on to a crawl.  This is due to the number of hits on the database.  The more times the code loops then the worse the situation becomes.  Each database hit takes the computer time to set it up (connection to the database, submission of the query, execution of the query, collection of the results, packaging of the results, return of the results).  Remember that with the opening of a regular GlideRecord that the connection with the database remains open for the entirety of execution (even after you do an update, insert, or delete).

 

If you encounter the need for a structure like this then my advice is to look at it carefully and determine if it is possible to refactor the code to limit the number of hits on the database.

 

Steven

 

 

// Example of GlideRecord Inside a GlideRecord.  THIS IS A BAD PRACTICE!

var problemRecords = new GlideRecord('problem');
problemRecords.addQuery('state', 1);
problemRecords.query();


gs.info('Number of problem records found: {0}', problemRecords.getRowCount());


// The more loops the worse the performance gets!
while(problemRecords.next()) {

      var incidentRecords = new GlideRecord('incident');
      incidentRecords.addQuery('problem_id', problemRecords.sys_id + '');
      incidentRecords.query();

 

      gs.info('Number of incident records for problem {0} is: {1}.', problemRecords.number, incidentRecords.getRowCount());
}

 

After some consideration the following would be a way to reduce this to just two calls to the database:

 

var problemRecords = new GlideRecord('problem');
problemRecords.addQuery('state', 1);
problemRecords.orderBy('problem_id');
problemRecords.query();


// push the problem sys_id's to a one-dimensional array. THIS IS A BEST PRACTICE!
var problemList = [];

while(problemRecords.next()) {
      problemList.push(problemRecords.sys_id + '');
}


// get rid of duplicates
problemList = new ArrayUtil().unique(problemList);


gs.info('Number of problem records found: {0}', problemList.length);


var countIncidents = new GlideAggregate('incident');
countIncidents.addAggregate('COUNT');
countIncidents.addQuery('problem_id', 'IN', problemList);  // forced to a comma delimited list
countIncidents.groupBy('problem_id');
countIncidents.query();


while(countIncidents.next()) {
      gs.info('Number of incident records for problem {0} is: {1}.', countIncidents.problem_id.getDisplayValue(), countIncidents.getAggregate('COUNT'));
}

 

Some timings for the GlideRecord inside of a loop:

 

[0:00:00.025] Total Time for 5
[0:00:00.018] Total Time
[0:00:00.021] Total Time

[0:00:00.037] Total Time for 17  <----- expected
[0:00:00.039] Total Time
[0:00:00.044] Total Time

 

Notice that the more loops the worse the performance.

Now some timings for the refactored code:

 

[0:00:00.029] Total Time for 5
[0:00:00.022] Total Time
[0:00:00.042] Total Time    <--- who knows  :-/

[0:00:00.022] Total Time For 17 <---- wow!
[0:00:00.018] Total Time
[0:00:00.013] Total Time  <--- double wow!

 

It is interesting that the efficiency appears to be bad for small number of records and improve significantly as more are lumped in!

This new video picks up where Best Practices for Debugging in Fuji Part 1 left off with ctomasi. In part one we looked at debug output control, in this demo we will go over session debugging, as well as our best practice recommendations for debugging in Fuji.

 

For best video quality, increase your player resolution to 1080p.



Notes on Debugging in Fuji:

  • For general debugging output use: gs.info() and gs.debug()
  • Use methods appropriately
    • use gs.error() for critical errors.
  • The new method uses MessageFormat parameters
  • You will not need to convert old code in Legacy apps


For more information on debugging best practices, see:

ServiceNow System Properties Best Practices video

Product Documentation - Application Scope

Product Documentation - Adding a Property

 

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

Here is a simple way to reset the record pointer back to the beginning (before the first record) on a GlideRecord without having to re-pull the entire recordset.

 

Steven

 

var incidentRecords = new GlideRecord('incident');

incidentRecords.addActiveQuery(); // same as .addQuery('active', true) but fancier :-)

incidentRecords.setLimit(10);  // did you know you could grab just the first x-number of records?

incidentRecords.orderBy('number');  // order ascending

incidentRecords.query();

 

 

// print off the first 10 incident numbers

while (incidentRecords.next()) {

  gs.info('Incident number: {0}', incidentRecords.number);

}

 

 

// print off the 10th one again, we are still at the end

gs.info('Current Incident number: {0}', incidentRecords.number);

 

 

// reset the pointer to beginning (before the first record)

incidentRecords.restoreLocation();

 

 

// print off the first 10 incident numbers starting with the first

while (incidentRecords.next()) {

  gs.info('Incident number: {0}', incidentRecords.number);

}

All:

 

My latest ServiceNow scripting article was published yesterday.  I focused on two different tools for unit testing your code.

 

Article Link

 

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!

 

accenture technology logo.pngsn-community-mvp.png

When we need additional information on a known problem that is affecting customers or our own instances, we make good use of our Internet_Explorer_9_icon.svg.pngextensive Known Error Knowledge Base, which is updated daily. These articles help identify issues that we are aware of and are either investigating, fixing, or offering a workaround solution. Internet Explorer navigation of the product tends to be a hot topic, so we try to address these as quickly as possible. Most of the known errors and issues in this list are around the user interface acting up when using the latest version of IE.

 

 

Here are the Top KE articles on Internet Explorer issues that ServiceNow Employees referenced in July:

 

1) Internet Explorer 11 clears content of second password field on a form

ServiceNow has recently discovered a bug in Internet Explorer 11 that, under very specific circumstances, causes password input fields to be cleared. Using IE 11 to visit any form that has two password input fields will cause the second password input field to be cleared when saving the form, resulting in loss of data in that field. As a workaround, use any other browser, including older versions of Internet Explorer. If you are required to use IE 11, a workaround is being discussed on Microsoft's forums here. For your convenience, we have provided instructions in this Known Error.

 

2) When using Internet Explorer while logged into HI, the browser is forced into compatibility mode

Under certain conditions, customers who use Internet Explorer to log in to HI.service-now.com have experienced their browsers running in compatibility mode. This condition is caused by *.service-now.com or HI.service-now.com being added to the Trusted sites list within Internet Explorer. To resolve this issue, remove the references to ServiceNow in the Trusted sites zone.

 

3) When using Internet Explorer, the UI14 interface does not work in Eureka when document mode/user agent string is less than 9 or compatibility view is enabled

Document mode in F12 Developer Tools is set to 7 by default on IE 9, 10, and 11 if service-now.com is treated as an intranet site. This causes UI14 to fail when accessing a Eureka instance. Users should try to figure out why service-now.com is being treated as an intranet site on their local machine. To resolve this issue, change the settings in Document mode and User agent string or clear the Display intranet sites in Compatibility View option. See the Workaround section of this Known Error for detailed instructions.

 

4) Edit UI Action menu in the top form button is hidden behind the form header (in IE)

When trying to edit UI actions on a form in Internet Explorer, the Edit UI Action. context menu is hidden behind the form header and only a very small portion of the button is displayed. In Google Chrome, right-clicking a form button on the top of the form does not render the Edit UI Action context menu. To workaround this issue in IE, users can right-click the buttons to show the full menu, or choose actions from the Personalize > UI Actions option.

 

4) In IE8 and IE9, click-through is not working on the Approvers form for a problem

Clicking through reference icons in the Problem > Approval form in IE8 and IE9 does not navigate to reference documents as expected. There is no workaround for this issue, so we recommend using Chrome.




The Time Ago format is a new UI feature of Fuji that makes reviewing incidents in a related list quicker and easier to access. The "Time Ago" feature can be used anywhere a date/time format is used in ServiceNow. "Time Ago" is one of three ways to represent date and time when reviewing data in your instance. You can set the date/time for "Time Ago," "Calendar date" or even both. A fourth option, "Compact list date/time" is also available and does not show date values for the current year or seconds for the time (does not work if "Time Ago" is enabled).

 

The "Time Ago" feature allows you to view dates as "6 days ago" or "31 minutes ago." Rather than telling you when the incident was submitted, it will tell you how much time has passed. This gives you a quick look at how long it has been since a user has been helped. Sorting by "Time Ago" allows you to easily see what issues need to be prioritized based on how long ago it was submitted

 

INT box.jpg

If you are on Eureka or a version prior to Fuji Patch 5, you may be experiencing a problem with setting your date/time to “time ago” or “both” in related lists to the correct time. This error is occurring because the “Time ago” option is calculated based on your system’s current time. You can fix this problem by changing your system’s time zone to match the ServiceNow profile time zone.

 

To change your ServiceNow System timezone:

    1. Navigate to System Properties > System.
    2. Locate the property called System timezone.
    3. Type a time zone in the field and click Save. This becomes the system time zone. The new time zone automatically cascades to all users who do not already have a specified time zone. If a user selects a different time zone or if the administrator selects a different time zone for them, the user is assigned the selected time zone and does not use the system time zone anymore.

 

To edit your profile's timezone:

      1. Navigate to Self-Service > My Profile
      2. Locate the property called Time zone.
      3. Select your timezone and Save.

 

Subscribe to PRB623943: Setting "Time Ago" in related list does not display correct time to get email updates when information in the workaround, seen in, and fixed in sections change.

Slava

Plug-ins ins and outs

Posted by Slava Aug 5, 2015

As I was preparing a demo instance for one of the customers last week, I realized how tedious plug-in activation could be. Activating a single plug-in is a piece of cake. But what about a dozen? Waiting for each process to complete before launching the next one is counter-productive. On the other hand, activating several plug-ins in parallel is risky due to increased performance impact and possible conflicts resulting from plug-in dependencies. Moreover, what if you needed to activate a plug-in at a specific time out of business hours?

 

The obvious answer is scripting, and it did not take me long to find the out-of-box script include that triggers activation of a plug-in when you press Activate button in the dialog window. With the following piece of code, you can launch plug-in activation from any server-side script or even schedule it to take place on a Christmas night without running the risk of missing the family reunion:

 

var worker = new GlidePluginManagerWorker();
worker.setProgressName('plugin_name');        // any descriptive name will do
worker.setPluginId('plugin_id');              // must match the ID of the plugin
worker.setBackground(true);
worker.start();

 

In this example, setPluginId() method tells the system which plug-in to activate while setProgressName() simply assigns a name to the corresponding progress worker. You would typically use the Name of the plug-in here but any descriptive name will do. Should any other plug-ins be required, they will be activated automatically and within the same progress worker.

 

If you are not familiar with progress workers, they are a means of tracking the status of certain types of background processes. When you preview or commit an update set, import data using import sets, or even clone a Service Catalog request, there is a record in sys_progress_worker table that displays the current status of the activity and is constantly updated as the process progresses. Progress workers are available from the System Diagnostics application menu.

 

GlidePluginManagerWorker class also has a couple of optional methods for working with plug-in demo data. Depending on your needs, include one of these statements before worker.start() command:

 

worker.setIncludeDemoData(true);      // load demo data during activation of a plug-in
worker.setLoadDemoDataOnly(true);     // load demo data for a previously activated plug-in

 

And last but not least – there is a method for grabbing the sys_id of the relevant progress worker record.

 

var pw = worker.getProgressID();

 

You can have your script query the progress worker record to determine whether activation has completed and trigger subsequent actions such as activation of the next plug-in, an e-mail notification or closure of the corresponding change request.

 

Now that you are fully equipped, give it a try and share your experience and use cases in the comments.

So how do you compare two times?  Strings won't work correctly (i.e. "04:00:00" is not necessarily less than "06:00:00").  The following code demonstrates how to manipulate a string time into a GlideTime, and how to extract the time component of a GlideDateTime to a GlideTime object.

 

I will also be demonstrating a couple of techniques that build slightly from my previous CodeSnippets DateTime article: link.

 

Try it out in Scripts - Background!

 

Steven

 

 

var start = "2015-08-05 0:00:00";

 

// Same as the last example.  We are getting the local date time from the GMT

var startDate = new GlideDateTime(start);

var offSet = new GlideTime(startDate.getTZOffset() * -1);

startDate.add(offSet);

 

var dayOfWeek = startDate.getDayOfWeek();

 

// You can get the amount of time between a date and a day of the week.

var duration = new GlideDuration();

var span = startDate.getSpanTime(dayOfWeek);

duration.setValue(span);

 

// Another thing: GlideDuration inherits from GlideDateTime.

var myTime = duration.getTime();  // you don't have to string split the date time, this method retrieves a GlideTime from a GlideDateTime

var checkTime = new GlideTime();  // set up the GlideTime object for later use

 

// the start of the week is Monday, not Sunday.  Sunday evaluates to 7.  Monday to 1.

if (dayOfWeek == 3) { 

    gs.info('so...Wednesday, I like Wednesdays');

    checkTime.setValue("06:00:00");  // use this method to convert a string time to a GlideTime

 

    // now you can accurately compare two GlideTime objects

    if (myTime < checkTime) {

        gs.info("The entered time is earlier than the current time.");

    }

    else {

        gs.info("The entered time is the same as or greater than the current time.");

    }

}

else if (dayOfWeek == 4) {

    gs.info("ok, so it's Thursday...");

    checkTime.setValue("04:00:00");

    if (myTime > checkTime) {

        gs.info("The entered time is greater than the current time.");

    }

    else {

        gs.info("The entered time is the same as or less than the current time.");

    }

}

In this two-part video with ctomasi, he demonstrates best practices for debugging issues in ServiceNow instances on the Fuji release. Using debugging methods like gs.print(), gs.log(), gs.logError(), and gs.logWarning() may have left you limited in scope. Now, with Fuji, you can specify the verbosity of the output and the output destination.


You’ll learn about the new features in Fuji that provide you with control over debugging output destination and verbosity.

 

For best video quality, increase your player resolution to 1080p.


 

 

For part two see Best Practices for Debugging in Fuji Part 2

 

For more information on debugging best practices, see:

ServiceNow System Properties Best Practices video

Product Documentation - Application Scope

Product Documentation - Adding a Property

Best practices for debugging in Fuji

 

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

Filter Blog

By date: By tag: