beingfluid
Kilo Sage
Kilo Sage

Recently, we had the requirement to create the default CSDM Lifecycle mappings for all CI classes. There are more than 713 OOTB CI classes on fresh PDI. Which means there is no way we would do it manually, so we did create this fix script. The Script creates the mapping (same as the parent table) based on existing or default (OOTB) mappings. In order to create the mappings for all the child classes you need to provide the base class name. E.g. providing "cmdb_ci" as base class will create the mappings for all CMDB classes of it, if not already exist. This can be further optimized, but I preferred to post it as is. Any suggestions or improvements are welcomed, We rely on your feedback.

(NOTE: if your instance have custom choices create the mappings for them manually for base class, prior to running this script)

//To get an existing mappings
var grLCM = new GlideAggregate("life_cycle_mapping");
grLCM.groupBy("table");
grLCM.addAggregate("COUNT");
grLCM.query();
var existingMappedRecords = [];
while (grLCM.next()) {
    existingMappedRecords.push(grLCM.getDisplayValue("table"));
}

//Provide the BASE_CLASS_NAME as parameter here
var table = new TableUtils("cmdb_ci");
// var table = new TableUtils("ast_contract");
// var table = new TableUtils("alm_asset");
// var table = new TableUtils("cmdb_model");
var ciTables = table.getAllExtensions();

gs.include("j2js");
var ciTablesArr = j2js(ciTables);

for (var i = 0; i < ciTablesArr.length; i++) {
    if (existingMappedRecords.indexOf(String(ciTablesArr[i])) === -1) {
        //CI classes not having mappings - Get closest parent class having mappings
        var parentMapClass = getMappingsClassParent(ciTablesArr[i]);

        //Apply Parent mappings to child class
        setMappings(ciTablesArr[i], parentMapClass);
    }
}

//Script for Mappings
function setMappings(childToApplyMap, parentToFetchMap) {
    //Get parent mappings
    try {

        var rParentMap = new sn_ws.RESTMessageV2();
        rParentMap.setEndpoint('https://' + gs.getProperty('instance_name') + '.service-now.com/api/now/table/life_cycle_mapping?sysparm_query=active%3Dtrue%5Etable%3D' + String(parentToFetchMap) + '&sysparm_display_value=false&sysparm_exclude_reference_link=true&sysparm_fields=active%2Clegacy_field_name%2Clegacy_field_value%2Clegacy_field_and_value%2Clegacy_subfield_name%2Clegacy_subfield_value%2Clegacy_subfield_and_value%2Clife_cycle_control%2Cpriority%2Clegacy_field_and_value%2Clegacy_subfield_and_value');
        rParentMap.setHttpMethod('GET');

        //Eg. UserName="admin", Password="admin" for this code sample.
        var user = 'admin';
        var password = 'admin_password';

        rParentMap.setBasicAuth(user, password);
        rParentMap.setRequestHeader("Accept", "application/json");

        var responseParentMap = rParentMap.execute();

        var httpParentMapStatus = responseParentMap.getStatusCode();

        if (httpParentMapStatus === 200) {
            var responseParentMapBody = responseParentMap.getBody();
            var parentMapObjResult = JSON.parse(responseParentMapBody);
            var parentMapObj = parentMapObjResult['result'];

            //set child mappings
            parentMapObj.forEach(function(singleObjParent) {
                singleObjParent["table"] = String(childToApplyMap);

                try {


                    var grLCMCreate = new GlideRecord("life_cycle_mapping");
                    grLCMCreate.newRecord();
                    grLCMCreate.setValue('legacy_field_name', singleObjParent["legacy_field_name"]);
                    grLCMCreate.setValue('legacy_field_value', singleObjParent["legacy_field_value"]);
                    grLCMCreate.setValue('legacy_field_and_value', singleObjParent["legacy_field_and_value"]);

                    if (singleObjParent.hasOwnProperty('legacy_subfield_name')) {
                        grLCMCreate.setValue('legacy_subfield_name', singleObjParent["legacy_subfield_name"]);
                        grLCMCreate.setValue('legacy_subfield_value', singleObjParent["legacy_subfield_value"]);
                        grLCMCreate.setValue('legacy_subfield_and_value', singleObjParent["legacy_subfield_and_value"]);
                    }

                    grLCMCreate.setValue('active', 'true');
                    grLCMCreate.setValue('life_cycle_control', singleObjParent["life_cycle_control"]);
                    grLCMCreate.setValue('priority', singleObjParent["priority"]);
                    grLCMCreate.setValue('table', String(childToApplyMap));
                    grLCMCreate.update();


                } catch (err) {
                    gs.info("Error while creating child map for table: " + String(childToApplyMap) + " -> " + err.message);
                }

            });

        }
    } catch (e) {
        gs.info("Error while fetching parent map for table: " + String(parentToFetchMap) + " -> " + e.message);
    }

}

//to remove empty keys from object
function replacer(key, value) {
    if (value === "") {
        return undefined;
    }
    return value;
}

function getMappingsClassParent(orginalCIClass) {

    // Get the hierarchy of tables
    var tableParent = new TableUtils(orginalCIClass);
    var tableParentArrayList = tableParent.getTables();

    var tableParentArray = j2js(tableParentArrayList);

    var j = 1;
    while (j < tableParentArray.length) {
        //Check if parrent have mappings & if  yes, return parent class

        var grCheckLCMofParent = new GlideAggregate("life_cycle_mapping");
        grCheckLCMofParent.addEncodedQuery("table=" + String(tableParentArray[j]));
        grCheckLCMofParent.addAggregate("COUNT");
        grCheckLCMofParent.query();
        var cnt = 0;
        if (grCheckLCMofParent.next()) {
            cnt = grCheckLCMofParent.getAggregate("COUNT");
        }
        if (cnt > 0) {

            return String(tableParentArray[j]);
        }

        j++;
    }

}
Comments
Maik Skoddow
Tera Patron
Tera Patron

Hi

good idea!

some hints, thoughts & questions of mine:

  1. Why do need an REST API request to just query table life_cycle_mapping? Makes no sense to me.
  2. In case method "getMappingsClassParent" cannot find anything it returns nothing which is not handled properly in the calling method. This can cause exceptions and unexpected behavior.
  3. At method "getMappingsClassParent" why do you need an GlideAggregate for just checking whether your query has at least one record? 
    Better approach (inklusive "null" returned value in case nothing is found)
    var grCheckLCMofParent = new GlideRecord("life_cycle_mapping");
    
    grCheckLCMofParent.addEncodedQuery("table=" + String(tableParentArray[j]));
    grCheckLCMofParent.setLimit(1);
    grCheckLCMofParent.query();
    
    return grCheckLCMofParent.next() ? String(tableParentArray[j]) : null;

Maik

beingfluid
Kilo Sage
Kilo Sage
Hi Maik, Thanks for the response. 1) Initially my plan was to get the json using API and just to add table property to it, to create child mappings. And then call POST message to create the the record. It was easier than scripting it. And less prone to error. But unfortunately POST method does not set the value for legacy field value field. SO We went with Gliderecord instead of post. I did not remove the GET method but it can be improved as you said. Now it is not much sensible to use rest here as the initial logic changed. 2) we have ootb mappings for base tables e.g. cmdb_ci, so there will always be mappings for atleast cmdb_ci if nothing is found. So no error handling is needed, it will always find the mapping. 3) I am glad you did ask. It is just my personal preference to use GlideAggregate over Gliderecord, for different reason. but it is substituteable. Thank you.
Version history
Last update:
‎08-01-2022 10:17 PM
Updated by: