Monday, 30 May 2016

Service Catalog: Record Producer Variables in your Change Ticket

At a recent project, we had a requirement to show the variables of a record producer 
on the change record that was created by the record producer. This can be done easily 
by adding the variable editor to the form.
However… the UI Policies and client scripts defined on the record producer cannot run 
on the produced record like you can do with Request Item variables.
The Challenge
The requirement of the client was to make the information of the 
variables available on the change record. Having the fields listed out in a change 
specific field was the next option the Client had for this requirement. 

The variables however needed to be listed in the same order as they appear on
 the record producer form. 

The functionality of a label and multiple check boxes is used regularly, this should be 
visualized the same way in the record producer.
Another reason why the variable pool option did not make it was because the variable 
pool can only be used to get the label,value and type of a variable related to the change 
record. We needed more information from the variable, for example the order field.

The Solution

Out of the box, Service-Now has the functionality that if the Variable that is used in 
the record producer has the same name as the target field, the value is automatic 
scopied to the newly created record. For all other options, the record producer has 
a script field in where the mapping between variables and fields can be scripted. The 
client was looking at a number of record producers (pre-approved change requests)
 that was going to exceed 200 cases. Due to this large number, we had to come up 
with a maintainable solution.
We created a script (script include) that loops through the related variables of a record 
producer and prints them in the right order in the description field of the target 
(change) record.



The variables of the types ‘container_start, container_end, container split’ are ignored.
The script include is called from the script field in the record producer, like this:
New u_RPUtils,process(‘string of the sys_id of the record producer’);
The Script

var u_RPUtils = Class.create();

u_RPUtils.prototype = {
    initialize: function() {
    },

    // function called from record producer, should have the producer object available.
    process: function(recProdSysID){
        var recProd = '';
        if(typeof recProdSysID != 'undefined'){
            var stringState = '-2';
            recProd = u_GlobalUtils.getRec('sc_cat_item_producer',recProdSysID);
            current.short_description = recProd.short_description;
            //map field from the variable set. the reference qualifiers from the change process are mapped to im_service in stead of the u_im_service variable
            current.u_im_service = producer.im_service;
            current.state = parseInt(stringState);
            current.type = 'Pre-Approved';
            current.requested_by = gs.getUserID();
            }
    },
    setValuesForQuestions: function (current){
        //variables that are already mapped are added to a system property, we dont want to see these questions in the description of the change
        var varNameStr = gs.getProperty('lsmc.pacr.mapped_variable_names');
        var varNameArray =[];
        if(varNameStr !=""){
            var aa = varNameStr.toString().split(',');
            for(var i=0; i<aa.length; i++){
                varNameArray.push(aa[i].toString().trim());
            }
        }
        var string3 = "";
        var producerVars = new GlideRecord('question_answer');
        producerVars.addQuery('table_sys_id', current.sys_id);
        producerVars.addQuery('question.type', '!=', 19); // Container Start
        producerVars.addQuery('question.type', '!=', 20); // Container End
        producerVars.addQuery('question.type', '!=', 24); // Container Split
        producerVars.orderBy('question.order');
        producerVars.query();

        while(producerVars.next()){
            var type = producerVars.question.type;
            var label = producerVars.question.question_text.toString();
            var name = producerVars.question.name.toString();
            var question = current.variables[producerVars.question.name.toString()];
            var value = question.getGlideObject().getQuestion().getDisplayValue() + '';
            //11==label
            if(new ArrayUtil().indexOf(varNameArray, name) == -1){
                if(type == 11){ 
                    string3 += "\n\n" + label + " : ";
                    var xxx = [];
                    while(producerVars.next()){
                        label = producerVars.question.question_text.toString();
                        name = producerVars.question.name.toString();
                        question = current.variables[producerVars.question.name.toString()];
                        value = question.getGlideObject().getQuestion().getDisplayValue() + '';
                        type = producerVars.question.type;
                        // 7==checkbox
                        if(type == 7){
                                if(value == 'true'){
                                xxx.push(label);
                                }
                        }
                        else{
                            string3 += xxx.toString();
                            if(name == 'pacr_businesss_requirement'){
                                string3 =  "" +  value + "\n" + string3;
                            }
                            else{
                                // next is evaluated, if you don't use it here, the glide record will not be processed.
                                string3 += "\n" + label + " : " +  value;
                            }
                            // clear the array
                          xxx = [];
                            break;
                        }
                    }
                    // when the last question is a checkbox, we need to add it to the string here, it will never get in the previous else state..
                    if(xxx.length >0){
                    string3 += xxx.toString();
                    }
                }
                else if(name == 'pacr_businesss_requirement'){
                    string3 = "" +  value + "\n" + string3;
                }
                else{
                    string3 += "\n" + label + " : " +  value;
                }
            }
        }
        current.description += "Questions and Answers from the request:\n" + string3;
        },
    type: 'u_RPUtils'
};
If you would like to know more about this specific subject or you need help with your 
Service Catalog, 
please contact me at abhi.jain39@outlook.com
-AJNow

Discovery and creating new CIs

If discovery tools are used within ServiceNow it can happen that based on the information provided it is not possible to determine the specific CI Class in ServiceNow. Therefore it could be useful to change the class after creation. This is possible as described in the following article by a colleague of mine: Change the Class of a CI. This article enables the option for the end user (without admin access) to change the ServiceNow CI class.
In this article a custom class Middleware CIs is used to explain the functionality.
image001
The class structure looks like this:
Base ClassExtended Class
Middleware Cis (u_cmdb_ci_middleware)(none)
Application Servers (u_cmdb_ci_application_server)u_cmdb_ci_middleware
Database Instances (u_cmdb_ci_database_instance)u_cmdb_ci_middleware
Interfaces (u_cmdb_ci_interface)u_cmdb_ci_middleware
MFX (u_cmdb_ci_mfx)u_cmdb_ci_middleware
MQ Cis (u_cmdb_ci_mq)u_cmdb_ci_middleware
Below an example of a Middleware record in ServiceNow which is created by the interface as a ‘Discovered’ CI.
image003
On the form in the “Related Links” section a link is available called ‘Change class’ to change the class of a CI. When clicking on this link the following Dialog Window is visible.
image005
The Dialog Window is initiated by using a UI Action and created with the following details below:
FieldValue
NameChange class
TableAny CI table on which you want to enable this functionality (in this example it is the u_cmdb_ci_middleware table)
OnclickchangeClass();
Conditionnew TableUtils(”+current.getTableName()).hasExtensions() && !current.isNewRecord() && current.operational_status == 0 && current.canWrite()
Scriptfunction changeClass() {
var gdw = new GlideDialogWindow(‘cmdb_change_class’);
gdw.setTitle(‘Change CI class’);
gdw.setPreference(‘sysparm_sys_id’, g_form.getUniqueValue());
gdw.setPreference(‘sysparm_sys_class_name’, g_form.getTableName());
gdw.render();
}
The condition can be changed as needed, in this example it is shown when the class of the CI has extended tables, if it is a existing record, the status of the CI is “Discovered” and the user can update (write) the record.
One of the options on how to make the UI Action visible can be chosen below (in this article the it is shown as a link):
image007
The Dialog window is a UI Page (cmdb_change_class), is created with the following details:
FieldValue
Namecmdb_change_class
CategoryGeneral
HTML<?xml version=”1.0″ encoding=”utf-8″ ?>
<j:jelly trim=”false” xmlns:j=”jelly:core” xmlns:g=”glide” xmlns:j2=”null” xmlns:g2=”null”>
<g:ui_form>
<input type=”hidden” name=”sysparm_sys_id” id=”sysparm_sys_id” value=”${RP.getParameterValue(‘sysparm_sys_id’)}”/>
<input type=”hidden” name=”sysparm_sys_class_name” id=”sysparm_sys_class_name” value=”${RP.getParameterValue(‘sysparm_sys_class_name’)}”/>
<g2:evaluate>
var ciclass = [];
var tbu = new TableUtils(‘${RP.getParameterValue(“sysparm_sys_class_name”)}’).getTableExtensions();
<!– tag “&lt” equals the “<” sign in html –>
for(var i=0;i&lt;tbu.size();i++) {
ciclass.push(tbu.get(i));
}
ciclass = ciclass.join(‘,’);
ciclass;
</g2:evaluate><g2:tokenize var=”jvar_ci_classes” delim=”,”>
$[ciclass]
</g2:tokenize>

<label for=”available_select”>${gs.getMessage(‘Change reason’)}</label><br/>
<g:ui_reference name=”u_change_reason” id=”u_change_reason” table=”task” query=”${new CIValidationUtils(current).refQualChangeReason()}”/><br/><br/>
<label for=”available_ci_classes”>${gs.getMessage(‘Please select a ci class to change to.’)}</label><br/>
<select name=”ci_class_chooser” id=”ci_class_chooser”>
<j2:forEach var=”jvar_ci_class” items=”$[jvar_ci_classes]”>
<option value=”$[jvar_ci_class]”>$[jvar_ci_class]</option>
</j2:forEach>
</select><br/>
<g:dialog_buttons_ok_cancel ok=”return validateMandatoryfields()”/>
</g:ui_form>
</j:jelly>
Client scriptfunction validateMandatoryfields() {
if(gel(‘u_change_reason’).value == ”) {
alert(‘Please provide a change reason.’);
return false;
} else if(gel(‘ci_class_chooser’).value == ”) {
alert(‘Please provide a ci class to convert’);
}
}
Processing scriptmoveCIClass();function moveCIClass() {
var grci = new GlideRecord(‘cmdb_ci’);
if(grci.get(sysparm_sys_id)) {
source = {};
source.x_need_change_reason = false;

grci.sys_class_name = ci_class_chooser;
grci.u_change_reason = u_change_reason;
grci.update();
var graudit = new GlideRecord(‘sys_audit’);
graudit.initialize();
graudit.documentkey = sysparm_sys_id;
graudit.fieldname = ‘sys_class_name’;
graudit.tablename = sysparm_sys_class_name;
var grchg = new GlideRecord(‘task’);
if(grchg.get(u_change_reason)) {
graudit.reason = grchg.getDisplayValue();
}
graudit.insert();
response.sendRedirect(grci.getLink());
}
}
I found a some helpful examples on the community website of ServiceNow (https://community.servicenow.com/thread/164789) which helped me to create the script below. It gets all the extended tables as a comma separated string and uses the g2:tokenize tag to split it is a array function does.
    <g2:evaluate>
      var ciclass = [];
      var tbu = new TableUtils('${RP.getParameterValue("sysparm_sys_class_name")}').getTableExtensions();
      <!-- tag "&lt" equals the  "<" sign in html -->
      for(var i=0;i&lt;tbu.size();i++) {
        ciclass.push(tbu.get(i));
      }
      ciclass = ciclass.join(',');
      ciclass;
    </g2:evaluate>

    <g2:tokenize var="jvar_ci_classes" delim=",">
      $[ciclass]
    </g2:tokenize>
Then it was possible to loop through the delimited string using the forEach tab below.
      <j2:forEach var="jvar_ci_class" items="$[jvar_ci_classes]">
        <option value="$[jvar_ci_class]">$[jvar_ci_class]</option>
      </j2:forEach>
In this article the extended tables were made available, however it is also possible to select parent tables or all the classes available within the class tree. The available options are listed below. (http://wiki.servicenow.com/index.php?title=TableUtils).
Function                        Purpose
getHierarchy()             Returns all the classes within the tree
getTableExtensions() Retuns all the extended classes in the tree
getTables()                    Returns all the parent classes in the tree
After a function is chosed in the table above, the following changes have to be done in the scripts:
UI Action
Change the condition of the UI Action, at least remove the “new TableUtils(”+current.getTableName()).hasExtensions()” part.
UI Page
Update the part “getTableExtensions” below with one of the functions available above.
  <g2:evaluate>
      var ciclass = [];
      var tbu = new TableUtils('${RP.getParameterValue("sysparm_sys_class_name")}').getTableExtensions();
      <!-- tag "&lt" equals the  "<" sign in html -->
      for(var i=0;i&lt;tbu.size();i++) {
        ciclass.push(tbu.get(i));
      }
      ciclass = ciclass.join(',');
      ciclass;
    </g2:evaluate>
In case the class (sys_class_name) is updated in ServiceNow it is not audited, therefore a ‘custom’ audit record is created so the change of the class is visible in the History List and Calendar.
image009
It’s a lot of code, but it’s also a nice result. Do you like it? Let me know!
abhi.jain39@outlook.com
-AJNow

Message Triggers


triggerWhen implementing Integration to external systems the normal way to trigger outbound messages is by using Business Rules and Script Includes that will validate and decide if a message needs to be transmitted to external systems.
The concept that I am going to discuss uses a different data driven approach:
Message triggers.
It has some specific advantages, I will list them below.
Let’s start with a real life example:
image001
The Integration table is a custom table for all data directly related to the specific Integration and consists of 7 main elements:
• Generic data: Id, Company, Interface Type and Service Type
• SOAP data: Outbound SOAP messages to use, SOAP inbound Data
• Mapping: Integration specific mapping data
• Process Flow: Allows for Integration specific data handling
• Translations: Uses a custom Translation table for translation inbound and outbound elements.
• Transactions: every inbound and outbound message for this Integration is logged in a transaction table for traceability and error solving by keeping track of the message transfer status. It also allows for easy retransmission in case of errors.
• and finally Message Triggers:
image003
The Trigger Table, Message, Order and Condition are the key parts used.
Trigger Table will decide for which table the trigger is valid.
Message will decide which outbound message will be created.
Order will decide the Order in which Transactions will be created.
Condition will hold the code that decides that a message needs to be created.
By writing a simple BR for the Trigger Table (or Task in this case because we also will trigger on other Task related tables):
image005
The script Include will handle following tasks:
• Get all Message trigger for the Table.
• Check all Message Trigger conditions for a True result.
• Sort the found Triggers on Order.
• Create a Transaction (Message = Transaction Type) for every found Trigger.
The advantages of this approach are:
• Cleaner and more robust triggering.
• Easier to debug.
• Trigger Coding is done in the Message Trigger conditions.
• Easy to trigger for multiple integrations and multiple messages on 1 update event.
• Easy to expand to other tables on which you would like to trigger.
• Easy to add new Triggers.
Work Order Example:
image007
I hope that during your next integration this will help you in using a more data driven approach.
If you have any question, please let me know or leave a comment abhi.jain39@outlook.com