Skip navigation

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM June 10, 2014 ON THE CLOUDSHERPAS SERVICENOW SCRIPTING 101 BLOG.

____________________________________________________________________________

 

One of our implementation specialists recently posed an interesting question: is it possible to create a link that jumps to the top of the page on a Service Catalog Item in ServiceNow?

 

He was working on several long Catalog Item forms that required users to scroll all the way back to the top once they reached the bottom of the page. Recognizing that getting back to the top could be painful if there were a significant number of text field variables embedded in the form, he thought why not add a link at the bottom that goes directly to the top of the page?

 

My first thought was, you can’t do that without modifying the out-of-the-box code. However, this is not a great option because when it comes to ServiceNow, if you touch it, you own it. In other words, ServiceNow will skip any modified out-of-the-box code when upgrading to a new release.

 

So the solution had to be something that would work inside of the existing framework for Service Catalog. And then it struck me! Why not create a UI Macro, a Service Catalog Macro to use it. and then add that to a Catalog Item? It couldn't be that simple… could it? It was, and the solution worked great!

 

To create your own “Top of Page” link for a Service Catalog page, just follow these steps:

 

 

1. Create a new UI Macro

 

a. In the navigation filter type: UI Macros

 

b. Click on the “UI Macros” link.

 

 

 

c. The UI Macro list view will appear.

 

d. Click “New” to create the new UI Macro. The new UI Macro form will appear with the XML partly filled in.

 

 

e. In the “Name” field, type something like “sc_top_of_page_link” for easy identification.

 

f. Add the following code in the XML field between the two jelly tags:

 

<a href="#top">Goto top of page</a>

The XML code should look like this when you are done:

 

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<a href="#top">Goto top of page</a>
</j:jelly>

 

NOTE: You can also create a similar #bottom or #middle UI Macro.

 

 

g. You’re now done with the UI Macro! It should look something like this:

 

 

h. Click the Submit button. This will take you back to the UI Macro list view.

 

i. In the list view, double click and set the Category to Service Catalog. This identifies it as a Service Catalog UI Macro.

 



2. Create a new Service Catalog Variable

 

a. Go to the Navigation filter and type “Catalog Variables”

 

     

 

b. Click on the “All Variables” link. This will display the Service Catalog Variables list view.

 

c. Click the “New” button to display the new Variable form.

 

 

d. Fill in the following fields as designated below, leaving the rest as default.

 

Type: Macro
Name: sr_goto_top_of_page
Order: 1999

Catalog Item: <your really long catalog item>

Macro: <will appear when Type: Macro is chosen>

When you pick the Macro type, the “Macro” field should appear. Click the magnifying glass to bring up a list of Macros and pick the “sc_top_of_page_link” Macro that you created in the first step.

 

 

By the way, you are not limited to how many of these you can create. So, if you have a really long Service Catalog page, you can create a couple of these and put one in the middle of the page. Location of the variable is determined by the “Variable Order” field. For the purposes of this article, I will show just the one variable at the end of the Service Catalog page.

 

 

Since you want the link to be at the end of the page, set the order to be greater than all the other variables. 1,999 should do the trick nicely!

 

For Catalog Item, choose “Schedule Relocation” because it is a long one and will demonstrate the link nicely. Add a prefix of “sr” to the “Name” field to identify it as a Schedule Relocation variable.

 

 

e. The completed form should look something like this when you are done:

 

 

f. Click on the “Submit” button to display the “All Variables” list view.



3. Test your Catalog Item

 

Okay, we’re done! The next step is to actually go see if the variable appears in your chosen Catalog Item. Since I chose the Schedule Relocation Service Catalog Item, I can see my new link by doing the following:

 

a. Type “Service Catalog” in the Navigation filter.

 

b. Choose “Service Catalog” in the self-service application.

 

c. Click on “Can We Help You?”

 

 

d. Scroll down and click on “Schedule Rotation”

 

 

e. Once the Schedule Rotation page appears, scroll down to the bottom to see the link.


 

f. Click on the link to test if it goes to the top. 

 



Now your new top of page variable is unit tested and ready for production!

 

 

Learn More

 

If you’re interested in obtaining the complete solution, it is now available on ServiceNow’s new online exchange, Share, along with additional free custom application and development content from Cloud Sherpas (Now Accenture Cloud First).

 

Steven Bell

 

accenture technology logo.pngsn-community-mvp.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!

 


Click for More Expert Blogs and also Find Expert Events!


But... but... #KillEmail

Yes, kill email when you can. In reality, Some systems and processes may still have components depending on an email initiation.  Here are some examples from my own history.

- support@company.com, service@company.com, contactus@company.com etc addresses that are externally facing need workflow driven responses.  (this is common in the hospitality space)

- legacy system is a starting point and has no rest/soap integration capabilities

- highly specialized staff that, for cultural or practical reasons, only uses email + 1 or 2 specialized tools (traders, field service staff, doctors, lawyers, etc)

 

Sometimes we have no choice in our inputs, and its better to win small than not win.

 

Before We Begin

  • Understand Inbound Email Actions, especially Section 6, which outlines how to handle email variables.
  • Understand Service Catalog Script API.  For further research, you can look at the Script Include "Cart" on your dev instance.
  • It is advised that you create your own email address in your environment to handle the intake.  In my example I created "term@company.com".  We ended up creating this such that anyone emailing the address was also sending to company@servicenow.com without realizing it.  In this fashion both the original sender & term@company.com are retained.  A further benefit is you can make separate addresses per workflow you wish to trigger. 
  • Text analysis is your enemy.  As much as possible ensure that your sender automated, not a person.  If that's not possible, resist attempts to make programatic decisions based on user entered email text.

 

Example:  Email initiated Termination

I've already created a Catalog Item with 3variables: terminee, subject, emailbody (HTML)

I'm assuming that whatever person / system sending will include in the body "priority:high" if priority needs adjustment.

Its also important to note that I don't generally use the request layer, so I will *also* want to modify the sc_req_item

 

Inbound Action

Name:  Email Based Term

Target Table:  Requested Item [sc_req_item]

Type: New

Order:  Anything lower (numerically) than your existing Incident items

Condition: email.recipients.toLowerCase().indexOf("term@company.com") > -1  (checks to ensure its coming to the controlling email address)

 

createRequest();
function createRequest() {
  var cart = new Cart();  //calling the cart API
  var item = cart.addItem('eb89a3b937e7d200a2982863b3990eab');  //sys_id of the catalog item I want to fire
  cart.setVariable(item, 'subject', email.subject) //sets catalog variable to the email's subject
  cart.setVariable(item, 'emailbody', email.body_html);  //sets catalog variable to email's body
   var rc = cart.placeOrder();  //this launches the catalog item, and creates a request object.  rc = the request object
  updateRITM(rc.sys_id);  //call a function immediately to update the ritm.  This must be a nested function, otherwise inbound actions get weird.
      //also, we're passing the sys_id of the request so we know what RITM to grab.
}

function updateRITM(req){
  var ritm = new GlideRecord('sc_req_item');
  ritm.addQuery('request', req);  //req is what we passed from the previous function. the sys_id of the request.
  ritm.query();
  while (ritm.next()){
    ritm.u_customer = gs.getUserID();  //my ritm table separately tracks its own customer, since I don't use Request
    ritm.description = email.body_text;  //we're still in the inbound action so why not exploit?
    ritm.priority = email.body.priority;   //good example of how to exploit email body variables
    ritm.update();
  }
}
event.state="stop_processing";  //stop evaluating inbound actions. This is why I keep this record's order lower than my incident from email rules.

 

Key Take-Aways

  • It is possible, and even handy to trigger workflow from email
  • if you want to modify RITM post insert, use a nested function where-ever you're calling the Cart API
  • Make sure inbound action(s) evaluate before your Incident creation inbound actions
  • Use stop processing once the script is complete.
  • Don't be afraid to exploit email.body.variables... especially if the email source is automated.

Did you know that in Helsinki you can force a list collector variable to display as a glide list field by adding an attribute to the variable definition record? Neither did I until this morning and I wanted to share that out.

 

Why would you even want to do this? Well, list collectors are nice and basically allow you a multiple select reference field where you have all the records on a table on the left and you can move one or more values to the right.

 

9bbb8924ee.png

 

It's a nice feature, but list collector variables take up a ton of space, load more slowly than other variable types, and require some DOM manipulation in order to set the values on the right hand side. It's a pretty common ask to use the list field type (like the watch list on the task table) as it takes up less room, loads more quickly, and can be manipulated using the g_form.setValue() method. It's been possible to do this in the past, but requires a ui macro and hasn't worked all that well for me.

 

In Helsinki, I can add the glide_list attribute to the variable definition and then that list collector magically turns into a glide list.

4fc2c09e20.png

Shows this:

9fdbbb2ea7.png

Expanded:

9d89822602.png

 

I think there is definitely a place for both of these types of renderings, but it's nice to have an easy way to toggle between the two.

 

Types of variables

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM December 30, 2014 ON THE CLOUDSHERPAS SERVICENOW ADMIN 101 BLOG.

 

EVEN THOUGH THIS WAS ORIGINALLY CREATED FOR THE EUREKA RELEASE I THOUGHT THAT IT WAS STILL RELEVANT AS THE TECHNIQUES USED HERE WILL STILL WORK IN THE LATEST RELEASE.

____________________________________________________________________________

 

 

The latest release of ServiceNow, Eureka, introduced a whole new interface: UI14. Among the many updates were a group of visual changes, including how the “background” color is displayed in the List View of all tables. Instead of filling in the background of a cell, there is now a colored dot next to the cell contents. Since that change can take some time to get used to, I’ve fielded a number of questions about how to change this back without losing all the other UI14 visual candy.

 

So is it possible? Yes! Here’s how it’s done:

 

We will need to do the following things to get us to where we want to go:

 

  1. Upload simple background images into the images table. These will act as our new background colors.
  2. Create or modify styles to set our background colors.

 

Got those? Then let’s get started!

 

I used the following site in conjunction with my experimentation: 

 

http://www.w3schools.com/cssref/default.asp

 

Import the Background Images

 

First I imported the (attached) list of images. To do this:

 

1. Navigate to System UI / Images

 

C:\Users\User 1\Desktop\1. import image zip.jpg

 

2. Right-click on the header bar and pick “Import Image ZIP File”

 

C:\Users\User 1\Desktop\2. choose zip file to import.jpg

 

3. Choose the zip file of images you want to upload. In this case, it’s the images.zip file.

 

4. Click the upload button. ServiceNow automatically extracts the image files from the zip and puts them in the images table where they are ready to reference. Cool, huh? 

 

C:\Users\User 1\Desktop\3. loaded images.jpg

 

 

Style Creation / Modification

 

Next, pick your favorite table. I chose the Incidents table. You can see here the "before" picture. I am going to pick on the Priority styles. Note the “dots.”

 

C:\Users\User 1\Desktop\4. before with dots.jpg

 

You will then need to create or modify existing styles. Remember: if you change it, you own it. I modified the two existing out-of-the-box styles for the Incident/Task Priority field for the purposes of this article.

 

A side note: One neat thing is that you can have multi-line styles -- it’s not necessary to one-line the information. When I first figured this out, I reasoned: Since you can have “chained,” semi-colon delimited-style information, you should be able to just put each on its own line. I tried it and it worked! You can also have comments just like in a normal CSS3 style. However, some CSS3 things flat out don't work (animations, for example). See more below about things I tried that worked -- you’ll have to play around and see what you can do as well!

 

To continue, I modified the following styles:

 

Table: Task
Field Name: Priority
Value: 1

 

/* background-color:tomato - the original value*/
font-size: 18px;
font-weight: bold;
font-family: "Times New Roman", Georgia, Serif;
font-style: italic;
color: darkred;


/* forces the image to the background - this MUST come before the background-image line */
border-style: solid;

background-image: url("background_red.jpg");
border: 2px solid darkred;
text-decoration: underline;
text-align: right;


Table: Task
Field Name: Priority
Value: 2

 

/* background-color:orange - the original value*/
font-size: 18px;
font-weight: bold;
font-family: "Times New Roman", Georgia, Serif;
font-style: italic;
color: orange;


/* forces the image to the background - this MUST come before the background-image line */
border-style: solid;

background-image: url("background_yellow.jpg");
border: 2px solid orange;
text-decoration: underline;
text-align: right;


Once I saved these, they immediately become active in the list view. Here is the "after" picture:

 

C:\Users\User 1\Desktop\5. after with background images.jpg

 

So what’s happening here? I set the following for the particular style:

Font color, size, weight, family and style. I also added in the underline and aligned the text to the right.

 

Then the magic happens: we replace the "dot" with one of our uploaded images. You MUST set the border-style before that line, as this causes the cell to "wake up" to the new image. If you skip this step, all you will get is a confused cell with a rounded square set to your default color (by the way, you can change that color using the background-color CSS tag). Our image is automatically set to a default of 100% x 100% so it fills the entire cell.

 

That’s all there is to it! The only “downside” to this is that it requires that you go out and make this modification to every one of the styles you want to appear different than the “dot,”  as this method does not apply the change to all “dots.”

 

While these style modifications do work in Dublin and the old UI, I recommend testing them out because they may not act quite the same.

 

Additional Capabilities

 

Ready for more? Here’s a list of some other things I tried out that worked:

 

/* --- COMMENTS! worked fine --- */

background-color: white;  /* worked of course - background color of the dot */

 

background-position: 0px 0px;

 

background-size: 16px 16px;  /* size of the image - without a norepeat available this acts pretty weird*/

 

background-repeat: repeat-y; /* needs more testing */

 

border-bottom: thick dotted #ff0000;

 

/* tested both the named and hex values */

outline: 2px solid green;

outline: #00FF00 dotted thick; 

 

visibility: hidden;

 

text-shadow: 2px 2px #00FF00;

 

opacity: 0.5;

 

letter-spacing: -3px;

 

cursor: crosshair;

 

/* --- The following column width setters worked fine in most cases --- */

-webkit-column-width: 150px;  /* Chrome, Safari, Opera */

-moz-column-width: 150px;  /* Firefox */

column-width: 150px;

 

 

On a final note, if you really, really want to back out of the UI14 interface in Eureka, you can simply go to sys_properties.list and look for the following property:

 

glide.ui.doctype

 

Set it to false, save and then log out and log back in and you should have your old interface back.

 

 

Steven Bell

 

accenture technology logo.pngsn-community-mvp.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!

 


Click for More Expert Blogs and also Find Expert Events!


Our platform makes difficult tasks easy. This simplification ServiceNow provides their customers effects how the customer developer thinks. They forget that programming is really not an easy task, and begin scripting without restraint. Developers must be strict on themselves: always testing, always checking, especially working in any platform that they did not create. Developers must ensure their customization coincides with the ServiceNow platform as one.

 

How an engineer customizes a ServiceNow instance relates to how many issues their company will have during business hours. Good customizations do not disrupt, bad customizations cause nightmares.

 

If you are going to customize ServiceNow do it right! Many customization issues are a simple fix on the engineer's side: check the variables.

 

Checking your variables while developing

If you’re not sure why your program is broken, then you may want to start with checking your variables.  Overall, variable checks establish a safe place for you to start your customization safely.

 

Checking your data will ensure that you are more than 50% done.

 

Checking your code allows you to:

  • See concrete data
  • Alert yourself when something is wrong
  • Ensure that it is not your mistake
  • Move on the the real issues in your system that deserve time, like the logical aspects of it.

 

Before assuming things just work, you owe it to yourself, to ensure you are working with good data.  For example, a manager opens an incident, frustrated with ServiceNow because their their engineer claims the one week issue they are experiencing is a ServiceNow error.  The business suffers and loses valuable work time, the manager says this is something ServiceNow must fix.  A ServiceNow support engineer reviews the incident and in a few hours can find the issue. In this case, it is clearly a modification on the client side.

 

The customization error, "variable is undefined," means the engineer's variables were non-existent or not defined well and caused the system to stop rendering correctly. An error like this can delay a roll out of a product for one month. This could have been resolved early on if the developer took a moment to review their variables.

 

How To Check Variables Basics in Javascript

The basic variable culprits that could be messing with your customization:

  • Undefined
  • Null
  • "" (empty string)

 

You need to check for these elements, to ensure that they do not prevent your instance from working correctly.

 

For any variable element where "elem" is the variable.

if (elem === null)  //try this
//OR

if (typeof elem === "undefined") //try this
//OR
if(elem === "")  //try this

 

You should be running the above three checks on every variable in your software, scripts, and applications. You can put the above example in a function so you don't have to rewrite them.  Once in a function you can return anonymous functions to correctly handle the variable's error in a safe manner rather then breaking your interaction.

 

For example:

function isEmpty(elem){
    if (elem === null) {
        return function(){ alert("Is Null");};
    }
    else if (typeof elem === "undefined"){
        return function(){alert(“IS UNDEFINED”);}
    }
    else if(elem === ""){
        return function(){alert(“IS EMPTY STRING”);}
    }
    alert(“NOT EMPTY”);    
}
//This would be more helpful if your anonymous functions found alterior methods to notify the user of the issue or just found an entirely different work
//around to the error

 

You can easily test this out in the developer console in Chrome, IE, or FireFox. You will see an error described by a red X.

 

For example:

developer console.jpg

 

Unchecked variables cause all sorts of funny stuff to happen.  Unchecked variables are usually hard to find and cause the working of a page to halt.  Many Customers assume variables are just there and work correctly all of the time. The assumption that something should just exist without question, is dangerous and can affect your company's bottom line.

You own your instance and your source code, so you know what goes on inside of it.

 

Want to save yourself some time and possible headache? Double check the data you are working with.

Hibernation Now

Photo by Metassus, licensed CC BY 2.0

The Developer Program has grown steadily since it was announced at Knowledge15, and along with that growth has been a corresponding increase in developer instance usage. The ability to give these away for free use for the community of ServiceNow developers is a great benefit and has reaped benefits for the entire ecosystem. However, it does come at a cost. The program was granted a dedicated chunk of infrastructure, and then another chunk later. Those of you who have been around for a while remember the Great Instance Crunch of late 2015/early 2016. We've done a lot with this gift of infrastructure, but at this point we can no longer keep going back to that well.

 

In order to be able to provide more instances to more developers, hibernation was recently introduced to the program. Roughly 2/3 of instances are not actually used on any given day. Hibernation allows the same hardware to serve a larger number of instances, which means we can keep handing them out without having to shorten the inactivity timeout (which we really do not want to do.) Hibernation is not ideal, but we are not in a situation to decide whether to hibernate or not. The choice is whether to have hibernation or no more instances to distribute.

 

In this post, I want to clarify a few misconceptions and set some expectations around this situation. Some of these are in the FAQ but they bear repeating:

Wake your Instance from the Developer Portal

 

If your instance is hibernating, you should get a notice to that effect when you attempt to access it via HTTP. The hibernation page will have a link to the part of the portal you need to load to wake it up. Alternately, if you just load your Manage -> Instance page in the Developer Portal, that will automatically wake your instance if it was hibernating.

 

Hibernation and Reclamation are Different

 

We see these in feedback and in instance help frequently, messages that conflate the two things. These are entirely separate. When hibernation was originally introduced, a number of developers were confused the first time they received a timeout warning. I personally answered a number of messages of the form "You said you would reclaim my instance in two days, but it was already inactive." A hibernating instance by definition has not been reclaimed. If it was reclaimed, you wouldn't have it around to wake up. You can always still extend your instance with the button on your management console at Manage -> Instance even without waking it up, or by the standard development activity that resets the time.

 

Waking Up Needs an Active Session

 

Another common pattern is that people report having to wake up hibernating instances multiple times. If you wake it up and do not log in, it will go back to sleep in around 30 minutes. If you log in and then don't actually keep the session around for very long (log out in less than 15 minutes) it might also go back to sleep in around 30 minutes. If you are continuing to use it actively through the day, you should have around 6 hours of inactivity before hibernation begins. That should be more than enough for anyone using it on a normal workday. If you come back to it in your evening, it might require waking up again but in most cases a single waking should last through the entire day unless you do it early and then don't touch it all day.

 

It is also worth noting that developer instances are reclaimed based on developer activity, AKA script changes, configuration changes or anything that would show up in an update set. Hibernation is based on any activity in an interactive session. In other words, creating records or changing data does not reset your activity time for your 10 day timeout. It absolutely does reset your hibernation timer. Any activity through the web interface will keep the instance from hibernating.

 

Hibernation Affects Your Code

 

One aspect to note about hibernation is that it does affect the scheduled jobs and other aspects of your development instance. If you have scheduled jobs or other deferred executions on your instance and it is hibernated, those will not execute on the set schedule and possibly not at all. It now makes sense as you configure those jobs on your developer instances to do it for business hours. Although standard in production to push those types of code to the overnight hours, for a developer instance you want to do the opposite if there are jobs you want to maximize the possibility that they run.

 

Waking Will Get Faster and Easier

 

At this time, it takes around three minutes to wake a developer instance from hibernation. Over time that will get faster. The development team behind it is shooting to get that down closer to one minute. Additionally, they hope to make the timeouts a little more permissive with a net effect of fewer hibernations in a day with a shorter time to get back to work when it happens.

 

There are Alternatives to Developer Instances

 

In the case where you just cannot handle the hibernation as a concept, there are alternatives. If you have the inclination and resources to join the Technology Partner Program, you will receive an instance with the program that does not hibernate nor expire from inactivity. This also has some advantages if the ultimate goal is to develop apps that will be published in the ServiceNow Store. The cost (currently $5k/year) is prohibitive for most individuals but if your developer instance is in use for a consultancy and you find hibernation in your way, this is a possibility.

 

A frequent question asked is some variation of "Can I pay $X/month and get an instance that does not (hibernate | expire)?" We do appreciate the sentiment and willingness of developers to want to pay some freight of the program in exchange for removing these aspects. For the moment, that is enough outside our model that it is not in our roadmap. We aren't currently set up to take small retail amounts from a large number of users. This may change at some point in the future but at this time, it is not in the plan.

 

Conclusion

 

Hibernation, while not ideal, is now a part of life for users of the developer instances. It does add a little bit of inconvenience for developers. Although we would love a situation where we did not have it, the reality of our program is that we need it to continue to provide more developer instances to an ever larger pool of developers. Every effort is being made to reduce that impact but this is a side effect of the success of the ServiceNow developer program. We thank you for your interest in the program and use of these instances, and will strive to make the program ever better.

 

Let's build something great!

Dave Slusher | Developer Advocate | @DaveSlusherNow | Get started at https://developer.servicenow.com

Sometime between 2008 and 2010, when I was a customer, ServiceNow introduced the tabbed interface for forms. It wasn’t until 2015 that this became the default. For years I had to tell people, “There’s a better way to look at these forms…” then show them what to click to get there (painful). Once they went to tabbed forms, few ever went back. I yearned for a way to make the system default to tabbed forms, then I where this gem was hiding - user preferences.

 

You can make tabbed forms, list sort order, theme, or any other user preference a default for new users quite easily. While you cannot prevent them from changing it to something else afterward, you can certainly put them on a path to a better (or at least consistent) experience.

 

Let’s say you want all new users to have their UI16 left navigator default to Favorites instead of the standard menus/modules. Here's how:

 

1. Click the star icon to change your view to favorites

2. Navigate to sys_user_preference.list (or filter on pref to filter down to User Administration> User Preferences.) DO NOT, go back to the menus/modules directly or you’ll change the value of the user preference we’re trying to set.

3. Sort the list by recently updated records.

4. Locate your recently change preference for favorites (navigator.activeView). You may have to filter on records where you are the user.

 

5. Open the record.

6. Check the System box

7. Remove the User value.

 

8. Insert (not Update, not Save) the record.

 

Login or impersonate a user who has never tried UI16 before… You’ll see it default to favorites. While this isn’t terribly useful for a new user, it serves as an example how you can make default user preferences.

In my previous blog post Scoped Applications and Client Scripts: A Primer  I point out some of the differences between the APIs available to a scoped client script and a global one. One of the APIs that is explicitly removed is client-side GlideRecord. There are a few reasons that access to this particular API has been removed. A few of the top reasons are:

  • It results in large data transfers
  • Its almost exclusively used to get the value of a single field
  • Almost everyone uses it synchronously
  • It can hold open a database connection

 

I wrote another post about Using utilities in Scoped Applications to replace inaccessible APIs . The example in that case was replacing an auto-generated encoded query string which used an inaccessible API directly within the query. We can expand on that concept, this time on the client rather than the server.

 

UI Script as a utility class

Keeping within the spirit laid out in Background and Philosophy of Scoped Applications  we want a lightweight, asynchronous robust method for making REST calls. Also, we don't want to be limited to just the table APIs. We will create a single UI Script in this post, but this would work with multiple scripts as well.

 

Our client-side scripts (such as onChange and onLoad client scripts) can access the ScriptLoader class. This is an asynchronous JavaScript library that is used for just-in-time loading UI scripts. Thanks to the default structure of our UI Scripts, we can load a UI Script once and access it from multiple places. Additionally, the ScriptLoader knows what it has loaded already, so we can safely ask it to load our utilities multiple times and be sure that it will do so only once.

 

Understanding the UI Script template

To start with, we need a UI Script. When we create a UI Script in our application, we get a template that we can build on. A new UI Script would look like this:

Screen Shot 2016-05-25 at 1.13.08 PM.PNG

 

There are a lot of comments in the code, but it looks a lot more complicated than it is. Before we actually write our UI Script, let's break down what this template is telling us.

 

var sn_ui_script_util = sn_ui_script_util || {};

 

This line creates a variable named sn_ui_script_util, and sets its value to itself, or to an empty object. This is code that runs on the browser, so this is saying If we already have an object in the page named after our scope, we're going to use that. Otherwise, let's create an empty object.

 

sn_ui_script_util.Utilities = (function() {
  "use strict";

 

These two lines add a "Utilities" property to our scope object, and set its value to the result of a function execution. The function itself must run in strict mode. This prevents us from accidentally creating global variables and gives us errors when we make mistakes.

 

/* set your private variables and functions here. For example: 
  var privateVar = 0; 
  function private_function() {
  return ++privateVar;
  }
*/


/* Share variables between multiple UI scripts by adding them to your scope object. For example: 
  sn_ui_script_util.sharedVar = 0; 


 Then access them in your scripts the same way. For example: 
  function get_shared() {
  return sn_ui_script_util.sharedVar;
  }
*/

 

There are three different but connected things going on in the lines above. Firstly, we create a private variable that can only be accessed within this UI Script, and function that changes that variable. Nothing outside of the code written in this UI Script can read the value of that private variable, and nothing can change it.

 

Second, we create another variable, but this one is public. It exists on our scoped object, but it can be accessed by outside scripts. They can read and write to this shared variable, but it's not polluting the global namespace.

 

Lastly, we define a function that will return a reference to our shared variable. This is nothing new or groundbreaking, and since other scripts can access out variable directly, it's not strictly necessary. However, it is impolite to trample someone else's variables and properties, so if they give you a function to access them, you should use that function.

 

    return {
        /* set your public API here. For example:
        incrementAndReturnPrivateVar: function() {
            return private_function();
        },
        */
        type:  "Utilities"
    };

 

 

Here, we are defining the public API. In this example, we are saying that our API is one function named "incrementAndReturnProvateVar". Scripts can call this method and they will increment the value of the private variable we declared earlier, and see the result. We could also add our get_shared function if we wanted to.

 

})();

Finally, we just close out our function definition, and execute it.

 

Creating the utility

So, with that framework, we can create our REST utility. I wrote this today and haven't put a lot of testing into it, but it should work as an example. Note that, while I'm happy to accept fixes and comments about things which might not work with it, I am not offering to support it. This is a demonstration, not a full-fledged solution.

 

var sn_ui_script_util = sn_ui_script_util || {};

sn_ui_script_util.Utilities = (function () {
    "use strict";

    function xhrSupportsJSON() {

        if (typeof XMLHttpRequest == 'undefined') {
            return false;
        }

        var xhr = new XMLHttpRequest();
        xhr.open('get', '/', true);

        try {
            // some browsers throw when setting `responseType` to an unsupported value
            xhr.responseType = 'json';
        } catch (error) {
            return false;
        }

        return 'response' in xhr && xhr.responseType == 'json';
    }

    function getXHR() {
        var xhr = typeof XMLHttpRequest != 'undefined' ?
            new XMLHttpRequest() :
            new ActiveXObject('Microsoft.XMLHTTP');
        return xhr;
    }

    function getParamString(params, q) {
        if (!q)
            q = '';
        else
            q = '?';

        var parts = [];
        for (var key in params) {
            if (params.hasOwnProperty(key))
                parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
        }
        return parts.length ? q + parts.join('&') : '';
    }

    function urlIsRelative(url) {
        return !url.startsWith('http');
    }

    return {

        rest: function (type) {
            var params = {};
            var headers = {};
            var parts = [];
            var successHandler = function (data) {
                try {
                    console.log("Success!");
                    console.log(data);
                } catch (e) {
                    //boo
                }
            };

            var errorHandler = function (data) {
                try {
                    console.log("Error!");
                    console.log(data);
                } catch (e) {
                    //boo
                }
            };

            var obj = {
                get: function (url) {
                    if (!url)
                        errorHandler();

                    url = url + getParamString(params, true);

                    if (urlIsRelative(url))
                        headers['X-UserToken'] = g_ck;

                    var xhr = getXHR();
                    var useJSON = type == 'json' && xhrSupportsJSON();
                    if (useJSON)
                        xhr.responseType = 'json';

                    xhr.open('get', url, true);
                    for (var hdr in headers)
                        if (headers.hasOwnProperty(hdr))
                            xhr.setRequestHeader(hdr, headers[hdr]);

                    xhr.onreadystatechange = function () {
                        var status;
                        var data;

                        if (xhr.readyState == 4) { // `DONE`
                            status = xhr.status;

                            if (status == 200) {
                                data = useJSON ? xhr.response : type == 'json' ? JSON.parse(xhr.responseText) : xhr.responseXML;
                                successHandler && successHandler(data);
                            } else {
                                errorHandler && errorHandler(status);
                            }
                        }
                    };

                    xhr.send();
                },

                post: function (url) {
                    if (!url)
                        errorHandler();

                    if (urlIsRelative(url))
                        headers['X-UserToken'] = g_ck;

                    var xhr = getXHR();
                    var useJSON = type == 'json' && xhrSupportsJSON();
                    if (useJSON)
                        xhr.responseType = 'json';

                    xhr.open('POST', url, true);
                    for (var hdr in headers)
                        if (headers.hasOwnProperty(hdr))
                            xhr.setRequestHeader(hdr, headers[hdr]);

                    xhr.onreadystatechange = function () {
                        var status;
                        var data;
                        if (xhr.readyState == 4) { // `DONE`
                            status = xhr.status;

                            if (status == 200) {
                                data = useJSON ? xhr.response : type == 'json' ? JSON.parse(xhr.responseText) : xhr.responseXML;
                                successHandler && successHandler(data);
                            } else {
                                errorHandler && errorHandler(status);
                            }
                        }
                    };

                    xhr.send(getParamString(params));
                },

                addParam: function (name, value) {
                    params[name] = value;
                },

                addHeader: function (name, value) {
                    headers[name] = value;
                },

                success: function (successCallback) {
                    successHandler = successCallback;
                },

                error: function (errorCallback) {
                    errorHandler = errorCallback;
                }
            }

            return obj;
        },
        type: "Utilities"
    };
})();

 

What?

OK, so that is a lot of code. But you should be able to break it down, using the explanation provided earlier. This is a UI Script with some private functions and variables, which returns a public API that has one method named 'rest'. When we call that, we get an object that allows us to make GET and POST requests, and to accept either XML or JSON. All the rest is just "the stuff that makes that work".

 

Using our Utility

Now that we have a utility defined, we want to make use of it. We have a fairly generic way to make REST requests, and we want to replace at least some of the functionality of client-side GlideRecord. Let's use a common example: get the value of a single field off a referenced record. Let's get the Department of an incident caller, and add that to the work notes of an incident. We'll do that onChange of the Caller field.

 

Screen Shot 2016-05-25 at 3.29.33 PM.PNG

Here is the code we are using:

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue === '') {
        return;
    }
    ScriptLoader.getScripts('sn_ui_script_util.Utilities.jsdbx', getDepartment);

    function getDepartment() {
        var req = sn_ui_script_util.Utilities.rest('json');
        req.addParam("sysparm_query", "sys_id=" + newValue);
        req.addParam("sysparm_fields", "department");
        req.addParam("sysparm_display_value", true);
        req.success(updateNotes);

        req.get("/api/now/table/sys_user");
    }

    function updateNotes(data) {
        g_form.setValue("work_notes", data.result[0].department.display_value);
    }
}

 

We have the ScriptLoader fetching our utility script for us, and then calling our function to get the value of the caller's department. When that comes back, we'll update the worknotes field.

You can combine multiple UI Scripts under one namespace (your scope object on the client-side), and multiple scripts can call the ScriptLoader to load them. Think of it like saying "I require the REST utility, so if it isn't already loaded, go get it."

 

This is just one example of a way you can write your own utilities which are scope-safe, asynchronous, and take advantage of the power of ServiceNow's built-in REST API's. There is nothing stopping you from calling your own script REST endpoints, too. And with scoped apps and dependencies, you can keep all these nifty scripts and tools in one application, and simply depend on them for your other apps. It's a very handy way to write-once, use-everywhere.

I have strange and mystical powers.

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY INAUGURAL CLOUDSHERPAS SCRIPTING 101 BLOG ARTICLE FROM February 4, 2015.

____________________________________________________________________________

 

Welcome to our new blog series, ServiceNow Scripting 101!  I have been writing this type of article under the ServiceNow Admin 101 series, but they really needed their own platform. Don’t worry though: the Admin 101 series will still continue, but it will contain more Admin-ish articles from this point forward. Enjoy!

 

Recently, our Web Service Integration team needed a method for looking up all of the fields belonging to a table and then displaying those fields in a pick list. This needed to be done for ALL of the fields in the table hierarchy. In other words, what was needed was a smart Dictionary Entry of Dictionary Entries for a given table!  While the solution turned out to be a little tricky, it wasn’t too bad...honest!

 

Requirements

What was needed?

  • A short list of tables to drive a lookup of a list of available fields.
  • A lookup that could present the user with all fields available in the table hierarchy.
  • A way to determine the hierarchy table list in order to build a list of available fields.

 

Solution

 

1. Field: Table - a choice list of table names.

For example:

2. Field:  Field - A lookup / reference field referencing Dictionary Entry and controlled by the Table field choice.

 

3. The format should look something like this:

 

 

Next, the Script Include will need to traverse a given table’s hierarchy. This type of method is best done with a recursive routine. Incidentally I decided to do the groundwork for future Dictionary methods like this by creating a Dictionary Utility library. It is a good practice to attempt to group similar functions into a library format. You will find several examples of this with out-of-the-box Script Includes; just search for “Util” in the list view.

Here is the Script Include:

  1. Name: DictionaryUtils
  2. Description:  Helper methods for Dictionary references
  3. Script:

 

Here we would place our new recursive method:

 

var tableList = ''; // resultant lookup string that would be returned


var DictionaryUtils = Class.create();
DictionaryUtils.prototype = {

  initialize: function() {
  },


  //Recursive method for retrieving all Tables in the Hierarchy for a given table.
  //USAGE: new DictionaryUtils().getTableNameList('cmdb_ci_computer');
  //RETURNED: name='cmdb_ci_computer'^ORname='cmdb_ci_hardware'^ORname='cmdb_ci'
  getTableNameList : function(tableName) {

      // construct our lookup string
      tableList += "name=" + tableName + "^OR";

      // lookup the given table to retrieve our next super class name
      var tableLookup = GlideRecord('sys_db_object');
      tableLookup.addQuery('name', tableName);
      tableLookup.query();


      // chase each super class (i.e. parent) up the chain.
      // the topmost parent will NOT have a parent
      if (tableLookup.next() && tableName != '') {
          this.getTableNameList(tableLookup.super_class.name + '');
      }

      // peel off the last ^OR on the end of the lookup 
      // string and return it to the calling method.
      return tableList.substring(0, tableList.length - 3) + '^element!=NULL';
  },

  type: 'DictionaryUtils'
};

 

 

So the magic is to work “up” the inheritance chain via the super_class reference field and build the list of tables accordingly.

The Script Include should look something like this:

 

NOTE: A good practice is to test this script from Scripts Background prior to implementing in your Dictionary Entry.

 

Finally, modify the Dictionary Entry of the lookup/reference field on the original form.

 

  1. Reference: Dictionary Entry
  2. Reference qual: javascript:new DictionaryUtils().getTableNameList(current.u_table);

Where "u_table" is the table field that where we get our table name.

This will call the new Script Include method and construct an "OR" string of tables representing the table hierarchy for current.u_table.

This in turn will cause the lookup to pull up a list of fields based on those tables. 

 

 

Test

  1. Pick a table. I chose Incident.
  2. Click on the “Fields” magnifying glass.
  3. Modify the list view (right-click / Personalize / List Layout) and your result should look something like this:

 

And that’s all there is to it!

 

Learn More

 

If you’re interested in finding out more about Advanced Reference Qualifiers, check out this Wiki article.

 

For more on JavaScript recursion, check out these resources:

 

Steven Bell

 

accenture technology logo.pngsn-community-mvp.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!

 


Click for More Expert Blogs and also Find Expert Events!


Last week at Knowledge I partnered up with Robert Fedoruk and Travis Toulson for the CreatorCon Hackathon. We had a blast and I think we did pretty well for only having 8 hours to put the application together. Our application was a real-time point of sale system on top of Service Catalog.

In many organizations, a good percentage of employees will still approach the help desk directly without going through self-service. This forces the help desk to manually create the requests on the spot. There isn’t really a quick and easy interface for this, so having a point of sale system would greatly expedite the process for face-to-face transactions with the help desk.

We built our application using Service Portal making use of record watchers and integrated it with the native mobile app for bar code scanning. It uses the Service Catalog and with no custom tables. The application was built with touch screens in mind with support for gestures. And best of all the whole application operates across multiple devices all in real time.

When the transaction is complete, it sends a push notification receipt to the users mobile device.

I don't think it’s just limited to point of sale, something similar could also be used for inventory management, supply chain management, warehouse management, etc. where managing products and inventory must happen in realtime across locations and devices.

What do you guys think? Would your organization use something like this? I'd love to hear your thoughts in comments below.

--------------------------------
Nathan Firth
Principal ServiceNow Architect
nathan.firth@newrocket.com
http://newrocket.com
http://serviceportal.io

ServiceNow has a plethora of debugging tools available on the instance which helps ServiceNow admins and technical support engineers troubleshoot performance issues and narrow down the cause. Out of all the options ServiceNow offers, the SQL Debugger is the most powerful and helps admins find out all queries involved in a particular transaction. It can be used for both MySQL as well as Oracle based queries which are the relational database query languages available for ServiceNow instances. It empowers you to find the slowness based on the queries and using the explain plan tool create the appropriate indexes to help improve the performance of the application.

 

There are two types of of SQL Debugging tools:

  1. Debug SQL
  2. Debug SQL (Detailed)

 

Debug SQL vs. Debug SQL (Detailed)

The Debug SQL option lists all the queries involved but doesn't give the stack trace from where the query is originating as opposed to Debug SQL (Detailed) which does give you the Java stack trace  with a  "+"  symbol. This primarily is the only difference between the two.

 

SQL DETAILED
Detailed sql debug.jpg

 

The MySQL debugger is only valid till your session exists. If you logout it automatically turns the debugger off. It is not necessary that you need to logout or wait for the session to timeout. Once done if you want to continue using the application it can be manually turned by navigating to System Security and clicking on Stop Debugging. If one of your users is experience unusual slowness, admins can turn on the SQL Debugger and then impersonate the user to investigate the queries involved in the transaction for that particular user transaction.

Appl_filter.png

 

Impersonating a user to investigate queries for a particular user transaction

We impersonate a user experiencing performance issues to see find the specific form, incident or transaction that could be causing slowness. In the Application

navigation filter type in Debug SQL. Click on either of the options to turn the MySQL Debugger on. You can impersonate any user after this and navigate to a form or list to the see a list of queries being executed.

 

 

For example:

On trying to open the incident list, it took more then 180 seconds to render. This is reproducible every time and no other list is affected. To determine if the slowness lies on the database, we need to find the slow database query(s) or the source from where it's originating from.

mysql debugger.jpg

There were approximately 3,800 insert queries that were loading with the list. Upon clicking the SQL Debugger (detail) to find the source, we see it is the the business rule. These queries were running against the incident table which was recursively adding gs.log() statements for every record being pulled, causing the slowness.

 

If a long running database query is responsible for the slowness, then you can use the explain plan to determine if there is a way to optimize it using an index. Cases where performance degradation is the underlying source (script includes and business rules) the queries can be identified and the appropriate steps or plan of action can be taken to rectify it to improve the performance of the application. The SQL debugger is a great must-use-tool recommended for troubleshooting performance of many ServiceNow applications. There are certain applications which do not use the SQL debugger such as the Visual Task Boards (VTB), ServiceNow support can help you out with that.

 

 

Thank you john.gonzalez  for the idea to write this post!

Coding, scripting,  and developing is a lot like the game Pitfall. In Pitfall it is jump over this, avoid that, skip to here and then you're safe! Until you realize that your code doesn't work because there is a mistake and it killed your entire application.

 

Then you're stuck, so when you have no where left to turn you call:

 

Your Friendly Support Engineer!

 

pitfall.png

 

Now The Questions Begin, Be Prepared, Look Like a Pro

 

What is Considered a Customization?

A Configuration is anything that is not shipped with the baseline product, but can be added using Service Now deployed tools. An example of this would be a new client script, a new business rule, or a new UI Policy using the Glide Libraries. A Customization is an altered configuration that goes beyond the platform methods. An example of this would be a new client script, business rule, or UI Policy that uses anything except Glide Libraries. For more on configurations and customizations, see Ch-ch-ch-changes: customizations vs. configurations.

 

Customer Support is limited to Configuration assistance with...

Examples of Configurations that are supported:

  1. UI policy that does not have any code associated with it.  The UI Policy is strictly made with the conditions builder.
  2. A Client Script that is limited to Service Now provided objects like GlideRecord.

 

 

Customizatihttp://www.google.comons that Support cannot assist with...

Examples of Customizations that are not supported:

  1. UI Policy is not working, because it is buried under client scripts with JQuery and Java Script not using the Glide Libraries.
  2. A Client Script that uses GlideRecord, and also has JavaScript, JQuery, Angular within it.

 

 

Support loves to help customers because it’s rewarding and fun to see customers excited about getting something done right.  Unfortunately, Support is limited to non-Customization incidents only. ServiceNow support is always looking for opportunities to help the customer set themselves up for success and with that we share our latest content on social media to help spread the reach of solutions, workarounds and best practices we put together.

 

I am hoping by outlining some of the things that ServiceNow support can and cannot do will help you...

  1. Find issues effectively.
  2. Ask better directed questions to support to get over the Customization HUMP.
  3. Get the support you need.

 

Over time I started noticing little things that customers CAN do to help themselves to avoid coding pitfalls. I will be sharing blog posts on coding best practices, to help you understand customizations and configurations a bit better so that support can work with you when things start getting dodgy.

one

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM December 11, 2014 ON THE CLOUDSHERPAS SERVICENOW SCRIPTING 101 BLOG.

 

WITH THE IMPLEMENTATION OF GENEVA gs.sql IS NO LONGER AVAILABLE.

____________________________________________________________________________

 

 

In my previous article (ServiceNow Admin 101: You Too Can Do DISTINCT Queries Using GlideRecord) I demonstrated how to do a variety of DISTINCT queries using GlideRecord.  Now let me show how you can do something similar to do a UNION query.

 

So, what is a UNION query?  It is where you take data from two unrelated tables and bring them together into a single record set.

 

For example to do this in SQL you might write something like this to obtain a list of Assigned To users from both the Asset and CMDB CI tables.  Notice that these fields are not linked in any way.  In other words the final list contains two different lists combined.  Both of these fields are reference fields to the sys_user table.   I put in the “type” field to show what table the user name is coming from.

 

SELECT DISTINCT user1.name as name, 'ASSET' as type
FROM alm_asset asset JOIN sys_user user1 ON asset.assigned_to = user1.sys_id
WHERE asset.assigned_to IS NOT NULL
GROUP BY user1.name
UNION
SELECT DISTINCT user2.name, 'CI' as type
FROM cmdb_ci ci JOIN sys_user user2 ON ci.assigned_to = user2.sys_id 
WHERE ci.assigned_to IS NOT NULL
GROUP BY user2.name

 

Now using Scripts – Background (don’t forget to elevate your privs to Security Admin) this can be modeled using gs.sql thusly:

 

var sql = "(SELECT DISTINCT user1.name as name, 'ASSET' as type FROM alm_asset asset JOIN sys_user user1 ON asset.assigned_to = user1.sys_id WHERE asset.assigned_to IS NOT NULL GROUP BY user1.name ) ";

sql += 'UNION ';

sql += "(SELECT DISTINCT user2.name, 'CI' as type FROM cmdb_ci ci JOIN sys_user user2 ON ci.assigned_to = user2.sys_id  WHERE ci.assigned_to IS NOT NULL GROUP BY user2.name ) ";

sql += 'ORDER BY name';

gs.sql(sql);

 

And produces something like the following list:

 

 

If we take the “type” field off we get a nicely DISTINCT’ed list of user names from both tables:

 

 

var sql = "(SELECT DISTINCT user1.name as name FROM alm_asset asset JOIN sys_user user1 ON asset.assigned_to = user1.sys_id WHERE asset.assigned_to IS NOT NULL GROUP BY user1.name ) ";
  
sql += 'UNION ';
  
sql += "(SELECT DISTINCT user2.name FROM cmdb_ci ci JOIN sys_user user2 ON ci.assigned_to = user2.sys_id  WHERE ci.assigned_to IS NOT NULL GROUP BY user2.name ) ";

 sql += 'ORDER BY name';
 
 gs.sql(sql);

 

 

Unfortunately the gs.sql statement does us no good as it does not return a result set we can use like GlideRecord does.

 

So, now we have to translate all of this into GlideRecord script like this:

 

// final result list
var userList = [];

// Asset names
var asset = new GlideRecord('alm_asset');
asset.addNotNullQuery('assigned_to');
asset.query();

while (asset.next()) {
   userList.push(asset.assigned_to.name + '');
}

// CMDB_CI names
var cmdbCI = new GlideRecord('cmdb_ci');
cmdbCI.addNotNullQuery('assigned_to');
cmdbCI.query();

while (cmdbCI.next()) {
   userList.push(cmdbCI.assigned_to.name + '');
}

// now sort (order by name) the array and DISTINCT the name list
userList = new ArrayUtil().unique(userList.sort());

// now print off the results
for (var i = 0; i < userList.length; i++) {
   gs.print(userList[i]);
}

 

This nicely re-produces our desired list:

 

 

Obviously this is just one of many possible UNION queries.  Some other possibilities are:

 

A combined list of:

 

  • IP Phones, and Printers using the cmdb_ci_ip_phone and cmdb_ci_printer tables
  • Business Systems and Applications using the cmdb_ci_systems and cmdb_ci_appl tables
  • Routers and Switches using cmdb_ci_ip_router and cmdb_ci_ip_switch

 

And so on!

 

BTW, ArrayUtil has a “union” function where you can join two arrays together. Check it out: ArrayUtil Wiki 

 

Also, a couple of best practices concerning gs.sql:

 

  1. Do not use it for anything but modeling SQL statements in Scripts – Background.  It makes a great way to verify counts.
  2. It does not return a resultset you can use in your code.
  3. If you use a table upstream you have to join in every table in the hierarchy (sic. cmdb_ci_computer has to be joined with cmdb_ci to get ALL fields into the query – this is a pain compared to GlideRecord which pulls everything in the hierarchy automatically for you).

 

Steven Bell

 

accenture technology logo.pngsn-community-mvp.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!

 


Click for More Expert Blogs and also Find Expert Events!


NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM July 14, 2015 ON THE CLOUDSHERPAS SERVICENOW SCRIPTING 101 BLOG.

____________________________________________________________________________

 

 

I have worked with ServiceNow encoded queries for several years now, using them to express more complex query logic. I usually copy what I need from the list view query builder breadcrumbs, though I’ve also rolled my own from scratch. Despite all this experience, I noticed something odd recently that caught me a bit by surprise.

 

To set the stage here, let me explain a couple of things about normal order of precedence.

 

First, let’s review the state table for AND and OR evaluation:

 

            TRUE AND TRUE:     TRUE

            TRUE AND FALSE:   FALSE

            FALSE AND TRUE:   FALSE

            FALSE AND FALSE:  FALSE

 

            TRUE OR TRUE:       TRUE

            TRUE OR FALSE:     TRUE

            FALSE OR TRUE:     TRUE

            FALSE OR FALSE:    FALSE

 

Next, let’s note that the order of precedence for evaluation is always:

 

  1. Inside the parenthesis
  2. AND
  3. OR

 

So, for example:

 

(TRUE OR FALSE) AND (FALSE OR FALSE)

 

Would evaluate to:

 

(FALSE) AND (FALSE)

 

Would evaluate to:

 

FALSE

 

Almost ALL computer and database languages follow this paradigm.

 

Now that we’ve established that, let’s get back to my recent revelation. I did my usual copying of an encoded query from query builder breadcrumbs, but this time I noticed that the copied query did not appear to reflect the correct order of precedence. Instead, it seemed to be evaluating the OR operators first and then the AND operators!

 

My query in query builder:

 

1. Building the query.JPG

 

Copying the query to use in an encoded query statement:

 

2. Copying the query.JPG

 

Some Notes:

 

  • I echoed out the encoded query after each of these examples to make sure that something wasn’t being re-interpreted in each case. It was not. In every case used in this article, the query was echoed back as expected -- no surprise reconstructions. 
  • I ran all of these queries using Scripts - Background, but they can also be executed just as easily using Fix Scripts. 
  • Remember that with ServiceNow GlideRecords, if an error is encountered in an .addQuery, the statement is ignored. 
  • If an error is encountered when evaluating an .addEncodedQuery, everything including and after the error is ignored.

 

// Query as it was originally copied. This gives the correct result so it would appear that the order of precedence is backwards!!!
var incidents = new GlideRecord('incident');
incidents.addEncodedQuery('active=true^priority=2^ORpriority=3^state=1^ORstate=2');
incidents.query();

gs.print('Original: ' + incidents.getRowCount());  // 7
gs.print(incidents.getEncodedQuery());

 

I was a bit surprised that it returned the same number of records that my list view query had returned. This should not have been the case if the order of precedence was properly followed,  so I decided to slap a few parenthesis in to make it more readable and to force it to behave correctly.

 

// Query re-written with logical parenthesis to force the order.  This does something completely wonky!
var incidents = new GlideRecord('incident');
incidents.addEncodedQuery('active=true^(priority=2^ORpriority=3)^(state=1^ORstate=2)');
incidents.query();

gs.print('Paren 1st: ' + incidents.getRowCount());
gs.print(incidents.getEncodedQuery());

 

This should have worked, but instead it just returned a count of all Active records. It would appear that everything after the first “^” was ignored! Could it be that the parenthesis were causing an error in the encoded query?

 

Next, I decided to rewrite the original encoded query as a true GlideRecord query.

 

// Query re-written in GlideRecord form to validate results. This is the correct precedence and actually mimics the original query builder.
var incidents = new GlideRecord('incident');
incidents.addActiveQuery();
incidents.addQuery('priority','2').addOrCondition('priority','3');
incidents.addQuery('state','1').addOrCondition('state','2');
incidents.query();

gs.print('GlideRecord: ' + incidents.getRowCount());  // 7
gs.print(incidents.getEncodedQuery());

 

This came back with the same seven records. Since this query actually demonstrates the correct order of precedence, it confirmed that the encoded queries were being evaluated in reverse order of precedence.

 

Undaunted, I decided to break out the active query into its own line and isolate the parenthesis portion of the query.

 

// Query re-written with logical parenthesis to force the order.
var incidents = new GlideRecord('incident');
incidents.addActiveQuery();
incidents.addEncodedQuery('(priority=2^ORpriority=3)^(state=1^ORstate=2)');
incidents.query();

gs.print('Paren 2nd: ' + incidents.getRowCount());
gs.print(incidents.getEncodedQuery());

 

Bang! Confirmed! The encoded query was completely ignored, and the results returned all 29 active records. So it turns out that parenthesis are an illegal “operator” in encoded queries. They actually cause an error and everything from the first parenthesis on is ignored.

 

I decided to do some more digging and check one more possibility. This time, I put parenthesis around my active check.

 

// Query re-written with logical parenthesis to force the order.  
var incidents = new GlideRecord('incident');
incidents.addEncodedQuery('(active=true)^((priority=2^ORpriority=3)^(state=1^ORstate=2))');
incidents.query();

gs.print('Paren 3rd: ' + incidents.getRowCount());
gs.print(incidents.getEncodedQuery());

 

...and this caused the entire encoded query line to error out and all 54 records in the table were returned! So this second test confirmed, without a doubt, that parenthesis are illegal in encoded query statements. Wow!

 

Okay, I thought, let’s try this a different way and see if multiple encoded queries work.

 

// Query rewritten to force the ANDs to work correctly
var incidents = new GlideRecord('incident');
incidents.addEncodedQuery('active=true');
incidents.addEncodedQuery('priority=2^ORpriority=3');
incidents.addEncodedQuery('state=1^ORstate=2');
incidents.query();

gs.print('Forced: ' + incidents.getRowCount());  // 7
gs.print(incidents.getEncodedQuery());

 

They did, and it stands to reason that, since there were no parenthesis, my order of precedence is obvious to a follow-on maintenance coder.

 

Finally, I had to verify if the records returned were actually REAL. I decided to break out the combination of queries represented by the original list view query.

 

var incidents = new GlideRecord('incident');
incidents.addActiveQuery();
incidents.addQuery('priority', '2');
incidents.addQuery('state', '1');
incidents.query();

var one = incidents.getRowCount();  // zero

var incidents = new GlideRecord('incident');
incidents.addActiveQuery();
incidents.addQuery('priority', '2');
incidents.addQuery('state', '2');
incidents.query();

var two = incidents.getRowCount();  // 3

var incidents = new GlideRecord('incident');
incidents.addActiveQuery();
incidents.addQuery('priority', '3');
incidents.addQuery('state', '1');
incidents.query();

var three = incidents.getRowCount();  // zero

var incidents = new GlideRecord('incident');
incidents.addActiveQuery();
incidents.addQuery('priority', '3');
incidents.addQuery('state', '2');
incidents.query();

var four = incidents.getRowCount();  // 4  for a total of 7

gs.print('Breakout: ' + (one+two+three+four));

 

There you go. Seven records were found. That test verified the list view AND confirmed in yet another way that the order of precedence in encoded queries is reversed.

 

At this point, I went back to the breadcrumbs and looked carefully, and I noticed something that had never caught my eye before: The breadcrumbs listing looks like this:

 

All>Active = true>Priority = 2 - High .or. Priority = 3 - Moderate>State = New .or. State = Active

 

Notice the “greater than” symbols? And how the order of precedence is preserved? OR is first, then the “>” (which represent “AND”)!

 

So, in summary, when using the encoded query breadcrumb builder in ServiceNow, you need to remember that:

 

  1. The order of precedence is reversed (ORs are evaluated first, then ANDs).
  2. Parenthesis cannot be used to change the order of precedence, and using parenthesis actually causes an error that does not manifest when running the GlideRecord query.

 

Learn More

 

Check out these resources to learn more about order of precedence and ServiceNow encoded queries:

 

 

The code for this article may be found at the following ServiceNow Share link.

 

 

Steven Bell

 

accenture technology logo.pngsn-community-mvp.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!

 


for Click for More Expert Blogs and also Find Expert Events!


Occasionally you might notice that a choice is displaying in blue text. This color indicates that the choice is sad (has the blues, get it?) because it is an invalid or inactive choice. This might occur, for example, if the related category has been deactivated but a record still contains the inactive value.

ChoiceListInvalid2.jpg

Or, as manjul.katare pointed out in an answer to this query, a sys_choice record has been deleted but its value is still being used in the record. The blue indicator is often misunderstood because red is usually used as a warning color, but in ServiceNow, it's text in blue in a choice list or drop-down list that indicates that the value has either been removed or is no longer active.

blue-dropdown.jpg

To return the blue text to the default black color, make sure that the choice is pointing to a valid record. You might also try to clear the cache by going to /cache.do to make sure that new values are properly cached.

 

If it makes you sad to see the sad blue text, you can turn off the color indicator for invalid choices by navigating to System Properties > UI Properties and clearing the checkbox for the Display missing choice list entries property.

 

More About Choice Lists

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM November 11, 2014 ON THE CLOUDSHERPAS SERVICENOW ADMIN 101 BLOG.

____________________________________________________________________________

 

I teach several different ServiceNow classes, and, invariably, I get a lot of questions about how to do DISTINCT queries. De-duplicating data in a result set is important, and there is no feature in GlideRecord that covers this purpose. So, the big question is: I can do a DISTINCT in SQL, but I can’t do one with GlideRecord? What gives? 

 

GlideRecords are designed to bring back all fields for each record. SQL has the flexibility to bring back one or more fields and to make any one of those fields unique.

 

For example, in SQL a simple query could be written thus:

 

SELECT DISTINCT caller_id

FROM incident

WHERE active = true

AND caller_id IS NOT NULL

ORDER BY caller_id

 

This would produce a list of unique caller sys_id’s that are active and that are not empty from the Incident table.

 

In this article, I will show a few methods I use to accomplish this using GlideRecord.

 

Starting with the SQL example, let’s render the query into a GlideRecord script.

 

 

var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addNotNullQuery('caller_id.name');
incidents.orderBy('caller_id.name');
incidents.query();

gs.print(incidents.getRowCount());

while(incidents.next()) {
   gs.print(incidents.caller_id.name + '');  // get the real english name
}



When run from Scripts – Background, this produces the following list (63 records):

 

C:\Users\User 1\Desktop\1. duplicates problem.jpg

 

You can see the need for some sort of method to get these down to one entry per name. By introducing an array in which to store the names, we can make use of the ArrayUtil library in ServiceNow. ArrayUtil contains several really great array utilities. Check it out here.

 

One of these array utilities is .unique(..), which allows us to take a simple, one-dimensional array and remove all the duplicate entries.

 

So, revisiting our original example, the .unique(..) array utility reduces the original list of 63 records to just seven! Nifty, huh?

 

 

var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addNotNullQuery('caller_id.name');
incidents.orderBy('caller_id.name');
incidents.query();

gs.print(incidents.getRowCount());

var callerIdList = [];
while(incidents.next()) {
   callerIdList.push(incidents.caller_id.name.trim());
}

var arrayUtil = new ArrayUtil();
callerIdList = arrayUtil.unique(callerIdList);

gs.print(callerIdList.length);

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



C:\Users\User 1\Desktop\2. unique list.jpg

 

You now have your DISTINCT GlideRecord query. All done, right? Well, no. Actually, this solution is only good for simple, one-dimensional arrays. So what if you want to get a unique list based on several values in the record? 

 

Let’s say we want the caller id name AND category to be unique. If we go back to the original query and expand it a little, you’ll see the problem.

 

var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addNotNullQuery('caller_id.name');
incidents.orderBy('caller_id.name');
incidents.query();

gs.print(incidents.getRowCount());

while(incidents.next()) {
   gs.print(incidents.caller_id.name.trim() + ' - ' + incidents.category.trim());
}

 

 

This produces:

 

C:\Users\User 1\Desktop\3. multiple field dup problem.jpg

 

Turns out there is a pretty easy way to clean this up: create a “key” field out of the various fields you want de-dup’d. This key is created simply by concatenating two (or more) of the fields together with a separator like the bar (“|”), caret (“^”) or something that would not normally be used in a description or name. I would avoid ampersands, commas, hyphens, etc. as they can appear in names and descriptions. You want something that, when you bust it apart, will give you back exactly what you started with. So, how does it work?

 

  1. In the while concatenate your two fields together with your separator of choice.
  2. Push the key onto your array.
  3. Do the unique against the array.


This creates a unique list that you can bust apart using the .split function against the bar to retrieve your values:

 

var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addNotNullQuery('caller_id.name');
incidents.orderBy('caller_id.name');
incidents.orderBy('category');
incidents.query();

gs.print(incidents.getRowCount());

var callerIdList = [];

while(incidents.next()) {   
    var key = incidents.caller_id.name.trim() + '|' + incidents.category.trim();   
    callerIdList.push(key);
}

var arrayUtil = new ArrayUtil();
callerIdList = arrayUtil.unique(callerIdList);

gs.print(callerIdList.length);

for (var i = 0; i < callerIdList.length; i++)  {   
    var callerIdListSplit = callerIdList[i].split('|');   
    var callerId = callerIdListSplit[0];   
    var category = callerIdListSplit[1];  
    gs.print(callerId + ' - ' + category);
}

 


Ultimately, this results in:
C:\Users\User 1\Desktop\4.multiple field dup solution.jpg
You can expand this to include as many fields as you want.


So now we have pushed our ArrayUtil unique function to its limit. Well, what if you want to bring along values that you don’t want uniqued, but would like made available? You have to move into the realm of objects to accomplish this, as it requires a different way of finding and removing duplicates. You don’t want all the fields, just specific ones. Further, you want to remove any duplicates based on one or more of those fields, and you want to bring along fields that will give you information and won’t be used in the duplication check.


The best mechanism for this is an Object. You need to place all of your desired fields from a record into an object and then store that object onto an array. The final result is a list of objects.


I didn’t want to have to reinvent the wheel, so I fished around on the web and found several examples that gave me an idea of what I wanted:  A function that would remove duplicated entries from a list of objects. The best examples used the JavaScript array function “splice” to remove an array value. 


Using the key field idea from the previous example allows us to “tag” each object with a value. That allows us to do a DISTINCT and bring along extra fields too.


So here is the process for de-duping an object array:

 

  1. In the while concatenate your two fields together with your separator of choice (I used the bar and stored it in a field named “key” in the object).
  2. Push the object onto the array.
  3. With the new unique function, loop through all of the records in the array. Using each object’s key field, compare against all of the other records in the array and remove any duplicates (adjusting the array size down each time one is removed).
  4. Loop through what is left of the original array and you will see that all of the duplicates are gone and you have your extra fields too.

 

 

var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addNotNullQuery('caller_id.name');
incidents.orderBy('caller_id.name');
incidents.orderBy('category');
incidents.query();

gs.print('---> Before unique: ' + incidents.getRowCount());

var callerIdList = [];

while(incidents.next()) {
   var incident = {};
   incident.caller_id = incidents.caller_id.name.trim();
   incident.category = incidents.category + '';
   incident.priority = incidents.priority.getDisplayValue();
   incident.description = incidents.short_description + '';
   incident.key = incident.caller_id + '|' + incident.category + '|' + incident.priority;

   callerIdList.push(incident);
}

callerIdList = uniqueObjectList(callerIdList);

gs.print('---> After unique: ' + callerIdList.length);

for (var caller in callerIdList) {
   var callerId = callerIdList[caller].caller_id;
   var category = callerIdList[caller].category;
   var priority = callerIdList[caller].priority;
   var description = callerIdList[caller].description;
   gs.print(callerId + ' - ' + category + ' - ' + priority + ' - ' + description);
}

function uniqueObjectList(callerList) {
   for( var i = 0; i < callerList.length; i++){
       for( var j = i + 1; j < callerList.length; j++){
           if( callerList[j].key == callerList[i].key ) {
               callerList.splice(j, 1);  // delete the duplicate
               --j;  // reduce the array length by one
           }
       }
   }

   return callerList;
}

 

This produces:

 

C:\Users\User 1\Desktop\5.object array dedup.jpg


That is pretty much the sum of the techniques you will find me using to do a DISTINCT with GlideRecord. Now it’s your turn!

 

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

 

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

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM September 23, 2014 ON THE CLOUDSHERPAS SERVICENOW ADMIN 101 BLOG.

____________________________________________________________________________

 

 

At least once a week I am asked: Is there a way to create a single combined report of my Service Catalog Requested Item variables? And, for

that matter, where does this information reside inside of ServiceNow?

 

Well the answer to the first question is yes, and the trick to how to do it is in the answer to the second question!

 

A lot of people want to see their Requested Item variables, including which variables are associated with which item by requestor, a list of

questions associated with a particular item or… well you get the idea!

 

It took a little bit to track down the solution when I first dug into it, but it really isn’t anything bad. The best approach is a Database View

(/tag/database-views/) that ties all of the necessary tables together to form the report.

 

So, where does everything live? If you dig down a bit, you’ll find that:

 

1) Service Catalog Request Items (RITM) are stored in the sc_req_item table

2) Variables are stored in the sc_item_option table

 

However, there does not appear to be any sort of direct relationship between the two.

 

In comes the trick:

 

3) RITM to sc_item_option associations are actually kept in a third table: sc_item_option_mtom

 

But how is this done? I nod sagely, and I say: “sys_id, my friend!”

 

The mtom table (by the way, does anyone have any idea what “mtom” stands for? Item–to-item maybe?) allows several Service Catalog

Requested Items to be associated to the same variables. Cool, huh?!

 

When I figured this out, I smacked myself in the forehead for not getting it the first time. After that, the rest was simple.

 

The relationships:

 

sc_req_item relates to sc_item_option_mtom (via sc_req_item.sys_id)

 

sc_item_option_mtom (via sc_item_option.sys_id) relates to sc_item_option

 

For the confused, here is a diagram:

 

 

The next step is to weave these together in a Database View, which can then be accessed via a report.

 

1. Create a Database View:

 

a. From the ServiceNow navigation search type: database

b. Click on System Definition / Database Views


C:\Users\User 1\Desktop\1. Database Views.jpg

 

c. From the Database Views list view, click on the new button to create a new Database View. Name it something meaningful so that you don’t have to search far when pulling in the Database View for your report.

 

i.  Name:  SC RITM Variables (u_sc_ritm_variables)

ii. Click on the save button

 

C:\Users\User 1\Desktop\2. Create New DB View.jpg

 

iii. At the bottom of the form you will see the View Tables list view. Click the new button.

 

1. Table: Variable Ownership (sc_item_option_mtom)

2. Variable prefix: mtom

3, Order: 100

4. Submit

 

C:\Users\User 1\Desktop\3. mtom.jpg

 

iv. Add another table. Click the new button again.

 

1. Table: Options (sc_item_option)

2. Variable prefix: options

3. Order: 200

4. Where clause: mtom.sc_item_option = options.sys_id

5. Submit

 

C:\Users\User 1\Desktop\4. options.jpg

 

v. Add yet another table (this is the final one, I promise). Click the new button again.

 

1. Table: Requested Item (sc_req_item)

2. Variable prefix: ritm

3. Order: 300

4. Where clause:  mtom.request_item = ritm.sys_id

5. Submit

 


C:\Users\User 1\Desktop\5. ritm.jpg

d. Your Database View should now look like this:

 

C:\Users\User 1\Desktop\6. database view.jpg

 

e. You are done with the Database View! To test it out click the “Try It” link under the Related Links section of the Database View form.

 

2. Now create a new report and use the “SC RITM Variables” (u_sc_ritm_variables) table.

 

So what exactly are we looking at with all this data we are getting from the view?

 

This would be a combination of all the Requested Items and their associated variables.

 

Some suggested variables for the report include:

 

Number (RITM)

Item

Question

Value

Quantity

Approval

Due Date

Priority

Impact

Urgency

Opened

Opened By

Price

Request

Requester

Short Description

State

 

That’s all it takes, you’re done! You should now have your Database View and a list of the best variables for the view.

 

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

 

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

The Helsinki release is a big upgrade to the ServiceNow developer experience. The overall Helsinki themes are i) Consumerizing the Service Experience and ii) Accelerate Time-to-Value. New developer and development features in the platform directly support both.

 

Service Portal Designer is the canvas for delightful employee experiences. Visually compelling with predefined widgets and templates, Service Portal enables developers  to build visually stunning employee portals using out of the box widgets and templates as well as creating their own widgets and styles with standard web tools such as Bootstrap, AngularJS, HTML, and CSS.

 

Delegated Development is one of the most requested features from our customers. They told us that they want to reduce the # of admins on their production instances. At the same time, IT is getting requests from other departments and business units to build their own apps on the ServiceNow Platform. IT could only support this by creating more admins, which leads to concerns about platform performance, individual app performance, and potential security issues. This led to shadow IT with departments and business units sourcing their own development platforms and resulted in increasing application backlogs for IT.

 

Delegated Development solves this headache by enabling IT to create a scoped app based on department/BU requirements, assign non-admin users and teams granular developer permissions against that app, define app cross-scope permissions that dictate resources and data accessible outside of the app scope,  control publishing to  production, and track and enforce app resource usage. Individuals or groups of developers can be assigned their own logical sandboxes with very granular definitions of what they are allowed to see and do. Now you can be sure that your app development team in one department is safely quarantined from touching, for example, your change management application.

 

This enables IT to become the at-scale PaaS provider to the enterprise and retain governance and control over the platform,  while at the same time scaling out the # of developers  across the enterprise. This leads to accelerated time-to-value and increased business agility for departments and BUs outside of IT. Now they can quickly build the apps on the ServiceNow Platform that meet their specific business needs without having to rely on IT all of the time.

 


developer permissions.png

Delegated Development: Enabling non-admin developers to leverage the ServiceNow Platform

 

Scoped Administration - allows organizations to protect sensitive application data by restricting how users acquire application-specific roles.

 

ECMAScript 5 - a modern JavaScript experience that developers expect, including the ability to leverage external 3rd party libraries such as Lodash. This increases developer productivity and accelerates time to value for new applications.

 

Studio - Git Source Control Integration - streamlines the agile development process by supporting any Git-compatible distributed source control solution such as GitHub or Bitbucket.

 

API Usage Analyticsidentifies who is using a Web Service API, and what versions are being used, providing more visibility into active integrations and assisting with application rationalization.

 

Excel XLSX support: - enables import and export of records in XLSX format.  XLSX Processing is significantly better in memory management

 

Client App Enablement:  Example REST Client applications have been created and their source made available on GitHub to illustrate how customers can connect applications to ServiceNow using our REST APIs. The app enablement applications include NodeJS and iOS example application as well as a Scripted REST API. Each repo includes documentation that walks developers through cloning the repo from GitHub to configuring and running the application. These example client applications can be accessed from with ServiceNow by navigating to REST -> Example Client Apps

 

Web Service Import Sets -  enhanced asynchronous behavior.

 

Reporting - Map reports, Single Score auto–refresh, and more efficient calendar rendering to improve the reporting experience.

 

Developers can get hands-on with Helsinki at ServiceNow Developers and at the CreatorCon Hackathon and all CreatorCon workshops at Knowledge16.

 

We can't wait to see what you build on H!

Martin Barclay
Director, Product Marketing
App Store and ISVs
ServiceNow
Santa Clara, CA

 

I can't remember when I was more disappointed with a ServiceNow message. 
Every year the Knowledge marketing message pushes deeper into near-IT and non-IT circles.  Stories of value delivered across business are elegantly written and beautifully shot in white papers and customer testimonials.  But what of those who's hands still bear the dust of the build?

 

"Get your geek on".  Oh you zany geeks, you!  Up to your old shenanigans again!  Anyone else feel like a box of crayons just got spilled onto the kids table at Thanksgiving? 
"Make something pretty now!"

 

But let me tell you this.. this product and this company would be *NOTHING* if it weren't for the kinds of people who #HackNow.  This company owes its dominant market position not only to the genius of Fred and his devs, but also to *YOU*, the developers and admins who slogged through terrible processes and work dynamics.  Expended your own sweat, tears, and effort to build great things.  Great things this company didn't even imagine when they sold it to you.

 

HACKERS LEAD THE WAY

What won the inaugural Hackathon at K13?  An ideation module.  Two product versions later, its emerging in the base product.

FruDevCon13?  A business continuity and disaster recovery automation app.  Two years later, 2/5 Innovation of the Year finalists are for BCDR

FruDevCon14?  Instantaneous portal deployment.  A year later... ServicePortal

 

Biometric scanning, robotics orchestration, social media asset databases, social media campaign automation, password archiving, public disaster portal pages, availability automation.

 

What will be next?  How many years would it have taken to realize those possibilities without Hackers like you?

 

A REAL MESSAGE FOR HACKNOW

 

Let the executives pat themselves on the back, toasting their success discussing solutions they've neither imagined nor built.

 

You, ServiceNow developer are not a geek at play.  It is the work of your hands that will be remembered.  YOU are the keystone of the Knowledge conference.  You are the font of the phalanx.  The very tip of the spear that's driving value creation in our industry. 

 

rain 300 hail

 

Join me on the front lines of the ServiceNow hacking.  Bring your frustrations, your rage, and your dreams.

PDF stands for “Portable Document Format” and because of its portability and widespread use, ServiceNow provides the capability of exporting many different types of information to PDF such as reports, homepages, and Performance Analytics dashboards. (Note that before users can export homepages or dashboards to PDF, you have to activate the WebKit HTML To PDF plugin, as described in Activate WebKit HTML To PDF.)

 

pdf-icon.jpg

When exporting to PDF, you might have encountered some issues in the following circumstances:

  • Exporting a large amount of image data
  • HTML fields not rendering properly
  • Difficulty exporting a report if Display Grid is selected
  • PDF exports of a Service Catalog item don't include custom variables

 

Here are some tips and workarounds for possible PDF problems.

 

Exporting a large amount of image data

Images are a special type of content and exporting a large number of images can cause problems with your system ranging from an export error to performance degradation or even possibly crashes. Try to export no more than 15 MB of images to a single PDF at a time.

 

HTML field data exports

Forms that contain HTML fields sometimes don’t render properly when exported to PDF, and the raw HTML coding appears. To export the data properly, either select the Print Preview format or create a UI page with the desired look and then print or export to PDF.

 

Note that if you can upgrade, this issue was resolved in Fuji Patch 12 and Geneva Patch 3. For more information about HTML fields, see the documentation topic HTML Field Administration.

 

Exporting reports with Display Grid enabled

If you have the Display Grid option enabled when you create a report, an error occurs when you select Export to PDF and click Export. To avoid the error, save the report before exporting it. If that doesn’t work, try to re-create the report, save it, and then export to PDF.

pdf error.jpg

 

Service Catalog exports to PDF don't include custom variables

When exported to PDF, a Service Catalog Requested Item (sc_req_item) does not include the catalog variables that the user entered when exported. To include this information, click the gear icon and select Printer friendly version, and then print or export the form to PDF.

 

pdf custom field varibales.jpg

 

More fun with PDFs

A ServiceNow group is a container for users that have similar purposes or functions. Creating and managing groups in ServiceNow should not be taken lightly, as many ServiceNow functionalities, such as task assignments, approvals, security, and emails rely on groups. With that in mind, here are my top tips and best practices for enhancing and managing groups in ServiceNow.

 

1. TASK ASSIGNMENTS

Assignment groups are the most common group type. ServiceNow has an out of the box dependency between assignment group and assigned to fields. Typically, it’s mandatory for the assignment group to be identified on task records before a user can click save. These assignment groups will allocate responsibility for working on the task to a specified team so that nothing drifts into the “ticket abyss.” Even if that particular team is not responsible for working on the task, the group is now responsible for reassigning the task to a more suitable group.

 

From a manager’s perspective, groups allow for detailed resource reporting, including what each team is working on, which teams may be overloaded and which teams have availability to take on new tasks.

 

Some examples of assignment groups are:

  • Service Desk
  • Network Administrators
  • Server Administrators
  • Database Administrators
  • Application Developers

 

These groups can also have a hierarchical structure. So, if you’re using ServiceNow for HR, you may consider setting a parent/child group dependency like this:

 

  • Human Resources
    • Benefits
    • Payroll
    • Recruiting
    • Learning and Development

 

2. APPROVALS

When designing approvals in your ServiceNow workflows, you have the option to select user approval or group approval. I highly recommend using group approvals as much as possible since it allows you to add or remove members from a group without any development. Even if this group only contains one person, it’s still a preferred method. You can also create custom groups for approving any process. A CAB (Change Advisory Board) group may be needed to approve high risk changes.

 

3. SECURITY

As a best practice, ServiceNow recommends never assigning roles (permissions) directly to a user. Instead, it recommends creating a group to which you can assign roles. A group can have as many roles as needed, and ServiceNow Admins can define those roles using the Access Control List (ACLs). The ACLs allow you to set read, write, create and delete access for fields data, records and tables. With this granular security ability, you may need to create additional groups for elevated permissions.

 

Examples of applying roles to groups include:

 

  • ITIL vs. ITIL Admin: ITIL has basic read/write capabilities whereas ITIL admin has an elevated level that includes delete. Typically we see the ITIL role assigned to groups such as Service Desk, Network Administrators, System Administrators, etc. while the ITIL Admin is usually assigned to a managers group, such as IT Managers, that contains each ITIL group’s functional manager.
  • Knowledge vs. Knowledge Admin: The Knowledge role is able to create, edit and review articles, while the Knowledge Admin role is also able to publish and retire articles. In this case, a hypothetical group called Technical Writers might have the Knowledge role while a Content Manager group might have the Knowledge Admin role.

 

You can refer to the ServiceNow Wiki for a more detailed description of each out of the box role.

 

Using groups can also improve security by limiting Catalog Items displayed on the Service Catalog or restricting visibility of certain reports for specified groups.

 

4. EMAILS

The last type of group to consider is email notification groups, which are most commonly used in task assignments. You can use these groups to automatically send an email letting users know that a task has been assigned to their group and is now waiting to be assigned to an individual.

 

5. ENHANCING GROUPS

Here are the recommended fields to fill out when populating group information:

  • Name: This one is fairly obvious, but you will want to give your group a name.
  • Manager: Setting a group manager allows the system to reference that individual for other processes.
  • Email: If you fill out email, any group emails will send to this address instead of individual users in that group. Unfortunately, distribution lists without a fully qualified email address will not work.
  • Parent: If you are setting up a group hierarchy like in the HR example above, you will need to add a parent group name that already exists.

 

6. AUTOMATION TIP: MANAGING USERS AND GROUPS USING A SERVICE CATALOG ITEM

Managing LDAP groups is out of the realm of ServiceNow administration. For manually created groups, I usually suggest creating a Service Catalog item called “Add/Remove user in ServiceNow group,” which will do all of the management autonomously.

 

To create this catalog item, you’ll need to have at the very least a mandatory reference or list variable referencing the user table and a reference to the group table. Other variables such as business reason are optional and can be added for your process. Once the requester fills out all of the other necessary details, he or she will order the request. The first step in the workflow is to send an approval to the group manager. Once approved, the user(s) will be added to the group requested via a workflow script. If rejected, no action is taken and the requester receives an email.

This method removes much of the manual maintenance from the ServiceNow administrator and provides a “paper trail” for when that user was added or removed and why.

A while back I was working on a project where I ended up having to write some GlideRecord queries based on quite a few different tables and parameters. I was using syntax editor macros along with the find and replace feature in the syntax editor, but it seemed to me like it should be easier than.

 

I started thinking about what I really wanted, and what I decided was that I wanted a way to generate a GlideRecord query from a list that contained the table name, the encoded query string, the sort field, and allow me to set the variable name of the GlideRecord object. Once I had those goals, the execution was actually pretty straightforward, so I built it out and uploaded it to share.

Get GlideRecord Query from List

 

Here's a screenshot demo of how it works after you've uploaded the update set into your instance.

 

First you can go to any list and add some queries to the filter:

f7ec25aab5.png

 

Then run the query and click the Get Query list banner ui action and you'll get a prompt to name you GlideRecord object variable.

6275146d65.png

 

Name it (something other than gr) and hit ok, and you'll get another popup with your generated GlideRecord code.

6df0851be5.png

 

Now hit CTRL+C and paste that into your script editor of choice:

db469d27c5.png

 

That's it! Just a simple way to easily generate a GlideRecord query from a list of records to use wherever you'd like.

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM August 6, 2015 ON THE CLOUDSHERPAS SERVICENOW SCRIPTING 101 BLOG.

____________________________________________________________________________

 

 

Do you ever develop code snippets to prove a concept? I know I do, but it always leads to the question: How can you test those snippets to see if they function correctly in ServiceNow? There are two methods: Scripts - Background and Fix Scripts. When I set out to write this article, I was a big proponent of Scripts - Background, although reviewing both methods closely opened my eyes to the value of Fix Scripts. Here’s my assessment of each, including pros, cons and demonstrations. 

Method 1: Scripts - Background

 

The Scripts - Background method includes three steps:

 

  1. Raise your security level to Security Admin by clicking on the "lock" icon next to your name on the banner bar
  2. Search for: Background or Scripts - Background and click on the link
  3. Type your code snippet into the free-form script field that appears


Pros: 

 

 

  • Quick and easy process that works great for quick-and-dirty tests
  • Allows you to run the script in scope
  • Echoes the code into the System Log as a form of audit (although you might consider this a con if your script is a pretty good size)
  • Instantly returns results when using gs.print, gs.info, gs.log, gs.error and gs.warning


Cons:

 

 

  • Does not provide line numbers or editors (it’s best to develop the actual script in something like Notepad++ when using this method)
  • Auditing only occurs via the System Log
  • Requires Security Admin role to access
  • Long running scripts lock up the user interface
  • No versioning
  • Cannot save the scripts to ServiceNow or a disk
  • Cannot export the script XML
  • Cannot export to Update Set
  • In Firefox, you will probably lose the just-executed script as the browser blanks it when you hit the back arrow (Internet Explorer and Google Chrome do not seem to have this problem, though you can avoid it in Firefox by copying the script out before clicking the Run Script button)
  • Includes a Security Admin timeout, which means if you leave your window for any length of time you will have to start over (when you use this method, you need to make a habit of copying out your code to a notepad!)


Demonstration of Scripts - Background

 

1. Click the lock symbol on the menu bar and elevate your privileges to Security Admin

 

2. In the navigation filter, type: Background

 

3. Go to System Definition > Scripts - Background

 

 

4. Add the following script into the “Run Script” field:

 

gs.print(“Hello world!”);

 

This can be ANY valid server-side script. This can be multi-line, include functions, glide script, GlideRecords, eval, etc.

Note the lack of line-numbers or any sort of editing features. This application was created only to allow for the literal quick-and-dirty execution of simple scripts.

 

5. Pick the scope (default is global), and click the “Run Script” button to execute your script



 

 

6. Result:



 

7. Navigate to System Logs > All

 

8. Filter on Source equals ScriptBackgorundCheck to view the “Audit” log


Method 2: Fix Scripts

 

The Fix Scripts method includes seven steps:

 

  1. Search for: Fix or Fix Scripts and click on the link
  2. Click the “New” button to start a new script and fill in name and type in the script you want to test
  3. Bring up the header bar context menu and click “Save”
  4. Go to the bottom of the form and click the “Run Fix Script” link
  5. Click the “OK” button on the first pop-up that appears
  6. Click the “Proceed” button on the second pop-up that appears
  7. View your results and click the “OK” button once you’re ready to close the results pop-up


Pros:

 

  • Provides the power of the full editor (note that you have line numbers, syntax checking, formatting, etc.)
  • Includes versioning
  • Instantly returns results when using gs.print, gs.info, gs.log, gs.error and gs.warning
  • Can be added to Update Sets
  • Includes auditing at the table/field level out of the box
  • Offers the option to turn on the deleted records audit
  • Easy to make a minor change and retry the script
  • Easy to save scripts
  • Allows you to export the script to XML
  • Allows you to push long running scripts to the background and then monitor via Progress Workers


Cons:

 

 

  • Requires more setup time than Scripts-Background to use
  • Does not allow you to run scripts in scope


Demonstration of Fix Scripts

 

1. Navigate to System Definition > Fix Scripts

 

2. Click the “New” button



 

3. Fill in the following:

a. Name: Two Methods for Code Development

b. Active: Checked

c. Run once: Checked

d. Description: Fix Scripts method for testing a code snippet.

e. Script: gs.info(“Hello world!”);

 

4. Click the “Submit” button to save the form



5. Note the Related Links section:

a. Configure all: Simply the Fix Scripts list and form view configuration (you will likely never need to use this)

b. Run Fix Script: Runs your script (you could use the Configure all to set this as a button on the form)

c. Show Progress Workers: Displays a list view of the Progress Workers table and the list view entries will display the Fix Script results (this is where you will go if you decide to push your script execution to the background)



 

6. Click the “Run Fix Script” related link

 

7. Click the “OK” button on the “Run Fix Script?” form that appears to continue running the script



 

8. When the “Warning!” form appears, you can push your script execution to the background or run it in the foreground (running in the foreground will lock your screen until your script completes, so this is best for short scripts)

 

 

9. Click the “Proceed” button



 

10. View your results and click the “Close” button to close out the results form and return to the Fix Scripts form

 


 

11. Repeat steps six through eight, and instead of clicking the “Proceed” button, click the “Proceed in Background” button to bring up the Fix Script in the background form



 

12. Click the “Close” button to return to the Fix Scripts form

 

13. Click the “Show Progress Workers” related link to display the Progress Workers list view (this will be automatically filtered to show you just the Fix Script entries)



 

14. Click into the most recent record on the list view to display the Progress Worker form



 

15. View the result of your code execution in the Message field

 

Which Method is Better?

 

I have used Scripts - Background for years, and it is still my first choice, but mostly by habit. Fix Scripts provides so much more in the “Pro” area that, frankly, I would advise using that method instead. Both run server side, but only Scripts - Background allows you to run scoped, so both methods do have their uses. My only complaint with Fix Scripts is that it should probably be available only to Security Admins, as it has the same power to do harm as Scripts - Background. Anyway, they are both super tools for the tool box.

 

 

Scripts - BackgroundFixed Scripts
Easy to useYesSort of, requires setup
Allows for running in scopeYesNo
AuditSystem Log onlyYes
Instant ReturnYesYes, when run in foreground
Can run in backgroundNoYes
Requires Security AdminYes, extra stepNo, and should
Full editing and syntax checkingNoYes
VersioningNoYes
Can be added to update setsNoYes
Easy to make changesSort of, clunkyYes
Easy saving scriptsNoYes
Export to XMLNoYes

Learn More

 

The Fix Scripts Wiki has useful information, but keep in mind that it has not yet been updated for Fuji.

 

 

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

 

 

Now that you have no excuses from attending, lets discuss the end-game:  Victory.  Having competed in 5 hackathons, with 4 final four finishes, and 3 wins, here's a few things I've learned.

 

1 - Think big, then think bigger

Almost every hackathon win starts with a *much* smaller idea revolving around a feature.  Your job is to think of progressively bigger consequences and applications to the feature.

  • "Hey, wouldn't it be cool if people could vote on projects?" turned into a complete rebuild of Kickstarter in ServiceNow, complete with skill / cash donations, and buzz aggregation.  We challenged the way an enterprise could propose AND decide AND fund AND staff their projects.
  • "Can we integrate with twitter" turned into a social media asset management system, with campaign automation.

 

Forcing yourself to think bigger and bigger will ensure a few things

  • your solution is framed inside a problem that people can feel
  • you expand your own horizons on the platform capability
  • you produce more angles to tell your story
  • you aren't dismissed as a "cute" feature that doesn't really do anything

 

2 - Story telling and development are equally important

None of it matters unless you can develop it.  But even if you develop it, you need someone to tell its story.  Lets not kid ourselves about human decision making.  For the most part the feelings rule, and the mind compensates.  Whatever it is you develop, your audience has to *feel* it.  In many ways this is a hack all on its own.  How do I maximize the *emotional* impact of whatever it is I'm doing.

 

Look, at K13 there were *far* more impressive technical feats than what we won with.  Turning ServiceNow into a blackjack game?  Biometric scanning integration?  FAR harder to pull off.  The only reason we won is we capitalized on the cold patronage of the modern corporation.  "Hey, isn't it bullsh!t that so few people influence what projects are actually done at a company?"  "You're right!  That *IS* BS!".  They were bought into our story before they even saw the product.

 

Another example?  At K15 our team didn't even finish, and I STILL have red hand prints on my back from the kudos we received.  It wasn't our product, it was the story we were (attempting) to tell.

 

3 - It *only* has to work

This was my hardest lesson over 5 hackathons.  Your application will *not* be used.  In only a matter of hours, it will cease to exist.  The care we take in our daily lives is to ensure the tool works reliably for the longest duration possible.  You, my readers and only friends, have a one-way trip to a single point in time (the pitch).  Cleanliness doesn't count.  There is no such thing as "proper".  "Done" is only measured by "did it work at the pitch".

 

Remove all safety measures.

 

(Practical and specific pro tip:  Don't put your app in a scope for hackathon, unless you're specifically hacking app scopes)

 

4 - Know your storyteller ahead of time

Not every one has their story made up before they get to Hackathon.  Statistically, 75% of all participants will be building someone else's idea.  It *is* advantageous to know who the story alpha is before you get to Hackathon.  You're not supposed to pre-develop or migrate code into the Hackathon, and I thoroughly agree with that.  But if you can deconstruct and plan the work ahead of time, you'll save yourself some churning guts.

Each year I hear the same story.

"I'd do the hackathon, but I'm not skilled enough"

"I'd do the hackathon, but I'm not a hacker"

"I'd do the hackathon, but I don't have a team already"

"I'd do the hackathon, but I don't have a cool idea"

 

As a three time Hackathon winner (K13, FruDevcon13, FruDevCon14), I'm here to tell you - forget your limitations, and hack.

 

HACKATHON REQUIRES ALL TYPES

In the 5 Hackathons I've attended, I've seen several roles emerge on hackathon teams.  Keep in mind, its not necessarily a 1-to-1 relationship between role and person

 

The Source - Because someone has to have the idea in the first place.  No hackathon team will win without the source.  You don't have to be a top tier developer to be the source, you just need that unscratched itch in your mind that "it would be way better if...."

 

The Power Dev - Don't have a winning idea, but you can code like blazes?  Its no secret, the winning hackathon entries almost always have *at least one* preposterously difficult technical barrier. 

 

The Auxiliary Developer - So if you're thinking big (which you should be to win the hackathon), you have a large solution to build.  Most of my hackathon success has emerged from weaving our narrative into the widest scope of the platform as possible.  That means while your Power Dev is solving some major technical hurdle there's still *plenty* of good old fashioned hard work to do.

 

The Story Teller - The story teller serves two purposes.  First, as an analog to the Source (if no the same person), and second as the spokesperson for the app.  Make no mistake, story tellers win hackathons.  None of my winning hackathon entries had jaw-dropping tech.  But they *did* have a problem the audience could *feel* and a solution that wrapped up the story in their minds.  You can do very little else in a hackathon (even though anyone can do auxiliary dev) and still be a critical part to the hack team.

 

The Task Master - You're kidding yourself if you think an 8 hour job with 4 people doesn't need a little bit of management.  I've found this to be one of the more difficult people components of the 5 hackathons I've been in.  Often times you won't "hit your stride" until 4 hours in.  If you're a natural at breaking down a job (quickly), ordering the tasks, and applying them to the right skill, you need to fill the task master role. 

 

BUT I DON'T HAVE A TEAM

In the *unlikely* event that you can't find a team before the Hackathon, just show up.  At every event I've competed at there's been plenty of people in the same position.  If I'm not mistaken, the victorious K15 team (or at least one of the finalists) was assembled on the spot, just before the Hackathon began.

 

 

 

The Hackathon is an invaluable for skill sharpening, networking, and broadening your understanding of platform possibilities.  If ServiceNow is your profession, you owe it to yourself to compete. 

 

Join me at the K16 Hackathon this year.  If you need help with teams, ideas, or suggestions as to which role you can fit, I am ever at your service.

This year I was asked by Dan Bruhn (dan.bruhn) who tries to watch over all of the ServiceNow Community MVPs (lots-of-luck doing that - kind of like herding cats) ...um, where was I?  Oh yeah, Dan asked if I would be interested in doing a couple of table topic sessions during lunches at this year's Knowledge.  Well, sure!

 

What is a table topic you ask?  Well let me tell ya. 

 

These will be one-hour, around the table, open discussion sessions, during the lunch hour, where people can come, sit down and discuss a particular topic.

 

So what topics will I be hosting at my table?  Cool stuff; as always!

 

17TA05 - Debugging Server-Side Script

Tuesday, May 17, 12:00 PM - 1:00 PM

Blurb: An opportunity to discuss the available approaches and best practices when debugging Script Includes, Workflows, Scheduled Jobs, etc.

 

18TA08 - Development Process in ServiceNow

Wednesday, May 18, 12:00 PM - 1:00 PM

Blurb: An opportunity to discuss process flow best practices (requirements, prototyping, design, development, unit testing), and how they may be applied to ServiceNow.

 

What other table topic tables are available?

 

1. Go to the following link: ServiceNow Events

2. Search on either 17TA or 18TA to list similar table sessions like mine.

3. For all table topic sessions search on Table Topics (and be prepared as the list is a long one!)

 

Look forward to seeing you at Knowledge 16!

 

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

Last year at the inaugural CreatorCon I had the opportunity to write and present a session called Building a Killer UX when students built out a front end for a custom, scoped ToDo application using Bootstrap and AngularJS. I had a lot of fun and learned a ton both writing and presenting the session, and it was pretty well received.

 

This year, my colleague Mitch Stutler and I are happy to present a couple of sessions around using AngularJS in ServiceNow.

 

From Jelly to Angular - Mitch Stutler

In this session you'll take a PPM dashboard built in Jelly and go step by step to rebuild it using AngularJS. You'll learn things like going from <j:while> in Jelly to ngRepeat in AngularJS. This will be ideal for a ServiceNow developer who is familiar with jelly and wants to learn more about AngularJS, someone who will be converting a CMS site to use something AngularJS based like Service Portal, or someone who just wants to learn more about building dashboards in AngularJS in ServiceNow.

 

Building a Killer UX - Brad Tilton

This session will be similar to last year's session, but updated a bit. In this session you'll build out a UI Page in Bootstrap and AngularJS that will act as a front end for an updated ToDo app as well as build a Scripted REST API in addition to using ServiceNow's REST Table API.

 

Dates/Times

19CB04 - From Jelly to AngularJS (repeat 1 of 2) - Thursday, May 19, 1:30 PM - 3:20 PM

20CA06 - From Jelly to AngularJS (repeat 2 of 2) - Friday, May 20, 8:00 AM - 9:50 AM

20CC04 - Building a KillerUX v2 - Friday, May 20, 10:30 AM - 12:20 PM

 

I'm really excited to be presenting this year and looking forward to being at CreatorCon in two weeks!

As a benefit of being part of the ServiceNow Developer Program, those that have personal developer instances can optionally upgrade to the Helsinki release of the platform.  We are providing this early access in order for registered developers to become familiar with the latest ServiceNow platform, leverage the latest features when building applications and to prepare for upgrades.

 

This optional upgrade is only offered on personal developer instances provided through the Developer Program.  If you have a personal developer instance, you now have the option to upgrade on the My Instance page on the Developer Site.  Under the Action menu, there is an option to "Upgrade to Helsinki".  If you choose to upgrade to Helsinki, it is difficult to return to an earlier release for your personal developer instance.

 

All other instances (production and non-production for customers and partners) must be requested through HI’s instance self-service or through calling support. The upgrade to Helsinki will be available for all customers and partners in a few weeks.

 

More information can be found in the FAQ.

Chuck Tomasi

K16 Lab: Source Control

Posted by Chuck Tomasi Employee May 4, 2016

Hello fellow ServiceNow Developers!

 

Chris Maloy and I are excited to be in the first lab session following the opening key note on Tuesday May 17 at 10:00 to introduce you to one of our favorite Helsinki developer features - Source Control.

 

Join us for this hands-on lab as we introduce you to our Git integration, the ways it can help you manage your development across multiple releases, and the best practices to become effective. Chris, me, and a team of us have been working closely with the platform and integrations teams on this feature extensively for the past few months. We know once you start using it, you'll want to use it all the time.

 

Here's our agenda for this lab:

  • Introduction to source control followed by a hands-on lab.
  • Tagging and branching followed by a hands-on lab.
  • Conflict management followed by, yes, another lab.
  • Stashing, and of course, another lab.

 

If you have been to one of my sessions in the past, you know that I encourage participation - OK, perhaps "bribe" is a better word. Note for newbies - sit close to the front.

 

Sign up today and I'll see you in Vegas!

Last year Mark Amann (mamann) and I were honored to be allowed to conduct two sessions of our Advanced Scripting class at the K15 CreatorCon.  We really enjoyed the turn-out and support.  We had two full double-room classes!

 

This year Mark and I are doing something a little different.  We will be conducting a class on Advanced GlideRecord Scripting, and we will be doing two sessions of that as well.

 

 

Class Agenda:

 

1. Best practices while working with GlideRecords (refactoring to reduce calls to the database)

2. Methods of rewriting GlideRecord queries for better maintainability (such as dot stacking of commands in a GlideRecord query like: .addQuery() and .addOrCondition() )

3. Extending the GlideRecord object (adding functionality to GlideRecord via JavaScript prototypes)

 

Advanced encoded queries Labs will include:

 

1. Taking a GlideRecord in a loop and refactoring it using object array handling.

2. Rework of a large GlideRecord query into a dot stacked query.

3. Extending the GlideRecord object to have a Between dates SQL functionality

4. Techniques for building advanced encoded query conditions

AND MORE!

 

 

Dates and signup:

 

19CD08 - Advanced GlideRecord Scripting (repeat 1 of 2) - Thursday, May 19, 3:50 PM - 5:40 PM - Steven & Mark

 

20CA05 - Advanced GlideRecord Scripting (repeat 2 of 2) - Friday, May 20, 8:00 AM - 9:50 AM - Steven & Mark

 

If you are planning on attending CreatorCon we would sure like to see you there! 

 

To see a full listing of all of the CreatorCon classes being given go to this link:  Knowledge16

Under Session Type check CreatorCon Breakout, and CreatorCon Workshop to see all of the CreatorCon sessions available!

 

I'm not shilling specifically for our class...honest!  :-)

 

CreatorCon Rocks!

 

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

 

 

One of the most common questions that I see on the ServiceNow community is some variation of “How do I populate values on my form via the URL?” There are many circumstances where you already have values for some fields/variables and you’re sending a form to someone to fill out whether redirecting there or through email. In those cases you don’t want to make your user go find that information or even have to enter it into the form if it’s already available. There are three main areas in ServiceNow where this is most commonly done:

  1. A table form (Incident, Facilities, etc.)
  2. A Catalog Item, Record Producer, or Order Guide
  3. A UI Page/CMS Page

The documentation is all out there already, but I thought I’d pull it all together in one place.

 

Table Form

One of my favorite wiki articles is called Navigating by URL and does a really good job explaining how to build a url that will prepopulate values on a form. The basic anatomy of the url is:

https://<instance name>.service-now.com/nav_to.do?uri=incident.do?sys_id=-1%26sysparm_query=caller_id2809952237b1300054b6a3549dbe5dd4

incident – this is the name of the table you’re redirecting to
sys_id=-1 – passing negative one as the sys_id tells ServiceNow you’re opening a new record
sysparm_query – this is the parameter where you will set your values
caller_id2809952237b1300054b6a3549dbe5dd4 – this is the encoded query you will use to set the caller to a specific indivdual

 

Catalog

Setting a value via URL requires a little more work in a catalog item/record producer, but luckily there’s a really great SNCGuru article called Parse URL Parameters in a Client Script that I’ve use many, many times to accomplish this.

For this method, you add some sort of parameter to the end of the url of the catalog item. In this case, you can pass the sys_id of a user with the sysparm_user parameter:
https://<instance name>.service-now.com/nav_to.do?uri=com.glideapp.servicecatalog_cat_item_view.do?sysparm_id=d65394f54ff2520020af09fd0210c759&sysparm_user=2809952237b1300054b6a3549dbe5dd4

 

Now that you’ve passed it you have to use an onload client script on the catalog item itself to consume that parameter and most likely set a value with it like this:

 

function onLoad() {
    //Use the 'getParameterValue' function below to get the parameter values from the URL
    var user = getParameterValue('sysparm_user');
    if (user) {
 g_form.setValue('user_variable', user);
    }
}

function getParameterValue(name) {
    var url = document.URL.parseQuery();
    if (url[name]) {
        return decodeURI(url[name]);
    } else {
        return;
    }
}

 

NOTE: This script looks a little different if you want to use it through the Service Portal as document and prototype will not work there. Check out the updated script here.

UI Page

The third place you might want to pass a url parameter would be a ui page. UI Pages in ServiceNow are custom pages built using jelly. The wiki article in this case isn’t quite as informative, and it’s called Extensions to Jelly Syntax. In this scenario, if you wanted to pass a user sys_id to the jelly in the page, you could do:

https://<instance name>.service-now.com/fancy_user_page.do?sysparm_user=2809952237b1300054b6a3549dbe5dd4

 

Then in jelly you can do something like:

<j:set var="jvar_user_sysid" value="${RP.getParameterValue('sysparm_user')}">

 

Then you can use that jelly variable later on in your UI Page.

 

Hope this post was helpful, and I'll be following up later with how to pass a url parameter into an angular based UI Page.

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM July 17, 2014 ON THE CLOUDSHERPAS SERVICENOW SCRIPTING 101 BLOG.

____________________________________________________________________________

 

 

Earlier this week, I shared some basic information on Database Views in ServiceNow. To recap, in a normal report you can only select a single table, but Database Views allow you to pull together data from two or more tables into a single table to report against.

 

In the second part of this post, I’ll present some examples of how to use the basics outlined in the previous post to your advantage.

 

 

Additional Notes and Observations

 

1. Reference Field using Left Join

 

Here is an example of how to use the Left Join to create a View that includes the user table but does not constrain on the related approved_by field.

 

The Database View:

 

Name: u_left_join_no_constraint

Label: Left Join No Constraint

 

View Tables:

 

Table: cmdb_ci (Configuration Item)

Order: 100

Variable Prefix: ci

Where clause: none.

 

Table: sys_user (User)

Order: 200

Variable Prefix: user

Where clause (I had to do a little research here to find the Assigned To field name to use here):

 

ci.assigned_to = user.sys_id

 

Left Join: true

 

C:\Users\User 1\Desktop\Left Join No Constraint View.jpg

 

Results:

 

C:\Users\User 1\Desktop\Left Join No Constraint Result.jpg

 

Discussion:

 

 

Note the results: assigned_to can be blank! This is because we are NOT constraining on the assigned_to field. If you uncheck the Left Join, you will see that those records with an empty assigned_to field are excluded from the View results.


Did you notice the Manager field to the right? That field is coming from the User table. Since this is a Join, all fields available from both tables are included in the view.

 

2. Unhiding a Hidden Table

Have you ever noticed that certain tables are not available for reporting? For example, tables like Field Labels and Dictionary. Well, if you wrap them in a View, you can then see them from a report!

 

The database view:

 

Name: u_hidden_tables

Label: Hidden Tables

 

View Tables:

 

Table: sys_documentation (Field Labels)

Order: 100

Variable Prefix: field

Where clause: none.

C:\Users\User 1\Desktop\Hidden Table View.jpg

 

Results:

C:\Users\User 1\Desktop\Hidden Table Result.jpg

 

Discussion:

 

Nothing really to add here. Just a neat trick to make the table available to the reporting application. Cool, huh? 



3. Constraining a View Using A Choice Label

Follow these steps to create a view that constrains on the choice label rather than on the choice value.

The database view:

 

Name: u_constraining_on_choice_label

Label: Constraining On Choice Label

 

View Tables:

Table: cmdb_ci (Configuration Item)

Order: 100

Variable Prefix: ci

Where clause: none.

 

Table: cmdb_ci_computer (Computer)

Order: 200

Variable Prefix: comp

Where clause:


comp.sys_id = ci.sys_id

Table: sys_choice (Choice)

Order: 300

Variable Prefix: choice

Where clause:


choice.name = 'cmdb_ci'

AND choice.element = 'install_status'

AND choice.label = 'Installed'

AND choice.value = ci.install_status

 

C:\Users\User 1\Desktop\Constraining on Choice Label View.jpg

 

Results:

 

C:\Users\User 1\Desktop\Constraining on Choice Labels Results.jpg

 

Discussion:

 

As we want to constrain the View to the Label “Installed” and not the value “1,” we have to join in the Choice table, then tell the View where to find the label. Once you do that, tell the View that you want only certain values from the Choice table: the cmdb_ci table (Name field), the Field name (Element). Next, tell it that you want only the label “Installed.” Finally, tie it back into the cmdb_ci table by the install_status field value. A lot of stuff!

What would happen if you marked the sys_choice record with Left Join of true? Give it a try and see what happens to your record count!

4. Two Table Union

 

Is it possible to join together two unrelated tables? Sort of. This is known as a Union in the database world. The two tables must have a common root table for it to work. Here is an example that has cmdb_ci as that root.

 

The database view:

 

Name: u_union_of_two_tables

Label: Union of Two Tables

 

View Tables:

 

Table: cmdb_ci (Configuration Item)

Order: 100

Variable Prefix: ci

Where clause: none.

 

Table: cmdb_ci (Configuration Item)

Order: 200

Variable Prefix: ref

Where clause:


ci.sys_id = ref.sys_id

AND (ref.sys_class_name = 'cmdb_ci_ip_router' OR ref.sys_class_name = 'cmdb_ci_computer')

Table: cmdb_ci_computer (Computer)

Order: 300

Variable Prefix: comp

Where clause:


ci.sys_id = comp.sys_id

Table: cmdb_ci_ip_router (Router)

Order: 400

Variable Prefix: router

Where clause:


ci.sys_id = router.sys_id

C:\Users\User 1\Desktop\Union of Two Tables View.jpg

   Results:
  C:\Users\User 1\Desktop\Union of Two Tables Results.jpg

Discussion:

 

With this example you can see how to bring in two tables with the same root (cmdb_ci) and have the unique fields from both present in the View.

I started out with our root: cmdb_ci, then constrained the View to just routers and computers. To bring in fields unique to each table, I tied in the Computer and IP Router tables to cmdb_ci via their respective sys_ids. It can be visualized like this:
 

C:\Users\User 1\Desktop\Union of Two Tables Diagram.jpg

 

This is a Union query only in a matter of speaking. The router and computer tables do not share records, but they are related in the hierarchy to cmdb_ci. In the database world, you could bring in two totally unrelated tables, but you won’t be able to do that with Database Views.

 



Some Additional Notes

 

1. It is possible to embed MySQL functions into the Where Clause.

 

Example: return all records from cmdb_ci where the updated date is less than the current date/time AND the Name field contains the letters ‘HP.’


ci.sys_updated_on < NOW() AND INSTR(ci.name, 'HP') > 0

2. Use parentheses to separate OR collections from AND statements.

 

Example:

(ci.name = 'Omniwobble Server' || ci.sys_class_name = 'cmdb_ci_server') && (ci.name = 'Guest Computer' || ci.sys_class_name = 'cmdb_ci_computer')

3. Not all MySQL commands are available for Database Views. For example, I found that the "IN" statement does not appear to work.


ci.sys_class_name IN (‘Computer’, ‘Software’)


or

FIND_IN_SET(ci.sys_class_name, ‘Computer, Software’)

4. Wildcards are allowed.

 

Example (Find all ci names that start with the letters “Omni”):

 

               ci.name LIKE 'Omni%'


5. Check for any empty fields by running a null check.

 

u_last_name IS NOT NULL

 

 


Some Cool Stuff

 

Querying Database Views from a GlideRecord? All of the View fields need to be underscore-walked if you want to reference them!

 

Example:

 

var referenceExample = new GlideRecord('u_reference_example');
referenceExample.addQuery('ci_sys_class_name', 'cmdb_ci_computer');
referenceExample.addQuery('ref_name', 'JASONHWXP');
referenceExample.query();
 
gs.print(referenceExample.getRowCount());
 
while(referenceExample.next()) {
    gs.print(referenceExample.ci_sys_class_name);
}

 


Some Final Thoughts

 

Watch out when joining large tables together as this will have a performance impact when pulling up data.

Here is the location of the MySQL function:  link

I have posted these and other examples in the following location on ServiceNow Share: 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

NOTE: ON APRIL 1, 2016 ACCENTURE COMPLETED THE ACQUISITION PROCESS OF CLOUDSHERPAS.  AT THAT TIME THE CLOUDSHERPAS BLOG SITES WERE DOWNED FOREVER.

 

THIS IS THE RE-PUBLICATION OF MY ARTICLE FROM July 14, 2014 ON THE CLOUDSHERPAS SERVICENOW SCRIPTING 101 BLOG.

____________________________________________________________________________

 

 

Database Views are useful for filling an important gap in ServiceNow: joining two or more tables together. In a normal report, you can only select a single table, but Database Views allow you to pull together data from two or more tables into a single “table” to report against.

 

Given the importance of Database Views, I wanted to take the opportunity to share some observations I have either read about, stumbled across or just plain hit-and-miss created from scratch.

 

To start off, check out the introduction to Database Views in the wiki

 

 

Some Techno-babble:

 

  • Constrain / Constraint – to limit the View
  • Where Clause – references the Constraint part of a SQL query. For example:


SELECT * FROM cmdb_ci WHERE install_status = 1

 

The “*” means to retrieve all fields. This example returns all records from the cmdb_ci table where the install_status field has a value of one.

 

  • Join – to bring together two related tables into a single View.
  • Union – to bring together two not necessarily related tables into a single View.
  • Alias or Variable Prefix – a short descriptor that identifies a table and is used to designate that table’s fields in a Where Clause.
  • Null – Empty field. Note that this is not the same as an empty string, which has to be checked for separately.
  • Operators - &&, ||, AND, OR, !=, = - Are used to chain constraints together. Note that && and AND mean the same, as do || and OR.

 

Caveats:

 

For all of the examples, I use the CMDB CI table structure. Of course you can use any database available for your views. Rather than showing how to use specific fields in the view output, I will demonstrate techniques for joining or union-ing multiple tables together.

 

 

 

Guidelines and Tips

 

Let’s get started! First I will describe some important tips and guidelines. 

 

1. Out of the box, the Database View / View Tables list view does not contain the Active and Left Join fields. I always personalize my view, using the Update Personalize View (gear button), and add those two fields.

 

C:\Users\User 1\Desktop\Personalize List View.jpg

 

2. Dot and Underscore work the same for writing Variable Prefixes (Aliases) into the Where Clause.

 

For example:

 

ci_sys_id = comp_sys_id

 

is the same as:

 

ci.sys_id = comp.sys_id

 

3. You can have a one-record View, but it cannot contain a Where Clause. If you do happen to put a Where Clause into the single record, the view engine will ignore it when displaying the results. 

 

C:\Users\User 1\Desktop\2. 1 record db view.jpg

 

As demonstrated below, the results still contain all records from the CMDB_CI table.

 

C:\Users\User 1\Desktop\2. cmdb_ci_db_view_1 record result.jpg

 

This scenario defeats the purpose of having a View as it is nothing more than a wrapper for the table you are referencing. To really get the power out of a Database View, you must join it to other tables!

 

4. Commenting your code is a best practice. To do this simply surround your comment text with /* … */.

 

C:\Users\User 1\Desktop\4. Comments example.jpg

 

5. Another good practice is to use short alias names. This will improve readability as some Where Clauses can get quite lengthy.

 

C:\Users\User 1\Desktop\5. two record db view.jpg

 

6. Like with all number ordering in ServiceNow, it’s a good practice to order in the 100’s. This allows you to go back and easily insert new records between others.

 

7. If no fields are specified with each View Tables record, then the default result includes all fields. Fields with the same names will be designated by their table name first, then by their field name in parenthesis. This helps with identification but will require some thought as to which fields you will actually want to display in your view and reports.

 

C:\Users\User 1\Desktop\6. personalized list columns.jpg

 

8. All fields from a view will be available to put in a report (even the record sys_ids). The fields are displayed in the picklist with their respective actual names in parenthesis prefixed with their table aliases.

 

C:\Users\User 1\Desktop\7. Report Header Example.jpg

 

9. If you include a choice field in a Where Clause, it will need to be against the “Value” field  not the “Label” field. 

 

Check out this wiki article for more information on choice list values and labels (section 6).

 

The following Where Clause will fail because it uses the install_status choice list Label instead of the Value:

 

C:\Users\User 1\Desktop\9. installed status is 'installed'.jpg

Instead, the Where Clause should read:

 

ci.sys_id = ref.sys_id AND ci.install_status = 1

 

(I will give an example in the second part of this article on how to get around this.)

 

10. The table hierarchy is “rolled” up. This is interesting, and, if you don’t know what is going on, it can be a frustrating thing. However, two simple rules should clear up any confusion:

 

a. All fields from all inherited tables are available to report against.

 

Say that you want to join cmdb_ci_computer and cmdb_ci_server. You are fine as long as you join the two tables on fields that are available in ONLY those tables. Since sys_id is available in both, you can join the two on that. 

 

b. If you do intend to constrain using any of the inherited table’s fields, you will need to include that table(s) in the view.

 

The following screenshot shows a Where Clause that would fail, as install_status is not in the computer table.

 

C:\Users\User 1\Desktop\9. rollup query.jpg

 

You would see an error similar to this:

 

C:\Users\User 1\Desktop\rollup failure error.jpg

 

If you want to constrain the cmdb_ci_computer / cmdb_ci_server view by install_status of 1 (installed), you have to include that table in the view as well. The Where Clause would then read:

 

serv.sys_id = comp.sys_id AND ci.install_status = 1

 

C:\Users\User 1\Desktop\11. Corrected Rollup Clause.jpg

 

11. You can chain the Where Clause onto multiple lines for improved organization and readability.

EXAMPLE:

 

This:

 

comp_sys_id = ref_sys_id && (ref_form_factor = 'Laptop' || ref_form_factor = 'Desktop') && ref_install_status = 1

 

Can be written as:

 

comp_sys_id = ref_sys_id
&& (ref_form_factor = 'Laptop' || ref_form_factor = 'Desktop')
&& ref_install_status = 1

 

By the way, remember that && and AND and || and OR are interchangeable. So, this Where Clause can also be written like this:

 

comp_sys_id = ref_sys_id
AND (ref_form_factor = 'Laptop' OR ref_form_factor = 'Desktop')
AND ref_install_status = 1

C:\Users\User 1\Desktop\Multiple Line Query.jpg

 

Learn More

Check back next week for the second part of this post, which will cover interesting examples about tying in a reference field using Left Join, constraining a view using a choice label and more.

 

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

 

Filter Blog

By date: By tag: