highlights Archives - ServiceNow Guru https://servicenowguru.com/tag/highlights/ ServiceNow Consulting Scripting Administration Development Tue, 28 May 2024 19:14:53 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.2 https://servicenowguru.com/wp-content/uploads/2024/05/cropped-SNGuru-Icon-32x32.png highlights Archives - ServiceNow Guru https://servicenowguru.com/tag/highlights/ 32 32 Add a user profile photo to a form https://servicenowguru.com/system-ui/add-user-profile-photo-to-form/ https://servicenowguru.com/system-ui/add-user-profile-photo-to-form/#comments Wed, 03 May 2017 20:50:26 +0000 https://servicenowguru.wpengine.com/?p=12532 There are times when it is helpful to just put a name with a face. In ServiceNow there are a couple places where a user photo might be stored but it’s not always intuitive how get at that photo in a way that can be helpful. Let’s walk through how this can be done with

The post Add a user profile photo to a form appeared first on ServiceNow Guru.

]]>
There are times when it is helpful to just put a name with a face. In ServiceNow there are a couple places where a user photo might be stored but it’s not always intuitive how get at that photo in a way that can be helpful.

Let’s walk through how this can be done with a handful of updates and relatively little trouble. The goal of this is to put a user photo just under the Caller field on the incident form so that the person working the incident can see who they’re working with. This could have numerous benefits ranging from a verification that you’ve got the right person in a walk-up scenario all the way to showing the agent on the phone that it’s a real person they are interacting with, prompting a little more thought into how they can truly help that person.

User Profile photo on the incident form

To set this up we need to understand that there are a couple places where the image might be stored. In the early days of ServiceNow it would have always been on the user record. But now we have collaboration tools built in and some great functionality within Service Portal and other areas that utilize the Live Profile which includes the user avatar photo and ability to easily update it.

This is most easily updated by users in Service Portal from the Profile page.

To set this up we need four main components and then a quick update to the form layout to add the Live Profile Photo to it.

First we need to set up a UI Macro that acts as our container on the form for the photo. It contains a simple div with an ID that we target from within a client script.

‘cf_live_profile_photo’ UI Macro
Name: cf_live_profile_photo
Description: Used in a formatter as the container for placing the live profile photo for a user. The div is populated by an onChange script on the form.
XML:

<!--?xml version="1.0" encoding="utf-8" ?-->

<script>
		function cf_setLiveProfilePhoto(photo_path){
			jslog("Setting photo to: " + photo_path);
			try{
				// Get the div from below so we can set the contents to the image
				var photo_div = document.getElementById('cf_live_profile_photo');
				if(photo_path){
					// This uses the thumbnail size image from the Live Profile and keeps the image small on the form
					var photo_html = "${LT}img style='max-width: 40%;' src='" + photo_path + "?t=small'/>";
					// This uses the full size image that was uploaded and scales it down to fit the space. 
					// This will generally produce a larger image and depending on the form, images, and 
					// space available, this may be a preferable option.
					//var photo_html = "${LT}img style='max-width: 40%;' src='" + photo_path + "'/>";
					photo_div.innerHTML = photo_html;
				} else {
					// No image was specified so clear out the div
					photo_div.innerHTML = '';
				}
			} catch(e){
				jslog("Unable to update photo: " + e.message);
			}
		}
	</script>
<div id="cf_live_profile_photo" style="text-align: center;"></div>

After that we need to create a Formatter that lets us place that UI Macro on the form.

‘CF Live Profile Photo’ UI Formatter
Name: CF Live Profile Photo
Formatter: cf_live_profile_photo
Table: Incident [incident] (or whatever table the photos should be shown on)
Type: Formatter

Now we need to create the script include that will look up the user photo. This first checks the live_profile record for the logged in user and if nothing is found there, checks for a photo on the user record.

‘cf_LiveProfile’ Script Include
Name: cf_LiveProfile
Client callable: True
Description: Intended to be called by a GlideAjax call. Retrieves the user photo from the Live Profile, if possible, otherwise gets the photo from the user record if possible. It then returns the path to the photo or an empty string if none can be found.
Script:

var cf_LiveProfile = Class.create();
cf_LiveProfile.prototype = Object.extendsObject(AbstractAjaxProcessor, {/*
* Retrieves the user photo from the Live Profile, if possible, otherwise
* gets the photo from the user record if possible.
*
* Returns the path to the photo or an empty string
*/
getPhoto: function(){
// Get the Sys ID of the user that we're retrieving the photo for
var user_id = this.getParameter('sysparm_user_id');
gs.log("getPhoto called for: " + user_id, "cf_LiveProfile");
var photo_path;

// Query for the live profile record
var live_profile_gr = new GlideRecord('live_profile');
live_profile_gr.addQuery('document', user_id);
live_profile_gr.query();
if(live_profile_gr.next()) {
if(live_profile_gr.photo.getDisplayValue()){
photo_path = live_profile_gr.photo.getDisplayValue();
gs.log("Retrieved photo from live profile: " + photo_path, "cf_LiveProfile");

}
}
// Check to see if we have a photo from the profile
if(!photo_path){
// No profile photo found, query for the user photo
var user_gr = new GlideRecord('sys_user');
user_gr.addQuery('sys_id', user_id);
user_gr.query();
if(user_gr.next()) {
photo_path = user_gr.photo.getDisplayValue();
gs.log("Retrieved photo from user record: " + photo_path, "cf_LiveProfile");
} else {
photo_path = '';
gs.log("No photo found", "cf_LiveProfile");
}
}
return photo_path;
},
type: 'cf_LiveProfile'
});

The last component before we add this to our form and see the photos is to set up a Client Script that will listen for a change in the user reference field on the form. For our example, this is the Caller field on the Incident form, but it could easily be shown on a different table. The important thing is that the reference field be for the User [sys_user] table.

‘Load Caller Photo’ Client Script
Name: Load Caller Photo
Table: Incident [incident] UI Type: Desktop
Type: onChange
Field name: Caller
Description: Loads the Live Profile photo of the user when the Name field is filled in. Requires the CF Live Profile Photo Formatter to be on the form.
Script:

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (newValue === '') {
cf_setLiveProfilePhoto('');
return;
}// Call back to the server to get the path of the user's profile picture
var ga = new GlideAjax('cf_LiveProfile');
ga.addParam('sysparm_name','getPhoto');
ga.addParam('sysparm_user_id', newValue);
ga.getXMLAnswer(function(answer){
cf_setLiveProfilePhoto(answer + '');
});

}

Now that we have all of these components set up, you should be able to go to the Incident form and add “CF Live Profile Photo” to the form layout. Then when you change the Caller field to a user that has a photo you should see the photo show up.

Thanks to Dan Andrews for the idea!

The post Add a user profile photo to a form appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/system-ui/add-user-profile-photo-to-form/feed/ 7
Service Portal-style Homepage Widgets https://servicenowguru.com/system-ui/service-portalstyle-homepage-widgets/ https://servicenowguru.com/system-ui/service-portalstyle-homepage-widgets/#comments Thu, 27 Apr 2017 18:13:43 +0000 https://servicenowguru.wpengine.com/?p=12469 ServiceNow has done a good job in creating a nice-looking customer service portal in recent releases. Although there are still some frustrating API limitations, overall it’s a nice improvement and is a good choice if you’re looking to roll out a customer service portal in ServiceNow. Depending on your organization, you may not be ready

The post Service Portal-style Homepage Widgets appeared first on ServiceNow Guru.

]]>
ServiceNow has done a good job in creating a nice-looking customer service portal in recent releases. Although there are still some frustrating API limitations, overall it’s a nice improvement and is a good choice if you’re looking to roll out a customer service portal in ServiceNow. Depending on your organization, you may not be ready to roll this out…or just wish you could replicate some of that nice-looking interface in the standard back-end where you get a more fully-featured experience. In this post I’ll show you how you can accomplish this with a couple of homepage widgets with Service Portal styling!


The first example is the full set of blocks on the Service Portal homepage as shown in this screenshot.

This can be replicated by taking the following steps…

1) Create a new UI page with the following settings

‘welcome_to_servicenow’ UI Page
Name: welcome_to_servicenow
HTML

<!-- Contains the HTML and CSS necessary for all styling and layout. Feel free to modify if need be. --> <style type="text/css">
    .homepage-quicklinks {<br />        background-color: #ffffff;<br />	    font-family: "SourceSansPro", Helvetica, Arial, sans-serif;<br />        font-size: 14px;<br />    }<br />	.text-info {<br />        color: #31708f;<br />    }<br />	a.text-info:hover, a.text-info:focus {<br />        color: #245269;<br />    }<br />	.text-success {<br />        color: #5cb85c;<br />    }<br />	a.text-success:hover, a.text-success:focus {<br />        color: #449d44;<br />    }<br />	.text-warning {<br />        color: #f0ad4e;<br />    }<br />	a.text-warning:hover, a.text-warning:focus {<br />        color: #ec971f;<br />    }<br />	.text-danger {<br />        color: #d9534f;<br />    }<br />    a.text-danger:hover, a.text-danger:focus {<br />        color: #c9302c;<br />    }<br />	.text-muted {<br />        color: #979797;<br />    }<br />	a.circle_icon {<br />        display: block;<br />        padding: 20px 0px 20px 70px;<br />        position: relative;<br />    }<br />	a.circle_icon .fa {<br />        position: absolute;<br />        left: 0px;<br />        top: 10px;<br />    }<br />	a.circle_icon h3 {<br />        font-weight: 300 !important;<br />        padding: 0;<br />        margin: 0 0 10px 0;<br />    }<br />	a:hover {<br />        text-decoration: none;<br />    }<br />	a:visited {<br />        text-decoration: none;<br />    }<br /></style>
<h1 style="color: #717171; font-size: 3em; text-align: center;">Welcome to ServiceNow!</h1>
<div class="homepage-quicklinks" style="background-size: initial; background-position: center center;">
<div>
<div class="row">
<div class="col-sm-6 col-md-3">
<div>
<div><a class="circle_icon text-info" href="catalog_home.do?sysparm_view=catalog_default" target="" rel="noopener">
<span class="fa fa-stack fa-2x">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-shopping-basket fa-stack-1x fa-inverse"></i>
</span></a>
<h3>Order Something</h3>
<span class="text-muted">Browse the catalog for services and items you need</span>

</div>
</div>
 

</div>
<div class="col-sm-6 col-md-3">
<div>
<div><a class="circle_icon text-success" href="kb_home.do" target="" rel="noopener">
<span class="fa fa-stack fa-2x">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-book fa-stack-1x fa-inverse"></i>
</span></a>
<h3>Knowledge Base</h3>
<span class="text-muted">Browse and search for articles, rate or submit feedback</span>

</div>
</div>
 

</div>
<div class="col-sm-6 col-md-3">
<div>
<div><a class="circle_icon text-warning" href="com.glideapp.servicecatalog_cat_item_view.do?sysparm_id=3f1dd0320a0a0b99000a53f7604a2ef9" target="" rel="noopener">
<span class="fa fa-stack fa-2x">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-fire fa-stack-1x fa-inverse"></i>
</span></a>
<h3>Report an Issue</h3>
<span class="text-muted">Contact support to make a request, or report a problem</span>

</div>
</div>
 

</div>
<div class="col-sm-6 col-md-3">
<div>
<div><a class="circle_icon text-danger" href="#" target="" rel="noopener">
<span class="fa fa-stack fa-2x">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-comments-o fa-stack-1x fa-inverse"></i>
</span></a>
<h3>Live Chat</h3>
<span class="text-muted">Start a chat conversation with customer service agents</span>

</div>
</div>
 

</div>
</div>
</div>
</div>

2) Create (or edit if you’ve already go one) a widget record

The widget record is what will allow you to add the widget to a ServiceNow homepage. The following example could be used in your system. You can add multiple widgets to the script of any widget record. The key is to include your widget UI page by referencing it in the widget ‘Script’ field. You can access widget records by navigating to ‘System UI -> Widgets’.

‘welcome_to_servicenow’ Widget
Name: Custom Widgets
Renderer type: JavaScript
Script:

function sections() {
return {
'Welcome to ServiceNow!' : { 'name' : 'welcome_to_servicenow'},
'EXAMPLE LINE 2' : { 'name' : 'other_ui_page_name_here'},
};
}function render() {
var name = renderer.getPreferences().get("name");
var gf = new GlideForm(renderer.getGC(), name, 0);
gf.setDirect(true);
gf.setRenderProperties(renderer.getRenderProperties());
return gf.getRenderedPage();
}function getEditLink() {
if (!gs.hasRole('admin'))
return '';return "sys_ui_page.do?sysparm_query=name=" + renderer.getPreferences().get("name");
}

3) Add the widget to your homepage

Once you’ve created your widget you should be able to navigate to your homepage and select and add the widget in the same way you’ve added other widgets in the past.

Including a single-block widget

You may also want to do the same thing with only one block of the widget. For example, maybe you want a standalone chat widget for the Service Portal that you can use from a homepage. The process there is the same; just a different UI page.

‘service_portal_chat’ UI Page
Name: service_portal_chat
HTML

<!-- Contains the HTML and CSS necessary for all styling and layout. Feel free to modify if need be. --> <style type="text/css">
    .homepage-quicklinks {<br />        background-color: #ffffff;<br />	    font-family: "SourceSansPro", Helvetica, Arial, sans-serif;<br />        font-size: 14px;<br />    }<br />	.text-info {<br />        color: #31708f;<br />    }<br />	a.text-info:hover, a.text-info:focus {<br />        color: #245269;<br />    }<br />	.text-success {<br />        color: #5cb85c;<br />    }<br />	a.text-success:hover, a.text-success:focus {<br />        color: #449d44;<br />    }<br />	.text-warning {<br />        color: #f0ad4e;<br />    }<br />	a.text-warning:hover, a.text-warning:focus {<br />        color: #ec971f;<br />    }<br />	.text-danger {<br />        color: #d9534f;<br />    }<br />    a.text-danger:hover, a.text-danger:focus {<br />        color: #c9302c;<br />    }<br />	.text-muted {<br />        color: #979797;<br />    }<br />	a.circle_icon {<br />        display: block;<br />        padding: 20px 0px 20px 70px;<br />        position: relative;<br />    }<br />	a.circle_icon .fa {<br />        position: absolute;<br />        left: 0px;<br />        top: 10px;<br />    }<br />	a.circle_icon h3 {<br />        font-weight: 300 !important;<br />        padding: 0;<br />        margin: 0 0 10px 0;<br />    }<br />	a:hover {<br />        text-decoration: none;<br />    }<br />	a:visited {<br />        text-decoration: none;<br />    }<br /></style>
<div class="homepage-quicklinks" style="background-size: initial; background-position: center center;">
<div>
<div class="row">
<div class="col-lg-6">
<div>
<div><a class="circle_icon text-danger" href="#" target="" rel="noopener">
<span class="fa fa-stack fa-2x">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-comments-o fa-stack-1x fa-inverse"></i>
</span></a>
<h3>Live Chat</h3>
<span class="text-muted">Start a chat conversation with customer service agents</span>

</div>
</div>
 

</div>
</div>
</div>
</div>

The post Service Portal-style Homepage Widgets appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/system-ui/service-portalstyle-homepage-widgets/feed/ 3
Adding Icons to UI Action Buttons in ServiceNow https://servicenowguru.com/client-scripts-scripting/button-colors-icons/ https://servicenowguru.com/client-scripts-scripting/button-colors-icons/#comments Mon, 17 Apr 2017 16:05:59 +0000 https://servicenowguru.wpengine.com/?p=12498 It’s hard to believe that it’s been seven years since I wrote a post showing how you can use client scripts to manipulate the appearance of form UI action buttons. This solution has been used in countless ServiceNow instances to help add more clarity to form buttons and processes in the system. A few months

The post Adding Icons to UI Action Buttons in ServiceNow appeared first on ServiceNow Guru.

]]>
It’s hard to believe that it’s been seven years since I wrote a post showing how you can use client scripts to manipulate the appearance of form UI action buttons. This solution has been used in countless ServiceNow instances to help add more clarity to form buttons and processes in the system. A few months ago, I decided to try and see if I could expand on this client script method a bit to add even more button flexibility with the addition of an option to include an icon along with the button text. Fortunately, with the addition of a new icon set in recent ServiceNow releases, this has become pretty simple to do…all while leveraging a consistent, nice-looking icon set.


Available icons

You’ve undoubtedly seen them in various parts of the system, but you may not have realized until now that there are over 250 icons available to choose from in ServiceNow! You can use these icons in various ways (client scripts, UI pages, UI macros, CMS and Service Portal), including with UI action buttons as I show here. All you have to do to show an icon is to add a class attribute with a name matching the icon you want to add.
ServiceNow had previously published a style guide that you could reference to view all of the icons (and corresponding names to reference them by). For the time being, you can access the icons in your instance by navigating to https://your_instance.service-now.com/styles/retina_icons/retina_icons.html

The following script can be used in any standard form client script. The key is the ‘transformButton’ function at the bottom. This could be included as a global UI script in your instance so that you don’t need to include it in every single client script. Once that’s in place (whether in a global UI script or in the client script itself) you can just call ‘transformButton’ with the appropriate parameters to change the button however you want. The parameters used are as follows…

  1. UI action name (Mandatory)
  2. Button background color (Optional)
  3. Button text color (Optional)
  4. Button icon name (Optional)
function onLoad() {
//Change the color of the 'Approve' button to green
transformButton('approve', '#77FF77', 'white', 'icon-success-circle');
//Change the color of the 'Reject' button to red
transformButton('reject', '#FF0022', 'white', 'icon-error-circle');
//Change the color of the 'Info' button to blue
transformButton('info_button', '#31708f', 'white', 'icon-info');
//Change the color of the 'Warning' button to yellow
transformButton('warning_button', '#f0ad4e', 'white', 'icon-warning-circle');
}

function transformButton(buttonID, buttonBackgroundColor, buttonTextColor, buttonIconName) {
try{
//Find the button(s) by ID and change the background color
$$('button[id=' + buttonID + ']').each(function(elmt) {
elmt.style.backgroundColor = buttonBackgroundColor;
if(buttonTextColor){
elmt.style.color = buttonTextColor;
}
if(buttonIconName){
elmt.addClassName(buttonIconName);
//Add some spacing between the icon and button label
elmt.innerHTML = ' ' + elmt.innerHTML;
}
});
}catch(e){}
}

The post Adding Icons to UI Action Buttons in ServiceNow appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/client-scripts-scripting/button-colors-icons/feed/ 19
Adding Video to Knowledge Articles https://servicenowguru.com/knowledge-management/adding-video-knowledge-articles/ https://servicenowguru.com/knowledge-management/adding-video-knowledge-articles/#comments Tue, 14 Mar 2017 11:37:23 +0000 https://servicenowguru.wpengine.com/?p=12460 Support for video in knowledge articles seems like it should be pretty straightforward but unfortunately history has proven otherwise. Standards have been shifting over time and the methods for displaying video in HTML have had varying levels of support depending on the browser. By default ServiceNow only supports out-of-date plugin methods of embedding video. While

The post Adding Video to Knowledge Articles appeared first on ServiceNow Guru.

]]>
Support for video in knowledge articles seems like it should be pretty straightforward but unfortunately history has proven otherwise. Standards have been shifting over time and the methods for displaying video in HTML have had varying levels of support depending on the browser. By default ServiceNow only supports out-of-date plugin methods of embedding video. While video support can certainly stand for some updating by ServiceNow, there are a few steps that can be taken now to allow for functional video use.

This method enables video to be used across the board in the HTML editor so it can be added wherever it is needed. It uses the HTML5 video tag and does require the user to be at least a little familiar with HTML but at this point that is difficult to avoid.


The first thing to do is whitelist the video and source tags in the HTMLSanitizerConfig script include. To do this replace this:

HTML_WHITELIST : {
  globalAttributes: {
    attribute:[],
    attributeValuePattern:{}
  },
},

With this:

HTML_WHITELIST : {
  globalAttributes: {
    attribute:[],
    attributeValuePattern:{}
  },
  video: {
    attribute:["width", "height", "controls", "autoplay", "loop", "muted", "poster", "preload", "src"],
    attributeValuePattern:{}
  },
  source: {
    attribute:["type", "src", "media", "sizes"],
    attributeValuePattern:{}
  }
},

To embed a video, open the article (or other record with an HTML field) and upload the video file.

Get the Sys ID of the attachment. An easy way that end users can do this is by clicking the “View” link next to the attachments.

Then copy the Sys ID from the URL.

Next click the “<>” icon to view the HTML source.

Paste in the following code, changing the value for the Sys ID parameter to that of the attachment.

<video controls="controls" width="100%" height="150">
  <source src="/sys_attachment.do?sys_id=[Sys ID of the attachment goes here]&view=true" type="video/mp4" />
</video>

NOTE: mp4 video looks to have the best support across browsers at the time of writing. The video tag has a number of options that allow for tailoring the content to the browser, device size, and other things as needed.

The post Adding Video to Knowledge Articles appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/knowledge-management/adding-video-knowledge-articles/feed/ 10
Simplifying Data Imports from Third Parties https://servicenowguru.com/imports/simplifying-data-imports-parties/ https://servicenowguru.com/imports/simplifying-data-imports-parties/#comments Wed, 01 Mar 2017 13:53:04 +0000 https://servicenowguru.wpengine.com/?p=12342 Recently along with my Crossfuze colleagues Josh McMullen and Scott Cornthwaite, I performed work for a client that needed to import asset data from several third party vendors. The company, a global manufacturer of household appliances, has dozens of vendors around the world, which supply data (CSV and Excel files) using proprietary formats and column names. The

The post Simplifying Data Imports from Third Parties appeared first on ServiceNow Guru.

]]>
Recently along with my Crossfuze colleagues Josh McMullen and Scott Cornthwaite, I performed work for a client that needed to import asset data from several third party vendors. The company, a global manufacturer of household appliances, has dozens of vendors around the world, which supply data (CSV and Excel files) using proprietary formats and column names. The client’s desired future state is to enforce a single format for use by all vendors. But to control their hardware and software assets today, they needed a solution to work with multiple vendors and data formats.

We faced a few challenges. Our solution needed to:

  • Allow data imports without elevated roles or system privileges
  • Handle the same kind of data from multiple vendors (e.g. hardware asset imports)
  • Handle data in a variety of file formats including text (.csv) and MS Excel
  • Provide feedback to the client’s IT asset management (ITAM) team
  • Check data quality and handle exception conditions defined by the client
  • Run with minimal input or intervention

We started to create custom inbound email actions to process emails with attached data files as they were sent from each vendor. But we discovered some big downsides with that approach. The most serious was that there’s no way to validate the accuracy or completeness of data being sent by the vendor. Inbound email processing also leaves the client at the mercy of the vendor for the timing of the imports. Finally, it isn’t very scalable, since a new inbound email action must be created for every additional vendor and/or data source.

What the client needed was a simple way to perform a data import via a Data Source with a file attachment. They were already aware of the Load Data wizard that ServiceNow provides on the System Import Sets application menu, but that solution isn’t very user-friendly, and it requires a lot of manual input each time new data are imported.

Asset Import Wizard

To make it easy for the client’s ITAM team to import their data in to ServiceNow, we leveraged the power of the Service Catalog. Specifically, we created a Record Producer to provide a simple front-end for importing vendors’ data files. Record producers can be used by anyone. Their visibility can be limited to only interested parties. Files attached to the record producer are automatically attached to the record that is created. And the record producer’s Script field enables powerful data processing.

We configured the record producer as follows:

  • Name: Asset Import Wizard
  • Table: Data Source (sys_data_source)

We added a single variable to the record producer, a lookup select box to allow the ITAM team member to specify the type of import they were performing.

Import Wizard Configurations

The variable gets its list of options from a custom table called Import Wizard Configurations. This table allowed us to build a flexible framework for defining different types of file imports from any vendor.
This table has many fields that are similar to those on a Data Source record. That’s because the record producer’s script queries the Wizard Configuration table for values to use when it inserts a new record in the Data Source table. Here’s how this form breaks down.

  • Vendor references records in the Company (core_company) table where the Vendor field is set to true.
  • Import Type is a choice list of options that describe the kind of asset data being imported (e.g. Hardware, Software, HW end of life disposal, or Lease Contract)
  • Expected Data Format is a choice list that allows either CSV or Excel (.xls) formats to be specified.
  • Data Source Name Prefix is a text field for naming the data source. The record producer’s script automatically appends the current time/date stamp to the prefix when each Data Source record is created
  • Import Set Table and Transform Map are fields that reference records in those tables. The import set and transform map to be used for these data imports must be created in advance.
  • Header Row and Sheet Number are used to specify values when an MS Excel file is the data source. For CSV files, the client just sets these to 1.
  • Active allows the client to deactivate the Wizard Configuration record if it is no longer needed. The Lookup Select Box variable on the record producer displays only active records in this table.

Record Producer Script

The record producer’s script is where the wizard’s “magic” happens. The script does several things:

  1. It validates that the wizard is submitted with an attachment of the correct format.
  2. It queries the Wizard Configuration table for the record that is selected in the Import Type variable, and inserts a new record into the Data Source table using values from the Wizard Configuration record.
  3. It imports the data into the import set table and executes the specified transform map, using ServiceNow helper functions GlideImportSetLoader and GlideImportSetTransformerWorker
  4. It provides feedback to the user upon successful execution of the script, or displays appropriate error messages if the script encountered errors. Error conditions cause the record producer to abort without creating the Data Source record.

Here is the code we used in the record producer script:


// Verify attachment is included and in correct format
var gr2 = new GlideRecord("sys_attachment");
gr2.addQuery("table_sys_id", current.sys_id);
var oC = gr2.addQuery("table_name", "sys_data_source");
oC.addOrCondition("table_name", "sc_cart_item");
gr2.query();
if (!gr2.next()) {
gs.addErrorMessage("You must attach a file to submit. Your import submission has been aborted.");
current.setAbortAction(true);
producer.redirect="com.glideapp.servicecatalog_cat_item_view.do?v=1&sysparm_id=<SysID of the Record Producer>";
}
else{
//Get the glide record for the selected import type
var gr = new GlideRecord('u_pmy_imp_wiz_cfg');
gr.addQuery('sys_id',producer.import_type);
gr.query();
if(gr.next()){
if(gr2.getRowCount() > 1){
gs.addErrorMessage("You may only attach one file at a time for this import wizard. Your import submission has been aborted.");
current.setAbortAction(true);
producer.redirect="com.glideapp.servicecatalog_cat_item_view.do?v=1&sysparm_id=<SysID of the Record Producer>";
}
//check to make sure the file format is correct on the attachment
var passedFormatCheck = false;
var errorCaught = true;
if (gr.u_format == 'CSV'){
if (gr2.file_name.endsWith('.csv') == true){
passedFormatCheck = true;
}
else{
gs.addErrorMessage("This import type is expecting submission of a CSV file (.csv), but a different file format was attached. Your import submission has been aborted.");
current.setAbortAction(true);
producer.redirect="com.glideapp.servicecatalog_cat_item_view.do?v=1&sysparm_id=<SysID of the Record Producer>";
}
}
else if (gr.u_format == 'Excel'){
if(gr2.file_name.endsWith('.xls') == true){
passedFormatCheck = true;
}
else{
gs.addErrorMessage("This import type is expecting submission of an Excel file (.xls), but a different file format was attached. Your import submission has been aborted.");
current.setAbortAction(true);
producer.redirect="com.glideapp.servicecatalog_cat_item_view.do?v=1&sysparm_id=<SysID of the Record Producer>";
}
}

if(passedFormatCheck == true){
// Create data source record (based on form import type selection record)

current.name = gr.u_ds_naming + '_' + gs.nowDateTime();
current.format = gr.u_format;
current.import_set_table_name = gr.u_import_set.name;
current.header_row = gr.u_header_row;
current.sheet_number = gr.u_sheet_number;
current.file_retrieval_method = "Attachment";
current.type = "File";

//Data source needs to be created before we can trigger the commands below, so we create the record outside of the normal record producer method
current.insert();

// Process file into data source record
var loader = new GlideImportSetLoader();
var importSetRec = loader.getImportSetGr(current);

// Import data from data source to import set table (based on form import type selection record)
var ranload = loader.loadImportSetTable(importSetRec, current);
importSetRec.state = "loaded";
importSetRec.update();

// Start appropriate transform map (will have the logic for logging exceptions within the transform map scripts, and will trigger an email once complete to the import submitter with an outline of the logged errors and warnings)
var transformMapID = gr.u_transform;
var transformWorker = new GlideImportSetTransformerWorker(importSetRec.sys_id, transformMapID);
transformWorker.setBackground(true);
transformWorker.start();

//Inform the user that a email outlining the status of the import will be sent once the import is complete
gs.addInfoMessage("Your import file has been submitted. An email will be sent to you once the import is completed to outline any errors or warnings encountered while importing.");
producer.redirect="home.do";
}

}
else{
gs.addErrorMessage('Something went wrong with the import. Please contact a system admin to investigate.');
}

// Since we inserted the data source already, abort additional insert by record producer
current.setAbortAction(true);
}

The Miracle of Transform Map Scripts

If the wizard works magic on the front end, then Transform Maps do the same on the back end. Even when the client’s ITAM team have an opportunity to review the vendors’ data before importing it, there can still be errors. We identified all of the potential failure points in each vendor’s data.

We used onBefore transform scripts to check each source row for exceptions before the source fields are mapped into the target table. We grouped these exceptions into two categories; Errors and Warnings. The transform scripts use log.error() and log.warn() to write exceptions to the import set log for each import run. Error exceptions cause the source row to be skipped by setting the ignore variable to true. Warning exceptions are logged, but the source row is transformed.

We also determined whether other tables required records to be updated or inserted as a result of the data import. For example, lease contract imports contain information about the hardware assets that are under lease. As each hardware record is updated, a related record has to be created in the Assets Covered table, in order to associate that asset with its lease contract.

We used onAfter transform scripts to handle these secondary table updates. OnAfter scripts run after the source record has been transformed into the target table. These scripts also logged exceptions if any were encountered during the update.

After all of the source rows have been evaluated and/or transformed, an onComplete script compiles the exceptions from the import set log in a block of text, then queues a system event. The user who initiated the import receives a notification containing that block of text. The notification provides feedback in near-real time, and lists exceptions from the import set log that would normally only be available to administrators.

The Universal Translator

The final cog in this machine is the Vendor Model Translation table. We created this custom table because the client found that their vendors used model identifiers that did not match the names in the client’s Model (cmdb_model) table. The lack of a common name, model number or some other identifier in the vendor’s data makes it impossible to match asset models.

The vendor translation table is a simple cross-reference table that associates a vendor’s name for a given model with the model record in ServiceNow. It contains just four fields:

  • Vendor is a reference to a vendor record in the Company (core_company) table
  • Active is a true/false value that can be used to filter records during queries
  • Vendor Model is a text field that stores the identifier used by the vendor to reference the model
  • ServiceNow Model is a reference to a record in the Model (cmdb_model) table

With this simple table it is possible to establish aliases for any number of models and/or vendors. It can also be used as a kind of normalization table, listing several vendor models that all refer to the same model in the client’s Model table. In the example below, three mobile phone models provided by the vendor are all associated with a single model in ServiceNow:

We put this table to use in a couple of ways. The transform map for hardware asset imports used an onBefore script to set values in the target Hardware (alm_hardware) table based on a match in the vendor translation table. This script also illustrates some of the exception logging we performed for the client:


(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {
var errorCondition = false;
var itemModel = '';
var modelDisp = '';
var vendorName = 'Name of the vendor company';
//Set the sourceRow variable to allow for input into the log statements the row from the source that failed
var sourceRow = source.sys_import_row + 2;
var excPrefix = 'Exception: Asset Import HW ASN ' + vendorName + ': Source Data Row ' + sourceRow + ': ';
// Do not transform the source record unless the source u_inventory_category field contains the text string "computer.system". This filters out non-computer hardware that may be included in the ASN. Log an Error if the inventory category value does not contain ".Computer Systems.".
if (source.u_inventory_category.indexOf('.COMPUTER SYSTEMS.') == -1){
//log.error(excPrefix + 'Item not a hardware asset (inventory category field does not contain ".computer system.")');
errorCondition = true;
}
else{
// Check for empty vendor model number in source, Log ERROR with message per error exception
if(JSUtil.nil(source.u_mfg_part_num) == true){
log.error(excPrefix + 'Manufacturer part number (mfg part num) missing in source data');
errorCondition = true;
}
else{
// Perform lookup against custom translation table, using vendor and vendor model key as the unique identifiers
//query the table for model translations for the selected vendor
var gr2 = new GlideRecord('u_vendor_translation');
gr2.addQuery('u_vendor', 'SysID of the vendor's record in the core_company table');
gr2.addQuery('u_vendor_model', source.u_mfg_part_num);
gr2.addActiveQuery();
gr2.query();
// Confirm if match found, if not, raise ERROR exception into import log
if(gr2.next()){
//Set the MODEL FIELD on the record so we don't need to query the table again in a field map script
source.model = gr2.u_sn_model.sys_id;
target.model = gr2.u_sn_model;
//set the itemModel field for use in another query later
itemModel = gr2.u_sn_model;
modelDisp = gr2.u_sn_model.getDisplayValue();
target.model_category = gr2.u_sn_model.cmdb_model_category;
}
else{
log.error(excPrefix + 'Source model ' + source.u_mfg_part_num + ' does not match a model in Service-Now. Check vendor translation table to ensure a translation is set up.');
errorCondition = true;
}
}

// Check for empty serial number, if empty, Log ERROR with message per error exception logging section below
if (JSUtil.nil(source.u_serial_num) == true){
log.error(excPrefix + 'Serial number missing in source data');
errorCondition = true;
}
else{
// Confirm that the serial number does not exist in the alm_asset table already as that same model, if so, raise ERROR exception
var modelDesc = '';
var gr1 = new GlideRecord('alm_asset');
gr1.addQuery('serial_number', source.u_serial_num);
//If we found a model through the translation table record, perform an additional filter on the model to make sure we don't have a duplicate model + serial number combo
if (itemModel != ''){
gr1.addQuery('model', itemModel);
//set the modelDesc variable to include that optionally in an error code if applicable
modelDesc = 'with model number ' + modelDisp + ' ';
}
gr1.query();
if(gr1.next()){
log.error(excPrefix + 'Serial number ' + source.u_serial_num + ' ' + modelDesc + 'matches existing hardware asset record ' + gr1.getDisplayValue());
errorCondition = true;
}
}

if(JSUtil.nil(source.u_customer_po_num) == true){
log.error(excPrefix + 'Customer PO Num field is empty on this record.');
errorCondition = true;
}
else{
// Perform lookup on the PO Line item table based on the PO provided and the model of hardware *Assume no more than one line item per model
var enc = 'purchase_order.u_po_number='+ source.u_customer_po_num +'^purchase_order.status!=canceled^model=' + itemModel;
var pol = new GlideRecord('proc_po_item');
pol.addEncodedQuery(enc);
pol.orderByDesc('sys_created_on');
pol.query();
if(pol.next()){
//Set the source record's Purchase Line to the sys_id of the Purchase Order Line Item
source.purchase_line = pol.sys_id;
}
else{
log.error(excPrefix + 'Unable to find a PO Line item under PO number ' + source.u_customer_po_num + ' for product model ' + modelDisp + '. This could be an issue with the vendor translation table record, or the wrong model was selected on a line item.');
errorCondition = true;
}
}

// If the Vendor is not found in ServiceNow's core_company table, log a Warning
var gr3 = new GlideRecord('core_company');
gr3.addQuery('sys_id', 'bbb81b896f8641009e4decd0be3ee4b1'); //sys_id of the vendor from the core_company table
gr3.query();
if(!gr3.next()){
log.warn(excPrefix + 'Vendor ABCDEFG (bbb81b896f8641009e4decd0be3ee4b1) does not match a record in the ServiceNow.');
}

// Check if the source record's quantity is greater than 1, if so, Log ERROR with message per error exception logging section below
if(source.u_qty_shipped > 1){
log.error(excPrefix + 'QTY shipped is greater than 1.');
errorCondition = true;
}
}

//Skip importing the source record if the transfer map is attempting to update an existing record
if (action == "update" && errorCondition != true){
log.error(excPrefix + 'Record is attempting to update an existing record.');
ignore = true;
}
// Skip importing the source record if any exceptions are found (ignore = true )
if(errorCondition == true){
ignore = true;
}

})(source, map, log, target);

For mobile device imports we wrote an onStart transform script that uses the Vendor Model Translation table to update source rows before any source rows are transformed. That reduces the ITAM team’s administrative task load by leveraging information already in ServiceNow’s Model table.


(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {
/***
* Before transforming any source rows we'll try to match them to models using the
* Vendor Model Translation table or the ServiceNow Model table
***/
log.info('import set ' + import_set.sys_id.toString());
//Query the import set table for rows in the import set we're transforming
var row = new GlideRecord('u_mobile_device_import');
row.addQuery('sys_import_set', import_set.sys_id.toString());
row.query();
while(row.next()){
if(!row.u_device_model_.nil()){
//Check the Vendor Model Translation table for a record that matches the source's Device Model
var gr2 = new GlideRecord('u_vendor_translation');
gr2.addQuery('u_vendor_model', row.u_device_model_);
gr2.addActiveQuery();
gr2.query();
if(gr2.next()){
//Set the MODEL FIELD on the row so that we can coalesce
row.u_device_model_ = gr2.u_sn_model.sys_id.toString();
row.update();
}
else{
//No match in the Vendor Translation table, so check ServiceNow's Model table for a match
var gr3 = new GlideRecord('cmdb_model');
gr3.addQuery('display_name', row.u_device_model_);
gr3.addQuery('status','In Production');
gr3.query();
if(gr3.next()){
//Set the MODEL FIELD on the row so that we can coalesce
row.u_device_model_ = gr3.sys_id.toString();
row.update();
}
}
}
}
})(source, map, log, target);

Putting It All Together

The Import Wizard gave the client a simple way to initiate data imports from any number of vendors at a convenient time, after they verified the quality of the data. The Wizard Configuration table provided a means to extend the wizard’s functionality for multiple vendors and data imports. And the Vendor Model Translation table allowed the ITAM team to associate a vendor’s model information with Model records in the client’s ServiceNow instance.

The client’s ITAM team reports that the wizard has already paid off.  In a few short days it has simplified their work, reduced errors, and made processing asset data much more efficient. And project managers are making plans to bring more vendors on board as they work toward enforcing common data formats across all of their vendors.

Standing on the Shoulders of Giants

This article wouldn’t be complete without acknowledging the assistance of others. We based our design for the Import Wizard record producer on this ServiceNow Community post by Michael Ritchie and endorsed by everyone’s favorite bow-tie wearing technical genius, Chuck Tomasi. The post is well worth reading, since Michael leads you through the steps you’ll need to follow before using the record producer to perform a data import.

The post Simplifying Data Imports from Third Parties appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/imports/simplifying-data-imports-parties/feed/ 14
Client Side Dates in ServiceNow https://servicenowguru.com/client-scripts-scripting/client-side-dates-in-servicenow/ https://servicenowguru.com/client-scripts-scripting/client-side-dates-in-servicenow/#comments Wed, 22 Feb 2017 13:29:15 +0000 https://servicenowguru.wpengine.com/?p=12051 Dates on the client side in ServiceNow have always been challenging. Javascript has its own complexities in this regard and mixing this with ServiceNow’s date formats and user preferences can make it a nightmare. Here we walk through some of the methods of working with client side dates that make it MUCH simpler and allow

The post Client Side Dates in ServiceNow appeared first on ServiceNow Guru.

]]>
Dates on the client side in ServiceNow have always been challenging. Javascript has its own complexities in this regard and mixing this with ServiceNow’s date formats and user preferences can make it a nightmare.

Here we walk through some of the methods of working with client side dates that make it MUCH simpler and allow us to avoid the situation of having to return to the server via GlideAjax for most, if not all, date calculations.

ServiceNow Date Formats drop-down

If you’re here as a reference and just need the key code snippets, here you go. Basically, to get a javascript date from a ServiceNow date field value do this:

var date_number = getDateFromFormat(g_form.getValue('the_date_field'), g_user_date_format);
var my_date = new Date(date_number);

Or, from a Date/Time field do this:

var date_number = getDateFromFormat(g_form.getValue('the_date_time_field'), g_user_date_time_format);
var my_date = new Date(date_number);

The Details

Working with dates on the client side of things in ServiceNow has always been a challenge. Part of this is just due to Javascript and the challenges associated with dates and the other is ServiceNow. Given that users can have their own date format makes it even more challenging. That said, we are given some tools (albeit undocumented ones) that can help improve the situation.

I ran across some information a while back (thanks to John Roberts) that helps the situation drastically. The key comes down to a couple global variables defined by the system as well as function that helps use those variables.

The variables give us the date and datetime formats for the logged in user, and the function lets us use those to get something we can work with a little easier.

User datetime format: g_user_date_time_format
User date format: g_user_date_format

These used with the getDateFromFormat function gives us an easy way to get a value for the date (essentially a Unix Timestamp). That value can then be used directly or immediately passed into a Javascript Date object to allow for reformatting, testing, or whatever else is needed.

Here are a few functions that I put together to simplify the date validation process that is so often needed.

Test for valid DateTime based on user format:

function isValidDateTime(value){
  if(value == '' || value == undefined || value == null){
    return false;
  }
  return(getDateFromFormat(value, g_user_date_time_format) != 0);
}

Test for valid Date based on user format:

function isValidDate(value){
  if(value == '' || value == undefined || value == null){
    return false;
  }
  return (getDateFromFormat(value, g_user_date_format) != 0);
}

To take this a step further, you could easily add comparisons of dates or add custom date formats based on the values.

One thing to keep in mind with this, depending on where the date is coming from you may still have two date formats. Any of the dates that will work as shown are going to be the values in fields on a form or the Display Value of the date. If you’re getting something from the server (e.g. via GlideAjax) as just a value then it will be stored in the regular system format of YYYY-MM-DD HH:MM:SS.

An additional example suggested by a co-worker (thank Thomas) to validate that the date is within an hour

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
  if (isLoading || newValue == '') {
    return;
  }

  //To change the date range from 1 hour, change var minutes_from_now to desired amount time
  var minutes_from_now = 60;
  var date_obj = new Date(getDateFromFormat(newValue, g_user_date_time_format));
  var future_date = new Date(new Date().getTime() + minutes_from_now*60000);
  if(future_date < date_obj){
    alert("Time is too far out");
  }
}

Hopefully this helps save you some of the frustration I’ve experienced over the years when dealing with client side dates in ServiceNow.

The post Client Side Dates in ServiceNow appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/client-scripts-scripting/client-side-dates-in-servicenow/feed/ 5
ServiceNow Guru is Getting a Facelift! https://servicenowguru.com/service-now-general-knowledge/servicenow-guru-facelift/ https://servicenowguru.com/service-now-general-knowledge/servicenow-guru-facelift/#comments Mon, 20 Feb 2017 16:54:17 +0000 https://servicenowguru.wpengine.com/?p=12312 Some of you may have noticed a bit of tinkering going on in the background at ServiceNow Guru over the past couple of weeks. First of all, I apologize about that handful of “lorem ipsum” sample content tweets that went out a few days ago :). Thankfully Twitter has a delete button and I also

The post ServiceNow Guru is Getting a Facelift! appeared first on ServiceNow Guru.

]]>
Some of you may have noticed a bit of tinkering going on in the background at ServiceNow Guru over the past couple of weeks. First of all, I apologize about that handful of “lorem ipsum” sample content tweets that went out a few days ago :). Thankfully Twitter has a delete button and I also have complete confidence that new Guru posts will be tweeted out as efficiently as ever from the site! I’m really excited to finally be able to announce the upcoming release of the results of all of that tinkering.


Since 2009 when I originally launched SN Guru, I’ve used it as a place to share cool solutions on the ServiceNow platform, and (probably much less glamorously) to have a place to put all of my stuff so I didn’t forget how to build it the next time I needed it for a client engagement :). It’s hard to believe how much things have changed and grown in the 7+ years since then. Over the past couple of years the site hasn’t had my full attention as it had previously. Some of you may have even wondered if the site was going away completely! My primary focus has been on the massive task of running a successful company and consulting organization at Crossfuze. During this time, the SN Guru audience has actually grown considerably and it’s time to return a bit more of my focus to the site.

The beginning of those efforts will be the site redesign. It’s been over 6 years since I did the last one. One of the reasons for this is that it’s a TON of work and I simply didn’t have the time. Instead, I just tried to do my best to keep all of the existing content (over 250 different articles and solutions) relevant and help answer questions as they came in. The site redesign will provide a new, more modern look and will hopefully breathe a bit of new life into the content and concepts at the same time. Much of the content and organization will be the same as it was previously…I don’t want to make things harder for people that have been using the site for a while…but I’m making a few new, helpful changes.

  • A new ‘SN | Guru’ brand, logo, and color scheme! This one is WAY overdue. No one has used ‘SNC’ for a while. This will be reflected on a new logo.
  • Clean, new site redesign and theme. Seeing is believing! A brand new theme and cleaned up site will help everybody.
  • Moving away (hopefully permanently) from the clunky built-in WordPress search to a full, Google custom search experience
  • More flexible and modern Mailchimp-based email subscription service.
  • Bringing in a few new expert authors to provide a fresh take on a larger variety of ServiceNow concepts on a more consistent basis

More changes will be coming over time as I continue to review older content and concepts and look for ways to do things even better but I’m extremely happy to have reached this goal of getting the redesign done and moving SN | Guru into a new, better place. Thanks so much to all of you for your support over the years and for your continued use of the site. It’s a ton of work but has been a very rewarding part of my career that I’m excited to continue.

Look for the redesigned site and brand to go live tomorrow!

The post ServiceNow Guru is Getting a Facelift! appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/service-now-general-knowledge/servicenow-guru-facelift/feed/ 9