Script includes Archives - ServiceNow Guru https://servicenowguru.com/category/script-includes-scripting/ ServiceNow Consulting Scripting Administration Development Thu, 05 Dec 2024 19:40:55 +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 Script includes Archives - ServiceNow Guru https://servicenowguru.com/category/script-includes-scripting/ 32 32 Implementing a Factory Pattern on ServiceNow https://servicenowguru.com/scripting/implementing-a-factory-pattern-on-servicenow/ Thu, 05 Dec 2024 19:40:55 +0000 https://servicenowguru.com/?p=16592 From time to time I've run into a situation where I need to branch to use a different set of code depending on some attribute or circumstance, but regardless of the branch the code is essentially doing the same thing, it just has to do it a different way. To put more of a point

The post Implementing a Factory Pattern on ServiceNow appeared first on ServiceNow Guru.

]]>
From time to time I’ve run into a situation where I need to branch to use a different set of code depending on some attribute or circumstance, but regardless of the branch the code is essentially doing the same thing, it just has to do it a different way. To put more of a point on this, imagine that you need to integrate to update an incident on another system…but it’s not just one other system, it’s potentially many and they are all different vendors. In the end, you need to update a record on a target system, but how you do that is most likely different as each system will have its own API to consume to do this. One way to accomplish it would be to build an if/then or switch block with each block calling the right code to interact with the other system. But another, more flexible way, is to build a factory that dynamically gives you the code you need based on attributes you have defined in your instance.

The factory analogy is apt here and is a fairly common pattern in programming.  At a high level:

  • Factory: The factory represents a central machine that produces different items.
  • Products: The items on the conveyor belt are the products created by the factory.
  • Clients: The workers who take items off the conveyor belt are the clients using the factory.

There are a couple of key components to this technique, and we’re going to walk through them. As we work through an example, we will tie what we build to the above concepts to connect the dots!

First, let’s simplify and refine the above general example. We have two external systems we need to integrate with to create/update incidents. Each external system has its own REST API to call with a unique payload. We know which system we have to integrate with by the company associated with the user caller attribute on the incident in our system.  There are 2 companies right now, CompanyOne & CompanyTwo, that we need to integrate with.

At a high level, the steps involved in creating our factory are:

  1. Create the code that would call each company’s API correctly.
  2. Create a blank/dummy script include (this will be our factory).
  3. Get GlideRecord reference to our factory.
  4. Overwrite the script value from 3 with the string that represents one of our script includes from step 1.
  5. Run the GlideRecord from 4 through GlideScriptEvaluator to get an object.

Now in more detail….

Step 1

Create script includes that call the respective API’s. As a standard we’ll refer to those script includes as CompanyOneAPI & CompanyTwoAPI. These script includes contain the unique logic needed to consume the respective company’s API. So far, so good…nothing really ‘new’, right?  Here’s the new part. In each script, the method that is called to fire things off is named the same. Something like ‘execute’. So to start the integration in each case would be:

new CompanyOneAPI().execute()

or

new CompanyTwoAPI().execute()

Step 2

Now create a ‘dummy’ script include. You can name it anything, but something descriptive is always recommended and helpful. The script include does NOT need to have any actual script in it, but you can take the default scripting given as part of creating a new script include. This script include will serve our ‘factory’. It takes some input and will give us a ‘product’. Here’s an example:

Name: Factory

Description: Just used by "factories" as a record for dynamically creating other objects.

Script:

var Factory = Class.create();
Factory.prototype = {
     /*
     This script include is used purely as a placeholder to build other objects for instantiation. It is referenced in other script includes and the script attribute is 'written' too but never saved. That 
     script is then executed to dynamically give the object that is needed based on the attributes passed in the the "factory".
     */
     initialize: function() {},
     type: 'Factory'
};

 

Step 3

Now that we have our “factory” script include we need to get a GlideRecord to our Factory script include:

var grFactory = newGlideRecord("sys_script_include");
grFactory.get("api_name", "Factory");

Step 4

Next, set the script value of the script include from step 3 to the name of one of the script includes that does the actual integration (created in step one):
grFactory.script = "CompanyOneAPI";
Notes:
  • If “CompanyOneAPI” is in a specific scope, that scope would be included as well (I.e. <scope_name>.CompanyOneAPI).
  • We do NOT ever save ( .update() ) the grFactory GlideRecord.

Step 5

Finally, you take the grFactory and run it through GlideScopedEvaluator to get an instantiated script, and then an object:

var evaluator = new GlideScopedEvaluator();
var model = evaluator.evaluateScript(grFactory, 'script');
var handler = new model();
”handler” can now be used as if you’d done this:
new CompanyOneAPI();
This gives us our ‘product’ from ‘factory’.

Putting it all together

Why go through all the hassle above?  Take a look at step 4 above again.  We explicitly set the script to be “CompanyOneAPI”…but what if we wrapped steps 3-5 in a utility and allowed the script string we want to be instantiated by being passed in as an attribute?  Something like:

getInstance = function( script_include_name ) {
     //Get a reference to the script include created for populating it's script field with the name of the script include passed in.
     var grFactory = newGlideRecord("sys_script_include");
     grFactory.get("api_name", "x_snc_trng_util.Factory");
     grFactory.script = script_include;
     //Use GlideScopedEvaluator to turn evaluate the script above, returning an instantiated handler that is returned.
     var evaluator = newGlideScopedEvaluator();
     var model = evaluator.evaluateScript(grFactory, 'script');
     var handler = new model();
     return handler;
};
Now we can pass any script include name and get an instance of it.  Couple this up with a data model that allows us to look up the right script include based on the company that it is for and we have the foundation for something truly flexible going forward as company3,4,… gets added. To do the lookup there are a couple of obvious approaches:
  • Add an attribute to core_company to contain the script include string
  • Add a look-up table that ties a reference to the company to the script include string (and possibly some other attribute for further delineation).

As we’ve gone the path of the second option for most of our implementations, we’ll look at that one at a high level:

 

Company (Ref) Attribute (Str) Script (Str)
CompanyA integration.incident CompanyOneAPI()
CompanyB integration.incident CompanyTwoAPI()

 

Now we have a table we can use to look up the specific API script for a company. In our example the company would come from the caller on the incident.  We would use that in the look-up table to find the right API to use:

var LookUpGr = new GlideRecord("LookupTable");
LookUpGr.addEncodedQuery("company=<company_sys_id>^attribute=integration.incident");
LookUpGr.query();

Then, assuming we found a look-up record we could take the script string and create our handle:

if (LookUpGr.next) { 
     var data = {<JSON object with info needed for API call>}
     //Get an instantiated javascript object
     var handler = getInstance(LookUpGr.getValue('script')); 
     //Take that object and run it's execute method.  Each company API should have the same method to call (i.e. execute)
     var response = handler.execute(data); //evaluate response 
}

The last line essentially being the ‘client’ that takes the ‘product’ from the factory and uses it.

Now that we have the above framework, when we get the next company that we need to integrate with we’d need to:

  1. Create CompanyThreeAPI (with an execute method) that calls the other systems API to create/update incidents.
  2. Create a mapping in the look-up table for CompanyThree

Hopefully the above is a pattern you can find useful as I am sure there are many other applications where it can be applied. Please let me know what you think or if there’re any questions!

The post Implementing a Factory Pattern on ServiceNow appeared first on ServiceNow Guru.

]]>
Leveraging User Criteria in your custom applications https://servicenowguru.com/scripting/leveraging-user-criteria-custom-applications/ https://servicenowguru.com/scripting/leveraging-user-criteria-custom-applications/#comments Mon, 03 Jun 2024 12:44:36 +0000 https://servicenowguru.com/?p=15374 User Criteria (UC) have been around on the ServiceNow platform for quite some time now. They were a welcome extension to providing a more flexible way for controlling the set of users who should or shouldn’t have access to specific records. With UCs you can define a grouping of users either by name, groups they

The post Leveraging User Criteria in your custom applications appeared first on ServiceNow Guru.

]]>
User Criteria (UC) have been around on the ServiceNow platform for quite some time now. They were a welcome extension to providing a more flexible way for controlling the set of users who should or shouldn’t have access to specific records. With UCs you can define a grouping of users either by name, groups they belong to, roles they have, or through a script.

Standard User Criteria definition form.

You can then take that UC and add/configure it on a record as having the ability to ‘can read’ or ‘cannot read’ a specific record or group of records. Probably the best known examples of this are in the OOB knowledge base and service catalog applications.

KB article can read/cannot read attributes.

Available For/Not Available For related lists on catalog items.

But what if you wanted to be able to use UCs in your custom applications? The following will provide a roadmap on how you can do this!

Lets review how UCs are used for security by using the concrete example from the knowledge base. For the Knowledge Base application there are can/cannot read attributes (KB article image shown above) on the knowledge base record as well as the individual article record.  On the knowledge base record the UCs are associated as related records and shown in the ‘Can Read’/’Cannot Read’ related lists.  On the knowledge base articles there are ‘Can Read’/’Cannot Read’ glidelist attributes directly on the record. The way the OOB security has been implemented to use those UC lists for determining if a user can read a specific knowledge article is that:

  • A user can NOT be in either the knowledge base or knowledge articles cannot read UCs to be able to read the article.
  • If there is a can read UC on the knowledge base, the user must be in the UC to read articles in the knowledge base.
  • If there is a can read UC on the knowledge article, the user must be in the UC to read the article.
  • If there are no UCs defined on either the knowledge base or article, the article is readable by any user.

This is generally the way the service catalog works as well with the catalog and catalog item.

There are other OOB applications using UCs as well….but what if you wanted to be able to use UCs in other places to control access that aren’t part of OOB applications? To do this you’d need some way of evaluating a user’s inclusion in a UC. Thankfully there is a scriptable object that does just that: UserCriteriaLoader.

UserCriteriaLoader was a utility Mike Sherman (also a long-time ServiceNow employee) and I found while trying to develop the security model for a project we were working on. It’s relatively easy to use and just needs some fairly straightforward wrappers to make it reusable for our needs.

UserCriteriaLoader has several functions on it but the main one we are going to make use of is userMatches(). The userMatches() function takes the sys_id of a user you’re checking and an array of UC sys_id’s to check against. The function then evaluates the user against those UCs and returns true/false indicating if the user is in at least one of the UCs. Something like the following:

var user = gs.getUserId();
var uc_array = [“<uc_sys_id_1>”, “<uc_sys_id_2>”, “<uc_sys_id_3>”];
var ret = sn_uc.UserCriteriaLoader.userMatches( user, uc_array);


The above is a great start as it gives you the base capability to evaluate a user’s inclusion in a set of UCs, but what we really want is a utility that wrappers the above to give us a generic way of evaluating users against UCs in the way that UCs tend to be applied to records.

The script include below gives us a reusable utility to do this in the two primary ways we found UCs to be applied to a record:

  • A glidelist directly on the record
  • A related list with references to the record and to UC’s


UserCriteriaLoader.userMatches() requires the user’s sys_user record sys_id and an array of UC record sys_ids. The ‘trick’ to the utility is converting either the glidelist or a list of related records into that array of UC record sys_ids. Let’s look at some specific examples of how to use each function.

//'UserCriteriaLoader.userMatches()' Sample Usage
var gr = new GlideRecord("my_custom_table"); //Table name of record for which are evaluating access
gr.get(""); //sys_id of record for which you are evaluating access
var user_can_read = new UserCriteria(gs.getUserID(), gr).evalUCGlideList("my_can_read", "my_cannot_read");

The above would return true/false depending on the user’s inclusion in the can/cannot read UCs. A couple of things to note:

  • The cannot read parameter on the function is optional.  If not included it is assumed that the user is NOT part of any cannot read UCs.
  • If given a cannot read parameter, the function will take it into account by determining if the given user is part of the cannot read UC(s) and if they are returning false as the ‘answer’ as being included in any can read UCs would no longer matter for functionality to be consistent with the way OOB applications us UCs.
  • The above is somewhat contrived as one of the biggest use cases is where you’d already HAVE a glide record to work with as part of the platform (BRs & ACLs mainly).

Usage when the UCs are associated via a related list is a bit more complicated….but just because data model is a more complicated. The overall approach is the same in that we want to build an array of UCs for the can read and cannot read related lists and feed them to base function. 

var gr = new GlideRecord("my_custom_table");
gr.get("");
var user_can_read = new UserCriteria(gs.getUserID(), gr).evalUCRelatedList("m2mCanTable", "canGrRefFieldName", "canUCRefFieldName", "m2mCannotTable", "cannotGrRefFieldName", "cannotUCRefFieldName");

//m2mCanTable: name of the table that contains the list of can read UCs related to the record in my_custom_table.
//canGrRefFieldName: reference to a record on my_custom_table.
//canUCRefFieldName: reference to a UC record
//m2mCannotTable: name of the table that contains the list of cannot read UCs related to the record in my_custom_table.
//cannotGrRefFieldName: reference to a record on my_custom_table
//cannotUCRefFieldName: reference to a UC record

Hopefully, this is helpful and gives the ServiceNow community an extra tool in their toolbox to solve their problems with the power of the ServiceNow platform!

NOTE:

  • UserCriteriaLoader is NOT documented on docs.servicenow.com or in developer.servicenow.com. However, if you search in OOB script includes you will see it being used. So a bit of ‘buyer beware’ but we believe it’s relatively safe to use….i.e. while not formally documented it seems unlikely that ServiceNow would deprecate the object/functions.
  • The script include given was factored for the use case/need we had at the time, it can certainly be reworked to fit your needs…but the building blocks are there for most use cases.

var UserCriteria = Class.create();
UserCriteria.prototype = {
    /**
    name: initialize
    description: Initializes the UserCriteria object by setting the user sys_id and the GlideRecord.
    param: {String} [userSysId] - sys_id of the user we want to check.
    param: {GlideRecord} [grToCheck] - GlideRecord of the record we want to check against.
    example:
    var matches = new UserCriteria(userSysID, grToCheck)
    returns: {UserCriteria} returns a initialized UserCriteria Object.
    */
    initialize: function(userSysId, grToCheck) {
        this.userSysId = userSysId;
        this.grToCheck = grToCheck;
    },

    /**
    name: evalUCGlideList
    description: Takes the field names on the table/GlideRecord that are GlideList(s) pointing too User Criteria for "Can" & "Cannot".
    param: {String} [ucCanField] - The field name of the GlideList for the "Can" User Criteria.
    param: {String} [ucCannotField] - The field name of the GlideList for the "Cannot" User Criteria.
    example:
    var matches = new UserCriteria(userSysID, grToCheck).evalUCGlideList("<can_read_uc_glidelist_field>", "<cannot_read_uc_glidelist_field>");
    returns: {boolean} Returns true if user passed in is part of any of the user criteria in the GlideList. 
    */
    evalUCGlideList: function(ucCanField, ucCannotField) {
        var ret = false;
        if (ucCannotField)
            ret = (!this._matchesUCListField(ucCannotField, false) && this._matchesUCListField(ucCanField, true));
        else
            ret = (this._matchesUCListField(ucCanField, true));

        return ret;

    },

    /**
    name: evalUCRelatedList
    description: Takes m2m tables for can and can't mappings, with ref field names of "connectors", that connects a record to User Criteria and returns true/false indicating a users access depending on related lists.
    param: {String} [m2mCanTable] - Name of the m2m table connecting the record with User Criteria 'can' access.
    param: {String} [canGrRefFieldName] - Name of the field on the m2m table that points to the GlideRecord (can access).
    param: {String} [canUCRefFieldName] - Name of the field on the m2m table that points to the User Criteria (can access).
    param: {String} [m2mCannotTable] - Name of the m2m table connecting the record with User Criteria for can't access
    param: {String} [cannotGrRefFieldName] - Name of the field on the m2m table that points to the GlideRecord (can't access).
    param: {String} [cannotUCRefFieldName] - Name of the field on the m2m table that points to the User Criteria (can't access).
    example:
    var matches = new UserCriteria(userSysID, grToCheck).evalUCRelatedList(m2mCanTable, canGrRefFieldName, canUCRefFieldName, m2mCannotTable, cannotGrRefFieldName, cannotUCRefFieldName)
    returns: {boolean} Returns true if user passed in is part of any of the user criteria in the GlideList. 
    */
    evalUCRelatedList: function(m2mCanTable, canGrRefFieldName, canUCRefFieldName, m2mCannotTable, cannotGrRefFieldName, cannotUCRefFieldName) {

        var ret = false;
        if (m2mCannotTable)
            ret = (!this._matchesUCListTable(m2mCannotTable, cannotGrRefFieldName, cannotUCRefFieldName, false) && this._matchesUCListTable(m2mCanTable, canGrRefFieldName, canUCRefFieldName, true));
        else
            ret = this._matchesUCListTable(m2mCanTable, canGrRefFieldName, canUCRefFieldName, true);
        return ret;

    },

    /**
    name: _matchesUCListField
    description: Takes the field name on the table/GlideRecord that is a GlideList pointing too User Criteria.
    param: {String} [ucField] - The field name of the GlideList for the User Criteria.
    param: {boolean} [emptyListReturn] - What to return in case the list of criteria is empty (false: for cannot read; true: for can read).

    returns: {boolean} Returns true if user passed in is part of any of the user criteria in the GlideList. 
    */
    _matchesUCListField: function(ucField, emptyListReturn) {

        var ucArray = [];
        var ucFieldValue = this.grToCheck.getValue(ucField);
        if (ucFieldValue != null)
            ucArray = ucFieldValue.split(",");

        return (ucFieldValue && !gs.isLoggedIn()) ? !emptyListReturn : this._matchesUCList(ucArray, emptyListReturn);

    },

    /**
    name: _matchesUCListTable
    description: Takes a m2m table, with ref field names of "connectors", that connects a record to User Criteria and returns true/false indicating if the user is in any of the User Criteria.
    param: {String} [m2mTable] - Name of the m2m table connecting the record with User Criteria
    param: {String} [grRefFieldName] - Name of the field on the m2m table that points to the GlideRecord.
    param: {String} [ucRefFieldName] - Name of the field on the m2m table that points to the User Criteria.
    param: {boolean} [emptyListReturn] - What to return in case the list of criteria is empty.
    
    returns: {boolean} Returns true if user passed in is part of any of the user criteria in the GlideList. 
    */
    _matchesUCListTable: function(m2mTable, grRefFieldName, ucRefFieldName, emptyListReturn) {

        //var ret = false;
        var ucArray = this._glideQuerytoArray(m2mTable, grRefFieldName, ucRefFieldName);
        return (ucArray.length > 0 && !gs.isLoggedIn()) ? !emptyListReturn : this._matchesUCList(ucArray, emptyListReturn);

    },


    /**
    name: individualUserCriteriaCheck
    description: Used to check the includance of a user in a single User Criteria. 
    param: {String} [uc] - Sys_id of a single User Criteria
    example:
    var matches = new UserCriteria(userSysID, grToCheck).individualUserCriteriaCheck("<uc_sys_id>")
    returns: {boolean} Returns true if user passed in is part of any of the user criteria. 
    */
    individualUserCriteriaCheck: function(uc) {

        var ucArrayofOne = [uc];
        return this._matchesUCList(ucArrayofOne, false);

    },

    /**
    name: _matchesUCList
    description: Makes call to OOB UserCriteriaLoader for evaluation of user/criteria and takes in to account empty user criteria.
    param: {Array} [ucArray] - Array of relevent User Criteria sys_ids.
    param: {boolean} [emptyListReturn] - what to return if User Criteria array is empty.

    returns: {boolean} boolean if user is in User Criteria passed.
    */
    _matchesUCList: function(ucArray, emptyListReturn) {

        var ret = false;
        if (this.isEmpty(ucArray))
            ret = emptyListReturn;
        else
            ret = sn_uc.UserCriteriaLoader.userMatches(this.userSysId, ucArray);
        return ret;

    },

    /**
    name: isEmpty
    description: Checks if the inpur array is empty or contains only inactive records.
    param: {Array} [ucArray] - Array of relevent User Criteria sys_ids.
    example:
    this.isEmpty(ucArray)
    returns: {boolean} True if array has no elements or if all associated records are inactive.
    */
    isEmpty: function(ucArray) {
        if (ucArray.length == 0) {
            return true;
        }
        var ucGr = new GlideRecord("user_criteria");
        ucGr.addActiveQuery();
        ucGr.addQuery("sys_id", "IN", ucArray);
        ucGr.setLimit(1);
        ucGr.query();
        return !ucGr.hasNext();
    },

    /**
    name: getAllUserCriteria
    description: Given a user sys_id returns the list of User Criteria that user is part of.
    example:
    new UserCriteria(userSysID, grToCheck).getAllUserCriteria()
    returns: {String} - List of User Criteria sys_id's that the user is part of.
    */
    getAllUserCriteria: function() {

        return sn_uc.UserCriteriaLoader.getAllUserCriteria(this.userSysId);

    },

    /**
    name: _glideQuerytoArray
    description: An internal/hidden function used to look up the related User Criteria records and build them in to an array.
    param: {String} [m2mTable] - Name of the m2m table connecting the record with User Criteria
    param: {String} [grRefFieldName] - Name of the field on the m2m table that points to the GlideRecord.
    param: {String} [ucRefFieldName] - Name of the field on the m2m table that points to the User Criteria.
    
    returns: {Array} An array of User Criteria that are related to the record via the m2m table.
    */
    _glideQuerytoArray: function(m2mTable, grRefFieldName, ucRefFieldName) {

        var ucArray = [];
        var grUC = new GlideRecord(m2mTable);
        grUC.addQuery(grRefFieldName, this.grToCheck.sys_id);
        grUC.query();
        while (grUC.next()) {
            ucArray.push(grUC.getValue(ucRefFieldName).toString());
        }

        return ucArray;
    },

    type: 'UserCriteria'
};

 

The post Leveraging User Criteria in your custom applications appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/scripting/leveraging-user-criteria-custom-applications/feed/ 6
Send a Knowledge Link when Attaching Knowledge https://servicenowguru.com/script-includes-scripting/attach-knowledge-link/ https://servicenowguru.com/script-includes-scripting/attach-knowledge-link/#comments Tue, 22 Jan 2013 17:09:08 +0000 https://servicenowguru.wpengine.com/?p=4735 If you’ve worked with ServiceNow much at all, you’re probably familiar with the capability provided to search knowledge from the incident form, and then attach the knowledge article back to the originating incident. This behavior is extremely useful and can be customized if needed. One complaint I’ve heard about this behavior before is that it

The post Send a Knowledge Link when Attaching Knowledge appeared first on ServiceNow Guru.

]]>
If you’ve worked with ServiceNow much at all, you’re probably familiar with the capability provided to search knowledge from the incident form, and then attach the knowledge article back to the originating incident. This behavior is extremely useful and can be customized if needed. One complaint I’ve heard about this behavior before is that it posts back the entire contents of the knowledge article into the ‘Comments’ field on the incident. The advantage to this setup is that the solution then gets sent directly out to the end user in their email, but it also comes with a disadvantage since the user never actually has to interact with the Knowledge Base or even know that they could have found their solution there.

One way to better promote knowledge use among your end user community (while still providing useful solutions) is to customize this default attach behavior to send a link to the knowledge article rather than the full text in the Incident ‘Comments’ field. In this article, I’ll show you how that can be done!

Attach Knowledge Link

While this solution works perfectly, it does require you to update or override an out-of-box script include. As such, you’ll want to make a note of the modification and evaluate it during upgrades.

 

You’ll only need to change a single record to make this modification. Navigate to ‘System Definition -> Script Includes’ in your left nav and open the ‘KnowledgeAjax’ script include. Look for the following chunk of code (which is responsible for returning the full KB article text to the incident form) and comment it out…

var s = "Knowledge article " + article.number + ":\n";
if (gs.getProperty("glide.ui.security.allow_codetag", "true") != "true")
s += article.short_description;
else {
var displayValue = new KnowledgeHelp(article).findDisplayValue();
s += "[code]" + displayValue + "[/code]";
}

Then, directly above or below that code, paste the following code and update the record…

//Return a link to the knowledge article instead of the full article text
var s = "Knowledge article " + article.number + ": " + article.short_description + "\n";
if (gs.getProperty("glide.ui.security.allow_codetag", "true") != "true")
s += gs.getProperty('glide.servlet.uri') + "kb_view.do?sysparm_article=" + article.number;
else {
s = "Knowledge article ";
s += '[code]<a style="color: blue;" href="' + gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=kb_view.do?sysparm_article=' + article.number + '">' + article.number + '</a>[/code]' + ": " + article.short_description + "\n";
}

 

Fuji and beyond!

Fuji instances introduce a dynamic search functionality that allows you to attach articles from a dynamic search component underneath the ‘Short Description’ field. For some reason, ServiceNow has changed the location of the attach code for the new components (even though the code is largely the same) to a script include named ‘cxs_Knowledge’. Unfortunately, they’ve also put a read-only protection policy on the script include so you can’t edit it! In order to get this to work in a Fuji instance, you’ll need to override the ‘cxs_Knowledge’ script include by creating an exact duplicate copy…same name, same code, etc. Then, follow the procedure described above to replace the code within your new ‘cxs_Knowledge’ script include.

If you’ve updated the record correctly, you should be able to see the results by testing the attach knowledge functionality from the incident screen.

The post Send a Knowledge Link when Attaching Knowledge appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/script-includes-scripting/attach-knowledge-link/feed/ 28
Modifying the Label of Form Fields With Client Scripts https://servicenowguru.com/ui-scripts-system-ui/modifying-label-form-fields-client-scripts/ https://servicenowguru.com/ui-scripts-system-ui/modifying-label-form-fields-client-scripts/#comments Mon, 10 Oct 2011 16:47:32 +0000 https://servicenowguru.wpengine.com/?p=4067 Please note that with the introduction of the Service Portal, many client-side scripting methods have been deprecated. The solution described in this article can now be accomplished using the ‘setLabel()’ method. To ensure that you are compliant with the latest functionality in ServiceNow such as the Service Portal, be sure to use the methods described

The post Modifying the Label of Form Fields With Client Scripts appeared first on ServiceNow Guru.

]]>
Please note that with the introduction of the Service Portal, many client-side scripting methods have been deprecated. The solution described in this article can now be accomplished using the ‘setLabel()‘ method. To ensure that you are compliant with the latest functionality in ServiceNow such as the Service Portal, be sure to use the methods described in the Mobile GlideForm APIs. Just make sure you set the ‘UI type’ field on the client script form to ‘Both‘.

Here’s a quick tip for a Monday. This post comes in response to a question on the ServiceNow forums asking if it is possible to change the label of a field dynamically based on some record criteria (such as record type). It is possible, and this post will show you how it can be done.
Dynamic Field Label

First, you should know that you can right-click any field and personalize the label for that field. For extended tables (such as incident) you can override the label for a higher-level table (such as task) just by changing the table name on the label record and doing an ‘Insert’ rather than a save. The question that I’ll address in this post is different than both of those scenarios though. What if you have a label that you want to change based on the user viewing the record? What if you need to change the label based on some criteria unique to that table (such as change type or incident priority)? In that case, you can’t simply modify the label record because you’ve got a narrower scope within that table that you need to work with.

The answer is client scripting. With a client script you can target any field on the form and modify its label. I’ve created a ‘changeFieldLabel’ function for this purpose. The function takes 4 possible parameters to allow for changing of the label text, color, and font weight.

Here’s an example that you could use in an ‘onLoad’ client script to change the ‘Description’ field label on a Change request form…

function onLoad() {
//Change the description label to 'My New Label' with bold red text
changeFieldLabel('description', 'My New Label', 'red', 'bold');
}

function changeFieldLabel(field, label, color, weight){
try{
var labelElement = $('label.' + g_form.getControl(field).id);
labelElement.select('.label-text').each(function(elmt) {
elmt.innerHTML = label + ':';
if(color)
elmt.style.color = color;
if(weight)
elmt.style.fontWeight = weight;
});
}catch(e){};
}

Of course, this is much more accessible if you include it in a global UI script. If you’re going to use this a lot I recommend setting up a global UI script with the following function…

function changeFieldLabel(field, label, color, weight){
try{
var labelElement = $('label.' + g_form.getControl(field).id);
labelElement.select('.label-text').each(function(elmt) {
elmt.innerHTML = label + ':';
if(color)
elmt.style.color = color;
if(weight)
elmt.style.fontWeight = weight;
});
}catch(e){};
}

Then you can invoke the function from any form with a single line.

If I wanted to change the label of the ‘description’ field to ‘My New Label’ I could do it like this…

changeFieldLabel('description', 'My New Label');

If I wanted to change the label to a bold green color I could do it like this…

changeFieldLabel('description', 'My New Label', 'green', 'bold');

Changing the label of a catalog variable

The principles described above can also be applied to catalog variables using catalog client scripts. The primary difference is in the way the elements need to be selected from the DOM. Here’s an example script…

function onLoad() {
//Change the description label to 'My New Label' with bold red text
changeFieldLabel('description', 'My New Label', 'red', 'bold');
}

function changeFieldLabel(field, label, color, weight){
try{
var labelElement = $('label_' + g_form.getControl(field).id);
labelElement.select('.sn-tooltip-basic').each(function(elmt) {
elmt.innerHTML = label;
if(color)
elmt.style.color = color;
if(weight)
elmt.style.fontWeight = weight;
});
}catch(e){};
}

The post Modifying the Label of Form Fields With Client Scripts appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/ui-scripts-system-ui/modifying-label-form-fields-client-scripts/feed/ 69
Advanced Reference Qualifier Using a Script Include https://servicenowguru.com/script-includes-scripting/advanced-reference-qualifier-script-include/ https://servicenowguru.com/script-includes-scripting/advanced-reference-qualifier-script-include/#comments Tue, 21 Jun 2011 16:22:57 +0000 https://servicenowguru.wpengine.com/?p=3751 Reference qualifiers are a powerful tool that every ServiceNow administrator and consultant should have in their tool belt. They allow you to dynamically filter the available options from a reference field. The ServiceNow wiki has some good documentation on this topic so I won’t re-hash that here. What I do want to address is the

The post Advanced Reference Qualifier Using a Script Include appeared first on ServiceNow Guru.

]]>
Reference qualifiers are a powerful tool that every ServiceNow administrator and consultant should have in their tool belt. They allow you to dynamically filter the available options from a reference field. The ServiceNow wiki has some good documentation on this topic so I won’t re-hash that here. What I do want to address is the topic of Advanced Reference Qualifiers…specifically how to leverage a Script Include instead of a global Business Rule to run your qualifier script.

Reference Qualifier Script Include

Up until recently, the only way to get Advanced Reference Qualifiers to work was to create a global business rule to run the qualifier script. This works great, but it results in a global script that gets loaded all the time when it really isn’t necessary. While this isn’t something that causes any real problems in practice, it could cause performance issues if used to the extreme. The best practice guidance for advanced reference qualifiers should be to use a Script Include rather than a global Business Rule to run the qualifier script. Using a Script Include means that the script only gets loaded and used when you actually need to use it! In this article I’ll show you how this can be done using a common example of filtering the ‘Assignment group’ to display only groups for the ‘Assigned to’ value.

The first piece is the ‘Reference qual’ field value on the dictionary entry of the reference field (Assignment group in this case). The ‘javascript:’ prefix is the same, but you need to reference your Script Include function instead of the business rule function. In this case, I’m using a Script Include named ‘u_backfillAssignmentGroup’. Since these scripts can potentially interfere with each other, it’s best to prefix any of your custom scripts with ‘u_’ or something similar in order to distinguish them from any that ServiceNow may introduce in the future.

javascript:u_backfillAssignmentGroup();

The other piece is obviously the Script Include. Since Script Includes now allow you to use On-Demand functions, your script is identical to the one you would use in a global business rule.

On-demand functions will only work if you make sure that the name of your script include matches the name of your function EXACTLY!
function u_backfillAssignmentGroup() {
   var gp = ' ';
   var a = current.assigned_to;
   
   //return everything if the assigned_to value is empty
   if(!a)
      return;
   
   //sys_user_grmember has the user to group relationship
   var grp = new GlideRecord('sys_user_grmember');
   grp.addQuery('user',a);
   grp.query();
   while(grp.next()) {
      if (gp.length > 0) {
         //build a comma separated string of groups if there is more than one
         gp += (',' + grp.group);
      }
      else {
         gp = grp.group;
      }
   }
   // return Groups where assigned to is in those groups we use IN for lists
   return 'sys_idIN' + gp;
}

The post Advanced Reference Qualifier Using a Script Include appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/script-includes-scripting/advanced-reference-qualifier-script-include/feed/ 20
Prevent Circular Relationships in ServiceNow https://servicenowguru.com/business-rules-scripting/prevent-circular-relationships-servicenow/ https://servicenowguru.com/business-rules-scripting/prevent-circular-relationships-servicenow/#comments Wed, 16 Mar 2011 12:52:59 +0000 https://servicenowguru.wpengine.com/?p=3457 Almost any database will have situations where a record in a table can relate back to other records in that same table. This happens in ServiceNow in several places and it’s common enough that you may find yourself building additional functionality that works this way. The classic case is the ‘Parent’ field. A good example

The post Prevent Circular Relationships in ServiceNow appeared first on ServiceNow Guru.

]]>
Almost any database will have situations where a record in a table can relate back to other records in that same table. This happens in ServiceNow in several places and it’s common enough that you may find yourself building additional functionality that works this way.
The classic case is the ‘Parent’ field. A good example is the ‘Parent’ field on the ‘Location (cmn_location)’ table. Each location record in ServiceNow can be related to a ‘Parent’ location. This allows you to build a nice hierarchy of locations within your Service-now instance. Unless you work in one of those very rare places that implements a completely flat location structure, this parent-child relationship is critical for representing locations in your system.

This same type of setup is used in other places (such as task tables) where a given record can result in the generation of another record of the same type. You may have scenarios where a change request can be the parent of other change request or where a major incident becomes the parent of other child incidents. In this post, I’ll address the problem of circular relationships that can exist when you’re working with parent-child relationships in ServiceNow.

In order to represent a parent-child relationship, you simply have to set up a reference field (or use an existing one) on the table of your choice. The thing you really have to be careful of with these fields is that they also allow users to relate a record to itself, making itself its own parent! While that scenario seems like it would be obvious to avoid, it’s not so easy to see situations where you might be making a record its own parent a few levels up. You’ll encounter problems when you need to query the parent hierarchy to get a list of the parents of a particular record. What ends up happening if a record has been made a parent of itself at some level is that your query runs in an endless loop and you end up killing the performance of your system or potentially making it completely unusable.
In order to prevent this type of problem, I’ve created the following ‘Prevent circular relationship’ script that you can use in a business rule for any table/field combination where you might have this problem in your system. It’s very simple to use. Simply construct the business rule as shown below and indicate the name of the field to check for circular relationships.

‘Prevent circular relationship’ business rule
Name: Prevent circular relationship
When: Before
Insert/Update: True
Condition: current.parent.changes() && !current.parent.nil()
Script:

(function executeRule(current, previous /*null when async*/) {	
	var fieldName = 'parent'; //Specify the name of the field to check for circular relationship on
	var firstParent = current[fieldName].getRefRecord();
	
	//If we have a valid record in the parent field check circular relationships
	if(firstParent && firstParent.isValidRecord()){
		checkCircularRelationship(firstParent, current, fieldName);
	}	
})(current, previous);


function checkCircularRelationship(parentRec, child, fieldName){
	var depth = 0;
	//Safety check to prevent endless loop in the event that a circular relationship already exists
	//Increase the current depth
	depth++;
	//If we have gone more than the allowed depth then abort
	if(depth > 20){
		current.setAbortAction(true);
		gs.addInfoMessage('Possible circular relationship already exists.  Aborting check to prevent endless loop.');
		return null;
	}
	
	//If the current parent being evaluated matches the child then abort
	if(parentRec.sys_id == child.sys_id){
		//Abort the submission
		gs.addErrorMessage('Relating to the chosen parent value creates a circular relationship.<br/>  Please choose a different parent.');
		current.setAbortAction(true);
	}
	else{
		//Check for next parent
		var nextParent = parentRec[fieldName].getRefRecord();
		if(nextParent && nextParent.isValidRecord()){
			//Test the next level
			checkCircularRelationship(nextParent, child, fieldName);
		}
	}
}

The post Prevent Circular Relationships in ServiceNow appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/business-rules-scripting/prevent-circular-relationships-servicenow/feed/ 9
Advanced ‘getMyGroups’ Function https://servicenowguru.com/system-definition/advanced-getmygroups-function/ https://servicenowguru.com/system-definition/advanced-getmygroups-function/#comments Tue, 04 May 2010 15:40:06 +0000 https://servicenowguru.wpengine.com/?p=1672 If you’ve taken a look at the ‘My Groups Work’ module (or maybe a particular security rule or report) you may have noticed that the condition or filter for that record makes a function call to return a list of values to compare against the value in the given field. In the case of the

The post Advanced ‘getMyGroups’ Function appeared first on ServiceNow Guru.

]]>
If you’ve taken a look at the ‘My Groups Work’ module (or maybe a particular security rule or report) you may have noticed that the condition or filter for that record makes a function call to return a list of values to compare against the value in the given field. In the case of the ‘My Groups Work’ module under the ‘Service Desk’ application there is a function called ‘getMyGroups’ that is used to identify task records where the assignment group value is one of the groups for which the current user is a member.

The ‘getMyGroups’ function simply returns an array of group sys_id values for the groups that a user belongs to. I saw a forum posting recently that pointed out (correctly) that the ‘getMyGroups’ function only returns group membership, but doesn’t return groups where the user is listed as the manager. The function also doesn’t attempt to return child groups of the groups where a person is a manager or group member. So, if I am the Director of Operations and I want to see a list of all tasks for the Operations group (which I am a manager of) as well as the sub-groups of that group, I would have to be added specifically to each sub-group to have all of the groups that I am interested in tracking be displayed in the list.

With some help from John Andersen, I’ve created the ‘Advanced getMyGroups’ function. This function is designed to give users a better way to display group membership information. It is used in the same way that you would use the ‘getMyGroups’ function, but also includes an optional parameter that allows you to return groups managed by an individual and/or sub-groups of a given group where I am a member or manager. The ‘maxDepth’ parameter returns the values as shown below…

  • No maxDepth returns groups where user is a group member (Same as current ‘getMyGroups’ function)
  • maxDepth of 0 returns groups where user is a group member or a manager
  • maxDepth greater than 0 returns groups where user is a group member or a manager PLUS all child groups up to ‘maxDepth’ levels deep

The most common usage of this function is probably in a filter on a module or a report. So, if I were creating a module to show all tickets where I am a member of the group OR where I am the group manager, PLUS all of the sub-groups up to 5 levels deep I could use the following in my filter…

‘javascript:getMyGroupsAdvanced(5)’

In order to use the function, you need to set up a new script include. The script include must also be marked as ‘Client callable’ so that you can call the function from filter/condition builders. The settings for the script include are shown here…

getMyGroupsAdvanced Script Include
Name: getMyGroupsAdvanced
Client callable: True
Script:

//Maximum number of levels to search for groups.
//No maxDepth returns groups where user is a group member (Same as 'getMyGroups' function)
//maxDepth of 0 returns groups where user is a group member or a manager
//maxDepth greater than 0 returns groups where user is a group member or a manager PLUS all child groups up to 'maxDepth' levels deep
var maxDepth;var groupArr = [];
var finalArr = [];

function getMyGroupsAdvanced(inputDepth){
//Set maxDepth to the given depth
maxDepth = inputDepth;
//Get the sys_id of the current user
var uID = gs.getUserID();

//Get all active groups where user is a member
var grmember = new GlideRecord('sys_user_grmember');
grmember.addQuery('user', uID);
grmember.addQuery('group.active', true);
grmember.query();
while(grmember.next()){
//Push the group sys_id values into the group array
groupArr.push(grmember.group.toString());
}

//If a maxDepth value is given then include groups where user is a manager
if(maxDepth >= 0){
//Get all active groups where user is a manager
var grman = new GlideRecord('sys_user_group');
grman.addQuery('manager', uID);
grman.addQuery('active', true);
grman.query();
while(grman.next()){
//Push the group sys_id values into the group array
groupArr.push(grman.sys_id.toString());
}
}

//Remove any duplicates from group string
groupArr = checkDuplicates(groupArr);

//If maxDepth > 0 then check for child groups
if(maxDepth > 0){
//Iterate through all of the groups and return all children for each group
for(var x in groupArr){
//Only process if group sys_id is not already in the returned array
if(finalArr.length == 0 || finalArr.join().indexOf(groupArr[x]) == -1){
finalArr.push(groupArr[x]);
}
recursChildGroups(groupArr[x], 0);
}
}

//If we didn't check for child groups then just return the group array
else{
finalArr = groupArr;
}

//Return the array of group IDs
return finalArr;
}

function recursChildGroups(group, depth){
//Increase the current depth
depth++;
//If we have gone more than the allowed depth then abort for the given group
if(depth > maxDepth){
//('Possible recursive group loop with group ' + group);
return null;
}
//Make sure that we have a valid group ID
if(group){
if(group.toString().length == 0){
return null;
}
//Query for the active child groups of this group
var rec = new GlideRecord('sys_user_group');
rec.addQuery('parent', group);
rec.addQuery('active', true);
rec.query();
while(rec.next()){
//If the group has already been added then do not add again
if(finalArr.join().indexOf(rec.sys_id.toString()) > -1){
continue;
}
//Add the group to the final array
finalArr.push(rec.sys_id.toString());
//Find the child groups of this group
recursChildGroups(rec.sys_id.toString(),depth);
}
}
return null;
}

function checkDuplicates(a){
//Check all values in the incoming array and eliminate any duplicates
var r = [];
o:for(var i = 0, n = a.length; i < n; i++){
for(var x = 0, y = r.length; x < y; x++){
if(r[x]==a[i]){
continue o;
}
}
r[r.length] = a[i];
}
return r;
}

The post Advanced ‘getMyGroups’ Function appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/system-definition/advanced-getmygroups-function/feed/ 33
Walking the ServiceNow CMDB Relationship Tree https://servicenowguru.com/cmdb/walking-servicenowcom-cmdb-relationship-tree/ https://servicenowguru.com/cmdb/walking-servicenowcom-cmdb-relationship-tree/#comments Wed, 06 Jan 2010 20:08:52 +0000 https://servicenowguru.wpengine.com/?p=521 One of the great features of Service-now.com is its CMDB and relational mapping. You can easily set up relationships between any CIs in the system. Once the relationships are defined, it becomes very simple to pull up a visual representation of a CI and its dependencies by using Service-now BSM maps. Using this feature allows an end user to look at that CI and identify what else in the environment is impacted by an outage or a change to that CI.

The post Walking the ServiceNow CMDB Relationship Tree appeared first on ServiceNow Guru.

]]>
Special thanks to Lyle Taylor for identifying…and fixing…a performance issue with this script that may occur with VERY large data sets! Most environments won’t be dealing with a CMDB large enough to have this be an issue, but it’s probably a good idea to update your CIUtils2 script include with the code below anyway.

 

One of the great features of ServiceNow is its CMDB and relational mapping. You can easily set up relationships between any CIs in the system. Once the relationships are defined, it becomes very simple to pull up a visual representation of a CI and its dependencies by using ServiceNow BSM maps.  Using this feature allows an end user to look at that CI and identify what else in the environment is impacted by an outage or a change to that CI.

While this works fine for most situations, it may be necessary to be able to use this relationship information outside of the map view.  What if you wanted to determine all of the Business Services impacted by a particular change request? What if you wanted to pull an approval group from each impacted Business Service to approve a specific step in your change workflow?

The ‘CIUtils’ Script Include is designed to help you get some of this information. You can find it by navigating to ‘System Definition -> Script Includes’.  To use it, you could do something like this in a Business rule, UI action, or Scheduled job…

var ciu = new CIUtils();
//Return all of the Business services affected by the current CI on this ticket
var services = ciu.servicesAffectedByCI(current.cmdb_ci);
//Do something with the array of Business Services returned

You can also use the ‘servicesAffectedByTask’ function to use a task record as your input parameter

var ciu = new CIUtils();
//Return all of the Business services affected by the current CI on this ticket
var services = ciu.servicesAffectedByTask(current);
//Do something with the array of Business Services returned

There are a couple of limitations with the CIUtils Script include though. To fill in some of those gaps, I recently created an enhancement to CIUtils called ‘CIUtils2’. It is used in pretty much the same way as the CIUtils Script include, but it gives you these added benefits…

  • Return all impacted Business Services based on the ‘cmdb_ci’ field AND the ‘Affected CIs’ related list
  • Return all impacted CIs based on the CI classes you specify
  • Return all impacted CIs throughout the entire CMDB

To use it, simply create a new Script include record with the script below WITH A NAME OF ‘CIUtils2’. You can then call the functions like this…

Usage:
var ciu = new CIUtils2();
//cisAffectedByTask takes a Task glideRecord as input and returns an array of CI sys_ids
var services = ciu.cisAffectedByTask(TaskGlideRecord); //Returns an array of impacted Business Service CIs
var services = ciu.cisAffectedByTask(TaskGlideRecord, [“cmdb_ci_service”, “cmdb_ci_web_server”]); //Returns an array of impacted CIs from the CI classes specified
var services = ciu.cisAffectedByTask(TaskGlideRecord, [“ALL”]); //Returns an array of ALL impacted CIs//cisAffectedByCI takes a CI glideRecord as input and returns an array of CI sys_ids
var services = ciu.cisAffectedByCI(current.cmdb_ci); //Returns an array of impacted Business Service CIs
var services = ciu.cisAffectedByCI(current.cmdb_ci, [“cmdb_ci_service”, “cmdb_ci_web_server”]); //Returns an array of impacted CIs from the CI classes specified
var services = ciu.cisAffectedByCI(current.cmdb_ci, [“ALL”]); //Returns an array of ALL impacted CIs. Use this with caution!!!//getCIXML takes a CI sys_id as input and returns an XML-formatted string showing all CIs impacted by an outage to the CI given
var myXML = ciu.getCIXML(current.cmdb_ci);Script:

gs.include('PrototypeServer');var CIUtils2 = Class.create();

CIUtils2.prototype = {
initialize: function () {
this.maxDepth = gs.getProperty('glide.relationship.max_depth', 10); // how deep to look
this.maxAffectedCIs = gs.getProperty('glide.relationship.threshold', 1000); // max records to return
this.defaultClasses = ['cmdb_ci_service', 'service_offering'];
this.arutil = new ArrayUtil();
},

/**
* Determine which CIs are affected by a specific CI
*
* Inputs:
* id is the sys_id of a configuration item (cmdb_ci)
* classArr is an array of CI class names that should be returned
* infoObj is an object created by _getInfoObj used to track data accross multiple
* calls to the function made by cisAffectedByTask. It is not needed when calling
* the function directly.
*
* Returns:
* an array of sys_id values for cmdb_ci records upstream of
* (or affected by) the input item
*/

cisAffectedByCI: function (id, classArr, infoObj /*optional*/) {
if (!infoObj) {
infoObj = this._getInfoObj();
}
if (infoObj.visitedCIs[id]) {
// We've already processed this CI
return [];
}
infoObj.visitedCIs[id] = true;
if (!classArr || classArr.length == 0) {
classArr = this.defaultClasses;
}

// This is to keep track of affected CIs from this CI only.
// CIs that are already listed in infoObj.affectedCIs from prior
// calls to the function will not be included.
var affectedCIs = [];
var ci = new GlideRecord('cmdb_ci');
if (ci.get(id)) {
//If class = 'ALL' then just add the CI
if (classArr[0] == 'ALL' || this.arutil.contains(classArr, ci.sys_class_name.toString())) {
affectedCIs.push(id);
this._addCI(id, infoObj);
}
this._addParentCIs(id, infoObj, affectedCIs, 1, classArr);
}
return this._unique(affectedCIs); // list of affected CIs
},

/**
* Determine which CIs are affected by a task
*
* Inputs:
* task is a task GlideRecord (e.g., incident, change_request, problem)
* classArr is an array of CI class names that should be returned
*
* Returns:
* an array of sys_id values for cmdb_ci records upstream from
* (or affected by) the configuration item referenced by the task's cmdb_ci field and Affected CIs list
*/

cisAffectedByTask: function (task, classArr) {
var infoObj = this._getInfoObj();
//Find the impacted CIs for the 'cmdb_ci' value
var id = task.cmdb_ci.toString();
if (id) {
this.cisAffectedByCI(id, classArr, infoObj);
}

//Find the impacted CIs for any Affected CIs listed on the task
var affCI = new GlideRecord('task_ci');
affCI.addQuery('task', task.sys_id);
affCI.query();
while (affCI.next()) {
this.cisAffectedByCI(affCI.ci_item.sys_id.toString(), classArr, infoObj);
}
return this._objToArray(infoObj.affectedCIs);
},

/**
* Returns an XML-formatted string showing all CIs impacted by an outage to the CI given
*
* Inputs:
* id is the sys_id of the root CI
*
* Returns:
* an XML-formatted string containing cmdb_ci records downstream of
* (or affected by) the configuration item provided as input
*/

getCIXML: function (id) {
var gr = new GlideRecord('cmdb_rel_ci');
gr.addQuery('child', id);
gr.query();
gr.next();
var str = '';
str += '';
str += '' + gr.child.sys_id + '';
str += '' + gr.child.name + '';
str += 'SELF';
ret = this._recurs(id);
if (ret) {
str += '';
str += ret;
str += '';
}
str += '';
return str;
},

_recurs: function (ci) {
var gr = new GlideRecord('cmdb_rel_ci');
gr.addQuery('child', ci);
gr.query();
var str = '';
while (gr.next()) {
str += '';
str += '' + gr.parent.sys_id + '';
str += '' + gr.parent.name + '';
str += '' + gr.type.name + '';
ret = this._recurs(gr.parent.sys_id);
if (ret) {
str += '';
str += ret;
str += '';
}
str += '';
}
return str;
},

_addParentCIs: function (id, infoObj, affectedCIs, currentDepth, classArr) {
if (infoObj.affectedCIsCount >= this.maxAffectedCIs)
return;

var rel = new GlideRecord('cmdb_rel_ci');
rel.addQuery('child', id);
rel.query();

var parents = [];
while (rel.next()) {
parents.push(rel.parent.toString());
}
if (parents.length) {
var parent = new GlideRecord('cmdb_ci');
parent.addQuery('sys_id', parents);
parent.query();

while (parent.next() && infoObj.affectedCIsCount < this.maxAffectedCIs) {
var pid = parent.sys_id.toString();
if (!infoObj.visitedCIs[pid]) {
infoObj.visitedCIs[pid] = true;
if (classArr[0] == 'ALL' || this.arutil.contains(classArr, parent.sys_class_name.toString())) {
affectedCIs.push(pid);
this._addCI(pid, infoObj);
}
if (currentDepth < this.maxDepth)
this._addParentCIs(pid, infoObj, affectedCIs, currentDepth + 1, classArr);
}
}
}
},

_addCI: function (id, infoObj) {
infoObj.affectedCIs[id] = true;
infoObj.affectedCIsCount++;
},

_getInfoObj: function () {
return {
affectedCIsCount: 0, // track how many added, since can't get size() for an Object
affectedCIs: {}, // full list of affected CIs for specified classes
visitedCIs: {} // track CIs already iterated over
};
},

_objToArray: function (obj) {
var ar = [];
for (var id in obj) {
ar.push(id);
}
return ar;
},

_unique: function (a) {
var obj = {};
for (var idx in a) {
obj[a[idx]] = true;
}
return this._objToArray(obj);
},

type: 'CIUtils2'
};

 

Another practical example for Change Management

Here’s an example UI action that I use for almost all of my clients. In change management, you’re often interested in the business services that will be impacted by a given change. While you can see this in a BSM map, it’s often useful to see this in a related list directly on your change form as well. By adding the ‘Impacted Services’ related list to your change form, you can populate this data. This UI action gathers all of the information about the CIs on your change request and adds the impacted services to the list.

‘Refresh Impacted Services’ UI Action
Name: Refresh Impacted Services
Table: Change request
Action name: refresh_impacted_services
Form context menu: True
Condition: gs.hasRole(‘itil’)
Script:

current.update();
action.setRedirectURL(current);
removeAffectedServices();
addAffectedServices();function removeAffectedServices() {
var m2m = new GlideRecord('task_cmdb_ci_service');
m2m.addQuery('task',current.sys_id);
m2m.addQuery('manually_added','false');
m2m.query();
m2m.deleteMultiple();
}function addAffectedServices() {
var ciu = new CIUtils2();
//Find all impacted business services
var services = ciu.cisAffectedByTask(current);
//var services = ciu.cisAffectedByTask(current, ["cmdb_ci_service", "cmdb_ci_windows_server"]);
//var services = ciu.cisAffectedByTask(current, ["ALL"]);
var m2m = new GlideRecord('task_cmdb_ci_service');
for (var i = 0; i < services.length; i++) {
m2m.initialize();
m2m.task = current.sys_id;
m2m.cmdb_ci_service = services[i];
m2m.manually_added = 'false';
m2m.insert();
}
}

The post Walking the ServiceNow CMDB Relationship Tree appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/cmdb/walking-servicenowcom-cmdb-relationship-tree/feed/ 46