Name

sn_cmdb_int_util.ApplicationValidationUtil

Description

Utility class to verify RTE entity based configurations follow conventions enforced by the Integration Hub ETL UI

Script

/*
Utility to validate that a new CMDB Application is valid and loadable by the
Integration Studio ETL UI.  Tests for a variety of correctness and 
convention based rules.

validateAllApplications : validates all applications found under cmdb_inst_application
validateApplicationFeed(cmdb_inst_application_feed.sys_id) : validates a single application feed given a sys_id
*/

var ApplicationValidationUtil = Class.create();
ApplicationValidationUtil.prototype = {
  initialize: function() {},

  validateAllApplications: function() {

      var result = {};
      result.errors = [];

      var applicationsGr = new GlideRecord('cmdb_inst_application');
      applicationsGr.query();

      while (applicationsGr.next()) {
          result.errors = result.errors.concat(this._validateApplication(applicationsGr));
      }

      if (!result.errors)
          result.status = "Valid";
      else
          result.status = "Error";

      return result;

  },

  validateApplicationFeed: function(cmdbInstApplicationFeedSysId) {

      var result = {};
      result.errors = [];

      var applicationsGr = new GlideRecord('cmdb_inst_application_feed');
      applicationsGr.addQuery('sys_id', cmdbInstApplicationFeedSysId);
      applicationsGr.query();
      applicationsGr.next();

      result.errors = result.errors.concat(this._validateApplicationFeed(applicationsGr));

      if (result.errors.length == 0)
          result.status = "Valid";
      else
          result.status = "Error";

      return result;

  },

  _validateApplication: function(applicationGr) {
      var errors = [];

      var applicationName = applicationGr.getValue('name');
      var applicationSysId = applicationGr.getUniqueValue();
      var applicationDisplay = this._buildDisplay('cmdb_inst_application', applicationSysId, applicationName);

      if (!applicationGr.getValue('discovery_source'))
          errors.push(applicationDisplay + "No discovery_source found.  Please select one.");

      var applicationFeedGr = new GlideRecord('cmdb_inst_application_feed');

      applicationFeedGr.addQuery('cmdb_inst_application', applicationGr.getUniqueValue());
      applicationFeedGr.query();

      while (applicationFeedGr.next()) {
          var feedSysId = applicationFeedGr.getUniqueValue();
          var feedName = applicationFeedGr.getValue('name');

          var feedErrors = this._validateApplicationFeed(applicationFeedGr);

          for (var i = 0; i < feedErrors.length; i++)
              feedErrors[i] = 'cmdb_inst_application_feed:(' + feedSysId + "," + feedName + ") : " + feedErrors[i];

          errors = errors.concat(feedErrors);
      }

      return errors;
  },

  _validateApplicationFeed: function(applicationFeedGr) {
      var errors = [];

      var applicationFeedName = applicationFeedGr.getValue('name');
      var applicationFeedSysId = applicationFeedGr.getUniqueValue();
      var applicationFeedDisplay = this._buildDisplay('cmdb_inst_application_feed', applicationFeedSysId, applicationFeedName);

      var importTableName = '';

      //check for data source
      var dataSourceSysId = applicationFeedGr.getValue('sys_data_source');
      if (!dataSourceSysId)
          errors.push(applicationFeedDisplay + "No data source.  UI will require a data source.");
      else {
          var dataSourceGr = new GlideRecord('sys_data_source');
          dataSourceGr.addQuery('sys_id', dataSourceSysId);
          dataSourceGr.query();

          if (!dataSourceGr.next())
              errors.push(applicationFeedDisplay + "Data source reference is somehow invalid.  The referenced data source does not exist or cannot be found.");
          else
              importTableName = dataSourceGr.getValue('import_set_table_name');
      }

      var tempEntitySysId;
      var importEntitySysId;

      // check entities
      var entityGr = new GlideRecord('cmdb_inst_entity');
      entityGr.addQuery('sys_rte_eb_definition', applicationFeedSysId);
      entityGr.query();

      entitySysIds = [];

      while (entityGr.next()) {
          var entityName = entityGr.getValue('name');
          var entitySysId = entityGr.getUniqueValue();
          var entityDisplay = this._buildDisplay('cmdb_inst_entity', entitySysId, entityName);

          var entityTargetTable = entityGr.getValue('table');
          var entityPath = entityGr.getValue('path');

          var lookupEntity = entityGr.getValue('lookup_for_entity');
          var relatedForEntity = entityGr.getValue('related_for_entity');
          var relationshipType = entityGr.getValue('relationship_type');

          entitySysIds.push(entitySysId);

          if (!entityName) {
              errors.push(entityDisplay + "Has no name.  Name should be added.");
              continue;
          }

          errors = errors.concat(this._checkForExistence('cmdb_inst_entity', lookupEntity, entityDisplay));
          errors = errors.concat(this._checkForExistence('cmdb_inst_entity', relatedForEntity, entityDisplay));
          errors = errors.concat(this._checkForExistence('cmdb_rel_type', relationshipType, entityDisplay));

          // import entity
          if (entityName.toLowerCase().contains('import') && (entityTargetTable && importTableName === entityTargetTable)) {
              if ('Import' != entityName)
                  errors.push(entityDisplay + "Should be named 'Import' by convention");

              if (entityPath)
                  errors.push(entityDisplay + "Path must be empty.  The import set processor will not append any path to the processing complex object.");

              if (importEntitySysId)
                  errors.push(applicationFeedDisplay + "Seems to have multiple 'Import' entities.  There should only be a single import entity to represent the import table.  Remove the other.");

              if (lookupEntity)
                  errors.push(entityDisplay + "Import entity should not be a lookup rule.  Remove the Lookup For Entity value.");

              if (relatedForEntity)
                  errors.push(entityDisplay + "Import entity should not be a related rule.  Remove the Related For Entity value.");

              if (relationshipType)
                  errors.push(entityDisplay + "Import entity should not be a relationship.  Remove the Relationship type value.");

              importEntitySysId = entitySysId;
          }
          // temp entity
          else if (entityName.toLowerCase().contains('temp')) {

              if ('Temp' != entityName)
                  errors.push(entityDisplay + "Should be named 'Temp' by convention");

              if (entityPath != 'temp')
                  errors.push(entityDisplay + "Path should be 'temp' by convention.");

              if (tempEntitySysId)
                  errors.push(applicationFeedDisplay + "Seems to have multiple 'Temp' entities.  There should only be a single temp entity to represent the working grid in the UI.  Remove the other.");

              if (lookupEntity)
                  errors.push(entityDisplay + "Temp entity should not be a lookup rule.  Remove the Lookup For Entity value.");

              if (relatedForEntity)
                  errors.push(entityDisplay + "Temp entity should not be a related rule.  Remove the Related For Entity value.");

              if (relationshipType)
                  errors.push(entityDisplay + "Temp entity should not be a relationship.  Remove the Relationship type value.");

              tempEntitySysId = entitySysId;
          }
          //== cmdb entity
          else {
              errors = errors.concat(this._validateCmdbEntity(entityDisplay, entityName, entityTargetTable, entityPath));

              // cmdb_rel_ci tables should have a relationship type set
              if ('cmdb_rel_ci' == entityTargetTable && !relationshipType)
                  errors.push(entityDisplay + "Cmdb Relationship tables should have a Relationship Type selected.  Please add one.");

              // all entities not cmdb_rel_ci should have source native key set
              else if ('cmdb_rel_ci' != entityTargetTable && !lookupEntity) {
                  var sourceNativeKeyGr = new GlideRecord('sys_rte_eb_field');
                  sourceNativeKeyGr.addQuery('field', 'source_native_key');
                  sourceNativeKeyGr.addQuery('sys_rte_eb_entity', entitySysId);
                  sourceNativeKeyGr.query();

                  if (!sourceNativeKeyGr.next())
                      errors.push(entityDisplay + "All entities (except relationships, lookups, temp, and import) should have a field called 'source_native_key'.  Please add and map.");
              }
          }

          errors = errors.concat(this._validateFields(entitySysId, applicationFeedSysId, entityTargetTable, importEntitySysId === entitySysId));
      }

      errors = errors.concat(this._validateEntityMappings(applicationFeedSysId, importEntitySysId, tempEntitySysId));
      errors = errors.concat(this._validateOperations(applicationFeedSysId, tempEntitySysId));

      return errors;

  },

  _validateOperations: function(applicationFeedSysId, tempEntitySysId) {
      var errors = [];

      if (!tempEntitySysId)
          return errors;

      var gr = new GlideRecord('sys_rte_eb_operation');
      gr.addQuery('sys_rte_eb_definition', applicationFeedSysId);
      gr.query();

      while (gr.next()) {
          var operationSysId = gr.getUniqueValue();
          var operationName = gr.getValue('name');
          var operationDisplay = this._buildDisplay('sys_rte_eb_operation', operationSysId, operationName);

          var operationEntity = gr.getValue('sys_rte_eb_entity');

          var targetField = gr.getValue('target_sys_rte_eb_field');
          var targetFields = gr.getValue('target_sys_rte_eb_fields');
          var sourceField = gr.getValue('source_sys_rte_eb_field');
          var sourceFields = gr.getValue('source_sys_rte_eb_fields');

          var operationType = gr.getValue('type');

          if (operationEntity != tempEntitySysId)
              errors.push(operationDisplay + "By convention only the Temp entity should have operations assigned to it.  Please move operation to the Temp entity.");

          // can't do the same check for sources because there are operations with no source fields
          if (!targetField && !targetFields)
              errors.push(operationDisplay + "Operation does not have any targets set.");

          if (!operationType)
              errors.push(operationDisplay + "Missing operation type.  Please add operation type.");

          if (sourceField) {
              errors = errors.concat(this._checkForExistence('sys_rte_eb_field', sourceField, operationDisplay));

              if (gr.source_sys_rte_eb_field.sys_rte_eb_entity != tempEntitySysId)
                  errors.push(operationDisplay + "By convention only the Temp entity should have operations assigned to it.  Source field:" + sourceField + " is not a Temp entity field.");

          }
          if (sourceFields) {
              var sourceFieldsSplit = sourceFields.split(',');

              for (var i = 0; i < sourceFieldsSplit.length; i++) {
                  var sourceFieldGr = new GlideRecord('sys_rte_eb_field');
                  sourceFieldGr.addQuery('sys_id', sourceFieldsSplit[i]);
                  sourceFieldGr.query();

                  if (!sourceFieldGr.next())
                      errors.push(operationDisplay + "Source field:" + sourceFieldsSplit[i] + " does not exist or is invalid.");
                  else if (sourceFieldGr.getValue('sys_rte_eb_entity') != tempEntitySysId)
                      errors.push(operationDisplay + "By convention only the Temp entity should have operations assigned to it. Source field:" + sourceFieldsSplit[i] + " is not a Temp entity field.");
              }
          }

          if (targetField) {
              errors = errors.concat(this._checkForExistence('sys_rte_eb_field', targetField, operationDisplay));

              if (gr.target_sys_rte_eb_field.sys_rte_eb_entity != tempEntitySysId) {
                  errors.push(operationDisplay + "By convention only the Temp entity should have operations assigned to it.  Target field:" + targetField + " is not a Temp entity field.");
              }
          }

          if (targetFields) {
              var targetFieldsSplit = targetFields.split(',');

              for (var j = 0; j < targetFieldsSplit.length; j++) {
                  var targetFieldGr = new GlideRecord('sys_rte_eb_field');
                  targetFieldGr.addQuery('sys_id', targetFieldsSplit[j]);
                  targetFieldGr.query();

                  if (!targetFieldGr.next())
                      errors.push(operationDisplay + "Target field:" + targetFieldsSplit[j] + " does not exist or is invalid.");
                  else if (targetFieldGr.getValue('sys_rte_eb_entity') != tempEntitySysId)
                      errors.push(operationDisplay + "By convention only the Temp entity should have operations assigned to it. Target field:" + targetFieldsSplit[j] + " is not a Temp entity field.");
              }
          }
      }

      return errors;
  },
  
  _validateEntityMappings: function(applicationFeedSysId, importEntitySysId, tempEntitySysId) {
      var errors = [];

      if (!tempEntitySysId || !importEntitySysId)
          return errors;

      var gr = new GlideRecord('sys_rte_eb_entity_mapping');
      gr.addQuery('sys_rte_eb_definition', applicationFeedSysId);
      gr.query();

      while (gr.next()) {
          var mappingSysId = gr.getUniqueValue();
          var mappingName = gr.getValue('name');
          var mappingDisplay = this._buildDisplay('sys_rte_eb_entity_mapping', mappingSysId, mappingName);

          var mappingSourceEntity = gr.getValue('source_sys_rte_eb_entity');
          var mappingTargetEntity = gr.getValue('target_sys_rte_eb_entity');

          errors = errors.concat(this._checkForExistence('cmdb_inst_entity', mappingSourceEntity, mappingDisplay));
          errors = errors.concat(this._checkForExistence('cmdb_inst_entity', mappingTargetEntity, mappingDisplay));

          if ((mappingSourceEntity == importEntitySysId && mappingTargetEntity != tempEntitySysId) ||
              (mappingSourceEntity != importEntitySysId && mappingTargetEntity == tempEntitySysId))
              errors.push(mappingDisplay + "By convention the Import entity should only map to the Temp entity.  Please change mapping.");

          if (mappingSourceEntity == mappingTargetEntity)
              errors.push(mappingDisplay + "Source and target entity cannot match.  Remove this mapping.");

          if (mappingTargetEntity == importEntitySysId)
              errors.push(mappingDisplay + "By convention nothing should map to the Import entity.  Remove this mapping.");

          if (mappingSourceEntity != tempEntitySysId && (mappingTargetEntity != importEntitySysId && mappingTargetEntity != tempEntitySysId))
              errors.push(mappingDisplay + "By convention only the Temp entity should map to any other non-Import entity.  Remove this mapping.");

          errors = errors.concat(this._validateEntityFieldMappings(applicationFeedSysId, mappingSysId, mappingSourceEntity, mappingTargetEntity, importEntitySysId, tempEntitySysId));
      }

      return errors;
  },

  _validateEntityFieldMappings: function(applicationFeedSysId, mappingSysId, mappingSourceEntity, mappingTargetEntity, importEntitySysId, tempEntitySysId) {
      var errors = [];

      var gr = new GlideRecord('sys_rte_eb_field_mapping');
      gr.addQuery('sys_rte_eb_entity_mapping', mappingSysId);
      gr.query();

      while (gr.next()) {
          var fieldMappingSysId = gr.getUniqueValue();
          var fieldMappingDisplay = this._buildDisplay('sys_rte_eb_field_mapping', fieldMappingSysId, null);

          var fieldMappingTargetField = gr.target_sys_rte_eb_field;

          var fieldMappingTargetEntity = gr.target_sys_rte_eb_field.sys_rte_eb_entity;
          var fieldMappingSourceEntity = gr.source_sys_rte_eb_field.sys_rte_eb_entity;
          var fieldMappingReferencedEntity = gr.referenced_sys_rte_eb_entity;

          var fieldMappingTargetFieldField = gr.target_sys_rte_eb_field.field;
          var fieldMappingSourceFieldField = gr.source_sys_rte_eb_field.field;

          if (!fieldMappingTargetEntity)
              errors.push(fieldMappingDisplay + "Field mappings must have a target.");

          if (fieldMappingTargetEntity != mappingTargetEntity)
              errors.push(fieldMappingDisplay + "Field mapping target not the same as entity mapping target.  Select a new field that is part of the target entity.");

          if (!fieldMappingSourceEntity && !fieldMappingReferencedEntity)
              errors.push(fieldMappingDisplay + "Must have either a source field or referenced entity.");

          if (fieldMappingSourceEntity && fieldMappingReferencedEntity)
              errors.push(fieldMappingDisplay + "Cannot have both a source field and referenced entity.  Remove one.");

          if (fieldMappingSourceEntity && (fieldMappingSourceEntity != mappingSourceEntity))
              errors.push(fieldMappingDisplay + "Field mapping source not the same as entity mapping source.  Select a new field that is part of the source entity.");

          if (fieldMappingReferencedEntity && (fieldMappingReferencedEntity == importEntitySysId || fieldMappingReferencedEntity == tempEntitySysId))
              errors.push(fieldMappingDisplay + "Referenced entity cannot be the Temp or Import entity.  Remove field mapping or change referenced entity.");

          if (fieldMappingSourceFieldField && mappingSourceEntity == importEntitySysId && mappingTargetEntity == tempEntitySysId && fieldMappingTargetFieldField != fieldMappingSourceFieldField)
              errors.push(fieldMappingDisplay + "When an Import entity field is mapped to a temp entity the field values must match.  Update entity field "+fieldMappingTargetField + " to change the 'field' value from:"+fieldMappingTargetFieldField+" to:" + fieldMappingSourceFieldField);

      }

      return errors;
  },

  _validateFields: function(entitySysId, applicationFeedSysId, entityTargetTable, isImportEntity) {
      var errors = [];

      var gr = new GlideRecord('sys_rte_eb_field');
      gr.addQuery('sys_rte_eb_entity', entitySysId);
      gr.query();

      while (gr.next()) {
          var fieldSysId = gr.getUniqueValue();
          var fieldName = gr.getValue('name');
          var fieldField = gr.getValue('field');
          var fieldDisplay = this._buildDisplay('sys_rte_eb_field', fieldSysId, fieldName);

          if (!fieldName)
              errors.push(fieldDisplay + "Name value is missing.  Please add.");
          
          if (!fieldField)
              errors.push(fieldDisplay + "Field value is missing.  Please add.");
          
          //if (fieldName && fieldField && fieldName != fieldField)
          //    errors.push(fieldDisplay + "Name and field value should match.  Change name:"+fieldName + " to:"+fieldField);

          if (gr.getValue('sys_rte_eb_definition') != applicationFeedSysId)
              errors.push(fieldDisplay + "Is not in the correct definition.  Set definition to be : " + applicationFeedSysId);

          // if this entity has a table, all of the fields should exist on that table
          if (entityTargetTable) {

              if (fieldField == 'source_native_key' || fieldField == 'source_recency_timestamp') {
                  continue;
              }

              var tableGr = new GlideRecord('sys_dictionary');
              tableGr.addQuery('name', entityTargetTable);
              tableGr.addQuery('element', fieldField);
              tableGr.query();

              if (!tableGr.next()) {
                  if (isImportEntity) {
                      var importSetRowTableGr = new GlideRecord('sys_dictionary');
                      importSetRowTableGr.addQuery('name', 'sys_import_set_row');
                      importSetRowTableGr.addQuery('element', fieldField);
                      importSetRowTableGr.query();

                      if (importSetRowTableGr.next())
                          continue;
                  }

                  errors.push(fieldDisplay + "Does not exist as a column in the target table:" + entityTargetTable + ".  All field values must be the a column name on the target table (except for source_native_key and source_recency_timestamp).");
              }

          }
      }

      return errors;
  },

  _validateCmdbEntity: function(entityDisplay, entityName, entityTargetTable, entityPath) {
      var errors = [];

      if (!entityPath)
          errors.push(entityDisplay + "Must have path set.");

      return errors;
  },

  _buildDisplay: function(table, sysId, name) {
      if (name)
          return table + "(" + name + "," + sysId + ") : ";

      return table + "(" + sysId + ") : ";
  },

  _checkForExistence: function(table, sysId, sourceName) {
      // needs to be a list so we don't add nulls to the collection
      var errors = [];

      if (!sysId)
          return errors;

      var gr = new GlideRecord(table);
      gr.addQuery('sys_id', sysId);
      gr.query();

      if (!gr.next())
          errors.push(sourceName + "Could not find sys_id:" + sysId + " in table " + table);

      return errors;
  },

  type: 'ApplicationValidationUtil'
};

Sys ID

1ecd2ca0732100102b6265a751ab9e44

Offical Documentation

Official Docs: