Name

sn_em_tbac.EvtMgmtTagBasedAlertClusteringUtils

Description

Basic reusable utilities for Tag Based Alert Clustering Engine feature.

Script

var EvtMgmtTagBasedAlertClusteringUtils = Class.create();
EvtMgmtTagBasedAlertClusteringUtils.prototype = {

  type: 'EvtMgmtTagBasedAlertClusteringUtils',

  initialize: function() {
      this.evtMgmtCommons = new global.EvtMgmtCommons();

      // A consistent prefix that will show up in logs
      this.logsPrefix = 'Event Management - Tag Based Alert Clustering Engine (sn_em_tbac) - ';

      // Properties
      this.LOG_ACR_DEBUG_PROPERTY = 'evt_mgmt.sn_em_tbac.log_acr_script_debugging';
      this.ALERT_TAGS_STATE_PROPERTY = 'evt_mgmt.alert_tags_state';
      this.ENABLE_VIRTUAL_ALERT_FOR_RULE_BASED_GROUPS = 'evt_mgmt.enable_virtual_alerts_for_rules_based_groups';
      this.DISABLE_ALL_CORRELATION_JOB = 'evt_mgmt.disable_all_correlation_job';
      this.ENABLE_ALERT_CORRELATION = 'evt_mgmt.enable_alert_correlation';


      // Constants
      this.GLOBAL = 'global';
      this.EM_ADMIN_ROLE = 'evt_mgmt_admin';
      this.EM_ALERT_TAGS_TABLE = 'em_alert_tags';
      this.SN_ALERT_TAGS_FIELD = 'sn_alert_tags';
      this.ACR_TABLE = 'em_alert_correlation_rule';
      this.GENERATE_VIRTUAL_ALERTS = 'generate_virtual_alerts';
      this.EM_AGG_GROUP = 'em_agg_group';
      this.SOURCE_RULE = 'source_rule';
      this.OVERRIDE_GROUP_DESCRIPTION = 'override_group_description';
      this.CUSTOM_GROUP_DESCRIPTION = 'custom_group_description';

      this.GROUP_OF_ALERTS = 'Group of alerts';

      this.ALERT_TAGS_STATE = {
          TRUE: 'true',
          PARTIAL: 'partial',
          FALSE: 'false',
          DEFAULT_VALUE: 'false'
      };

      this.SOURCE_FIELD_MAPPING = {
          'em_alert': 'selected_field',
          'alert_tags': 'additional_info_key',
          'additional_info': 'additional_info_key',
          'cmdb_ci': 'selected_field',
          'cmdb_key_value': 'cmdb_key'
      };

      this.MATCH_METHOD_RELATED_FIELDS = {
          'exact': '',
          'fuzzy': 'fuzzy_similarity',
          'pattern': 'pattern'
      };
  },

  /**
   * Returns true if domain separation is enabled.
   * This is determined by a property and the existence of the 'domain' table.
   */
  isDomainSeparationEnabled: function() {
      var isPartitioned = gs.getProperty('glide.sys.domain.partitioning', 'false') == 'true';
      var gr = new GlideRecord('domain');
      var isDomainTableExists = gr.isValid();

      return isPartitioned && isDomainTableExists;
  },

  /**
   * Returns the user domain ID or 'global' by default.
   * getCurrentDomainID may return null when the domain picker is not used, see:
   * https://developer.servicenow.com/dev.do#!/reference/api/quebec/client/ScopedSessionDomainAPI
   */
  getUserDomainID: function() {
      return gs.getSession().getCurrentDomainID() || this.GLOBAL;
  },

  /**
   * Returns the user domain display name.
   * Usable for readable and user-friendly messages.
   */
  getUserDomainDisplayName: function() {
      return this.getDomainDisplayName(this.getUserDomainID());
  },

  /**
   * Virtual alert feature for tag based alert clustering engine was introduced in Vancouver.
   * EvtMgmtAlertCorrelationRulesUtils was included in that same release.
   * If EvtMgmtAlertCorrelationRulesUtils exists then call its isVirtualAlertFeatureActive method,
   * otherwise, return false.
   *
   * Returns true if the feature is enabled and active (ready to be used).
   */
  isVirtualAlertFeatureActive: function() {
      var isScriptExists = typeof(global.EvtMgmtAlertCorrelationRulesUtils) !== 'undefined';
      if (isScriptExists) {
          var acrUtils = new global.EvtMgmtAlertCorrelationRulesUtils();
          return acrUtils.isVirtualAlertFeatureActive();
      }
      return false;
  },

  /**
   * Returns the name of the domain that's associated with the given domain ID
   */
  getDomainDisplayName: function(domainId) {
      var domainName = 'global';

      var domainGr = new GlideRecord('domain');
      // If domain separation enabled then GR table is valid
      if (domainGr.isValid()) {
          domainGr.get(domainId);
          // If the given domain ID was found
          if (domainGr.isValidRecord()) {
              domainName = domainGr.getDisplayValue('name');
          }
      }

      return domainName;
  },

  /**
   * Returns true if the given domainId is identical to the current user's domain id, false otherwise.
   * @param domainId
   * @returns {GlideRecord}
   */
  isUserDomain: function(domainId) {
      return (domainId == this.getUserDomainID());
  },

  /**
   * Returns true if the given domainId is not identical to the current user's domain id, false otherwise.
   * @param domainId
   * @returns {GlideRecord}
   */
  isNotUserDomain: function(domainId) {
      return !(this.isUserDomain(domainId));
  },

  /**
   * Short name for isUserDomainWithParentAndContainsDomainId
   * @param domainId
   * @returns {boolean}
   */
  isDomainPC: function(domainId) {
      return this.isUserDomainWithParentAndContainsDomainId(domainId);
  },

  /**
   * Short name and the negate of isUserDomainWithParentAndContainsDomainId
   * @param domainId
   * @returns {boolean}
   */
  isDomainNotPC: function(domainId) {
      return !this.isDomainPC(domainId);
  },

  /**
   * A domain that has a parent domain but also contains that parent domain 
   * causes its overridden records to have the domain of its parent instead of its own.
   * This doesn't happen when a domain only contains another domain, or a domain is only the parent of another domain.
   * This function returns true if the given domain id is both the parent of the user domain and also contained by it.
   *
   * Example:
   ** TOP/MSP contains TOP domain, while TOP domain is the parent domain of TOP/MSP.
   ** Trying to override a record of TOP domain from TOP/MSP domain will override it in TOP, and not TOP/MSP, domain.
   *
   * @param domainId
   * @returns {boolean} - true if the given domain id is contained by the user domain and also is the parent of the user domain.
   */
  isUserDomainWithParentAndContainsDomainId: function(domainId) {
      // Return false if domain separation is disabled.
      if (!this.isDomainSeparationEnabled())
          return false;

      var gr = new GlideRecord('domain_contains');
      gr.addQuery('domain', this.getUserDomainID());
      gr.addQuery('contains', domainId);
      gr.query();

      if (!gr.next()) {
          return false;
      }

      gr = new GlideRecord('domain');
      gr.addQuery('sys_id', this.getUserDomainID());
      gr.addQuery('parent', domainId);
      gr.query();

      return gr.next();
  },


  /**
   * Used in the "Condition" field of alert clustering definitions "Save" UI action.
   * Returns true if the alert clustering definition "Save" UI action can be performed for "current" definition.
   * @param current - a GlideRecord of an alert clustering definition form.
   * @returns {boolean} - true if "Save" is allowed, false otherwise.
   */
  canSaveDefinitionUIAction: function(current) {
      if (!current)
          return false;

      var currentDomainId = current.getValue('sys_domain');

      var isInUserDomain = this.isUserDomain(currentDomainId); // Allow "Save" if the user is in same domain.
      var isDomainNotPC = this.isDomainNotPC(currentDomainId); // Allow "Save" if domain is not a contained child domain.

      return gs.hasRole(this.EM_ADMIN_ROLE) && isInUserDomain && isDomainNotPC;
  },

  /**
   * Used in the "Condition" field of alert clustering definitions "Delete" UI action.
   * Returns true if the alert clustering definition "Delete" UI action can be performed for "current" definition.
   * @param current - a GlideRecord of an alert clustering definition form.
   * @returns {boolean} - true if "Delete" is allowed, false otherwise.
   */
  canDeleteDefinitionUIAction: function(current) {
      if (!current)
          return false;

      var currentDomainId = current.getValue('sys_domain');
      var isInUserDomain = this.isUserDomain(currentDomainId); // Allow delete if the user is in same domain

      return gs.hasRole(this.EM_ADMIN_ROLE) && isInUserDomain;
  },

  /**
   * Used in the "Condition" field of alert clustering definitions "Insert with Tags" UI action.
   * Returns true if the alert clustering definition "Insert with Tags" UI action can be performed for "current" definition.
   * @param current - a GlideRecord of an alert clustering definition form.
   * @returns {boolean} - true if "Insert with Tags" is allowed, false otherwise.
   */
  canInsertWithTagsUIAction: function(current) {
      if (!current)
          return false;

      var currentDomainId = current.getValue('sys_domain');

      var isInUserDomain = this.isUserDomain(currentDomainId); // Allow "Insert with Tags" if the user is in same domain.
      var isDomainNotPC = this.isDomainNotPC(currentDomainId); // Allow "Insert with Tags" if domain is not a contained child domain.

      return gs.hasRole(this.EM_ADMIN_ROLE) && isInUserDomain && isDomainNotPC;
  },
  /**
   * Used in the "Condition" field of alert clustering definitions "Override to my domain" UI action.
   * Returns true if the alert clustering definition "Override to my domain" UI action can be performed for "current" definition.
   * @param current - a GlideRecord of an alert clustering definition form.
   * @returns {boolean} - true if "Override to my domain" is allowed, false otherwise.
   */
  canOverrideUIAction: function(current) {

      if (!current || !this.isDomainSeparationEnabled())
          return false;

      var currentDomainId = current.getValue('sys_domain');

      var isNotUserDomain = this.isNotUserDomain(currentDomainId); // Allow override if the user is in a different domain
      var isNotGlobal = isNotUserDomain && (this.getUserDomainID() != this.GLOBAL); // So a global user won't be able to override a definition in a different (child) domain.
      var isDomainNotPC = this.isDomainNotPC(currentDomainId); // Allow override if domain is not a contained child domain.

      return gs.hasRole(this.EM_ADMIN_ROLE) && isNotUserDomain && isNotGlobal && isDomainNotPC;
  },

  /**
   * Performs a query and returns the gr.
   * If domainId is provided then the query will be limited to 
   * the records in the provided domainId and records in parent domains won't be queried.
   * @param gr - a valid GlideRecord, before it was called with .query();
   * @param domainId [OPTIONAL] - the domain ID to limit the query to.
   * @returns {GlideRecord} - a GlideRecord of the query results (in the given domain, if given).
   */
  queryInDomain: function(gr, domainId) {
      if (!gr) return;

      if (domainId) {
          gr.addQuery('sys_domain', domainId);
      }

      gr.query();
      return gr;
  },

  /**
   * Used to prevent duplicate definition names in the same domains.
   * @param definitionName - String, name of an Alert Clustering Definition.
   * @param definitionSysId - String, sysId of an Alert Clustering Definition to exclude from the check.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {boolean} - true if the given definition name already exists in the current domain, false otherwise.
   */
  isExistingDefinition: function(definitionName, definitionSysId, domainId) {
      if (!definitionName) {
          return false;
      }
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions');
      gr.addQuery('name', definitionName);

      if (definitionSysId) {
          gr.addQuery('sys_id', '!=', definitionSysId); // Look for other definitions than the current.
      }

      gr = this.queryInDomain(gr, domainId);

      return gr.next();
  },

  /**
   * Used to prevent duplicate tag names in the same domains.
   * @param tagName - String, name of an Alert Clustering Tag.
   * @param tagSysId - String, sysId of an Alert Clustering Tag to exclude from the check.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {boolean} - true if the given tag name already exists in the current domain, false otherwise.
   */
  isExistingTag: function(tagName, tagSysId, domainId) {
      if (!tagName) {
          return false;
      }
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_tags');
      gr.addQuery('name', tagName);

      if (tagSysId) {
          gr.addQuery('sys_id', '!=', tagSysId); // Look for other tags than the current.
      }

      gr = this.queryInDomain(gr, domainId);

      return gr.next();
  },

  /**
   * Used to prevent duplicate M2M records in the same domains.
   * @param definitionSysId - String, sys_id of an Alert Clustering Definition.
   * @param tagSysId - String, sys_id of an Alert Clustering Tag.
   * @param m2mSysId - String, sysId of an M2M record to exclude from the check.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {boolean} - true if the given m2m pair already exists in the current domain, false otherwise.
   */
  isExistingM2M: function(definitionSysId, tagSysId, m2mSysId, domainId) {
      if (!definitionSysId || !tagSysId) {
          return false;
      }
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');

      gr.addQuery('alert_clustering_definition', definitionSysId);
      gr.addQuery('alert_clustering_tag', tagSysId);

      if (m2mSysId) {
          gr.addQuery('sys_id', '!=', m2mSysId); // Look for other m2ms than the current.
      }

      gr = this.queryInDomain(gr, domainId);

      return gr.next();
  },

  /**
   * Returns true if the given tag is assigned to 1 or more definitions.
   * @param tagSysId - String, sys_id of an Alert Clustering Tag.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  isTagAssignedToDefinitions: function(tagSysId, domainId) {
      return this.getM2MRecordsByTagSysId(tagSysId, domainId).next();
  },

  /**
   * Returns true if the given definition has tags assigned to it.
   * @param definitionSysId - String, sys_id of an Alert Clustering Definition
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  isDefinitionWithTags: function(definitionSysId, domainId) {
      return this.getM2MRecordsByDefinitionSysId(definitionSysId, domainId).next();
  },

  /**
   * Returns GlideRecord of Definitions from M2M that the given tag is assigned to.
   * @param tagSysId - String, sys_id of an Alert Clustering Tag.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getM2MRecordsByTagSysId: function(tagSysId, domainId) {
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');

      if (!tagSysId) return gr;

      // Get all the m2m record of the related tag.
      gr.addQuery('alert_clustering_tag', tagSysId);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  /**
   * Returns GlideRecord of Tags from M2M that are assigned to the given Definition sys_id.
   * @param definitionSysId - String, sys_id of an Alert Clustering Definition.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getM2MRecordsByDefinitionSysId: function(definitionSysId, domainId) {
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');

      if (!definitionSysId) return gr;

      // Get all the m2m records of the related definition.
      gr.addQuery('alert_clustering_definition', definitionSysId);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  getM2MRecordsByPair: function(definitionSysId, tagSysId, domainId) {
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');

      gr.addQuery('alert_clustering_definition', definitionSysId);
      gr.addQuery('alert_clustering_tag', tagSysId);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  /**
   * Creates the given definitionSysId-tagSysId pair in m2m table, only if it doesn't already exist.
   * @param definitionSysId
   * @param definitionSysId
   * @returns {string} - the sys_id of the inserted record, if inserted successfully.
   */
  createPairInM2M: function(definitionSysId, tagSysId) {
      if (!this.isExistingM2M(definitionSysId, tagSysId)) {
          var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');
          gr.initialize();
          gr.setValue('alert_clustering_definition', definitionSysId);
          gr.setValue('alert_clustering_tag', tagSysId);
          gr.setValue('sys_domain', this.getUserDomainID());
          return gr.insert();
      }
  },

  /**
   * Deletes all the m2m records that the given definitionSyId is part of.
   * @param definitionSysId
   */
  deleteM2MRecordByDefinitionSysId: function(definitionSysId) {
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');
      gr.addQuery('alert_clustering_definition', definitionSysId);
      gr.query();
      while (gr.next()) {
          gr.deleteRecord();
      }
  },

  /**
   * Returns GlideRecord of the tags that are associated with the given sys_ids in tagsSysIds.
   * @param tagsSysIds - A comma separated string OR an array of strings, that contain sys_ids of tags.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getTagsGrByTagsSysIds: function(tagsSysIds, domainId) {
      if (!tagsSysIds) return;

      if (tagsSysIds instanceof Array) {
          tagsSysIds = tagsSysIds.join(',');
      }

      var gr = new GlideRecord('sn_em_tbac_alert_clustering_tags');
      gr.addQuery('sys_id', 'IN', tagsSysIds);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  /**
   * Returns GlideRecord of the tags that are associated with the given names in tagsNames.
   * @param tagsNames - A comma separated string OR an array of strings, that contain names of tags.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getTagsGrByTagsNames: function(tagsNames, domainId) {
      if (!tagsNames) return;

      if (tagsNames instanceof Array) {
          tagsNames = tagsNames.join(',');
      }

      var gr = new GlideRecord('sn_em_tbac_alert_clustering_tags');
      gr.addQuery('name', 'IN', tagsNames);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  /**
   * Returns GlideRecord of the tags that are assigned to the definition identified with definitionSysId.
   * @param definitionSysId
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getTagsSysIdsArrayForDefinition: function(definitionSysId, domainId) {
      var tagsSysIds = [];
      var m2mGr = this.getM2MRecordsByDefinitionSysId(definitionSysId, domainId);

      while (m2mGr.next()) {
          tagsSysIds.push(m2mGr.getValue('alert_clustering_tag'));
      }

      return tagsSysIds;
  },

  /**
   * Returns GlideRecord of the tags that are assigned to the definition identified with definitionSysId.
   * @param definitionSysId
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getTagsGrForDefinition: function(definitionSysId, domainId) {
      var tagsSysIds = this.getTagsSysIdsArrayForDefinition(definitionSysId, domainId);

      return this.getTagsGrByTagsSysIds(tagsSysIds, domainId);
  },

  /**
   * Returns GlideRecord of alert clustering tags in the given domain that are overriding the tags in the tagsIdsArray.
   * @param tagsIdsArray - an array of string representing tags sys_ids, or a single tag sys_id string
   * @param domainId - the domain in which to look for the overriding tags
   * @returns {GlideRecord} - a valid GlideRecord (with 0 or more results) if tagsIdsArray is given and valid, or an invalid GlideRecord otherwise.
   */
  getOverridingTagsInDomain: function(tagsIdsArray, domainId) {
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_tags');

      // Return an invalid GR (gr.isValidRecord() will return false) if the given tags is not a valid parameter
      // .length property is a valid property for strings and for arrays
      if (!tagsIdsArray || !tagsIdsArray.length)
          return gr;

      // If tagsIdsArray is not an array then make it one (e.g. in the case of a single sys_id string)
      if (!(tagsIdsArray instanceof Array)) {
          tagsIdsArray = [String(tagsIdsArray)];
      }

      // Query for the overriding tags in the given domainId that override the tags in tagsIdsArray
      gr.addQuery('sys_overrides', 'IN', tagsIdsArray);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  /**
   * Returns GlideRecord of the definitions that are associated with the given sys_ids in definitionsSysId.
   * @param definitionsSysId - A comma separated string OR an array of strings, that contain sys_ids of definitions.
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getDefinitionsGrByDefinitionsSysIds: function(definitionsSysIds, domainId) {
      if (!definitionsSysIds) return;

      if (definitionsSysIds instanceof Array) {
          definitionsSysIds = definitionsSysIds.join(',');
      }

      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions');
      gr.addQuery('sys_id', 'IN', definitionsSysIds);

      gr = this.queryInDomain(gr, domainId);

      return gr;
  },

  /**
   * Returns GlideRecord of an alert clustering definition in the given domain that is overriding the definition that's identified with the given definitionSysId.
   * @param definitionSysId - a sys_id of an alert clustering definition
   * @param domainId - the domain in which to look for the overriding definition
   * @returns {GlideRecord} - a valid GlideRecord (with 0 or more results) if definitionSysId is given and valid, or an invalid GlideRecord otherwise. 
   */
  getOverridingDefinitionInDomain: function(definitionSysId, domainId) {
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_definitions');

      // Query for the overriding definition in the given domainId that overrides the definition that's identified with definitionSysId
      if (definitionSysId) {
          gr.addQuery('sys_overrides', 'IN', definitionSysId);
          gr = this.queryInDomain(gr, domainId);
      }

      // Return an invalid GR (gr.isValidRecord() will return false) if the given definitionSysId is not a valid parameter
      // Or a valid GR (gr.isValidRecord() will return true) if definitionSysId has value.
      return gr;
  },

  /**
   * Returns GlideRecord of the definitions that the given tag is assigned to.
   * @param definitionSysId
   * @param domainId - OPTIONAL - the domain ID in which to run the query.
   * @returns {GlideRecord}
   */
  getDefinitionsGrForTag: function(tagSysId, domainId) {
      var definitionsSysIds = [];
      var m2mGr = this.getM2MRecordsByTagSysId(tagSysId, domainId);

      while (m2mGr.next()) {
          definitionsSysIds.push(m2mGr.getValue('alert_clustering_definition'));
      }

      return this.getDefinitionsGrByDefinitionsSysIds(definitionsSysIds, domainId);
  },

  /**
   * Returns an Object with classified arrays that represent alert clustering tags, and basic methods.
   * @param definitionSysId - String, sys_id of an alert clustering definition
   * @param definitionDomainId - String, sys_domain of an alert clustering definition
   * @returns {}
   */
  getTagsDataForDefinition: function(definitionSysId, definitionDomainId) {
      // Initialize tagsData object that will store data about the given Definition's tags.
      var tagsData = {
          tags: {
              em_alert: [], // Classified em_alert tags.
              cmdb_ci: [], // Classified cmdb_ci tags.
              additional_info: [], // Classified additional_info tags.
              cmdb_key_value: [], // Classified ci_key tags
              alert_tags: [], // Classified alert_tags tags.
          },

          hasFuzzy: false, // True if fuzzy match tags exist, to know if EvtMgmtTagBasedComparators needs to be included.

          // Function to retrieve all tags in a single array.
          getAllTags: function() {
              var allTags = [];
              for (var classifiedTags in this.tags) {
                  allTags = allTags.concat(this.tags[classifiedTags]);
              }
              return allTags;
          },
          // Returns true if there are tags (non-empty arrays of tags).
          hasTags: function() {
              return this.getAllTags().length;
          },
          // Returns true if there are tags with additional_info as source.
          hasAdditionalInfoTags: function() {
              return this.tags.additional_info.length;
          },
          // Returns true if there are tags with em_alert as source.
          hasAlertTags: function() {
              return this.tags.em_alert.length;
          },
          // Returns true if there are tags with cmdb_ci as source.
          hasCITags: function() {
              return this.tags.cmdb_ci.length;
          },
          // Returns true if there are tags with cmdb_key_value as source.
          hasCMDBKeyTags: function() {
              return this.tags.cmdb_key_value.length;
          },
          // Returns true if there are tags with alert_tags as source.
          hasAlertTagsTags: function() {
              return this.tags.alert_tags.length;
          }

      }; // end tagsData init

      // Get the tags that belong to the given Definition.
      var tagsGr = this.getTagsGrForDefinition(definitionSysId, definitionDomainId);

      // Populate tagsData object according to the Definition's tags.
      while (tagsGr && tagsGr.next()) {
          // tagSource - either em_alert, cmdb_ci, additional_info, alert_tags or cmdb_key_value
          var tagSource = tagsGr.getValue('source');
          // tagMatchMethod - exact, fuzzy, etc.
          var tagMatchMethod = tagsGr.getValue('match_method');

          // Add a TagEntity instance to the array classified by its source.
          if (tagsData.tags[tagSource]) {
              tagsData.tags[tagSource].push(new EvtMgmtTagBasedTagEntity(tagsGr));
              if (tagMatchMethod == 'fuzzy') {
                  tagsData.hasFuzzy = true;
              }
          } else {
              // Warn in case a new source was created but wasn't been added to tagsData.
              gs.warn(this.logsPrefix + this.type + ' getTagsDataForDefinition function - a tag source "' + tagSource + '" exists, but has not been initialized in tagsData.tags object.');
          }
      }

      // Returns all tags in a data object.
      return tagsData;
  },

  /**
   * Called when there's a need to detach a Definition's ACR from it,
   * and to deactivate/delete the ACR (release based decision).
   *
   * The ACR will be detached only in the following cases:
   * 1. The ACR exists.
   * 2. The ACR exists and it can be DETACHED, which means: 
   * 2.1. The ACR CAN be deleted (prior to San-Diego).
   * 3.2. The ACR DOESN'T domain overriding a parent ACR.
   *
   * If the ACR can be detached and the release is San-Diego then it will also be deleted.
   *
   * Example use case for this function: After all tags were removed from a Definition, but the Definition wasn't deleted.
   *
   * @param String - definitionSysId, sys_id of an Alert Clustering Definition.
   */
  detachACRAndDeactivateDefinition: function(definitionSysId) {
      var definition = new GlideRecord('sn_em_tbac_alert_clustering_definitions');
      definition.get(definitionSysId);

      // Return if the Definition doesn't exist.
      if (!definition.isValidRecord()) return;

      // ACR detachment needs to be done BEFORE deleting the ACR because of the reference_cascade_rule="restrict" configuration.
      // ACR detachment won't be done if it's overriding another ACR. 
      // 
      // Therefore, detach ACR only if:
      // 1. ACR exists for the Definition.
      // 2. It can be deleted (San-Diego and on). 
      // 3. It doesn't domain override a parent Definition.
      var acrSysId = definition.getValue('alert_correlation_rule');
      var canDetachACR = acrSysId && this.canDeleteACR() && !definition.getValue('sys_overrides');

      if (canDetachACR) {
          definition.setValue('alert_correlation_rule', '');
      }

      // Deactivate Definition whether its ACR is remained or detached.
      definition.setValue('active', false); // Make the Definition inactive.
      definition.setWorkflow(false); // If not called, update will fail because of the business rule that prevents updates for Definitions without any tags.
      var isUpdated = definition.update(); // isUpdated is null if update failed.
      gs.info(this.logsPrefix + this.type + ' detachACRAndDeactivateDefinition function - Definition ' + definitionSysId + ' was ' + (isUpdated ? 'updated successfully' : 'not updated') + '.');

      // From San-Diego - if the ACR could be detached then we can delete it. 
      if (canDetachACR) {
          this.deleteACR(acrSysId);
      } else {
          // Deactivate the ACR if it can't be detached.
          this.deactivateACR(acrSysId);
      }
  },

  /**
   * Deletes the ACR identified with the given acrSysId. 
   * This function must be called if and only if the Definition that pointed to it was deleted,
   * i.e. if and only if the ACR is not referenced by any Definition.
   * Make sure you call detachACRAndDeactivateDefinition(definitionSysId) to remove the reference to this ACR before deleting it, unless the Definition doesn't exist.
   * @param String - acrSysId - String, sys_id of an alert correlation rule.
   */
  deleteACR: function(acrSysId) {

      var acr = new GlideRecord('em_alert_correlation_rule');
      acr.get(acrSysId);

      // Return if the ACR doesn't exist.
      if (!acr.isValidRecord()) return;

      if (this.canDeleteACR()) {
          var isDeleted = acr.deleteRecord();
          gs.info(this.logsPrefix + this.type + ' deleteACR - alert correlation rule ' + acrSysId + ' was ' + (isDeleted ? 'deleted successfully' : 'not deleted') + '.');
      } else {
          // deactivateACR function doesn't remove overrides because it's also called from "detachACRAndDeactivateDefinition",
          // where all Tags of a Definition were removed and it's still referencing its ACR.
          // In this function the Definition of the provided ACR was deleted and we also want to remove the overrides.
          acr.setValue('sys_overrides', '');
          acr.update();

          this.deactivateACR(acrSysId);
      }
  },

  /**
   * Deactivates the ACR identified with the given acrSysId. 
   * @param String - acrSysId - String, sys_id of an alert correlation rule.
   */
  deactivateACR: function(acrSysId) {
      var acr = new GlideRecord('em_alert_correlation_rule');
      acr.get(acrSysId);

      // Return if the ACR doesn't exist.
      if (!acr.isValidRecord()) return;

      // Inactivate the ACR.
      acr.setValue('active', 'false');

      // Prepend to the description of the ACR the reason it's inactivated only if it DOESN'T already exist.
      var acrDescription = acr.getValue('description');
      var prependDescription = "NOTICE: RULE IS INACTIVE because its referencing Alert Clustering Definition is invalid or was deleted!";
      if (!acrDescription.contains(prependDescription)) {
          acr.setValue('description', prependDescription + '\n\n\n\n' + acrDescription);
      }

      // Prepend to the name of the ACR the reason that it's inactivated, only if the reason DOESN'T already exist.
      var acrName = acr.getValue('name');
      var prependName = 'INVALID OR DELETED DEFINITION';
      if (!acrName.contains(prependName)) {
          acr.setValue('name', prependName + ': ' + acrName);
      }

      var isDeactivated = acr.update();
      gs.info(this.logsPrefix + this.type + ' deactivateACR - alert correlation rule ' + acrSysId + ' was ' + (isDeactivated ? 'deactivated successfully' : 'not deactivated') + '.');
  },

  /**
   * This function returns true if alert correlation rules (ACR) can be deleted from all application scopes (not only from the global scope).
   * In order to support the deletion of ACRs from this app scope, the 'delete_access' value of ACR must be set to true.
   * This is determined by checking "delete_access" of alert correlation rule (em_alert_correlation_rule) table in "sys_db_object".
   * "delete_access" of "em_alert_correlation_rule" in "sys_db_object" is set to "false" in family releases prior to San Diego.
   * Trying to delete an ACR when 'delete_access' is set to false will throw an error.
   * So, in order to be able to use this app in releases prior to San Diego, we'll have to skip the deletion of ACR based on the returned value.
   *
   * @returns Boolean - 'true' if ACRs can be delete from scopes other than Global, false otherwise.
   */
  canDeleteACR: function() {
      var acrDict = new GlideRecord('sys_db_object');
      acrDict.addQuery('name', 'em_alert_correlation_rule');
      acrDict.query();

      if (acrDict.next()) {
          return acrDict.getValue('delete_access') == true;
      }

      return false;
  },

  /**
   * This function copies an existing alert clustering definition with all of its alert clustering tags, if and only if:
   * 1. The copied alert clustering definition is assigned with tags (at least 1).
   * 2. The copied alert clustering definition name is unique in its domain.
   *
   * @param GlideRecord - definitionGr, the alert clustering definition GR to be copied.
   *
   * @returns The inserted record, or null on errors (if the definition name is not unique in its domain, if the definition doesn't have tags, or if the record was not inserted).
   */
  insertWithTags: function(definitionGr) {
      var isValidDefinition = definitionGr && definitionGr.isValidRecord();
      var isInUserDomain = this.isUserDomain(definitionGr.getValue('sys_domain'));

      // Return if the definition is not in the user domain
      if (!isValidDefinition || !isInUserDomain) {
          return;
      }

      // Return if the copied Definition doesn't have tags.
      var m2mGr = this.getM2MRecordsByDefinitionSysId(definitionGr.getUniqueValue(), definitionGr.getValue('sys_domain'));
      if (!m2mGr.hasNext()) {
          gs.addErrorMessage(gs.getMessage('Error: The alert clustering definition must have tags for it to be copied.'));

          gs.error(this.logsPrefix + ' The alert clustering definition [code]' + this.generateLinkToDefinition(definitionGr.getUniqueValue()) + '[/code] must have tags for it to be copied.');
          return null;
      }

      var copyName = 'Copy of' + ' ' + definitionGr.getValue('name');
      // Don't insert if the copied definition name already exists for the user's domain because it should be unique.
      if (this.isExistingDefinition(copyName, undefined, this.getUserDomainID())) {
          gs.addErrorMessage(gs.getMessage('Error: An alert clustering definition with the name {0} already exists in {1} domain.', [copyName, this.getUserDomainDisplayName()]));
          return null;
      }

      // From here on, tags exist and the name is unique, start the process to create the new record.
      var definitionGrCopy = this.copyDefinitionWithoutInsert(definitionGr);
      definitionGrCopy.setValue('name', copyName);

      // Create Definition-Tags pairs records in m2m table.
      var msg = '';
      var tagLink = '';
      var existingTagsList = [];

      // Loop through the copied definition's m2m tags.
      while (m2mGr.next()) {
          // Create a new m2m relation for the copy definition.
          var m2mGrCopy = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');
          //             m2mGrCopy.setValue('sys_domain', this.getUserDomainID());

          // Set the copy definition as the definition of its m2m record.
          m2mGrCopy.setValue('alert_clustering_definition', definitionGrCopy.getUniqueValue());

          // Check if the copied tag exists in the user domain and use it.
          var tagGr = this.getTagsGrByTagsSysIds(m2mGr.alert_clustering_tag.toString());

          // If a tag with identical name exists in the domain then use it, otherwise create it.
          if (tagGr.next()) {
              // Reuse the tag for this definition.
              m2mGrCopy.setValue('alert_clustering_tag', tagGr.getUniqueValue());

              // Generate a link for the tag so it will be shown in an info message.
              tagLink = this.generateLinkToTag(tagGr.getUniqueValue(), tagGr.getValue('name'));
              existingTagsList.push(tagLink);

              // Insert the m2m record of the copy definition with its copy tag.
              if (m2mGrCopy.getValue('alert_clustering_definition') && m2mGrCopy.getValue('alert_clustering_tag')) {
                  m2mGrCopy.insert();
              }
          }
      }

      // Show info message for used tags, taking singular/plural form into consideration.
      if (existingTagsList.length) {
          if (existingTagsList.length == 1) {
              msg = gs.getMessage("Alert clustering tag {0} exists in {1} domain and was used for this alert clustering definition.", [existingTagsList.join(', '), this.getUserDomainDisplayName()]);
          } else {
              msg = gs.getMessage("Alert clustering tags {0} exist in {1} domain and were used for this alert clustering definition.", [existingTagsList.join(', '), this.getUserDomainDisplayName()]);
          }
          gs.addInfoMessage(msg);
      }

      // Insert the copy definition.
      var copiedDefinitionSysId = definitionGrCopy.insert();

      // Return the copy definition.
      return definitionGrCopy;
  },

  /**
   * This function overrides an existing alert clustering definition with all of its alert clustering tags, if and only if:
   * 1. The overridden alert clustering definition is assigned with tags (at least 1).
   * 2. The overridden alert clustering definition is not overridden already.
   *
   * @param GlideRecord - definitionGr, the alert clustering definition GR to be overridden.
   *
   * @returns The inserted record, or null on errors (if the definition was overridden already, if the definition doesn't have tags, or if the record was not inserted).
   */
  overrideToUserDomain: function(definitionGr) {
      var isValidDefinition = definitionGr && definitionGr.isValidRecord();
      var isInUserDomain = this.isUserDomain(definitionGr.getValue('sys_domain'));

      // Return if the definition is in the user domain - there's no need to override it.
      if (!isValidDefinition || !this.isDomainSeparationEnabled() || isInUserDomain) {
          return;
      }

      // Return when override is prevented because it's illegal
      // For example, when a user tries to remove a tag from a definition before overriding it.
      // Search for gs.getSession().setClientData('sn_em_tbac.delete_from_different_domain', 'true') to understand where it's being set
      // and under which circumstance
      if (gs.getSession().getClientData('sn_em_tbac.delete_from_different_domain') == 'true') {
          gs.addErrorMessage(gs.getMessage("Error: Override action of alert clustering definition {0} was prevented. You cannot remove tags before overriding an alert clustering definition; tags can be removed only after the definition was successfully overridden.",
              [definitionGr.getValue('name')]
          ));
          gs.getSession().putClientData('sn_em_tbac.delete_from_different_domain', 'false');
          return null;
      }

      // Return if a definition with the same name already exists in the user domain
      if (this.isExistingDefinition(definitionGr.getValue('name'), definitionGr.getUniqueValue(), this.getUserDomainID())) {
          gs.addErrorMessage(gs.getMessage("Error: An alert clustering definition with the name {0} already exists in {1} domain.",
              [definitionGr.getValue('name'), this.getUserDomainDisplayName()]
          ));
          return null;
      }

      // Return if the definition doesn't have tags so it doesn't make lots of sense to override it.
      var m2mGr = this.getM2MRecordsByDefinitionSysId(definitionGr.getUniqueValue(), definitionGr.getValue('sys_domain'));
      if (!m2mGr.hasNext()) {
          gs.addErrorMessage(gs.getMessage('Error: The alert clustering definition {0} must have tags assigned to it before it can be overridden.',
              [this.generateLinkToDefinition(definitionGr.getUniqueValue(), definitionGr.getValue('name'))]
          ));
          return null;
      }

      // Return if the definition is already overridden in the user domain.
      var overridingGr = this.getOverridingDefinitionInDomain(definitionGr.getUniqueValue(), this.getUserDomainID());
      if (overridingGr && overridingGr.next()) {
          gs.addErrorMessage(gs.getMessage('Error: The alert clustering definition {0} is already overridden in {1} domain by {2}.', [
              this.generateLinkToDefinition(definitionGr.getUniqueValue(), definitionGr.getValue('name')),
              this.getUserDomainDisplayName(),
              this.generateLinkToDefinition(overridingGr.getUniqueValue(), overridingGr.getValue('name'))
          ]));
          return null;
      }

      // From here on, tags exist and the name is unique, start the process to create the new record.
      // Don't insert because the definitions business rules will fail it as it doesn't have tags assigned to it, yet
      // We'd like the business rules to run because they also check for valid definition name, etc.
      overridingGr = this.copyDefinitionWithoutInsert(definitionGr);

      // Create Definition-Tags pairs records in m2m table.
      var msg = '';
      var tagLink = '';
      var existingTagsList = [];
      var existingOverridingTags = [];
      var copiedTagsList = [];
      var copiedOverridingTags = [];
      var isM2MCreatedFlag = false;

      // Loop through the copied definition's m2m tags.
      while (m2mGr.next()) {
          // Create a new m2m relation for the copy definition.
          var m2mGrCopy = new GlideRecord('sn_em_tbac_alert_clustering_definitions_tags_m2m');
          m2mGrCopy.initialize();
          m2mGrCopy.setNewGuidValue(gs.generateGUID());

          // Set the m2m record domain to be the same as the user's domain
          m2mGrCopy.setValue('sys_domain', this.getUserDomainID());

          // Set the copy definition as the definition of its m2m record.
          m2mGrCopy.setValue('alert_clustering_definition', overridingGr.getUniqueValue());

          // Init data for the overridden tag from the m2m record
          var tagIdToOverride = m2mGr.getValue('alert_clustering_tag');
          var tagNameToOverride = String(m2mGr.alert_clustering_tag.name);
          var tagDomainIdToOverride = String(m2mGr.alert_clustering_tag.sys_domain);
          var tagDomainNameToOverride = m2mGr.alert_clustering_tag.sys_domain.getDisplayValue() || tagDomainIdToOverride;

          // Get data for the overridden tag from its record, if it is found
          var tagToOverrideGr = this.getTagsGrByTagsSysIds(tagIdToOverride);
          if (tagToOverrideGr.next()) {
              tagNameToOverride = tagToOverrideGr.getValue('name');
              tagDomainIdToOverride = tagToOverrideGr.getValue('sys_domain');
              tagDomainNameToOverride = tagToOverrideGr.sys_domain.getDisplayValue();
          }

          // Look for tags in the user domain that override the definition's tag
          var tagGr = this.getOverridingTagsInDomain(tagIdToOverride, this.getUserDomainID());

          // If such tag wasn't found then look for tags in the parent domains that override the definition's tag
          if (!tagGr.hasNext()) {
              tagGr = this.getOverridingTagsInDomain(tagIdToOverride);
          }

          // If such tag wasn't found then look for a tag that has the same name in the user domain
          if (!tagGr.hasNext()) {
              tagGr = this.getTagsGrByTagsNames(tagNameToOverride, this.getUserDomainID());
          }

          // If such tag wasn't found then look for a tag that has the same name in the parent domains 
          if (!tagGr.hasNext()) {
              tagGr = this.getTagsGrByTagsNames(tagNameToOverride);
          }

          // If a tag is existing and found then use it, otherwise create it.
          if (tagGr.next()) {
              if (this.isUserDomain(tagGr.getValue('sys_domain'))) {
                  // If the existing tag doesn't override a tag and its domain is different than the overridden tag then override it
                  if (!tagGr.getValue('sys_overrides') && (tagGr.getValue('sys_domain') != tagDomainIdToOverride)) {
                      tagGr.setValue('sys_overrides', tagIdToOverride);
                      if (tagGr.update()) {
                          // Add a message because the tag was overridden.
                          existingOverridingTags.push(tagDomainNameToOverride);
                      }
                  }

                  // Reuse the tag for this definition.
                  m2mGrCopy.setValue('alert_clustering_tag', tagGr.getUniqueValue());

                  // Generate a link for the tag so it will be shown in an info message.
                  tagLink = this.generateLinkToTag(tagGr.getUniqueValue(), tagGr.getValue('name'));
                  existingTagsList.push(tagLink);
              } else {
                  // Copy the values of the original tag and override it.
                  var tagGrCopy = this.copyAndOverrideTag(tagGr);

                  if (tagGrCopy && tagGrCopy.isValidRecord()) {
                      // Set the copy tag as the tag of the m2m record that belongs to the copy definition.
                      m2mGrCopy.setValue('alert_clustering_tag', tagGrCopy.getUniqueValue());

                      // Generate a link for the tag so it will be displayed in an info message.
                      tagLink = this.generateLinkToTag(tagGrCopy.getUniqueValue(), tagGrCopy.getValue('name'));
                      copiedTagsList.push(tagLink);

                      // Add a message because the original tag was overridden.
                      copiedOverridingTags.push(tagGr.sys_domain.getDisplayValue());
                  }
              }

              // Insert the m2m record of the copy definition with its copy tag.
              if (m2mGrCopy.getValue('alert_clustering_definition') && m2mGrCopy.getValue('alert_clustering_tag')) {
                  var isCreated = m2mGrCopy.insert();
                  if (isCreated && !isM2MCreatedFlag) {
                      isM2MCreatedFlag = true;
                  }
              }
          }
      }

      // Show info message for used tags, taking singular/plural form into consideration.
      if (existingTagsList.length) {
          if (existingTagsList.length == 1) {
              if (!existingOverridingTags.length) {
                  msg = gs.getMessage("Alert clustering tag {0} exists in {1} domain and was used for this alert clustering definition.",
                      [existingTagsList.join(', '), this.getUserDomainDisplayName()]);
              } else { //  existingOverridingTags.length == 1 because existingTagsList.length == 1
                  msg = gs.getMessage("Alert clustering tag {0} exists in {1} domain and was used for this alert clustering definition. It was set to override its matching tag in the {2} domain.",
                      [existingTagsList.join(', '), this.getUserDomainDisplayName(), this.getUniqueArrayElements(existingOverridingTags).join(', ')]);
              }
          } else {
              if (!existingOverridingTags.length) {
                  msg = gs.getMessage("Alert clustering tags {0} exist in {1} domain and were used for this alert clustering definition.",
                      [existingTagsList.join(', '), this.getUserDomainDisplayName()]);
              } else if (existingOverridingTags.length == existingTagsList.length) {
                  msg = gs.getMessage("Alert clustering tags {0} exist in {1} domain and were used for this alert clustering definition. They were set to override their matching tags in the {2} domain.",
                      [existingTagsList.join(', '), this.getUserDomainDisplayName(), this.getUniqueArrayElements(existingOverridingTags).join(', ')]);
              } else {
                  msg = gs.getMessage("Alert clustering tags {0} exist in {1} domain and were used for this alert clustering definition. Some were set to override their matching tags in the {2} domain.",
                      [existingTagsList.join(', '), this.getUserDomainDisplayName(), this.getUniqueArrayElements(existingOverridingTags).join(', ')]);
              }
          }
          gs.addInfoMessage(msg);
      }

      // Show info message for copied tags, taking singular/plural form into consideration.
      if (copiedTagsList.length) {
          if (copiedTagsList.length == 1) {
              if (!copiedOverridingTags.length) {
                  msg = gs.getMessage("Alert clustering tag {0} was created in {1} domain.", [copiedTagsList.join(', '), this.getUserDomainDisplayName()]);
              } else { // copiedOverridingTags.length == 1 because copiedTagsList.length == 1
                  msg = gs.getMessage("Alert clustering tag {0} was created in {1} domain and was set to override its matching tag in the {2} domain.",
                      [copiedTagsList.join(', '), this.getUserDomainDisplayName(), this.getUniqueArrayElements(copiedOverridingTags).join(', ')]);
              }
          } else {
              if (!copiedOverridingTags.length) {
                  msg = gs.getMessage("Alert clustering tags {0} were created in {1} domain.", [copiedTagsList.join(', '), this.getUserDomainDisplayName()]);
              } else if (copiedOverridingTags.length == copiedTagsList.length) {
                  msg = gs.getMessage("Alert clustering tags {0} were created in {1} domain. They were set to override their matching tags in the {2} domain.",
                      [copiedTagsList.join(', '), this.getUserDomainDisplayName(), this.getUniqueArrayElements(copiedOverridingTags).join(', ')]);
              } else {
                  msg = gs.getMessage("Alert clustering tags {0} were created in {1} domain. Some were set to override their matching tags in the {2} domain.",
                      [copiedTagsList.join(', '), this.getUserDomainDisplayName(), this.getUniqueArrayElements(copiedOverridingTags).join(', ')]);
              }
          }
          gs.addInfoMessage(msg);
      }

      // Try to insert the overriding definition only if it had at least one successful m2m insertion.
      var overridingDefinitionSysId;
      if (isM2MCreatedFlag) {
          overridingDefinitionSysId = overridingGr.insert();
      }

      // In case of a copy from different domain (override ui action),
      // then override the ACR of the source ACR as well.
      if (overridingDefinitionSysId && overridingGr.isValidRecord()) {
          var overridingDefinitionAcrGr = new GlideRecord('em_alert_correlation_rule');
          overridingDefinitionAcrGr.get(overridingGr.getValue('alert_correlation_rule'));
          if (overridingDefinitionAcrGr.isValidRecord()) {
              overridingDefinitionAcrGr.setValue('sys_overrides', definitionGr.getValue('alert_correlation_rule'));
              overridingDefinitionAcrGr.update();
          }
      }

      // Return the overriding definition.
      return overridingGr;
  },

  /**
   * Copies the values of a given definition GR to a new definition GR and overrides it if it's in a different domain than the user's domain.
   * The definition is not inserted yet because if it will then it will fail for not having m2m records assigned to it.
   *
   * @param GlideRecord - definitionGr, the alert clustering definition GR to be copied.
   *
   * @returns {GlideRecord} The copy definition GR with its own unique sys_id, BEFORE .insert() was called.
   */
  copyDefinitionWithoutInsert: function(definitionGr) {
      if (!definitionGr || !definitionGr.isValidRecord()) {
          return;
      }

      // Create a definition copy
      var definitionGrCopy = new GlideRecord('sn_em_tbac_alert_clustering_definitions');
      definitionGrCopy.initialize();

      // Copy source definition to target definition
      this.copyFieldValuesFromSourceGrToTargetGr(definitionGr, definitionGrCopy);

      // Set specific values.
      definitionGrCopy.setNewGuidValue(gs.generateGUID());
      definitionGrCopy.setValue('sys_domain', this.getUserDomainID());
      definitionGrCopy.setValue('submit_counter', 0);

      // Override the copied definition if it's in a different domain
      if (this.isNotUserDomain(definitionGr.getValue('sys_domain'))) {
          definitionGrCopy.setValue('sys_overrides', definitionGr.getUniqueValue());
      }

      // Return the GlideRecord of the copy definition, before calling its insert() method
      return definitionGrCopy;
  },


  /**
   * Copies a given tag GR to a new tag GR and overrides it if it's in a different domain than the user's domain.
   *
   * @param GlideRecord - tagGr, the alert clustering tag GR to be copied and overridden.
   *
   * @returns {GlideRecord} The copy tag GR with its own unique sys_id, AFTER .insert() was called.
   */
  copyAndOverrideTag: function(tagGr) {
      if (!tagGr || !tagGr.isValidRecord()) {
          return;
      }

      // Create a tag copy
      var tagGrCopy = new GlideRecord('sn_em_tbac_alert_clustering_tags');
      tagGrCopy.initialize();

      // Copy source tag to target tag
      tagGrCopy = this.copyFieldValuesFromSourceGrToTargetGr(tagGr, tagGrCopy);

      // Set specific values.
      tagGrCopy.setNewGuidValue(gs.generateGUID());
      tagGrCopy.setValue('sys_domain', this.getUserDomainID());

      // Override the copied tag if it's in a different domain
      if (this.isNotUserDomain(tagGr.getValue('sys_domain'))) {
          tagGrCopy.setValue('sys_overrides', tagGr.getUniqueValue());
      }

      // Insert the copy tag
      tagGrCopy.insert();

      // Return the GlideRecord of the copy
      return tagGrCopy;
  },

  /**
   * Copies the values of all fields from sourceGr to targetGr. Doesn't call .update() on targetGr.
   * @param sourceGr - a valid GlideRecord to copy its field values from.
   * @param targetGr - a valid GlideRecord to contain the copied values.
   * @param excludedFieldsArray - An array of strings. Includes fields that will be skipped and not copied.
   * @param keepSysFields - boolean, default to false - not to copy system fields. If true then all system field will be copied, except for those who are listed in excludedFieldsArray.
   */
  copyFieldValuesFromSourceGrToTargetGr: function(sourceGr, targetGr, excludedFieldsArray, keepSysFields) {
      var isValidSourceGr = !sourceGr || !sourceGr.isValidRecord();
      var isValidTargetGr = !targetGr || !targetGr.isValidRecord();

      if (!excludedFieldsArray || !Array.isArray(excludedFieldsArray)) {
          excludedFieldsArray = [];
      }

      // keepSysFields should be true only if its value is true
      keepSysFields = (keepSysFields == 'true' || keepSysFields == true);

      for (var field in sourceGr) {
          if (sourceGr.hasOwnProperty(field)) {
              // Skip copy of system fields if keepSysField is false
              var isSystemField = field.indexOf('sys_') == 0;
              if (isSystemField && !keepSysFields) {
                  continue;
              }

              // Skip copy of the current field value if the field is contained in excludedFieldsArray
              var skipExcludedField = excludedFieldsArray.indexOf(field) > -1;
              if (skipExcludedField) {
                  continue;
              }

              // Copy field values from sourceGr to targetGr
              targetGr.setValue(field, sourceGr.getValue(field));
          }
      }

      return targetGr;
  },

  /**
   * Returns a link element that links to an alert clustering definition.
   *
   * @param String - sysId, the alert clustering definition sys_id to link to.
   * @param String - name, the alert clustering definition name to show as the link content.
   * @param String - isNewTab, defaults to true - whether to open the link in a new tab (true) or the same tab (false).
   *
   * @returns {String} an anchor (<a>) element to an alert clustering definition.
   */
  generateLinkToDefinition: function(sysId, name, isNewTab) {
      return this.generateTBACLink(sysId, name, 'definitions', isNewTab);
  },

  /**
   * Returns a link element that links to an alert clustering tag.
   *
   * @param String - sysId, the alert clustering tag sys_id to link to.
   * @param String - name, the alert clustering tag name to show as the link content.
   * @param String - isNewTab, defaults to true - whether to open the link in a new tab (true) or the same tab (false).
   *
   * @returns {String} an anchor (<a>) element to an alert clustering tag.
   */
  generateLinkToTag: function(sysId, name, isNewTab) {
      return this.generateTBACLink(sysId, name, 'tags', isNewTab || true);
  },

  /**
   * Returns a link element that links to an alert clustering table record.
   *
   * @param String - sysId, the alert clustering table record sys_id to link to.
   * @param String - name, the alert clustering table record name to show as the link content.
   * @param String - table, the alert clustering table name to link to in which the record sysId is.
   * @param String - isNewTab, defaults to true - whether to open the link in a new tab (true) or the same tab (false).
   *
   * @returns {String} an anchor (<a>) element to an alert clustering table record.
   */
  generateTBACLink: function(sysId, name, table, isNewTab) {
      if (!sysId || !table)
          return '';
      name = name || sysId; // Use sysId as name if not given.
      var target = (isNewTab === false) ? '_self' : '_blank'; // Open in a new tab by default.
      return '<a href="sn_em_tbac_alert_clustering_' + table + '?sys_id=' + sysId + '" target="' + target + '">' + name + '</a>';
  },

  /**
   * Remove duplicate values. indexOf returns the index of the first element that was found.
   * Returns an array of unique elements in the given array.
   */
  getUniqueArrayElements: function(array) {
      if (!Array.isArray(array))
          return array;
      return array.filter(function(element, position) {
          return array.indexOf(element) == position;
      });
  },

  /*
   * 1. Remove JS escape sequences (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences).
   * 2. Strips the escape character ( \ ).
   * 3. Strips block comments (with asterisk) and line comments ( // ).
   * 4. Escapes single quotes ( ' ), double quotes ( " ) and ticks ( ` ) from the given string.
   * @param str (required) - string, the string to strip and escape.
   */
  stripAndEscapeChars: function(str) {
      str = String(str); // Must be casted to string before use, for string values coming from dot walk.

      // 1. Remove JS escape sequences
      // Using JSON.stringify escapes the escape character ( \ ), Nul char (\0),
      // newline (\n), tab (\t), form feed (\f), carriage return (\r), vertical tab (\v), backspace (\b).
      str = JSON.stringify(String(str));

      // 2. Strip the escape character.
      str = str.replace(/\\/g, ''); // Strip the escape character

      // 3. Strip comments.
      str = str.replace(/\/\*|\*\//g, ''); // Strip block comments
      str = str.replace(/\//g, ''); // Strip line comments

      // 4. Escape quotes and ticks.
      return str.replace(/\'|\"|\`/g, '\\$&'); // Escape other characters
  },

  /**
   * Creates tag based alert clustering tag with the given parameters.
   * @param source - REQUIRED - the source from which to choose the field to be matched (em_alert, cmdb_key_value, etc...).
   * @param sourceField - REQUIRED - the field or key value to group on, in relation to the chosen source.
   * @param matchMethod - REQUIRED - the type of match required for the alerts to be included in a group (exact, fuzzy, etc...).
   * @param matchMethodValue - CONDITIONALLY REQUIRED - Required only when matchMethod is different than "exact". The value for the given match method, e.g. the fuzzy_similarity value if matchMethod=fuzzy.
   * @param description - OPTIONAL - description for the TBAC tag record.
   * @param name - OPTIONAL - name for the TBAC tag record.
   * @param domainId - OPTIONAL - the domain ID for the TBAC tag record. Defaults to the current user domain ID.
   * @returns {string} - the sys_id of the inserted record, if inserted successfully, or undefined/false otherwise.
   */
  createTag: function(source, sourceField, matchMethod, matchMethodValue, description, name, domainId) {
      // Mandatory parameters - source
      if (!source || !this.SOURCE_FIELD_MAPPING[source] || !sourceField)
          return;

      // Mandatory parameters - match method
      if (!matchMethod || ((matchMethod != 'exact') && !matchMethodValue))
          return;

      // Set default values for optional parameters
      matchMethodValue = matchMethodValue || '';
      description = description || '';
      domainId = domainId || this.getUserDomainID();
      name = name || '';

      // Create tag
      var gr = new GlideRecord('sn_em_tbac_alert_clustering_tags');
      gr.initialize();

      gr.setValue('source', source);
      gr.setValue(this.SOURCE_FIELD_MAPPING[source], sourceField); // Source dependent field, e.g. 'selected_field' when source=em_alert

      gr.setValue('match_method', matchMethod);
      // Include a value for match_method dependent field, relevant only if match method is not "exact"
      if (matchMethod != 'exact')
          gr.setValue(this.MATCH_METHOD_RELATED_FIELDS[matchMethod], matchMethodValue);

      if (name) {
          gr.setValue('customize_name', 'true');
          gr.setValue('name', name);
      }

      gr.setValue('sys_domain', domainId);

      return gr.insert();
  },

  /**
   * When the source is alert tags, the field to compare clustering depends on the 'evt_mgmt.alert_tags_state' property:
   * 1. true - alert tags table and field exist, returns the sn_alert_tags field.
   * 2. partial - alert tags table exist, returns the additional_info field.
   * 3. false - alert tags not supported, returns the additional_info field.
   * @returns {String} the name of the filed to compare clustering with.
   */
  getAlertTagsField: function(current) {
      var alertTagsState = gs.getProperty(this.ALERT_TAGS_STATE_PROPERTY, this.ALERT_TAGS_STATE.DEFAULT_VALUE);
      var alertTagsTableExists = gs.tableExists(this.EM_ALERT_TAGS_TABLE);

      var alertGr = new GlideRecord('em_alert');
      alertGr.initialize();
      var alertTagsFieldExists = alertGr.isValid() && alertGr.isValidField(this.SN_ALERT_TAGS_FIELD);

      if (alertTagsState == this.ALERT_TAGS_STATE.TRUE && alertTagsFieldExists && alertTagsTableExists) {
          return this.SN_ALERT_TAGS_FIELD;
      }
      if (alertTagsState == this.ALERT_TAGS_STATE.PARTIAL && alertTagsTableExists) {
          return 'additional_info';
      }
      gs.error(this.logsPrefix + this.type + ' getAlertTagsField function - the "evt_mgmt.alert_tags_state" property is not coincide to the instance state.');
      return 'additional_info';
  }

};

Sys ID

762f1dcab72030107c038229ce11a9ef

Offical Documentation

Official Docs: